AI 技术
#RAG#重排序#交叉编码器#双编码器#信息检索#级联架构

重排序模型在 RAG 中的精度-延迟权衡:交叉编码器与双编码器的对比

本文聚焦 RAG 检索后重排序阶段,以多路召回融合为场景,对比交叉编码器(如 BGE-reranker)与双编码器在精度和延迟上的差异。通过分析级联架构、模型蒸馏与批处理优化,解释重排序如何缓解“中间丢失”问题,帮助读者在工程实践中做出权衡。

在 RAG 系统中,检索阶段通常返回数十甚至上百个候选文档,但其中只有少数真正包含答案。一个常见的工程问题是:第一轮检索的 top-k 结果中,相关文档可能排在靠后位置,而无关文档反而靠前。这种现象在混合多路召回(例如同时使用稀疏检索 BM25 和稠密向量检索)时尤为突出——不同召回通路对“相关性”的定义存在差异,导致结果列表中存在大量噪声。如果直接把这些文档拼接后交给大模型生成,不仅会浪费上下文窗口,还可能引入误导信息。

直觉上,我们可以提高检索阶段的召回量,比如从 top-20 增加到 top-100,但这样会显著增加后续处理的负担。更精细的做法是在检索之后插入一个重排序(Reranking)环节,对候选文档进行二次筛选和重排。重排序模型能够更准确地判断查询与文档的匹配程度,但代价是更高的计算开销。本文将以一个企业知识库问答系统为贯穿场景,该系统使用 BM25 和稠密向量检索进行多路召回,每路返回 20 个文档,合并去重后得到约 35 个候选。我们将对比两种主流重排序架构——交叉编码器和双编码器——在精度和延迟上的权衡,并讨论如何通过级联架构、模型蒸馏和批处理优化来平衡系统性能。

为什么需要重排序:多路召回与“中间丢失”

多路召回融合是提升检索覆盖率的常用手段。例如,BM25 擅长关键词匹配,能捕捉到精确的术语出现;而稠密向量检索通过语义相似度,可以找到表达方式不同但含义相近的文档。然而,简单合并两路结果会带来两个问题:一是排名不一致,同一文档在两路中的得分尺度不同,无法直接比较;二是“中间丢失”现象——某些相关文档在各自通路中都未进入 top-k,但综合来看却应该被选中。

“中间丢失”指的是:一个文档在 BM25 中排第 15 名,在向量检索中排第 18 名,如果每路只取 top-10,它就会被遗漏。即使扩大每路的召回量,合并后的列表仍然缺乏统一的排序标准。重排序模型的作用正是对合并后的候选集进行统一评分,利用更精细的交互方式重新排序,从而把那些被埋没的相关文档提升到前列。

交叉编码器:深度交互的精度优势

交叉编码器(Cross-Encoder)将查询和文档拼接成一个输入序列,直接送入预训练语言模型(如 BERT),通过全自注意力机制让查询和文档的每个 token 进行交互。模型输出一个相关性分数,通常由一个线性层投影得到。这种联合编码方式能够捕捉到细粒度的语义匹配信号,例如同义词替换、词序变化和上下文依赖。

以 BGE-reranker-large 为例,它基于 XLM-RoBERTa 架构,在多个重排序基准上表现优异。在 MTEB 的 CMedQAv2 数据集上,其 MAP 达到 84.10,MRR 达到 86.79。在实际使用中,可以通过 HuggingFace Transformers 加载模型进行推理:

from transformers import AutoTokenizer, AutoModelForSequenceClassification
tokenizer = AutoTokenizer.from_pretrained("BAAI/bge-reranker-large")
model = AutoModelForSequenceClassification.from_pretrained("BAAI/bge-reranker-large")
model.eval()

pairs = [["什么是重排序?", "重排序是对检索结果进行二次排序的过程。"]]
inputs = tokenizer(pairs, padding=True, truncation=True, return_tensors='pt')
scores = model(**inputs).logits.view(-1).tolist()

交叉编码器的关键优势在于其深度交互能力。双编码器在编码阶段就将查询和文档独立映射为向量,交互仅发生在最终的点积或余弦相似度计算中,这是一种浅层交互。而交叉编码器允许查询的每个 token 关注文档的所有 token,从而更准确地判断相关性。在需要高精度的场景,如医疗问答或法律文书检索,这种深度交互带来的提升尤为明显。

双编码器:效率优先的架构

双编码器(Dual Encoder)或双塔模型,将查询和文档分别编码为固定长度的向量,然后通过向量相似度计算相关性。由于文档向量可以预先计算并存储,在线推理时只需编码查询,然后执行最近邻搜索,因此延迟极低。

在重排序场景中,双编码器可以作为轻量级重排序器,对候选文档进行快速筛选。例如,使用一个经过微调的双塔模型对多路召回结果重新打分,虽然精度不如交叉编码器,但速度可以快 10~100 倍。这种架构适合对延迟要求苛刻的在线服务,或候选文档数量极大的情况。

然而,双编码器的瓶颈在于其表达能力受限于向量维度。查询和文档的交互被压缩到一个点积值中,无法建模复杂的 token 级匹配。当查询和文档的词汇重叠度低但语义相关时,双编码器可能会漏掉相关文档。

精度与延迟的量化对比

为了直观展示两种架构的差异,我们基于典型的企业知识库场景进行对比。假设候选文档数量为 35,使用 BGE-reranker-large 作为交叉编码器代表,使用 Sentence-BERT 作为双编码器代表。以下表格总结了关键指标:

指标交叉编码器 (BGE-reranker-large)双编码器 (Sentence-BERT)
重排序 MAP (CMedQAv2)84.10约 78.00(估计)
单次推理延迟 (35 个文档)约 200ms (GPU) / 800ms (CPU)约 5ms (GPU) / 20ms (CPU)
吞吐量 (文档/秒, GPU)约 175约 7000
显存占用约 1.2GB约 0.4GB
是否需要预计算是(文档向量可预计算)
交互深度全自注意力 token 级交互向量点积

表中双编码器的 MAP 为估计值,因为不同模型和数据集结果有差异,但总体趋势是交叉编码器精度更高。延迟数据基于 V100 GPU 和 8 核 CPU 的测试环境,实际值受批处理大小、序列长度等因素影响。从表中可以看出,交叉编码器在精度上领先约 6 个点,但延迟高出 40 倍以上。在在线服务中,如果每个查询需要 200ms 进行重排序,将显著增加用户感知延迟。

级联架构:平衡精度与延迟的工程方案

为了兼顾精度和效率,工程上常采用级联架构(Cascade Architecture)。其核心思想是分阶段过滤:第一阶段使用快速但粗略的方法(如双编码器)将候选集从数百个缩减到数十个;第二阶段再使用交叉编码器对缩减后的集合进行精细重排。

在我们的企业知识库场景中,流程如下:多路召回合并得到 35 个文档后,首先用双编码器对所有文档打分,保留 top-15;然后对这 15 个文档使用交叉编码器重排序,最终返回 top-5 给大模型。这样,交叉编码器只需处理 15 个文档,延迟从 200ms 降至约 85ms,而精度损失很小。

下图展示了级联架构的数据流:

flowchart TD
    A[用户查询] --> B[多路召回]
    B --> C[BM25 召回 20 篇]
    B --> D[向量召回 20 篇]
    C --> E[合并去重]
    D --> E
    E --> F[候选集 35 篇]
    F --> G[双编码器快速打分]
    G --> H[Top-15 文档]
    H --> I[交叉编码器精排]
    I --> J[Top-5 文档]
    J --> K[大模型生成]

级联架构的关键在于第一阶段筛选器的质量。如果双编码器漏掉了真正相关的文档,第二阶段交叉编码器也无法补救。因此,第一阶段的召回率必须足够高,通常需要保证 top-15 中包含至少 95% 的相关文档。这可以通过调整第一阶段的保留数量或使用更好的双编码器模型来实现。

模型蒸馏:轻量级交叉编码器的尝试

交叉编码器的高延迟主要源于其庞大的模型体积和全自注意力计算。模型蒸馏(Knowledge Distillation)是一种将大型交叉编码器的知识迁移到小型模型的技术,旨在保持精度的同时降低延迟。

蒸馏过程通常使用一个预训练的大交叉编码器作为教师模型,一个小型双编码器或浅层交叉编码器作为学生模型。教师模型对查询-文档对进行评分,学生模型学习模仿这些分数。例如,可以使用 BGE-reranker-large 作为教师,蒸馏出一个 6 层的 MiniLM 模型。实验表明,蒸馏后的模型在精度上仅下降 2~3 个点,但推理速度可提升 5~10 倍。

然而,蒸馏并非没有代价。学生模型的表达能力受限于其架构,对于需要复杂推理的查询,蒸馏模型可能无法捕捉到教师模型学到的细粒度模式。此外,蒸馏需要额外的训练数据和计算资源。在实际部署中,如果对延迟有严格要求,蒸馏模型可以作为交叉编码器的替代品,直接用于单阶段重排序,从而简化系统架构。

批处理优化:提升在线服务吞吐量

在线服务中,重排序通常是计算密集型的瓶颈。批处理(Batching)是提高 GPU 利用率、降低平均延迟的有效手段。交叉编码器可以同时处理多个查询-文档对,充分利用 GPU 的并行计算能力。

在实现中,我们可以将多个查询的候选文档拼接成一个大 batch,一次性送入模型。例如,如果有 10 个并发查询,每个查询有 15 个候选文档,我们可以构建一个 150 对的 batch。这样,原本需要 10 次前向传播的操作现在只需 1 次,吞吐量可提升近 10 倍。但批处理也会引入额外的延迟,因为需要等待 batch 填满。在低负载时,动态批处理(Dynamic Batching)可以自适应地调整 batch 大小,平衡延迟和吞吐量。

另一个优化方向是使用 ONNX Runtime 或 TensorRT 对模型进行推理加速。BGE-reranker-large 提供了 ONNX 格式的模型文件,可以配合 ONNX Runtime 实现更快的推理。在我们的场景中,将 PyTorch 模型转换为 ONNX 后,在 CPU 上的延迟降低了约 30%。这些优化手段可以与级联架构结合,进一步降低重排序的端到端延迟。

失败模式与适用边界

尽管重排序能显著提升 RAG 系统的精度,但它并非万能。以下是几种常见的失败模式:

  1. 第一阶段召回缺失:如果相关文档根本没有进入候选集,重排序无法凭空找回。这要求第一阶段的召回率足够高,通常需要设置较大的 top-k 或使用多种召回通路。
  2. 分布偏移:重排序模型通常在通用数据集上训练,当目标领域的语言模式与训练数据差异较大时,模型可能给出错误的相关性判断。例如,在医疗领域,交叉编码器可能将症状描述与无关疾病文档错误匹配。
  3. 长文档处理:交叉编码器通常有最大输入长度限制(如 512 tokens)。对于长文档,需要截断或分段处理,这可能导致信息丢失。BGE-reranker 支持更长的长度,但仍有限制。
  4. 多语言场景:虽然 BGE-reranker 支持中文和英文,但在低资源语言上性能可能下降。
  5. 查询意图模糊:当查询本身含糊不清时,重排序模型可能无法准确判断相关性,导致排序结果不稳定。

在决定是否使用重排序时,需要评估系统的性能瓶颈。如果检索阶段已经能提供高质量的结果,重排序的收益可能有限。此外,在延迟极其敏感的应用(如实时对话)中,交叉编码器可能不适用,此时可以考虑双编码器或蒸馏模型。

重排序是 RAG 系统中提升检索质量的关键环节。交叉编码器通过深度交互提供了最高的精度,但延迟较高;双编码器效率高但精度有限。级联架构在两者之间取得了平衡,而模型蒸馏和批处理优化进一步降低了部署成本。在实际工程中,选择哪种方案取决于对精度、延迟和复杂度的权衡。最终,一个有效的重排序系统需要与召回阶段紧密配合,并针对具体领域进行充分的评估和调优。

资料来源

  1. BGE Reranker: Cross-Encoder for Reranking
  2. Improving Passage Retrieval with Zero-Shot Question Generation
  3. Cohere Rerank Documentation