混合检索与重排序 Hybrid Retrieval & Reranking
所属:01-fundamentals / 03-rag
面试权重:★★★
来源:通用领域知识(B站「RAG 面试」合集高频);未做一手考证
一句话#
BM25 与稠密向量是互补的:前者擅长精确词面(专有名词、型号、代码、数字),后者擅长语义改写(同义词、换一种说法);hybrid 检索用融合把两路结果并起来解决「召回」,reranker(cross-encoder) 再对候选池做精细排序解决「精度」—— 前者保 recall,后者保 precision,两者不是替代关系而是流水线的两级。
为什么重要(面试角度)#
这是 RAG 里最能体现「你真的调过系统」的一题。面试官想听的是分工逻辑:
- 为什么单靠稠密向量会输给 BM25(举得出具体形态:把
RFC 7231或Qwen2.5-4B这类字符串 embed 到语义空间里就失真了) - 为什么单靠 BM25 也不行(用户说「笔记本发烫怎么办」,文档写「设备过热处理」,没有共现词)
- RRF 为什么好用(只用排名、不用分数,绕开两路分数量纲不可比的问题)
- bi-encoder 与 cross-encoder 的本质差别:一次编码 vs 联合编码,以及由此决定的「能不能预建索引」
- 什么时候 reranking 不值得它的延迟(这点最能区分有没有算过账)
核心内容#
为什么互补:两种「相似」的定义#
| 稀疏检索(BM25) | 稠密检索(dense) | |
|---|---|---|
| 匹配依据 | 词面重合(term overlap) | 语义距离(embedding 相似度) |
| 词表 | 显式倒排,精确 | 隐式,模糊 |
| 强项 | 专有名词、型号、代码、错误码、数字、长尾术语 | 同义改写、口语→术语、跨语言 |
| 弱项 | 无法处理同义词/改写、词形变化 | 罕见串/ID 会被「平均」掉语义、无法保证精确命中 |
| 可解释性 | 高(能指到具体词) | 低 |
| 需要训练 | 几乎不需要(统计) | 需要(且域不匹配就得微调) |
典型互补场景:用户问「RFC 7231 里的 503 是什么」—— BM25 精确命中 RFC 7231 与 503,稠密向量反而因为把这些稀有串投进语义空间而丢失精确性;用户问「网站被爬太狠了怎么保护」—— 文档写「设置 rate limit」,两者零词面重合,只有稠密向量能救。
BM25 的两个旋钮#
BM25 打分(按查询词求和,直观理解即可):
IDF:稀有词权重高,「的」「是」这类高频词几乎不贡献分数 —— 这是它天然抗噪声的地方。
⚠️ 这里此前只写了泛化的 ,被追问「BM25 的 IDF 具体是什么形式」时会答不上。 两条要知道:
① BM25 的 IDF 与 TF-IDF 的 IDF 不是同一个公式。 BM25 常用的平滑形式是 ( 是文档总数, 是含该词的文档数)。 ② 它有负值区间:当 (词出现在超过一半文档里)时,这个 取负值 —— 也就是「这个常见词反而让文档减分」。实际实现里必须给它设下限(常见做法是把负的 IDF 截到 0 或一个很小的正数),否则一个高频词会把整篇文档的分数拉低。这是一个知道就知道、不知道会被问住的实现细节。
另有一条脉络值得有:稀疏检索的演进是 BoW → TF-IDF → BM25,而倒排索引(词 → 含该词的文档列表)是它们共享的底层数据结构 —— 理解倒排索引就理解了稀疏检索为什么「精确匹配强、语义弱」。
:控制词频饱和 —— 一个词出现 10 次不该比出现 3 次线性地更重要。
:控制长度归一化强度,防止长文档仅因「长」而累计高分。
BM25 无需训练、极致可解释,在长尾/精确匹配上是很难打败的基线 —— 先上 BM25 再谈向量,是很常见的工程判断。
融合:RRF 与加权求和#
两路检索各自返回一个排序,融合方式主要有两类:
① 加权分数融合:把两路分数归一化后加权求和 α·s_dense + (1-α)·s_sparse。
问题:两路分数量纲不可比(BM25 无上界、cosine 在 [-1,1]),归一化方式会显著影响结果,α 还得调。工程上能用但脆弱。
② RRF(Reciprocal Rank Fusion):
只用排名、完全不管分数,所以天然规避了量纲问题,也天然鲁棒(不同检索器、不同数据集都免调参)。原论文取 k = 60,实践中也常用其他取值 —— k 越小、靠前的排名权重差异越大。这是面试里最该主动说出的融合方案:简单、无超参、效果好。
RRF 的局限:它丢弃了分数信息,若某一路的分数分布本身很有信息(比如 reranker 的分数),加权融合或直接串联更合适。实践中常见架构是「BM25 ∪ dense → RRF → reranker」。
Bi-Encoder vs Cross-Encoder#
这是全题的核心,用一张表说清:
| Bi-Encoder(召回用) | Cross-Encoder(重排用) | |
|---|---|---|
| 编码方式 | query 与 doc 各自独立编码 | query+doc 拼在一起送入同一模型 |
| 交互 | 无(最后才算相似度) | 全交叉自注意力,逐 token 交互 |
| 精度 | 较低(信息交互被压缩到向量里) | 高(能捕捉细粒度匹配与否定) |
| 速度 | 极快,doc 向量可离线预建索引 | 慢,每个 query 都要对每个候选现算一次前向 |
| 可扩展性 | 支持百万~十亿级语料 | 只能对几十~几百个候选做 |
| 经典用法 | 粗筛(retrieve) | 精排(rerank) |
关键洞察:不是「cross-encoder 更强所以用它检索」,而是成本结构决定分工 —— cross-encoder 无法预建索引,对全语料打分是 O(N) 次前向,完全不可行;但对 50–100 个候选打分是完全可接受的。所以流水线必然是「廉价高召回 → 昂贵高精度」。
ColBERT(late interaction) 是中间路线:每篇文档预先编码成多向量(每个 token 一个向量),查询时做 MaxSim 的 token 级最大匹配 —— 介于 bi-encoder 与 cross-encoder 之间,可预建索引但索引体积大(多条向量/文档)。知道它存在、以及它的取舍即可。
什么时候 reranking 值得它的延迟#
Reranking 不是免费的:它引入额外延迟(对 50–100 个候选跑一次 cross-encoder 前向,量级在几十到几百毫秒,具体取决于模型大小与硬件,需实测)和额外部署。值得投入的判断:
- ✅ 召回没问题、排序有问题:正确 chunk 已在 top-50 里但排不进 top-5 —— 这是 reranker 最典型的用武之地(也正是
pipeline.md决策树第 3 步)。 - ✅ 注入的上下文槽位稀缺:只能给 LLM 5 个 chunk,把最相关的挑出来直接影响 Faithfulness。
- ✅ 查询类型多样,单一检索器覆盖不全,需要统一的精排层收口。
- ❌ 检索本身召回就低:正确文档压根不在候选池里,reranker 无中生有不了 —— 先修检索(chunking / hybrid / query rewriting)。
- ❌ 延迟极度敏感的场景(如实时语音助手),几十到几百毫秒可能是不可接受的,需权衡或用更小的 reranker。
- ❌ 语料小且问题简单:top-5 已经对了,加 reranker 只是徒增延迟。
工程做法:把 reranker 做成可开关 / 可降级的一层,用 A/B 或离线评测对比「有 rerank vs 无 rerank」在端到端质量与 P95 延迟上的净收益,再决定压在哪个查询集合上。
一条典型流水线#
query
├─ BM25 → top-50 ┐
└─ dense (ANN) → top-50 ┤
├─ RRF 融合 → top-20~50
└─ cross-encoder rerank → top-5
↓ 注入 prompt规模上注意:RRF 融合后的候选池越大,reranker 越慢 —— 候选池大小就是这一步的主要延迟旋钮。
常见面试问法#
Q:已经有向量检索了,为什么还要 BM25? A:两者匹配的东西不同。BM25 匹配词面,稠密向量匹配语义。专有名词、型号、错误码、数字这类稀有串在 embedding 里会被「平均」掉,向量检索不可靠;而同义词改写又只有向量能处理。hybrid 是把两类失败互补掉,而不是谁取代谁。
Q:两路分数怎么融合?
A:首选 RRF,因为只用排名不用分数,绕开了 BM25 与 cosine 分数量纲不可比的问题,且几乎免调参(原论文 k=60)。加权分数融合也可行,但要先归一化且要调 α,脆弱一些。实践中常见 BM25 ∪ dense → RRF → reranker。
Q:bi-encoder 和 cross-encoder 的区别? A:bi-encoder 分别编码 query 和 doc,交互被压缩到向量点积里,精度低但可离线预建索引,能扛百万级语料;cross-encoder 把两者拼起来做全交叉注意力,精度高但每个候选都要现算一次前向,只能对几十~几百个候选做。所以流水线是「bi-encoder 粗筛 + cross-encoder 精排」。
Q:加 reranker 一定更好吗? A:不一定。前提是正确结果已经进了候选池 —— 召回本身低的话 reranker 无能为力,先修检索。其次看延迟预算:对几十个候选跑一次 cross-encoder 会增加延迟,实时性敏感的场景可能不划算。所以它应该是可开关的一层,用评测决定是否净收益。
Q:top-k 怎么定? A:分两段看。候选池要够大以保住召回(几十量级),注入 LLM 的 top-n 要小以控噪声与成本(几个量级)。中间靠 reranker 把信号挑出来 —— 用「宽召回 + 精排序 + 窄注入」替代「一个 k 打天下」。
易错点#
- ❌ 说「有向量检索就不需要 BM25 了」—— 忽略了精确词面匹配这一类硬需求。
- ❌ 用未归一化的分数直接加权融合,或声称「权重怎么定都行」。
- ❌ 混淆 bi-encoder / cross-encoder 的成本结构,说出「用 cross-encoder 建索引」这种不可能的事。
- ❌ 把 reranking 当默认必开,不评估其延迟与「召回不足时无效」的前提。
- ❌ 用一个大 k 同时当候选池和注入数,导致噪声与成本双高。
- ❌ 把 RRF 的
k=60当唯一正确取值(它是原论文取值,实践中有别的用法)。
自测#
1. 举一个稠密向量必定输给 BM25 的例子,和一个 BM25 必定输给稠密向量的例子。
答案
BM25 胜:查「RFC 7231 的 503 状态码」「错误码 E5023 怎么解决」「Qwen2.5-4B 的显存占用」—— 这类稀有字符串/编号/型号在稠密空间里会被语义平滑掉,词面精确匹配才可靠。稠密胜:用户问「网页被爬虫抓爆了怎么办」而文档写「配置 rate limit 与 WAF 规则」—— 零词面重合,只有语义向量能建立联系。这正是 hybrid 检索的动机。
2. 为什么 RRF 比「加权分数求和」更受工程欢迎?
答案
因为 RRF 只用排名,不用分数:BM25 分数无上界、cosine 在 [-1,1],两路量纲不可比,加权求和必须先归一化并调 α,脆弱且换数据集就失效。RRF 的形式 Σ 1/(k+rank) 天然把不同检索器的输出统一到可比尺度,几乎免调参,对新增检索器也鲁棒。代价是丢弃了分数本身的信息。
3. 为什么不能直接用 cross-encoder 做检索?
答案
成本结构不允许。cross-encoder 把 query 与 doc 拼接后做全交叉注意力,无法为文档预计算并复用的向量(表示依赖具体的 query),所以对全语料是 O(N) 次前向,百万级语料完全不可行。它只能对已由廉价检索器缩小的候选池(几十~几百)做精排。这就是「粗筛用 bi-encoder、精排用 cross-encoder」这一流水线结构的根本原因,而不是精度差异本身。
4. 你的 RAG 已经用了 hybrid + reranker,端到端还是不准。你会怎么判断 reranker 是不是有用?
答案
先验证前提:用 gold chunk 算 Recall@候选池,确认正确答案确实进了 reranker 的输入池。若没进,reranker 无从发挥,问题在检索侧(chunking / 召回 / 查询改写)。若进了,再看 reranker 输出里 gold 的排名 —— 排不进 top-n 说明重排无效或候选池存在大量强干扰;做「开/关 reranker」的离线对照(Recall@n、Faithfulness)和延迟对照(P95),若质量提升不显著、延迟增加明显,就该关掉或只对特定查询启用。
5. 「宽召回 + 精排序 + 窄注入」为什么比「一个 top-k 打天下」好?
答案
因为召回与注入的最优粒度方向相反:候选池要大才能保住 recall(正确结果可能在 50 名开外),注入 LLM 的上下文要小才能控噪声、控 token 成本、避免 lost-in-the-middle。一个统一的 k 必然在某一端妥协 —— 小了召回不足,大了噪声与成本失控。中间用一个便宜的粗排 + 一个高质量的精排把「大池」收敛成「小集」,是同时满足两端约束的结构。