RAG 混合检索
一句话回答
混合检索(Hybrid Search)通常是把关键词/稀疏检索与语义/稠密检索并行执行,再通过分数融合或排序融合合并候选集,最后用 Reranker 精排。
- 稀疏检索擅长精确匹配:专有名词、型号、错误码、人名、数字。
- 稠密检索擅长语义匹配:同义表达、口语化问题、没有共享关键词的相关文本。
- 混合检索的目标不是简单地“多查一次”,而是利用两类检索器的互补性,提高 Recall@K,并在精排后提高最终答案质量。
面试时可以先给出下面这条主线:
Query 预处理 → 稀疏检索与稠密检索并行召回 → 候选去重与融合 → Reranker 精排 → 上下文组装 → LLM 生成。
为什么需要混合检索
只用一种检索器会有明显盲区。
| 查询 | BM25/关键词检索 | 向量检索 |
|---|---|---|
ORA-01555、iPhone 15 Pro Max |
通常很好,能精确命中关键词 | 可能因罕见 token 或向量平滑而漏召回 |
| “合同到期后还能续多久”与“协议期满可延长三个月” | 词面重合少,可能漏召回 | 能利用语义相近性命中 |
| 带数字、版本号、缩写的查询 | 更稳定 | 容易忽略数字或细粒度差异 |
| 含同义词、改写、自然语言描述 | 容易受词汇鸿沟影响 | 更有优势 |
因此,混合检索主要解决两个问题:
- 词汇鸿沟(lexical gap):用户与文档表达相同含义时不一定使用相同词汇。
- 语义过度泛化:向量相似不等于答案相关,尤其容易混淆数字、否定词、实体和相近型号。
两类基础检索器
稀疏检索:BM25
BM25 是一种基于词频、逆文档频率和文档长度归一化的关键词检索算法。简化形式为:
其中:
- $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 编码为低维连续向量,然后以余弦相似度、点积或欧氏距离检索近邻:
文档量较大时通常使用 ANN(Approximate Nearest Neighbor,近似最近邻)索引,例如 HNSW、IVF 等,在召回率、延迟和内存之间做权衡。
优势:能处理同义表达和语义改写。缺点:模型有领域偏差,对精确 token、数字、罕见实体和否定关系不一定敏感,而且向量召回结果不容易解释。
注意:Embedding 相似度回答的是“语义上像不像”,并不直接等于“这段文档能不能回答问题”。这正是后续 Reranker 有价值的原因。
标准架构
1 | ┌─ BM25 / Sparse Top-N ─┐ |
各阶段职责:
- Query 处理:规范化、纠错、实体识别、时间解析、必要时改写或拆分查询。
- 多路召回:每路重点保证 Recall,不必要求首轮排序非常精确。
- 融合:处理不同检索器的分数尺度,合并重复结果。
- 精排:用 Cross-Encoder 或 LLM Reranker 对 Query-Document 对进行更细粒度判断。
- 上下文组装:去冗余、扩展相邻块、控制 token 预算,然后交给 LLM。
融合方法:面试重点
RRF:Reciprocal Rank Fusion
RRF 不关心原始分数,只使用结果在各路列表中的排名:
- $rank_r(d)$:文档 $d$ 在检索器 $r$ 中的名次;未被该路召回时可视为没有贡献。
- $k$:平滑常数,工程中常见经验值为 60,但应结合数据验证,不能把它当作定律。
例子:设 $k=60$,文档 A 在 BM25 中排第 1、在向量检索中排第 3:
文档同时被两路排在前面时会得到更高分。
RRF 的优势:
- 不需要校准不同检索器的分数尺度;
- 对极端分数不敏感;
- 实现简单,通常是很强的生产基线。
RRF 的限制:
- 丢失了原始分数中的置信度信息;
- 默认各路检索器同等重要;
- 仍需调每路召回数量、$k$ 以及最终候选数量。
如果需要为不同检索器设置重要性,也可以做加权 RRF:
并行混合与级联混合
并行召回
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 | Bi-Encoder:Encode(Query) × Encode(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. 分阶段调优
- 分别测 BM25 与 Dense,找到各自合理的 Top-N。
- 查看两路候选的重合率与独有相关文档,确认是否真的互补。
- 比较 RRF、加权融合等策略。
- 固定召回策略后再调 Reranker 的候选数和最终 Top-K。
- 最后评估端到端答案,不要只看检索指标。
3. 不要同时乱调所有参数
关键参数包括:
- Chunk 大小与 overlap;
- BM25 分词器、字段权重、同义词;
- Embedding 模型及距离函数;
- ANN 搜索深度;
- 每路 Top-N;
- 融合权重或 RRF 的 $k$;
- Rerank 候选数;
- 最终给 LLM 的 Top-K 和 token 预算。
应通过消融实验逐项判断收益,否则无法知道改进来自哪里。
评估指标
检索阶段
先设一个贯穿 Recall 和 Precision 的例子。对查询 $q$,语料库中共有 4 个相关文档:
检索系统返回的前 5 个结果依次为:
其中命中的相关文档为 ${D_3,D_1}$,一共 2 个。
Recall@K
前 K 个结果覆盖了多少相关文档。RAG 首轮召回最应关注这个指标,因为未召回的证据无法被后续精排恢复。
在上面的例子中:
含义是:系统找回了全部相关文档中的 50%,还有 $D_5$ 和 $D_7$ 没有被召回。即使后面使用再强的 Reranker,也无法恢复这两个不在候选集中的文档。
Precision@K
前 K 个结果中有多少是相关的:
在同一个例子中:
含义是:返回的 5 个结果中有 2 个相关,准确率为 40%。如果为了提高 Recall 把 K 调得很大,通常会召回更多相关文档,但也可能引入更多无关文档,使 Precision 降低。
MRR
关注第一个相关结果出现的位置,适合每个查询通常只有一个主要正确答案的场景:
例如有 3 个查询,它们的第一个相关结果分别出现在第 2、1、4 位,则:
MRR 只关心第一个相关结果:第一个相关结果从第 4 名升到第 1 名会显著提高 MRR,但第 1 名已经相关时,后面的相关文档如何排序不会改变该查询的 Reciprocal Rank。如果某个查询没有召回任何相关结果,通常令该查询的 Reciprocal Rank 为 0。
nDCG@K
同时考虑相关性等级和排序位置,适合存在“高度相关、部分相关”等多级标注的场景。
一种常见定义是:
其中 $rel_i$ 是第 $i$ 个结果的相关性等级,$IDCG$ 是把相关文档按相关性从高到低进行理想排序后得到的 DCG。
例如,系统前 4 个结果的相关性等级为:
等级 3 表示高度相关,0 表示不相关。该排序的 DCG 为:
理想排序为 $[3,2,1,0]$,因此:
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 | 高层:节点少、连接跨度大,用于快速接近目标区域 |
它类似于跳表:高层负责“远距离跳跃”,底层负责“局部精查”。大多数节点只存在于底层,少数节点会按随机机制出现在更高层。
查询过程
- 从最高层的入口节点开始;
- 在当前层不断移动到更接近 Query 的邻居;
- 当前层无法继续改进时,下降一层;
- 到达第 0 层后,不只做单路径贪心搜索,而是维护一个候选集合,在局部图中扩展;
- 返回距离最近的 Top-K 节点。
高层搜索用于快速定位,底层候选搜索决定最终 Recall。HNSW 在实践中通常有很低的查询延迟,但它不是严格意义上保证 $O(\log N)$ 的算法;性能取决于数据分布、图质量和参数配置。
建索引过程
插入一个新向量时:
- 随机决定该节点能出现的最高层;
- 从全局入口点开始,自顶向下寻找邻近区域;
- 在节点所属的每一层选择若干近邻并连边;
- 如果邻居数量超过限制,则使用启发式规则裁剪边,以兼顾局部邻近和图的可导航性。
构建优质图需要较多距离计算,所以 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 | Centroid 1 → [v2, v8, v15, ...] |
查询时先寻找与 Query 最近的几个聚类中心,只扫描这些聚类中的向量,而不是扫描整个数据库。
建索引过程
- 从语料向量中抽取有代表性的训练样本;
- 训练 $nlist$ 个聚类中心;
- 把每个文档向量分配给最近的中心;
- 在中心对应的倒排列表中保存向量 ID,以及原始向量或压缩编码。
IVF 存在训练阶段。训练样本不足、分布不均或线上数据发生漂移,都会导致聚类不平衡,进而影响 Recall 和延迟。
查询过程
- 计算 Query 与所有聚类中心的距离;
- 选择最近的 $nprobe$ 个聚类;
- 扫描这些聚类中的候选向量;
- 计算候选与 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 |
| 核心构建参数 | M、efConstruction |
nlist,以及 PQ 参数 |
| Recall | 在充足内存和合理参数下通常很高 | 取决于聚类质量和nprobe |
| 内存 | 较高,需要保存图边 | IVF-Flat 中等,IVF-PQ 较低 |
| 建索引 | 通常较慢 | 需要训练,但批量构建较规则 |
| 增量写入 | 通常较友好 | 可插入,但数据漂移会影响聚类质量 |
| 适合场景 | 低延迟、高 Recall 的在线检索 | 超大规模、内存受限、批量或 GPU 检索 |
不能简单地说 HNSW 一定比 IVF 好。选型应基于相同硬件和数据集上的 Recall-Latency-Memory 基准测试。
如何调优和选型
HNSW 调优顺序
- 根据内存预算选择 $M$;
- 用较高的
efConstruction构建质量较好的索引; - 固定 Top-K,逐渐增大
efSearch; - 绘制 Recall@K 与 P95/P99 Latency 曲线,选择满足 SLA 的点。
IVF 调优顺序
- 使用有代表性的样本训练聚类中心;
- 调整
nlist,观察列表大小是否均衡; - 固定 Top-K,逐渐增加
nprobe; - 内存不足时比较 IVF-Flat 与 IVF-PQ,并评估压缩损失;
- 同样根据 Recall-Latency-Memory 曲线选择参数。
一个简单的选择思路
- 数据规模不大:先测试 Flat,可能已经足够快且 Recall 为 100%;
- 追求高 Recall、低在线延迟,且内存充足:优先测试 HNSW;
- 数据规模极大或内存紧张:优先测试 IVF-PQ;
- GPU 批量查询或规则的离线检索:IVF 往往更容易发挥吞吐优势;
- 数据持续变化:HNSW 增量插入通常更直接,但仍要测试删除、过滤和维护成本。
在 RAG 混合检索中的位置
HNSW 和 IVF 只负责 Dense Retrieval 的向量候选召回,不能替代 BM25、RRF 或 Reranker:
1 | Query ─┬─ BM25 倒排索引 ───────────┐ |
如果把 efSearch 或 nprobe 调得过小,向量召回会漏掉真实近邻;这些文档无法进入融合与精排阶段。因此,RAG 中通常先保证 Dense 路径有足够的 Recall,再在延迟预算内调低搜索深度。
还要注意:ANN 的 Recall 是“找回精确向量近邻的比例”,而业务 Recall 是“找回人工标注相关文档的比例”。即使 ANN Recall 很高,Embedding 模型本身也可能没有把真正相关文档排在向量空间近处,所以两种 Recall 都应评估。
高频面试题
1. HNSW 为什么要分层?
高层节点少、边的跨度大,可以快速接近 Query 所在区域;底层节点完整,用于局部精细搜索。这样避免从入口点开始在完整图中进行大范围遍历,思想上类似跳表的多级跳跃。
2. efSearch 和 nprobe 有什么共同点?
它们都控制查询时的搜索深度。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 延迟、吞吐量、索引大小、构建时间和更新成本。
