第 5 章 向量索引:用 Recall 换速度


文档摘要

第 5 章 向量索引:用 Recall 换速度 没有索引,向量检索只能暴力比对全部向量(Flat),百万级数据就要等几秒。索引的本质是一场交易——用一点点精度(Recall),换取速度和规模的质变。这一章把 Zvec 的五种索引讲清楚,并给你一张选型决策表,让你面对任何场景都知道该选哪个。 5.1 先理解:精确检索 vs 近似检索(ANN) 向量索引大多基于 ANN(近似最近邻,Approximate Nearest Neighbor) 算法。理解 ANN,要先理解它放弃的是什么: 精确检索(Flat/暴力):把查询向量和库里每一个向量都比一遍,结果绝对准确,但规模一上去就慢到不可用。 近似检索(ANN):不再追求"绝对最近",而是"高度接近的最近"。

第 5 章 向量索引:用 Recall 换速度

没有索引,向量检索只能暴力比对全部向量(Flat),百万级数据就要等几秒。索引的本质是一场交易——用一点点精度(Recall),换取速度和规模的质变。这一章把 Zvec 的五种索引讲清楚,并给你一张选型决策表,让你面对任何场景都知道该选哪个。

5.1 先理解:精确检索 vs 近似检索(ANN)

向量索引大多基于 ANN(近似最近邻,Approximate Nearest Neighbor) 算法。理解 ANN,要先理解它放弃的是什么:

  • 精确检索(Flat/暴力):把查询向量和库里每一个向量都比一遍,结果绝对准确,但规模一上去就慢到不可用。
  • 近似检索(ANN):不再追求"绝对最近",而是"高度接近的最近"。牺牲很小的精度,换来速度和扩展性的数量级提升

衡量 ANN 精度的指标是 Recall@k(召回率)

近似方法返回的 top-k 中,真实最近邻的数量 Recall@k = ──────────────────────────────────────────── k

💡 为什么 RAG 能接受近似? 因为 LM 生成本身就有容错性——top-10 里混进 1 个"第 11 名"的文档,几乎不影响最终答案质量。业界经验:Recall ≥ 96% 时,近似检索和精确检索在 RAG 效果上几乎无差别。所以这笔交易几乎稳赚。

Zvec 提供五种索引,对应不同的"交易策略":

索引 一句话定位 查询复杂度 内存 适用规模
Flat 暴力基线,绝对精确 O(N) 几万以内 / 调试
HNSW 分层图,生产首选 O(log N) 百万~千万,内存够
HNSW-RaBitQ HNSW + 量化,省内存 O(log N) 大规模、要省内存(需 x86+AVX2)
IVF 聚类倒排桶,参数敏感 O(N/n_list × n_probe) 海量、内存敏感
DiskANN 磁盘图 + PQ,面向十亿级 含磁盘 I/O 极低 十亿级、内存装不下(仅 Linux)

5.2 HNSW:分层小世界图(生产首选)

绝大多数 RAG 场景,从 HNSW 开始就对了。它官方推荐为生产环境首选,在速度、精度、稳定性间平衡最好。

原理:高速公路 + 局部精修

HNSW(Hierarchical Navigable Small World)构建一个多层图,每层节点代表一个向量,边按相似度连接:

Layer 2 (稀疏,长距离"高速公路") ●─────●─────● │ Layer 1 (中等) ●─●───●─● │ │ Layer 0 (稠密,精细) ●─●─●─●─●─●─●─●─●─●─●─● ← 所有节点都在这层

查询时从顶层入口出发,逐层贪心下降:上层快速跳到目标区域(像走高速),下层精细搜索局部邻域(像走小路找门牌)。这就是它"既快又准"的原因——上层负责速度,底层负责精度。

关键参数

参数 作用 调参方向
m 每节点最大邻居数(构建时) ↑ 召回更高、内存更多;默认即可起步
ef_construction 构建时候选池大小 ↑ 图质量更好、构建更慢;不影响查询速度
ef(查询时) 查询时候选池大小 调召回率的第一旋钮:↑ 召回更高、延迟增加
quantize_type 量化压缩 见 5.5 节

💡 调参顺序心法:先从默认值起步,优先调查询时的 ef 来平衡召回和延迟(它不改索引、零成本切换)。只有召回还不够时,才加大 ef_constructionm 重建索引——因为后两者会增加构建时间和内存。

5.3 IVF:聚类倒排桶(内存友好)

IVF(Inverted File)的思路是先把向量空间聚类分桶,查询时只搜最近的几个桶

原理:先分桶,再局部搜

索引构建:N 个向量 ──k-means──► n_list 个簇(每簇一个质心) │ 查询: 查询向量 ──► 找最近 n_probe 个质心 ──► 只在这些桶里暴力搜

它把全量扫描变成"只扫几个桶",复杂度约 O(N/n_list × n_probe)n_list 越大、n_probe 越小,越快但召回可能降。

关键参数

参数 作用 调参建议
n_list 聚类数(桶数) 初始值 ≈ √N(N 为向量数);↑ 桶更小更快但构建更贵
n_iters 质心优化迭代数 ↑ 聚类质量更好、构建更慢
n_probe(查询时) 查询时搜多少个桶 调召回率的旋钮:↑ 召回更高、更慢

⚠️ IVF 是"参数敏感型"选手n_listn_probe 对效果影响大,适合愿意做系统化调参的场景。它的优势是内存效率高(向量存紧凑的倒排列表里),常与乘积量化组合成 IVF-PQ 应对海量数据。

5.4 DiskANN:十亿级的磁盘方案

当数据集远超内存(十亿级),HNSW 装不下时,DiskANN 登场。它把压缩编码放内存、全精度向量放磁盘,用 Vamana 图导航 + PQ 距离估计 + 带缓存的束搜索。

特性 说明
内存 极低(仅 PQ 编码驻留,每向量每 chunk 1 字节)
Recall 高(最终候选重新算精确距离)
延迟 比内存索引高(有磁盘 I/O),QPS 较低
平台 仅 Linux,需 libaio

⚠️ DiskANN 适合"对延迟不敏感、但必须在有限内存下跑超大规模"的批处理/离线场景。如果你的数据能装进内存,老老实实用 HNSW 或 HNSW-RaBitQ,延迟低得多。参数核心是查询时的 list_size:↑ 召回更高但磁盘 I/O 更多。

5.5 量化:用精度换内存

当 HNSW 内存吃紧时,不必换索引——开启**量化(Quantization)**即可。量化把 FP32 向量压缩成更紧凑的表示,Zvec 同时存原始向量和量化版(构建/检索只加载量化版,省内存;需要时可取回原始向量)。

量化类型 每维比特 精度损失 适用
FP16 16 bit 极小 想接近 FP32 精度又省一半内存
INT8 8 bit 小(多数任务可接受) 速度/大小/精度的平衡点
INT4 4 bit 较大 极致省内存、延迟敏感、可接受精度损失
# 在 VectorSchema 里加 quantize_type 即可开启 zvec.VectorSchema( name="dense", data_type=zvec.DataType.VECTOR_FP32, dimension=768, index_param=zvec.HnswIndexParam( metric_type=zvec.MetricType.COSINE, quantize_type=zvec.QuantizeType.INT8, # 量化压缩 ), )

⚠️ 量化是有损且不可逆的压缩,会可能降低召回。开启后务必验证对检索质量的影响。一个补救手段是查询时开精排(refiner):对头部候选重新用原始向量算精确分数,挽回精度损失(代价是增加延迟)。

# 查询时开启精排,配合量化使用,找回精度 result = collection.query( queries=zvec.Query( field_name="dense", vector=q_vec, param=zvec.HnswQueryParam(ef=500, is_using_refiner=True), # 精排 ), topk=10, )

5.6 索引选型决策表

把前面五种索引浓缩成一张决策表。从上到下,第一个满足条件的即是推荐选择

你的数据规模 / 约束 │ ├─ 调试、小数据(< 几万)、要绝对精确 │ └─► Flat │ ├─ 数据能装进内存(百万~千万级,常规 RAG) │ ├─ 内存充裕 ──► HNSW(生产首选) │ └─ 内存吃紧(x86+AVX2)──► HNSW-RaBitQ 或 HNSW + 量化(INT8/FP16) │ └─ 数据远超内存(十亿级,仅 Linux) └─► DiskANN
场景 推荐索引 理由
个人/小团队 RAG 起步 HNSW 平衡最好,调参简单(主要调 ef
中大型 RAG、内存紧张 HNSW + INT8 量化 省内存,开 refiner 保精度
超大规模、内存装不下 DiskANN 唯一能在有限内存跑十亿级
愿意精调、内存敏感 IVF 内存高效,但参数敏感

5.7 索引特定查询参数:检索时还能调什么

索引不仅在构建时可调,查询时也有专属参数,这是召回调优的"第二战场"。这些参数通过 Queryparam 传入,不改索引、可随时切换

# HNSW 查询:调大 ef 提召回 collection.query( queries=zvec.Query(field_name="dense", vector=q_vec, param=zvec.HnswQueryParam(ef=600)), topk=10, ) # IVF 查询:调大 n_probe 提召回 collection.query( queries=zvec.Query(field_name="dense", vector=q_vec, param=zvec.IVFQueryParam(nprobe=100)), topk=10, ) # DiskANN 查询:调大 list_size 提召回(更多磁盘 I/O) collection.query( queries=zvec.Query(field_name="dense", vector=q_vec, param=zvec.DiskAnnQueryParam(list_size=200)), topk=10, )
索引 查询参数 还有个通用招
HNSW efradiusis_using_refineris_linear(强制暴力,仅调试) radius:设距离阈值,剔除低质量匹配
IVF n_proberadiusis_linear radius:内积时 radius=0.6 只留分数>0.6
DiskANN list_size 适合"宁可少返回也要高质量"的场景

⚠️ 参数类必须匹配索引类型:对 HNSW 索引用 IVFQueryParam 会报错。索引类型和查询参数类是一一对应的。

5.8 动手实验

  1. Recall 直觉:用 Flat 索引建一个库作为" ground truth",再用 HNSW 建同样的库;对同一批查询,对比两者 top-10 的重合度,估算 Recall@10。
  2. ef 调参:固定数据,分别用 ef=100/300/600 查询,记录 Recall 和延迟,画出"召回-延迟"曲线,体会第一旋钮的效果。
  3. 量化 + 精排:建 HNSW + INT8 量化的库,对比"不开 refiner"和"开 refiner"的 Recall,体会量化损失如何被精排挽回。

本章小结

  • 索引是 Recall 与速度/规模的交易;Recall ≥ 96% 时,RAG 几乎无感损失。
  • HNSW 是生产首选(分层图,O(log N),主要调查询时 ef);IVF 内存友好但参数敏感;DiskANN 面向十亿级磁盘场景。
  • 量化(FP16/INT8/INT4)用精度换内存,配合 is_using_refiner 精排可挽回精度。
  • 给了一张选型决策表:起步无脑 HNSW,内存紧加量化,十亿级上 DiskANN。
  • 查询时的索引特定参数(ef/n_probe/list_size)是召回调优第二战场,不改索引随时切。

索引选好了,接下来真正开始检索。详见第 6 章


发布者: 作者: 转发
评论区 (0)
U