一句话回答

混合检索(Hybrid Search)通常是把关键词/稀疏检索与语义/稠密检索并行执行,再通过分数融合或排序融合合并候选集,最后用 Reranker 精排。

  • 稀疏检索擅长精确匹配:专有名词、型号、错误码、人名、数字。
  • 稠密检索擅长语义匹配:同义表达、口语化问题、没有共享关键词的相关文本。
  • 混合检索的目标不是简单地“多查一次”,而是利用两类检索器的互补性,提高 Recall@K,并在精排后提高最终答案质量。

面试时可以先给出下面这条主线:

Query 预处理 → 稀疏检索与稠密检索并行召回 → 候选去重与融合 → Reranker 精排 → 上下文组装 → LLM 生成。

为什么需要混合检索

只用一种检索器会有明显盲区。

查询 BM25/关键词检索 向量检索
ORA-01555iPhone 15 Pro Max 通常很好,能精确命中关键词 可能因罕见 token 或向量平滑而漏召回
“合同到期后还能续多久”与“协议期满可延长三个月” 词面重合少,可能漏召回 能利用语义相近性命中
带数字、版本号、缩写的查询 更稳定 容易忽略数字或细粒度差异
含同义词、改写、自然语言描述 容易受词汇鸿沟影响 更有优势

因此,混合检索主要解决两个问题:

  1. 词汇鸿沟(lexical gap):用户与文档表达相同含义时不一定使用相同词汇。
  2. 语义过度泛化:向量相似不等于答案相关,尤其容易混淆数字、否定词、实体和相近型号。

两类基础检索器

稀疏检索:BM25

BM25 是一种基于词频、逆文档频率和文档长度归一化的关键词检索算法。简化形式为:

$$ score(D,Q)=\sum_{q_i\in Q}IDF(q_i)\cdot \frac{f(q_i,D)(k_1+1)} {f(q_i,D)+k_1(1-b+b\cdot\frac{|D|}{avgdl})} $$

其中:

  • $f(q_i,D)$:词 $q_i$ 在文档 $D$ 中出现的次数;
  • $IDF(q_i)$:词越稀有,区分度越高;
  • $|D|/avgdl$:文档长度归一化;
  • $k_1$:控制词频饱和,常见默认值约为 1.2~2.0;
  • $b$:控制长度归一化程度,常见默认值约为 0.75。

BM25 主要考虑三个因素

① Query 中的词是否出现在文档中

② 一个词是不是“稀有词”

③ 词出现次数

比如 Query:

shared_buffers

文档 A 出现 5 次,文档 B 出现 1 次。

通常 A 更可能相关。

但是 BM25 不会让词频无限增长。

大概是:

1次 → 有帮助
3次 → 更有帮助
10次 → 不会比3次高特别多

避免一篇文档疯狂堆关键词就获得巨大分数。

稠密检索:Dense Retrieval

稠密检索使用 Embedding 模型把 Query 和 Chunk 编码为低维连续向量,然后以余弦相似度、点积或欧氏距离检索近邻:

$$ cos(q,d)=\frac{q\cdot d}{\|q\|\|d\|} $$

文档量较大时通常使用 ANN(Approximate Nearest Neighbor,近似最近邻)索引,例如 HNSW、IVF 等,在召回率、延迟和内存之间做权衡。

优势:能处理同义表达和语义改写。缺点:模型有领域偏差,对精确 token、数字、罕见实体和否定关系不一定敏感,而且向量召回结果不容易解释。

注意:Embedding 相似度回答的是“语义上像不像”,并不直接等于“这段文档能不能回答问题”。这正是后续 Reranker 有价值的原因。

标准架构

1
2
3
                         ┌─ BM25 / Sparse Top-N ─┐
用户问题 → Query 处理 ──┤ ├→ 合并、去重、融合 → Top-M → Reranker → Top-K → LLM
└─ Vector / Dense Top-N ┘

各阶段职责:

  1. Query 处理:规范化、纠错、实体识别、时间解析、必要时改写或拆分查询。
  2. 多路召回:每路重点保证 Recall,不必要求首轮排序非常精确。
  3. 融合:处理不同检索器的分数尺度,合并重复结果。
  4. 精排:用 Cross-Encoder 或 LLM Reranker 对 Query-Document 对进行更细粒度判断。
  5. 上下文组装:去冗余、扩展相邻块、控制 token 预算,然后交给 LLM。

融合方法:面试重点

RRF:Reciprocal Rank Fusion

RRF 不关心原始分数,只使用结果在各路列表中的排名:

$$ RRF(d)=\sum_{r\in R}\frac{1}{k+rank_r(d)} $$
  • $rank_r(d)$:文档 $d$ 在检索器 $r$ 中的名次;未被该路召回时可视为没有贡献。
  • $k$:平滑常数,工程中常见经验值为 60,但应结合数据验证,不能把它当作定律。

例子:设 $k=60$,文档 A 在 BM25 中排第 1、在向量检索中排第 3:

$$ RRF(A)=\frac{1}{61}+\frac{1}{63} $$

文档同时被两路排在前面时会得到更高分。

RRF 的优势:

  • 不需要校准不同检索器的分数尺度;
  • 对极端分数不敏感;
  • 实现简单,通常是很强的生产基线。

RRF 的限制:

  • 丢失了原始分数中的置信度信息;
  • 默认各路检索器同等重要;
  • 仍需调每路召回数量、$k$ 以及最终候选数量。

如果需要为不同检索器设置重要性,也可以做加权 RRF:

$$ WRRF(d)=\sum_{r\in R}\frac{w_r}{k+rank_r(d)} $$

并行混合与级联混合

并行召回

BM25 与向量检索同时执行,然后融合。这是最典型的 Hybrid Search。

  • 优点:两路独立召回,互补性强,Recall 通常更高。
  • 缺点:索引、计算和工程成本更高。

级联召回

先用一种便宜的检索器取较大候选集,再由另一种模型排序。例如 BM25 Top-1000 → Dense/Reranker Top-20。

  • 优点:低成本模型缩小范围,吞吐量更高。
  • 缺点:第一阶段没召回的文档,后面永远无法恢复;性能上限受第一阶段 Recall 限制。

面试中若被问“Hybrid Search 与 Rerank 的区别”:

Hybrid Search 主要解决候选集召回问题;Rerank 主要解决候选集内部的排序精度问题。前者追求不漏,后者追求排准,两者通常同时使用。

Reranker 为什么有效

Embedding 检索通常计算的是:

sim(q,d)=cos(E(q),E(d)

问题和文档是分别编码的,速度快,很适合从几万、几十万 chunk 中先找 Top-K,但它对细粒度相关性的判断没有那么准确。

而 Cross-Encoder Reranker 通常会直接把:

[Query,Document]
一起输入模型:

score=f(q,d)
模型可以同时观察问题和文档之间的词语对应、语义关系、上下文,因此相关性判断通常更细。

1
2
Bi-Encoder:Encode(Query) × Encode(Document) → 相似度,快,适合召回
Cross-Encoder:Encode([Query; Document]) → 相关性分数,准,适合精排

因此常见配置是:

  • BM25 Top-50~200;
  • Dense Top-50~200;
  • 融合去重后保留 Top-50~100;
  • Rerank 后取 Top-5~20 给 LLM。

这些数字只是起始实验值,最终应由延迟预算和离线评测决定。

为什么不直接用reranker:

BGE-M3 稠密检索 的文档向量可以离线预计算,Reranker 的分数依赖具体的 Query,每次 Query 来了都必须重新计算。Reranker 的相关性判断能力确实更强,但它通常采用 Cross-Encoder 结构,需要把 query 和每个文档一起输入模型进行推理,因此无法像向量检索一样预先计算文档表示。如果直接对整个知识库进行 Rerank,计算成本和查询延迟都会非常高。所以系统一般采用两阶段检索:先用 BM25 和 BGE-M3 做高效粗召回,把候选范围缩小,再用 Reranker 做精排。

为什么使用BEG-M3:

我选择 BGE-M3 主要考虑了三个方面。第一,我们的知识库主要是 PostgreSQL 官方英文文档,但用户可能使用中文提问,所以模型需要有比较好的多语言和跨语言检索能力,BGE-M3 在这方面比较适合。第二,它本身是针对检索任务训练的语义表示模型,能够弥补 BM25 只依赖关键词匹配的问题,对技术文档中的语义相似内容有更好的召回能力。第三,它是开源模型,可以本地部署并提前离线计算文档向量,工程成本比较低。BGE-M3 本身还支持 Dense、Sparse 和 Multi-vector 等多种检索方式,不过我的系统当前主要使用它的 Dense Embedding 能力。

过滤条件应该放在哪里

元数据过滤(租户、权限、语言、时间、文档类型)应尽量在检索阶段下推,而不是召回后才过滤。

原因:如果先取 Top-100 再过滤,可能只剩很少结果,即使索引中本来有大量满足条件的相关文档,也没有机会进入候选集。这会造成“后过滤截断”问题。

生产注意点:

  • 权限过滤必须在所有召回路径都生效,不能只过滤其中一路;
  • Dense 和 Sparse 索引中的 chunk_id、版本和元数据要一致;
  • 文档更新时要考虑双索引的一致性,常用版本号、幂等写入、双写监控和延迟删除;
  • 对过滤条件极强的查询,应单独评估 ANN 在过滤下的 Recall 与延迟。

Chunking 对混合检索的影响

检索算法再好,也无法完全弥补错误切块。

  • Chunk 太小:上下文不完整,但关键词密度高;
  • Chunk 太大:包含噪声,向量语义被稀释,BM25 也受长度归一化影响;
  • 固定长度切分可能截断表格、标题与正文、问答对;
  • 可以使用按标题/段落的语义切分,并保留适度 overlap;
  • 检索命中后可做 Parent-Child Retrieval:用子块定位,再返回更完整的父块;
  • 对相邻块扩展时要去重,并防止上下文被同一文档垄断。

如何调参

1. 先构建评测集

至少包含:

  • 自然语言改写和同义表达;
  • 专有名词、缩写、数字、错误码;
  • 否定式问题和容易混淆的实体;
  • 需要元数据过滤的问题;
  • 无答案问题;
  • 热门查询与长尾查询。

每个 Query 标注一个或多个相关 Chunk/Document,最好区分相关性等级。

2. 分阶段调优

  1. 分别测 BM25 与 Dense,找到各自合理的 Top-N。
  2. 查看两路候选的重合率与独有相关文档,确认是否真的互补。
  3. 比较 RRF、加权融合等策略。
  4. 固定召回策略后再调 Reranker 的候选数和最终 Top-K。
  5. 最后评估端到端答案,不要只看检索指标。

3. 不要同时乱调所有参数

关键参数包括:

  • Chunk 大小与 overlap;
  • BM25 分词器、字段权重、同义词;
  • Embedding 模型及距离函数;
  • ANN 搜索深度;
  • 每路 Top-N;
  • 融合权重或 RRF 的 $k$;
  • Rerank 候选数;
  • 最终给 LLM 的 Top-K 和 token 预算。

应通过消融实验逐项判断收益,否则无法知道改进来自哪里。

评估指标

检索阶段

先设一个贯穿 Recall 和 Precision 的例子。对查询 $q$,语料库中共有 4 个相关文档:

$$ Relevant=\{D_1,D_3,D_5,D_7\} $$

检索系统返回的前 5 个结果依次为:

$$ Retrieved@5=[D_2,D_3,D_4,D_1,D_6] $$

其中命中的相关文档为 ${D_3,D_1}$,一共 2 个。

Recall@K

前 K 个结果覆盖了多少相关文档。RAG 首轮召回最应关注这个指标,因为未召回的证据无法被后续精排恢复。

$$ Recall@K=\frac{|Relevant\cap Retrieved@K|}{|Relevant|} $$

在上面的例子中:

$$ Recall@5=\frac{2}{4}=0.5 $$

含义是:系统找回了全部相关文档中的 50%,还有 $D_5$ 和 $D_7$ 没有被召回。即使后面使用再强的 Reranker,也无法恢复这两个不在候选集中的文档。

Precision@K

前 K 个结果中有多少是相关的:

$$ Precision@K=\frac{|Relevant\cap Retrieved@K|}{K} $$

在同一个例子中:

$$ Precision@5=\frac{2}{5}=0.4 $$

含义是:返回的 5 个结果中有 2 个相关,准确率为 40%。如果为了提高 Recall 把 K 调得很大,通常会召回更多相关文档,但也可能引入更多无关文档,使 Precision 降低。

MRR

关注第一个相关结果出现的位置,适合每个查询通常只有一个主要正确答案的场景:

$$ MRR=\frac{1}{|Q|}\sum_{q\in Q}\frac{1}{rank_q} $$

例如有 3 个查询,它们的第一个相关结果分别出现在第 2、1、4 位,则:

$$ MRR=\frac{\frac{1}{2}+\frac{1}{1}+\frac{1}{4}}{3} =\frac{1.75}{3}\approx0.583 $$

MRR 只关心第一个相关结果:第一个相关结果从第 4 名升到第 1 名会显著提高 MRR,但第 1 名已经相关时,后面的相关文档如何排序不会改变该查询的 Reciprocal Rank。如果某个查询没有召回任何相关结果,通常令该查询的 Reciprocal Rank 为 0。

nDCG@K

同时考虑相关性等级和排序位置,适合存在“高度相关、部分相关”等多级标注的场景。

一种常见定义是:

$$ DCG@K=\sum_{i=1}^{K}\frac{2^{rel_i}-1}{\log_2(i+1)} $$
$$ nDCG@K=\frac{DCG@K}{IDCG@K} $$

其中 $rel_i$ 是第 $i$ 个结果的相关性等级,$IDCG$ 是把相关文档按相关性从高到低进行理想排序后得到的 DCG。

例如,系统前 4 个结果的相关性等级为:

$$ [3,0,2,1] $$

等级 3 表示高度相关,0 表示不相关。该排序的 DCG 为:

$$ DCG@4=\frac{2^3-1}{\log_2 2} +\frac{2^0-1}{\log_2 3} +\frac{2^2-1}{\log_2 4} +\frac{2^1-1}{\log_2 5} \approx 8.931 $$

理想排序为 $[3,2,1,0]$,因此:

$$ IDCG@4=\frac{7}{\log_2 2} +\frac{3}{\log_2 3} +\frac{1}{\log_2 4} +\frac{0}{\log_2 5} \approx 9.393 $$
$$ nDCG@4=\frac{8.931}{9.393}\approx0.951 $$

nDCG 越接近 1,说明排序越接近理想顺序。这个例子得分较高,是因为等级为 3 的最相关文档排在第 1 位;但不相关文档排在第 2 位,因此没有达到 1。

面试提醒:DCG 也存在直接使用 $rel_i$ 而不是 $2^{rel_i}-1$ 的定义。回答时说明自己采用的公式即可,比较实验时必须保持定义一致。

生成阶段

除了检索指标,还应评估:

  • Answer Correctness:答案是否正确;
  • Faithfulness / Groundedness:答案是否有检索证据支持;
  • Context Relevance:送给模型的上下文是否真正相关;
  • Citation Accuracy:引用是否对应正确证据;
  • 无答案拒答率、延迟、吞吐量和单次成本。

Recall@K 提升不保证最终答案一定提升。候选太多会增加噪声,出现“lost in the middle”,所以必须同时观察 Rerank 和端到端指标。

常见故障与排查

1. Dense 结果都“看起来相关”,但没有答案

可能原因:Embedding 只捕捉主题相似,未捕捉答案相关性。

处理:加入 Reranker;补充 hard negatives 微调 Embedding;改善 Chunk;分析数字、否定和实体错误。

2. BM25 总被某些重复词主导

处理:检查分词与停用词;降低某些字段权重;处理模板化页眉页脚;去除重复 Chunk;评估同义词扩展。

3. 混合后反而比单路差

可能原因:

  • 直接相加了不同量纲的原始分数;
  • 融合权重不合适;
  • 某一路质量太差,把噪声引入候选集;
  • 每路 Top-N 太小或候选过多导致 Reranker 压力增大;
  • 两路 chunk_id 不一致,未正确去重;
  • 评测集只覆盖了某一种查询类型。

排查时应分别记录每个候选的来源、原始排名、融合分数与 Rerank 分数,并按 Query 类型切片评估。

4. 离线很好,线上效果不稳定

检查:查询分布漂移、索引更新延迟、Embedding 版本不一致、权限过滤、缓存污染、点击日志偏差,以及 P95/P99 延迟下的超时降级。

生产系统设计要点

延迟与降级

  • 两路并行,整体检索延迟接近较慢的一路,而不是简单相加;
  • 为 Sparse、Dense、Reranker 分别设置超时和监控;
  • Dense 超时时可降级为 BM25,Reranker 超时时可返回融合结果;
  • 对热门 Query 缓存 Query Embedding 或最终检索结果,但要处理权限和索引版本;
  • 批量计算 Reranker,减少模型调用开销。

可观测性

建议记录:

  • 每路耗时、命中数和超时率;
  • 候选来源及交集率;
  • 各阶段 Top-K 的变化;
  • 空结果率、Reranker 分数分布;
  • 按查询类型、租户、语言切片后的 Recall/nDCG;
  • 最终答案质量、引用命中率和用户反馈。

索引一致性

一份文档通常同时写入倒排索引和向量索引。更新失败可能导致两路检索看到不同版本。可以使用稳定的 document_id/chunk_id、版本字段、幂等更新、补偿任务和一致性巡检。

高频面试题与参考回答

1. 什么是混合检索?为什么比纯向量检索好?

混合检索将 BM25 等词法检索与向量语义检索结合。BM25 对实体、数字、错误码和精确关键词更敏感,向量检索可以跨越同义词和表达差异。两者先提高候选集 Recall,再通过融合和 Reranker 提高排序精度,因此通常比只使用一种检索方式更稳健,但效果仍要通过业务评测集验证。

2. 为什么不能直接相加 BM25 分数和余弦相似度?

因为两者的含义、范围和分布不同。BM25 分数没有固定上界,并随 Query 和语料变化;余弦相似度通常位于有限范围内。直接相加会让某一路因为数值尺度更大而支配排序。可先归一化并调权重,或使用只依赖排名的 RRF。

3. RRF 为什么常用?

RRF 不要求不同检索器分数可比,只根据各路排名累加倒数得分。它简单、稳定、无需标注数据,是很好的融合基线。缺点是忽略原始置信度,而且各路权重和候选深度仍需调优。

4. 混合检索是否一定优于单路检索?

不一定。如果某一路与领域不匹配、融合权重错误、切块质量差,混合检索可能引入更多噪声。判断标准不是架构更复杂,而是验证集上的 Recall@K、nDCG、端到端答案质量以及线上延迟和成本。

5. 如何选择 BM25 与 Dense 的权重?

先按查询类型建立验证集,用网格搜索或贝叶斯优化选择权重,并检查不同切片。例如代码、错误码、商品型号可提高 Sparse 权重;自然语言问答可提高 Dense 权重。还可以先做 Query 分类,再动态选择权重,但要评估分类错误带来的影响。

6. 为什么召回后还要 Rerank?

首轮检索器为了低延迟,一般用词项统计或独立向量编码,无法充分建模 Query 与文档的细粒度交互。Cross-Encoder Reranker 联合读取 Query 和候选文档,通常更能识别答案相关性、否定关系和实体细节。代价是计算量大,所以只对几十个候选执行。

7. 线上如何降低延迟?

两路并行执行;控制每路 Top-N;使用 ANN;缓存 Query Embedding;对 Reranker 做批处理、蒸馏或量化;设置分阶段超时;在向量检索或 Reranker 超时时降级到 BM25 或融合结果。同时监控 P95/P99,而不只看平均延迟。

8. 如何证明混合检索真的有收益?

做消融实验,比较 BM25、Dense、BM25+Dense、Hybrid+Rerank 四组;在同一评测集上报告 Recall@K、MRR/nDCG、答案正确性、忠实度、延迟与成本;再按精确词、语义改写、数字实体、长尾查询等类型切片。这样可以明确收益来自融合还是 Reranker。

9. 如果 Query 含有错误码和自然语言描述,怎么处理?

保留错误码原文供 BM25 精确匹配,同时将完整 Query 编码供 Dense 检索;必要时抽取错误码作为字段过滤或字段 boost。两路并行召回后用 RRF 融合,再用 Reranker 判断文档是否真正解释该错误码和用户描述的场景。

10. 如何处理无答案问题?

不能只靠“Top-1 总会返回一个结果”。需要相关性阈值、Reranker 置信度、证据一致性检查或单独的 answerability 分类器,并在评测集中加入无答案样本。阈值应基于验证集校准;低置信度时应拒答、澄清或转人工。

HNSW 和 IVF

HNSW 和 IVF 都是向量数据库中常见的 ANN(Approximate Nearest Neighbor,近似最近邻)索引。它们牺牲少量召回率,避免让查询向量与库中所有向量逐一计算距离,从而降低搜索延迟。

在讨论索引之前,要区分两个概念:

  • 精确检索(Flat/Brute Force):遍历全部 $N$ 个向量,结果最准,但计算量约为 $O(Nd)$,其中 $d$ 为向量维度;
  • 近似检索(ANN):只访问一部分有希望的向量,速度更快,但可能漏掉真实的最近邻。

因此,HNSW 和 IVF 的核心目标都是在 Recall、Latency、Memory、Build Time 之间做权衡。

HNSW

HNSW 全称是 Hierarchical Navigable Small World,即分层可导航小世界图。它是一种基于图的 ANN 索引。

核心思想

HNSW 把每个向量看作图中的一个节点,并为相近节点建立边。图分为多层:

1
2
3
4
5
6
高层:节点少、连接跨度大,用于快速接近目标区域
L2 A ───────────── G
\ /
L1 A ── C ─────── G ── J
\ \ / /
L0 A─B─C─D─E─F─G─H─I─J 节点最多,用于精细搜索

它类似于跳表:高层负责“远距离跳跃”,底层负责“局部精查”。大多数节点只存在于底层,少数节点会按随机机制出现在更高层。

查询过程

  1. 从最高层的入口节点开始;
  2. 在当前层不断移动到更接近 Query 的邻居;
  3. 当前层无法继续改进时,下降一层;
  4. 到达第 0 层后,不只做单路径贪心搜索,而是维护一个候选集合,在局部图中扩展;
  5. 返回距离最近的 Top-K 节点。

高层搜索用于快速定位,底层候选搜索决定最终 Recall。HNSW 在实践中通常有很低的查询延迟,但它不是严格意义上保证 $O(\log N)$ 的算法;性能取决于数据分布、图质量和参数配置。

建索引过程

插入一个新向量时:

  1. 随机决定该节点能出现的最高层;
  2. 从全局入口点开始,自顶向下寻找邻近区域;
  3. 在节点所属的每一层选择若干近邻并连边;
  4. 如果邻居数量超过限制,则使用启发式规则裁剪边,以兼顾局部邻近和图的可导航性。

构建优质图需要较多距离计算,所以 HNSW 的建索引时间和内存开销通常高于简单的 IVF。

关键参数

M

每个节点最多保留多少条邻接边,控制图的连通性。

  • $M$ 增大:图更密,Recall 通常提高;
  • 代价:索引体积、建索引时间和查询时的距离计算增加。

efConstruction

建索引时维护的候选邻居集合大小。

  • 越大越有机会构建高质量连接,Recall 通常更高;
  • 但建索引更慢;
  • 它主要影响索引质量,不是单次查询参数。

efSearch

查询时在底层图中维护的候选集合大小,有些系统中简称为 ef

在高层:通常使用 ef=1 的贪心搜索。在第 0 层:使用 efSearch 进行多候选扩展搜索。高层不是只检查一个节点,而是从当前节点反复检查它的所有邻居,并不断移动到更近的节点,直到没有更近的邻居;最终只保留一个局部最近节点,作为下一层的入口。

  • 越大,探索节点越多,Recall 越高;
  • 但查询延迟增加;
  • 一般应满足 efSearch >= top_k,然后在验证集上根据 Recall-Latency 曲线调节。

优缺点

优点:

  • 查询延迟低,Recall 通常很高;
  • 不需要像 IVF 那样先训练聚类中心;
  • 支持增量插入,适合持续写入的场景;
  • 对中等到较大规模、低延迟在线查询非常常见。

缺点:

  • 图中的边会带来较高内存开销;
  • 构建索引较慢;
  • 原生删除和更新比插入复杂,具体行为取决于实现,有些系统会先做逻辑删除再重建;
  • 强过滤条件会破坏图遍历的连通性或造成大量无效访问,需要专门的过滤策略。

IVF

IVF 全称是 Inverted File Index,即倒排文件索引。这里的“倒排”不是 BM25 的词项倒排表,而是 聚类中心到向量列表 的映射。

核心思想

IVF 先用 K-Means 等算法把向量空间划分为多个聚类,每个聚类由一个中心向量表示:

1
2
3
4
Centroid 1 → [v2, v8, v15, ...]
Centroid 2 → [v1, v6, v20, ...]
Centroid 3 → [v4, v9, v11, ...]
...

查询时先寻找与 Query 最近的几个聚类中心,只扫描这些聚类中的向量,而不是扫描整个数据库。

建索引过程

  1. 从语料向量中抽取有代表性的训练样本;
  2. 训练 $nlist$ 个聚类中心;
  3. 把每个文档向量分配给最近的中心;
  4. 在中心对应的倒排列表中保存向量 ID,以及原始向量或压缩编码。

IVF 存在训练阶段。训练样本不足、分布不均或线上数据发生漂移,都会导致聚类不平衡,进而影响 Recall 和延迟。

查询过程

  1. 计算 Query 与所有聚类中心的距离;
  2. 选择最近的 $nprobe$ 个聚类;
  3. 扫描这些聚类中的候选向量;
  4. 计算候选与 Query 的距离并返回 Top-K。

例如有 100 万个向量,被比较均匀地分到 1000 个聚类中。如果 nprobe=10,理想情况下只需扫描约 1% 的向量。真实扫描量会受聚类是否均衡、过滤条件和实现方式影响。

关键参数

nlist

聚类中心,也就是倒排列表的数量。

  • 太小:每个列表很长,查询需要扫描很多向量;
  • 太大:中心搜索、训练和索引管理成本增加,每个列表的训练质量也可能下降;
  • 应结合数据量、维度和分布选择,而不是机械套用固定公式。

nprobe

一次查询探测的聚类数量,是 IVF 最重要的查询参数。

  • 增大 nprobe:扫描范围变大,Recall 通常提高;
  • 代价:延迟与计算量增加;
  • nprobe=nlist 时,搜索范围接近全部列表,IVF 的剪枝优势基本消失。

Query


├─ 与全部 nlist 个聚类中心比较

├─ 选出最近的 nprobe 个中心

└─ 只扫描这 nprobe 个倒排列表中的向量

IVF-Flat 与 IVF-PQ

IVF-Flat

倒排列表中保存完整向量。查询时对选中列表中的向量做精确距离计算。

  • 选中列表内计算准确;
  • 内存占用仍然较高;
  • IVF 的近似误差主要来自没有探测其他列表。

IVF-PQ

在 IVF 的基础上使用 PQ(Product Quantization,乘积量化)压缩向量。PQ 将向量切成多个子空间,每个子空间用较短的码表示。

  • 大幅降低内存占用,并可提高扫描吞吐量;
  • 压缩会引入额外距离误差,使 Recall 下降;
  • 常在超大规模、内存受限的场景使用,必要时再用原始向量对候选重算距离。

优缺点

优点:

  • 可通过 nprobe 灵活控制 Recall 与延迟;
  • 索引结构规则,适合大规模批量检索以及 GPU、磁盘或压缩存储;
  • 与 PQ 组合后内存效率很高;
  • 相比图索引,每个向量不需要保存大量邻接边。

缺点:

  • 需要训练聚类中心;
  • 数据分布变化后可能需要重新训练或重建索引;
  • 聚类边界附近的真实近邻可能落在未探测列表中;
  • 聚类不均衡会造成部分查询延迟过高;
  • 少量数据时,聚类训练和中心搜索的收益不一定明显。

HNSW 与 IVF 对比

对比项 HNSW IVF
基本结构 分层近邻图 聚类中心 + 倒排列表
是否需要训练 不需要聚类训练 需要训练聚类中心
查询方式 在图上导航和扩展候选 先找中心,再扫描部分列表
核心查询参数 efSearch nprobe
核心构建参数 MefConstruction nlist,以及 PQ 参数
Recall 在充足内存和合理参数下通常很高 取决于聚类质量和nprobe
内存 较高,需要保存图边 IVF-Flat 中等,IVF-PQ 较低
建索引 通常较慢 需要训练,但批量构建较规则
增量写入 通常较友好 可插入,但数据漂移会影响聚类质量
适合场景 低延迟、高 Recall 的在线检索 超大规模、内存受限、批量或 GPU 检索

不能简单地说 HNSW 一定比 IVF 好。选型应基于相同硬件和数据集上的 Recall-Latency-Memory 基准测试。

如何调优和选型

HNSW 调优顺序

  1. 根据内存预算选择 $M$;
  2. 用较高的 efConstruction 构建质量较好的索引;
  3. 固定 Top-K,逐渐增大 efSearch
  4. 绘制 Recall@K 与 P95/P99 Latency 曲线,选择满足 SLA 的点。

IVF 调优顺序

  1. 使用有代表性的样本训练聚类中心;
  2. 调整 nlist,观察列表大小是否均衡;
  3. 固定 Top-K,逐渐增加 nprobe
  4. 内存不足时比较 IVF-Flat 与 IVF-PQ,并评估压缩损失;
  5. 同样根据 Recall-Latency-Memory 曲线选择参数。

一个简单的选择思路

  • 数据规模不大:先测试 Flat,可能已经足够快且 Recall 为 100%;
  • 追求高 Recall、低在线延迟,且内存充足:优先测试 HNSW;
  • 数据规模极大或内存紧张:优先测试 IVF-PQ;
  • GPU 批量查询或规则的离线检索:IVF 往往更容易发挥吞吐优势;
  • 数据持续变化:HNSW 增量插入通常更直接,但仍要测试删除、过滤和维护成本。

在 RAG 混合检索中的位置

HNSW 和 IVF 只负责 Dense Retrieval 的向量候选召回,不能替代 BM25、RRF 或 Reranker:

1
2
3
Query ─┬─ BM25 倒排索引 ───────────┐
│ ├→ RRF/加权融合 → Reranker → LLM
└─ Embedding → HNSW 或 IVF ─┘

如果把 efSearchnprobe 调得过小,向量召回会漏掉真实近邻;这些文档无法进入融合与精排阶段。因此,RAG 中通常先保证 Dense 路径有足够的 Recall,再在延迟预算内调低搜索深度。

还要注意:ANN 的 Recall 是“找回精确向量近邻的比例”,而业务 Recall 是“找回人工标注相关文档的比例”。即使 ANN Recall 很高,Embedding 模型本身也可能没有把真正相关文档排在向量空间近处,所以两种 Recall 都应评估。

高频面试题

1. HNSW 为什么要分层?

高层节点少、边的跨度大,可以快速接近 Query 所在区域;底层节点完整,用于局部精细搜索。这样避免从入口点开始在完整图中进行大范围遍历,思想上类似跳表的多级跳跃。

2. efSearchnprobe 有什么共同点?

它们都控制查询时的搜索深度。efSearch 控制 HNSW 候选集合的规模,nprobe 控制 IVF 要探测的聚类数量。增大它们通常会提高 Recall,同时增加延迟,但二者作用于完全不同的索引结构。

3. HNSW 的 $M$ 越大越好吗?

不是。$M$ 越大通常能提高图的连通性和 Recall,但会增加内存、构建成本与查询距离计算。达到目标 Recall 后继续增大,边际收益可能很小,应结合内存和延迟预算选择。

4. IVF 为什么会漏掉最近邻?

Query 只探测部分聚类,而真实最近邻可能位于未探测的聚类中,尤其容易发生在聚类边界附近。增加 nprobe 能降低这种漏召回,但会扫描更多向量。

5. IVF-PQ 为什么比 IVF-Flat 更省内存但 Recall 更低?

IVF-PQ 保存的是原始向量的量化编码,计算距离时使用近似向量,因此除了 IVF 未探测全部列表的误差,还增加了量化误差。它用精度换取了更小的索引和更高的扫描吞吐量。

6. HNSW 和 IVF 应该怎么选?

没有脱离数据和硬件的固定答案。HNSW 通常适合内存充足、强调低延迟和高 Recall 的在线检索;IVF,特别是 IVF-PQ,适合超大规模或内存受限场景。最终应在同一数据集上比较 Recall@K、P95/P99 延迟、吞吐量、索引大小、构建时间和更新成本。