Skip to content

feat(ivf): support RaBitQ x+y split storage and search - #2707

Open
LightWant wants to merge 9 commits into
antgroup:mainfrom
LightWant:feat/issue-2443
Open

feat(ivf): support RaBitQ x+y split storage and search#2707
LightWant wants to merge 9 commits into
antgroup:mainfrom
LightWant:feat/issue-2443

Conversation

@LightWant

@LightWant LightWant commented Aug 18, 2026

Copy link
Copy Markdown
Collaborator

性能结果

本报告只比较 PR 前已有的 IVF RaBitQ 与本 PR 最终支持的 IVF split RaBitQ 布局,
不包含中间实现或内部优化阶段。

测试配置:

  • 数据集:gist-960-euclidean.hdf5,1,000,000 条 960 维 L2 base 向量
  • 构建:1,024 个 IVF buckets、100,000 条训练样本、32 个构建线程,显式设置
    fast_encode_rabitq = truefast_encode_rabitq_rounds = 12
  • 检索:单线程、扫描 32 个 buckets、factor = 10、top-10、计时 1,000 次查询
  • Query 编码:rabitq_bits_per_dim_query = 32
版本 存储布局 构建时间(秒) 构建 TPS 索引内存(bytes) 索引文件(bytes) 搜索 QPS 平均延迟(ms) Recall@10
PR 前:IVF RaBitQ 传统 8-bit 149.369 6,694.851 1,284,595,232 946,484,662 16.784 59.576 0.9166
PR 最终版本:IVF split RaBitQ 1-bit filter + 7-bit supplement 135.152 7,399.071 1,314,934,688 1,014,237,096 104.479 9.567 0.9013
PR 最终版本:IVF split RaBitQ 2-bit filter + 6-bit supplement 154.686 6,464.702 1,327,086,848 1,020,411,816 80.271 12.454 0.9069
PR 最终版本:IVF split RaBitQ 3-bit filter + 5-bit supplement 168.062 5,950.188 1,328,623,376 1,022,416,040 72.852 13.722 0.9027

相对传统 8-bit,1+7 的构建时间减少 9.5%,构建吞吐提升 10.5%;2+6 的构建时间
增加 3.6%,构建吞吐下降 3.4%;3+5 的构建时间增加 12.5%,构建吞吐下降 11.1%。
三种 split 布局的索引内存分别只增加 2.4%、3.3% 和 3.4%。filter xbits 只保留
bucket-local 32-vector packed blocks,不再同时保留按 inner ID 排列的 scalar xbits。
1+7 到 3+5 的内存差值为 13,688,688 bytes;剩余差值主要来自增加的 bit-plane、
每个 bucket 尾部不足 32 条向量的 padding,以及容器 capacity 对齐。

默认 candidate-reorder 检索下,1+7、2+6、3+5 的吞吐分别是传统 8-bit 的
6.2 倍、4.8 倍和 4.3 倍,平均延迟分别降低 83.9%、79.1% 和 77.0%。对应的
Recall@10 绝对差值分别为 -0.0153、-0.0097 和 -0.0139。本次结果中 1+7 吞吐最高,
2+6 在吞吐与召回之间最均衡。

以上为同一轮单机结果,不代表所有数据集和硬件环境都能获得相同比例的性能变化。

Residual IVF 检索

最终版本在相同的 32 buckets、factor = 10、candidate-reorder 配置下,对
1+7、2+6、3+5 residual split 进行了额外测试。每项使用相同的 1,000 条查询;
16 线程数据表示并行执行这 1,000 条查询。1+7 同时列出 non-residual 基线。

查询线程 Split use_residual 搜索 QPS 平均延迟(ms) Recall@10
1 1+7 false 101.170 9.881 0.9029
1 1+7 true 96.753 10.332 0.9206
1 2+6 true 78.143 12.793 0.9038
1 3+5 true 69.248 14.437 0.9031
16 1+7 false 1,654.859 9.497 0.9029
16 1+7 true 1,539.762 10.216 0.9206
16 2+6 true 1,227.363 12.792 0.9038
16 3+5 true 1,069.335 14.730 0.9031

在 1+7 同参数 A/B 中,residual 将 Recall@10 提升 0.0177;单线程和 16 线程 QPS
分别下降 4.4% 和 7.0%。Residual 2+6 与 3+5 的 Recall@10 分别为 0.9038 和
0.9031;随着 filter bit-plane 增加,扫描开销相应上升。Residual 查询路径只为路由
选中的 buckets 构建 computer/LUT,并将同 bucket 的 reorder 合并执行,避免对未扫描
buckets 做查询侧工作。

实现细节

Split 存储

  • 将每条 RaBitQ code 拆成 x-bit filter 与 y-bit supplement。xbits 直接写入
    bucket-local 32-vector packed blocks 并作为唯一 filter 存储;supplement 继续按 IVF
    global inner ID 存储。按需读取完整 code 时,从 packed block 反解一条 xbits 后与
    supplement 合并,不创建长期 scalar xbits 副本。
  • 支持 1+7、2+6 和 3+5 布局,以及 residual IVF、L2/IP/cosine、内存和已支持的
    磁盘 IO、序列化与内存统计。
  • 未配置 split 参数的现有 IVF RaBitQ 继续使用原有路径。

检索

  • filter code 按 bucket 内每 32 条向量一组进行 pack,使用 SIMD byte-LUT 扫描。
    查询只构建一份未加权的 4 维子集和 LUT;1-bit、2-bit 和 3-bit filter 的每个
    bit-plane 共享这份 LUT,分别扫描后再按二进制位权重累加。例如 3-bit 为
    4 * dot(q, x2) + 2 * dot(q, x1) + dot(q, x0)
  • candidate_reorder 保持为默认策略:先扫描 x-bit record,保留
    factor * topk 个候选,再只为这些候选读取 y-bit supplement。
  • 提供可选的 lower-bound heap 策略。只有当 x-bit lower bound 可能优于当前堆顶时
    才读取 supplement;最终 x+y 距离估计直接复用 lower-bound 扫描保存的量化 x-bit
    内积。
  • Residual IVF 在索引加载完成时重建 centroid transform cache,避免首批并发查询
    竞争同一把初始化 mutex;查询只对已路由 buckets 批量构建 computer/LUT。
  • Residual reorder 先按 bucket 对候选分组,每个 bucket 只获取一次共享锁并批量计算
    supplement 距离,再按原候选顺序回填结果。

构建

  • 对首次多线程 IVF 构建,split data cell 使用可选的 bulk-build 生命周期。worker
    写入互不重叠的临时 scalar-code slot,插入完成后再并行 pack 成正常可检索的
    x-bit/y-bit record。
  • 在线或增量 Add、不支持的 quantizer、设置 fast_encode_rabitq=false 的精确
    编码以及单线程构建,继续使用原有插入行为。
  • 如果插入或 finalize 失败,RAII cleanup 会清理临时构建状态;只有最终 record
    完整生成后才执行 Package

兼容性

  • 不删除或修改现有 public API;新增接口只位于内部 data-cell 抽象中。
  • 只有当 IVF base 和 precise quantization 都为 RaBitQ,并配置
    rabitq_bits_per_dim_precise 时,才选择 split storage。
  • 没有配置 split 参数时,现有 IVF RaBitQ 的行为保持不变。
  • 新实现可以加载旧版保存 scalar xbits 的 split 索引,加载时一次性转换为 packed
    blocks;新索引直接序列化 packed blocks。

测试情况

  • clang-format 15:所有修改的 C++ 文件通过格式化和 git diff --check
  • clang-tidy 15:主要修改源文件通过完整 translation-unit 检查。
  • RaBitQ 定向单元测试:22 个用例、3,652,129 个 assertions 通过。
  • IVF split bucket 定向单元测试:2 个 routed-query 用例、728 个 assertions 通过,覆盖
    packed-only 存储、按路由 bucket 批量构建 residual computers、optimized build 生命周期
    和构建路径回退。
  • IVF split 功能测试:15 个用例、15,711 个 assertions 通过,覆盖构建、搜索、
    序列化、并发 Add,以及 optimized Build 后继续 Add;IVF streaming compatibility
    另有 1 个用例、32,443 个 assertions 通过。
  • 完整单元测试运行了 709 个用例,其中 703 个通过;6 个失败均位于本 PR 未修改的
    INT8 精确浮点比较和 non-finite searcher 测试。上述所有 RaBitQ/IVF 相关测试通过。

Fixes: #2443

附录:评测 YAML

global:
  num_threads_building: 32
  num_threads_searching: 1
  exporters:
    stdout:
      format: table
      to: stdout
    json:
      format: json
      to: file:///tmp/vsag-ivf-packed-shared-lut-gist1m-results.json

traditional_rabitq_8_r12:
  datapath: /root/data/gist1M/gist-960-euclidean.hdf5
  type: build,search
  index_name: ivf
  index_path: /tmp/vsag-ivf-packed-shared-lut-gist1m-traditional.index
  create_params: '{"dtype":"float32","metric_type":"l2","dim":960,"index_param":{"buckets_count":1024,"base_quantization_type":"rabitq","rabitq_bits_per_dim_query":32,"rabitq_bits_per_dim_base":8,"use_reorder":false,"partition_strategy_type":"ivf","ivf_train_type":"kmeans","train_sample_count":100000,"thread_count":32,"fast_encode_rabitq":true,"fast_encode_rabitq_rounds":12}}'
  search_params: '{"ivf":{"scan_buckets_count":32,"factor":10.0,"parallelism":1}}'
  search_mode: knn
  topk: 10
  search_query_count: 1000

split_rabitq_1_7_r12:
  datapath: /root/data/gist1M/gist-960-euclidean.hdf5
  type: build,search
  index_name: ivf
  index_path: /tmp/vsag-ivf-packed-shared-lut-gist1m-1-7.index
  create_params: '{"dtype":"float32","metric_type":"l2","dim":960,"index_param":{"buckets_count":1024,"base_quantization_type":"rabitq","precise_quantization_type":"rabitq","rabitq_bits_per_dim_query":32,"rabitq_bits_per_dim_base":1,"rabitq_bits_per_dim_precise":7,"use_reorder":true,"partition_strategy_type":"ivf","ivf_train_type":"kmeans","train_sample_count":100000,"thread_count":32,"fast_encode_rabitq":true,"fast_encode_rabitq_rounds":12}}'
  search_params: '{"ivf":{"scan_buckets_count":32,"factor":10.0,"parallelism":1}}'
  search_mode: knn
  topk: 10
  search_query_count: 1000

split_rabitq_2_6_r12:
  datapath: /root/data/gist1M/gist-960-euclidean.hdf5
  type: build,search
  index_name: ivf
  index_path: /tmp/vsag-ivf-packed-shared-lut-gist1m-2-6.index
  create_params: '{"dtype":"float32","metric_type":"l2","dim":960,"index_param":{"buckets_count":1024,"base_quantization_type":"rabitq","precise_quantization_type":"rabitq","rabitq_bits_per_dim_query":32,"rabitq_bits_per_dim_base":2,"rabitq_bits_per_dim_precise":6,"use_reorder":true,"partition_strategy_type":"ivf","ivf_train_type":"kmeans","train_sample_count":100000,"thread_count":32,"fast_encode_rabitq":true,"fast_encode_rabitq_rounds":12}}'
  search_params: '{"ivf":{"scan_buckets_count":32,"factor":10.0,"parallelism":1}}'
  search_mode: knn
  topk: 10
  search_query_count: 1000

split_rabitq_3_5_r12:
  datapath: /root/data/gist1M/gist-960-euclidean.hdf5
  type: build,search
  index_name: ivf
  index_path: /tmp/vsag-ivf-packed-shared-lut-gist1m-3-5.index
  create_params: '{"dtype":"float32","metric_type":"l2","dim":960,"index_param":{"buckets_count":1024,"base_quantization_type":"rabitq","precise_quantization_type":"rabitq","rabitq_bits_per_dim_query":32,"rabitq_bits_per_dim_base":3,"rabitq_bits_per_dim_precise":5,"use_reorder":true,"partition_strategy_type":"ivf","ivf_train_type":"kmeans","train_sample_count":100000,"thread_count":32,"fast_encode_rabitq":true,"fast_encode_rabitq_rounds":12}}'
  search_params: '{"ivf":{"scan_buckets_count":32,"factor":10.0,"parallelism":1}}'
  search_mode: knn
  topk: 10
  search_query_count: 1000

@vsag-bot

vsag-bot commented Aug 18, 2026

Copy link
Copy Markdown
Collaborator

/label status/waiting-for-review
/waiting-on reviewer
/request-review @jiaweizone
/request-review @wxyucs
/request-review @inabao
/request-review @LHT129

@mergify

mergify Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Merge Protections

🟢 All 3 merge protections satisfied — ready to merge.

Show 3 satisfied protections

🟢 Require kind label

  • label~=^kind/

🟢 Require version label

  • label~=^version/

🟢 Require linked issue for feature/bug PRs

  • body~=(?im)(?:^|[\s\-\*])(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?)\s*:?\s+(?:#\d+|[\w.\-]+/[\w.\-]+#\d+|https?://github\.com/[\w.\-]+/[\w.\-]+/issues/\d+)

@LightWant

Copy link
Copy Markdown
Collaborator Author

Maintainers: GitHub rejected the fork author's attempt to set upstream labels. Please add kind/feature and version/1.1, matching linked issue #2443.

Signed-off-by: zhuangye.yxw <2510035537@qq.com>
Assisted-by: Codex:gpt-5
@LightWant LightWant added kind/feature Brand-new functionality or capabilities 引入全新的功能、新特性或新能力 version/1.0 labels Aug 18, 2026
Signed-off-by: zhuangye.yxw <2510035537@qq.com>
Assisted-by: Codex:gpt-5
@mergify mergify Bot added the module/simd label Aug 18, 2026
Signed-off-by: zhuangye.yxw <2510035537@qq.com>
Assisted-by: Codex:gpt-5
Signed-off-by: zhuangye.yxw <2510035537@qq.com>
Assisted-by: Codex:gpt-5
Signed-off-by: zhuangye.yxw <2510035537@qq.com>
Assisted-by: Codex:gpt-5.5
Signed-off-by: zhuangye.yxw <2510035537@qq.com>
Assisted-by: Codex:gpt-5.5
Signed-off-by: zhuangye.yxw <2510035537@qq.com>
Assisted-by: Codex:gpt-5.5
Signed-off-by: zhuangye.yxw <2510035537@qq.com>
Assisted-by: Codex:gpt-5.5
Signed-off-by: zhuangye.yxw <2510035537@qq.com>
Assisted-by: Codex:gpt-5
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

kind/feature Brand-new functionality or capabilities 引入全新的功能、新特性或新能力 module/docs module/simd module/testing size/XXL version/1.1

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[feat](ivf): support RaBitQ x+y split storage and search

3 participants