Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

IVF

IVF(Inverted File,倒排索引)是 VSAG 的 分桶式 索引。它在构建时将语料聚类成若干桶, 查询时只扫描与查询距离最近的若干个桶的中心对应的倒排列表,把 O(N) 的线性扫描降为 O(N · scan_buckets_count / buckets_count),并通过这两个参数在召回与延迟之间进行权衡。

与图索引相比,IVF 在召回上略有损失,但换来了更低的内存开销、更高的批量吞吐以及更简单的 切片方式——因此在语料非常大(数亿及以上)、内存紧张、或查询可天然并行化的场景中, IVF 通常是一个更合适的默认选择。

工作原理

  1. 聚类。 在数据集的采样上运行 k-means(或随机采样,ivf_train_type: "random") 得到 buckets_count 个中心(centroid)。
  2. 分配。 每条向量被写入距离最近的中心对应的倒排列表,以配置的粗量化 (base_quantization_type)存储;可选地再保留一份高精度副本(use_reorder: true) 用于精排。
  3. 检索。 查询时先计算查询向量与所有中心的距离,选出最近的 scan_buckets_count 个桶;然后只在这些桶内对向量打分。启用精排时,factor 控制从粗排阶段多取多少候选 再送入精排器重打分。

此外还有一种 GNO-IMI 策略(partition_strategy_type: "gno_imi"),它把空间按两组 正交中心划分(first_order_buckets_count × second_order_buckets_count),在超大规模 语料上能得到更精细的分区。

快速开始

#include <vsag/vsag.h>

std::string params = R"({
    "dtype": "float32",
    "metric_type": "l2",
    "dim": 128,
    "index_param": {
        "buckets_count": 256,
        "base_quantization_type": "sq8",
        "partition_strategy_type": "ivf",
        "ivf_train_type": "kmeans"
    }
})";
auto index = vsag::Factory::CreateIndex("ivf", params).value();

// 构建索引。
auto base = vsag::Dataset::Make();
base->NumElements(n)->Dim(128)->Ids(ids)->Float32Vectors(data)->Owner(false);
index->Build(base);

// 执行检索。
auto query = vsag::Dataset::Make();
query->NumElements(1)->Dim(128)->Float32Vectors(q)->Owner(false);
auto result = index->KnnSearch(
    query, /*topk=*/10,
    R"({"ivf": {"scan_buckets_count": 16}})").value();

支持的输入数据类型

当前公开的 BuildAdd 和检索路径接收通过 Dataset::Float32Vectors 提供的 FP32 向量,dtype 应设为 "float32"base_quantization_type 选择的是内部编码和存储,不会使 API 接受 FP16/BF16 输入。创建 IVF 索引时不支持 dtype: "int8"

构建参数

构建参数放在 index_param 下。完整列表请见 索引参数

参数类型默认值说明
partition_strategy_typestring"ivf"分桶策略:ivf(单层)或 gno_imi(双层正交)
buckets_countint10倒排列表数量(ivf 策略下生效)
first_order_buckets_countint10第一级桶数(gno_imi 策略下生效)
second_order_buckets_countint10第二级桶数(gno_imi 策略下生效)
ivf_train_typestring"kmeans"中心训练方式:kmeansrandom
route_max_degreeint64路由 HGraph 的最大度数(ivf 策略下生效)
route_ef_constructionint300路由 HGraph 的构建搜索宽度(ivf 策略下生效)
base_quantization_typestring"fp32"fp32fp16bf16sq8sq4sq8_uniformsq4_uniformpqpqfsrabitq —— 各量化器细节见量化章节
base_pq_dimint1PQ 子空间数(pq / pqfs 时必填)
rabitq_pca_dimint0base_quantization_type: "rabitq" 时可选的 PCA 预处理维度
rabitq_bits_per_dim_queryint32rabitq 查询每维位数;允许值为 432
rabitq_bits_per_dim_baseint1rabitq 底库存储码每维位数;允许范围为 [1, 8]
rabitq_versionstring"standard"rabitq 布局:"standard""split_1bit_7bit"
rabitq_error_ratefloat1.9rabitq 编码的正数误差预算参数
rabitq_use_fhtboolfalserabitq 二值化前是否启用 FHT 旋转
fast_encode_rabitqbooltrue多 bit rabitq 是否使用 CAQ 快速构建;设为 false 时使用精确编码
fast_encode_rabitq_roundsint6CAQ 微调轮数,允许范围 [1, 32]
use_reorderboolfalse是否保留高精度副本用于精排
precise_quantization_typestring"fp32"精排量化类型(use_reorder: true 时使用)
precise_codes_layoutstring"flat"精排 codes 的存储布局:"flat" 保持旧的一向量一码布局;"bucket" 在 basic posting 的相同 bucket 和 offset 保存高精度 code
base_io_typestring"memory_io"粗排向量的存储后端;以 liburing 构建时支持 uring_io
precise_io_typestring"block_memory_io"精排向量的存储后端(memory_ioblock_memory_iommap_iobuffer_ioasync_iouring_ioreader_io
precise_file_pathstring""当精排 IO 为磁盘后端时的文件路径

precise_codes_layout: "bucket" 要求 use_reorder: true,支持 memory_ioblock_memory_iobuffer_ioasync_iouring_io (需要构建环境支持 io_uring)。不支持 mmap_iopqfs 精排量化。bucket 布局当前 要求 buckets_per_data: 1;一个向量分配到多个 bucket 的配置会被拒绝。

对于 flatbucket 两种布局,序列化后的精排 codes 都可以通过外部 Reader 只读加载。向 Index::Load 传入 precise_io_type: "reader_io"precise_readerflat 布局的 reader 必须恰好对应 high_precision_codes block payload,bucket 布局 则必须对应 ivf_precise_bucket block payload。启用 precise_enable_read_cache 后, reader_io 会使用通用读缓存。

vsag::LoadParameters load_parameters;
load_parameters.Set("precise_io_type", "reader_io")
    .Set("precise_enable_read_cache", true)
    .Set("precise_cache_total_size", 256ULL * 1024 * 1024)
    .SetReader("precise_reader", precise_codes_reader);
auto loaded = vsag::Index::Load(stream, load_parameters).value();

reader_io 是加载与查询阶段的只读放置策略。构建时应使用可写的 precise IO,序列化后 再在查询服务加载索引时绑定外部 reader。

buckets_count 的经验值一般为 sqrt(N) ~ 4 * sqrt(N),其中 N 是语料规模。

检索参数

检索参数放在 ivf 子对象下:

参数类型默认值说明
scan_buckets_countint—(必填)每次查询扫描的桶数,须 ≤ buckets_countdisable_bucket_scan 为 true 时可更大,空槽位补 -1
disable_bucket_scanboolfalse返回桶 ID 及到桶中心距离,不扫描桶内向量。支持批量查询。
factorfloat2.0启用精排时,粗排阶段会预取 factor * topk 个候选再重打分
enable_reorderbooltrue即使索引构建时启用了 reorder,也可以在单次请求里设为 false 跳过最终精排
parallelismint1单次查询内扫描桶时使用的线程数
timeout_msdouble+∞单次查询最长耗时(毫秒),超时会返回当前的部分结果
auto result = index->KnnSearch(
    query, topk,
    R"({"ivf": {"scan_buckets_count": 32, "factor": 2.0, "parallelism": 4}})").value();
auto fast_result = index->KnnSearch(
    query, topk,
    R"({"ivf": {"scan_buckets_count": 32, "factor": 2.0, "enable_reorder": false}})").value();

何时选择 IVF

  • 超大规模语料(数亿及以上),工作集无法完全放入内存。
  • 对每秒查询数(QPS)敏感、对单次延迟相对宽松的批量或高吞吐场景。
  • 内存紧张的部署,可使用激进的量化方案(sq8sq4_uniformpqpqfs)配合 use_reorder 恢复召回。
  • 对切片友好的部署:桶天然映射到分片或磁盘块。

对于延迟敏感、要求高召回的稠密 embedding 场景,请优先比较 HGraph

相关文档