跳到主要内容

重排序才是核心:为什么检索系统的瓶颈从来不在索引

· 阅读需 12 分钟
Tian Pan
Software Engineer

构建 RAG 系统的团队几乎普遍都会遇到同样的瓶颈:他们花一周时间调整 HNSW 索引参数,添加乘积量化(product quantization),将 recall@100 从 0.81 提高到 0.87 —— 然后发现 LLM 的输出质量几乎没有任何改观。投入数月努力所基于的假设是:更好的索引等于更好的回答。事实并非如此。瓶颈从来不在索引上。

真正的卡点在于候选集与上下文窗口(context window)之间的重排序(ranking)步骤。你喂给 LLM 的内容决定了它的输出,而重排序的工作就是确保那些真正相关的文档,而不仅仅是语义上最相似的文档,能够进入上下文。这种区别比你调整的任何 HNSW 配置都更重要。

为什么嵌入模型会误导相关性

双编码器(Bi-encoder)嵌入模型独立地对查询和每个文档进行编码,然后通过余弦相似度(cosine similarity)评分。模型在编码文档时并不知道查询的内容 —— 它将每段文本编码为向量空间中的独立点,然后测量几何邻近度。对于主题相关的内容,这很有效。但只要相关性涉及到上下文,它就难免失效。

想象一个法律语料库,查询语句是“根据 UCC §2-709 的延迟付款处罚”。通过余弦相似度检索到的前几块内容可能包括:UCC 第 2 条的总体概述、关于合同法中付款条款的段落,以及关于救济限制的段落。它们在语义上都很接近。但没有一个能回答问题。正确的段落被埋在第 23 位 —— 它明确提到了 §2-709,但在嵌入器(embedder)将其映射到远离查询质心的上下文中。

这不是什么罕见的极端情况。在专业领域,这是常态。嵌入捕获的是主题邻近性;而相关性关乎意图、具体性和上下文。索引调优无法修复一个缺乏对查询实际需求感知的模型。

结构性差距:双编码器独立地对查询和文档评分,因此没有交互信号。交叉编码器(cross-encoder)模型将查询-文档对作为组合输入,并对两者运行全注意力机制(full attention)。它能看出一个文档回答了一个特定问题,即使该文档的主题指纹很普通。这就是让重排序强大的洞察力。

两阶段架构及其存在的理由

你不能直接在整个语料库上运行交叉编码器的原因是延迟。交叉编码器在查询时需要对每个候选文档进行一次完整的正向传播 —— 无法预先计算。在 40 QPS 下,如果交叉编码器为每个查询对 10,000 个文档评分,你的 p99 延迟会直接崩溃。在这种规模下,交叉编码器的开销会使 p99.9 超过 21 秒。

两阶段流水线解决了这个问题:

第一阶段 —— 候选生成(Candidate generation):运行快速的近似最近邻检索(使用 HNSW 的 ANN)或 BM25,在 5-30 毫秒内生成 100–500 个候选文档。这一阶段优化的是召回率(recall)—— 目标是不遗漏任何相关内容,即使代价是包含了一些不相关的文档。

第二阶段 —— 重排序(Reranking):仅对候选清单应用昂贵且精确的模型。交叉编码器处理 50–100 个文档会增加 100–200 毫秒,使大多数生产用例的总延迟保持在 300 毫秒以下。

关键的设计约束:第一阶段的工作是召回,而不是精确度(precision)。你不需要它非常擅长排序。你需要它确保真正相关的文档出现在前 500 名中。然后由第二阶段负责精确度工作。

这在实践中的意义是:停止用 NDCG 衡量第一阶段。请用 recall@k 来衡量。你需要的是“是否有相关文档出现在前 200 名中?”,并以此为目标进行优化。NDCG 会惩罚候选集内部的排序失败,但在你将其交给重排序器(reranker)时,这并不重要。

交叉编码器、ColBERT 和稀疏模型如何各司其职

并非所有重排序器的表现都一样。在它们之间做选择是延迟、吞吐量和准确性之间的权衡。

交叉编码器(例如 BGE Reranker、SBERT 交叉编码器、Cohere Rerank)联合编码查询-文档对。它们达到了最先进的准确率 —— 顶尖的交叉编码器在 MS MARCO 上的 MRR@10 超过了 0.40 —— 但每个查询的成本很高。它们无法预先计算文档表示,因此每个请求都需要从头开始对所有候选文档评分。这适用于质量比吞吐量更重要,且候选集限定在约 50–100 个文档的应用场景。

ColBERT(Contextualized Late Interaction,上下文延迟交互)采用了不同的方法:它离线预先计算文档的令牌级(token-level)嵌入,然后在查询时通过令牌交互的 MaxSim 操作进行评分,而不是进行完整的正向传播。这在 40 QPS 下产生了约 23 毫秒的 p50 延迟 —— 在质量相当的情况下,比交叉编码器快大约 10 倍。当你需要在更高吞吐量下获得接近交叉编码器的质量时,这是正确的选择。

学习型稀疏模型像 SPLADE 这样的工作方式与前两者都不同。它们产生的不是稠密向量,而是在词汇空间上的稀疏表示,识别哪些术语(terms)重要以及权重是多少。结果是:索引内存比稠密模型少 71%,查询时使用 CPU 兼容的倒排索引,并内置术语扩展功能,可以捕捉到原始查询漏掉的变体。SPLADE 并不取代重排序器,而是改变了第一阶段的架构:稀疏检索生成的候选集比 BM25 语义更丰富,但在大规模运行时比稠密 ANN 成本更低。

混合第一阶段 + 交叉编码器重排序器是目前性能的天花板。将 BM25 与稠密 ANN 检索结合(使用互惠排名融合 Reciprocal Rank Fusion 合并它们的排名列表),然后对合并后的前 50 名应用交叉编码器,在 BEIR 基准测试上比单一方法产生 12% 的 NDCG 提升,在 TREC 评估上比纯稠密检索产生 24–48% 的提升。

当你仅优化索引时会发生什么

过度投入向量索引调优会导致一种微妙且危险的失效模式:基础设施指标看起来很健康,但输出质量却在下降。

随着语料库的增长,即使索引参数保持不变,HNSW 的召回率也会下降。虽然嵌入模型和距离度量完全相同,但对于大规模数据集,图结构的扩展性不如暴力搜索(flat search)。此时,延迟和吞吐量指标保持稳定,查询成本也没有激增。然而,喂给 LLM 的上下文质量正在缓慢侵蚀——top-k 中相关的段落变少了,更多无关内容填满了上下文窗口,下游输出的幻觉率也随之升高。在 SRE 团队监控的每个仪表盘上,系统依然显示为代表健康的绿色。

如果团队将工程周期花费在调优 ef_constructionm 和量化参数上,那么他们优化的是一个在召回率达到合理水平后,对端到端输出质量的边际贡献极小的组件。单纯索引优化的上限受限于双编码器(bi-encoder)模型的表征能力——而任何 HNSW 参数都无法改变这一点。

转折点在于:一旦你所在领域的 recall@100 超过 0.75–0.80,额外的索引调优收益将递减。这时你应该停止衡量第一阶段(Stage 1)的质量,开始投入到第二阶段(Stage 2)。

为生产环境选择重排序器(Reranker)

选择重排序器的实际决策树如下:

如果成本不是限制因素,首选 Cohere Rerank。它是专有 API,在 BEIR 基准测试中始终保持顶尖水平,集成简单。对于追求极致准确率且希望零基础设施开销的生产级 RAG 来说,这是正确答案。

BGE Reranker (BAAI) 是最强大的开源交叉编码器(cross-encoder)选项。提供 base 和 large 两种尺寸。它在 BEIR 和 MS MARCO 上表现强劲,被广泛采用,成本低于 Cohere,可以本地运行或部署在自己的推理基础设施上。当你需要避免 API 依赖时,它是默认选择。

当吞吐量比简洁性更重要时,选择 ColBERT。虽然它的基础设施复杂度更高(你需要管理 MaxSim 索引,而不只是模型权重),但在大规模场景下具有显著更好的延迟特性。

基于 LLM 的重排序器(如 RankLLM, RankVicuna)适用于尚不存在微调交叉编码器的专业领域的零样本列表排序(zero-shot listwise ranking)。它们延迟高、质量高,当你正在评估一个新领域且尚未拥有用于微调的训练数据时非常有用。

一个经常让团队感到惊讶的发现是:重排序器之间的域内(in-domain)差距很小。如果你的数据分布与重排序器的训练分布相匹配,BGE 和 Cohere Rerank 的表现将非常接近。但这种差距在跨域(out-of-domain)时会急剧扩大。对于专业语料库(如法律、生物医学、金融),要么在你的数据上微调开源重排序器,要么在投入使用通用模型之前进行仔细衡量。

指标陷阱:当 Recall@k 误导你时

RAG 评估中最昂贵的错误是将检索指标视为端到端质量的代标。

Recall@100 仅告诉你前 100 个结果中是否出现了任何相关文档。它完全没有说明相关文档是排在第一、第五还是第九十七位。当你的上下文窗口只能容纳 5 个段落时,排名第 1 和第 50 之间的差距就是正确答案与幻觉之间的差距。

一个 recall@100 为 0.87 且没有重排序器的系统,很容易被一个 recall@100 仅为 0.70 但配备了调优良好的交叉编码器的系统超越,因为后者能确保找到的那一个相关文档排在第一位。

能够发现这一问题的评估规范是:始终进行端到端衡量。忠实度(Faithfulness,答案是否与检索到的段落匹配?)和答案相关性(Answer Relevance,答案是否解决了查询?)捕获了检索指标无法捕获的信息。如果 recall@k 的提升没有带来忠实度或答案相关性的提升,那么这种检索层面的指标改进就没有转化为输出质量——这通常意味着排名(ranking)而非召回(recall)才是瓶颈。

下一次迭代的样子

重排序领域发展迅速。HyperRAG (2025) 引入了跨文档的重排序器 KV 缓存复用,减少了交叉编码器在多文档设置中昂贵的重复计算。FlashRank 提供了一个约 4MB 的超轻量级重排序包,不依赖 Torch,适用于边缘端部署。Answer.AI 的 rerankers 库为交叉编码器、ColBERT 和 LLM 重排序器提供了统一的 Python API,降低了尝试不同方法的集成成本。

重排序的缩放定律(Scaling laws)正逐渐被刻画出来。与生成和稠密检索(dense retrieval)——其缩放行为已有详尽记录——不同,重排序模型的计算-质量曲线尚未被完全映射。早期证据表明重排序具有良好的缩放效应,这意味着当前模型的质量可能远未达到上限。

实际意义在于:现在就开始构建重排序基础设施的团队——即使使用的是今天的模型——也正在为吸收未来的模型质量提升做好准备,而无需更改其检索架构。管道投资具有复利效应,而索引调优投资则会陷入停滞。

应该把工程时间花在哪里

这一分析得出的优先级顺序如下:

一旦你的领域 recall@k 超过 0.75,就停止优化索引。构建一个两阶段检索流水线:第一阶段使用 ANN 或混合检索,第二阶段使用 cross-encoder。使用端到端的质量指标进行衡量,而不是检索层级的代理指标。在假定仅靠稠密检索(dense retrieval)就足够之前,先尝试使用倒数排名融合(Reciprocal Rank Fusion, RRF)的混合第一阶段检索(BM25 + 稠密检索)。

在生产环境中交付最高质量 RAG 系统的团队,并不是那些把 HNSW 图调得最好的团队。而是那些尽早意识到排序(ranking)才是真正的问题所在,并围绕这一点构建架构的团队。

References:Let's stay in touch and Follow me for more thoughts and updates