跳到主要内容

生产环境中的混合检索:为什么 BM25 在关键查询上仍然更胜一筹

· 阅读需 13 分钟
Tian Pan
Software Engineer

BM25 发表于 1994 年,其数学原理简单到可以写在白板上。然而在 2025 年的生产检索基准测试中,它在真实世界查询的相当一部分场景中,依然超越了数十亿参数规模的稠密嵌入模型。那些在部署纯向量检索后才发现这一问题的团队,往往是以最糟糕的方式发现的:用户反馈幻觉,而他们在评估集中却无法复现——因为评估集是从那些已经能正常工作的查询中构建的。

这是检索领域的抽样偏差。稠密检索在特定且可预测的查询形式上会失败。这种失败是无声的——大语言模型仍会从检索到的任何片段中生成流畅、自信的答案。没有错误日志,没有延迟异常,只是对查询产品 SKU、错误码、API 名称或任何词汇特定而非语义通用内容的用户,悄悄给出了错误的回答。

解决方案是混合检索。但"混合检索"作为一个工程决策,本身定义不够充分。本文将介绍实际的失败模式、如何正确融合检索信号、重排序层的位置,以及——最关键的——如何在用户发现之前,找到当前流水线正在悄悄失败的查询类型。

稠密检索出错的查询类型

稠密嵌入模型通过对所有词元表示做池化,将一段文本编码为单一的固定大小向量。这种池化正是实现语义泛化的关键——"vacation"(假期)和"time off"(休假)在几何空间中距离很近——但同时也会模糊具体字符串的词汇特征。

当用户查询 ERR_SSL_VERSION_OR_CIPHER_MISMATCH 时,这个精确的词元序列会与上下文窗口中的所有其他词元取平均。最终生成的向量捕捉到的是"关于 SSL 错误的文档",而不是"包含这个特定错误字符串的文档"。相比之下,BM25 对精确词元的倒排索引进行打分,要么找到该词条,要么找不到,没有任何模糊性。

在纯稠密检索下系统性失败的查询类型遵循一致的规律:

  • 错误码和标识符0x80070005INV-2024-00847ENOMEM 等精确字符串的语义表示信号几乎为零。嵌入模型无法有原则地区分外观相似的标识符。
  • 产品 SKU 和型号RTX-4090RTX-4070 在语义上几乎相同——在嵌入空间中是相邻向量——但它们是规格和价格都不同的两款产品。
  • 函数名和库标识符:查询 torch.nn.functional.cross_entropy 生成的嵌入接近其他 PyTorch 文档,但不能可靠地接近包含该特定调用签名的文档
  • 罕见命名实体:当查询包含一个罕见且高度特定的词条与常见词语时,罕见词条的信号会被平均掉。BM25 的倒排索引不受稀疏性影响——出现在 5 个文档中的词条与出现在 500 万个文档中的词条匹配精度一样高。
  • 受控词汇的领域术语:技术文档、法律文本和医疗记录使用特定的专业术语,这些术语出现一致。在这里,语义模糊会在精确性至关重要的地方引入错误。

2021 年的 BEIR 基准测试让这一点显得格外尴尬:在 MS MARCO 上训练的稠密检索模型,在零样本跨领域评估中经常无法超越 BM25。这一结果促使大多数认真对待 RAG 的团队,停止将稠密检索视为 BM25 的替代品,而开始将其视为补充。

无声失败模式

更难处理的问题是这些失败不会主动暴露。

当 BM25 漏检一个文档时,检索集明显不完整。当稠密检索因查询包含罕见标识符而漏检文档时,大语言模型仍然得到一个检索集——只是检索到了错误的内容。它从检索到的任何片段中生成流畅、合理的回答。这恰恰是最难在评估中捕捉到的幻觉形式:自信、内部一致,却在具体细节上是错误的。

这种诊断不对称性会随时间加剧。从生产查询日志中构建的评估集包含系统给出用户接受答案的案例。而系统自信地向不了解情况的用户提供了错误具体信息的那些案例,系统性地缺失于反馈信号中。最终你会针对那些已经处理好的查询来优化检索。

实际后果是:部署了纯稠密检索的团队,往往存在其评估指标无法反映的检索质量问题。他们内部评估集上的 Recall@10 看起来尚可接受。涉及精确标识符的查询的 Recall@10 很低,但没有人检查,因为这些查询从未进入评估集。

诊断部分会介绍如何从测量角度解决这个问题。但架构上的解决方案是混合检索——同时运行两条检索路径并融合结果。

分数融合:RRF 与凸组合

混合检索产生两个排序列表——一个来自 BM25,一个来自稠密 ANN——需要将它们合并为单一的排序结果。两种主要方法有着有意义的权衡差异。

互惠排名融合(RRF) 将每个候选文档在各排序列表中的位置转化为分数:1/(k + rank),其中按惯例 k=60。在两个列表中均排名靠前的文档会积累更高的分数。RRF 的关键优势在于它与分数量纲无关——BM25 分数和余弦相似度处于完全不兼容的量纲上,而 RRF 通过对排名而非分数进行操作来规避归一化问题。

这使 RRF 成为安全的默认选项。它不需要标注数据,对分布偏移具有鲁棒性,所有主流向量数据库都原生支持:Elasticsearch 8.x、OpenSearch 2.12+、Weaviate、Qdrant。对于冷启动的混合检索,从这里开始。

局限性在于 RRF 丢弃了分数的量级信息。余弦相似度 0.99 排名第一的文档,与相似度 0.51 排名第一的文档,得到相同的 RRF 贡献。当检索集中确实有分数所携带的质量信号时——而不仅仅是排名——这些信息就丢失了。

凸组合 解决了这个问题:score = α × score_dense + (1-α) × score_sparse。Bruch 等人(2022 年)的研究表明,只需约 40 个标注的查询-相关性对,调整这个单一的 α 参数就能在域内和域外评估中持续超越 RRF。归一化方法(最小-最大、z-score 或其他线性归一化)是次要因素——任何线性归一化都能产生相近的结果。

实践中,α 应根据查询领域进行调整:

  • 使用受控术语的技术文档:α ≈ 0.3(重点加权稀疏)
  • 对话类和策略文档:α ≈ 0.7–0.8(重点加权语义)
  • 均衡混合内容:α ≈ 0.6

2025 年的前沿是按查询动态调整 alpha——检测传入查询是关键词型还是语义型,在查询时动态调整 α,而不是按集合统一设定。

Elasticsearch 的实际基准测试(Wands 家具数据集,2025 年)说明了这一差距:简单 RRF 在 BM25 基线上提升了约 1.3% 的 NDCG,而一种分级方法——将全词匹配文档提权 100 倍、任意词匹配提权 10 倍、回退向量权重 0.1——提升了 7.5%,表明朴素 RRF 显著低估了经过精调的混合检索所能达到的水平。

重排序层

检索是高召回问题。重排序是高精度问题。它们需要不同的模型,混淆这两点是常见的架构错误。

结构是两阶段的:

  1. 第一阶段(检索):混合 BM25 + 稠密 ANN 加 RRF 融合,获取 top-100 候选。速度快、召回率高,基于预计算索引运行。
  2. 第二阶段(重排序):交叉编码器模型对每个(查询,候选)对进行联合评分——模型同时看到两者,并以两者间的完整注意力机制产生相关性分数。应用于 top 30–50 候选,而非全集。

交叉编码器比双编码器精度高得多,因为它能捕捉短语级别的对齐和词元间依赖关系。代价是无法预计算文档表示——每个(查询,文档)对都需要一次前向传播。这使其在任何有意义的规模下都不适合作为第一阶段检索器,但适合对小型候选集进行重排序。

延迟计算不太直观。对 30 个候选应用交叉编码器大约需要 100–200ms。对 200 个候选应用同一模型会使延迟预算超出 5–10 倍。实际规则:固定好第一阶段召回,使其不需要重排序超过 50 个候选。如果需要重排序 200+ 候选才能获得可接受的精度,说明第一阶段检索已经出问题了。

ColBERT 风格的后期交互提供了介于双编码器和交叉编码器之间的替代方案。MaxSim 机制将相关性计算为每个查询词元在文档词元上的最大相似度之和——在保留短语级对齐的同时允许预计算文档表示。BGE-M3 等模型(2024 年 1 月发布)在单个 550M 参数检查点中统一了稠密、稀疏和后期交互三种模式,大幅降低了此前需要三个独立模型的团队的基础设施复杂度。

推荐的生产流水线如下:混合检索 → top-100 → MMR 去重以移除近乎相同的片段 → 交叉编码器重排序 → top 5 到 top 10 进入 LLM 上下文窗口。在重排序前加入 MMR 很重要,因为向交叉编码器发送近乎重复的片段会浪费其容量,并将真正不同的相关文档推出排名。

诊断你在哪些查询类型上失败

标准检索指标——Recall@10、MRR、NDCG@10——如果评估集从生产查询日志构建,看起来会不错。它们捕捉系统能处理的查询,却遗漏了悄然失败的查询。

修复需要对评估集的构建和分析方式进行两项改变。

按查询类型分层。 按结构类型对查询日志进行聚类:精确标识符查询(包含特定代码、SKU 或名称)、关键词密集型查询(包含特定罕见技术术语)、语义查询(没有特定标识符的概念性问题)、导航型查询(按标题或引用查找特定文档)。分别计算每个聚类的 Recall@K。如果关键词和标识符聚类的召回率为 40%,而语义聚类为 85%,你就清楚地知道该把精力投向何处。

合成对抗性查询。 生产查询系统性地排除了失败案例。使用大语言模型生成包含从文档语料中提取的精确标识符的合成查询——型号、错误码、函数名、合同 ID。通过你的检索流水线运行这些查询,并检查源文档是否能被检索回来。这些是你目前未能测量到的失败案例。

嵌入退化问题与此相关但有所不同:随着文档语料更新、大语言模型换代以及嵌入模型重新训练,你的向量索引会逐渐退化。监测余弦相似度分数分布随时间的变化。平均 top-1 相似度的下降趋势,是在下游大语言模型输出质量可见之前,检索质量已在侵蚀的信号。

具有健康混合检索的生产系统,在 FAQ 类应用中应能看到 Recall@10 约 85–91%,MRR 高于 0.80,以及 Hit Rate@10(top 10 中至少有一个相关文档)高于 90%。达到这些数字既需要正确的架构,也需要正确的评估方法才能准确衡量。

实际应该构建什么

对大多数团队而言,正确的起点是利用现有向量数据库的原生能力进行基于 RRF 的混合检索。Elasticsearch、Weaviate、OpenSearch、Qdrant 和 Pinecone 都开箱即支持。工作量在于配置,而非实现。

一旦你拥有 40 个以上针对你的领域的标注查询-相关性对,就从 RRF 迁移到经过调优的凸组合。跨基准测试的边际改进是一致的,实现也很简单。

当你观察到持续的精度差距时——当正确文档出现在位置 3–8 而非 1–2 时——才加入交叉编码器重排序。将候选集保持在 50 以下,以控制在延迟预算内。

最高 ROI 的诊断投资,是在优化任何其他方面之前,先用精确标识符查询构建对抗性评估集。跳过这一步的团队,会针对已经能处理的查询来优化检索,上线混合检索,然后对标识符密集型查询的幻觉率没有改善感到困惑。问题不在于检索架构——而在于流水线从未将那些查询纳入测量范围,现在你加入了混合检索,却没有衡量它是否帮助了最需要帮助的查询类型。

稠密嵌入在语义检索上确实强大。BM25 在词汇检索上确实强大。几年前"哪个更好"就不再是生产中的争论焦点了。现在完全取决于你对各自失败场景的理解有多精确——以及你是否构建了知道哪个缺口代价最大的测量基础设施。

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