AI 工程基础体系 · 第 90/100 篇。内容覆盖机器学习、深度学习与生成式 AI;模型、数据、评测、权限和成本会作为同一生产系统处理。
RAG 重排:Cross Encoder、Late Interaction、候选规模和延迟
RAG(Retrieval-Augmented Generation,检索增强生成)的基本流程是:先从外部知识源检索与问题相关的内容,再把检索结果交给生成模型生成答案。Lewis 等人在 RAG 原始论文中将其描述为“参数化生成模型”与“非参数化外部记忆”的组合;在工程系统中,这通常表现为:
其中,“召回”负责尽量不要漏掉可能有用的文档,“重排”负责在有限候选中提高顺序质量。重排不是生成模型的附属步骤,而是一个独立的相关性估计问题:给定查询 和候选文档或文本块 ,计算一个更准确的相关性分数 ,然后按分数排序。
本文重点讨论三件相互制约的事情:
- Cross Encoder 如何通过联合编码查询和文档建模细粒度交互;
- Late Interaction 如何保留部分交互能力,同时降低在线计算成本;
- 候选规模 如何影响召回质量、重排成本、上下文长度和端到端延迟。
1. 重排在 RAG 中到底解决什么问题
1.1 召回和重排优化的是不同目标
最常见的第一阶段召回器是双编码器(Bi-Encoder)。它分别编码查询和文档:
然后使用向量相似度,例如余弦相似度:
文档向量可以预先计算并建立 ANN(Approximate Nearest Neighbor,近似最近邻)索引,因此在线查询时只需要编码查询,再在大规模索引中查找近邻。
双编码器的关键优势是:
但它也有一个结构性限制:查询和文档在进入相似度计算前已经分别压缩为单个向量。模型在编码文档时不知道当前查询,因而难以精确处理以下关系:
- 查询中的否定词是否改变文档含义;
- 某个实体是否与查询中的另一个实体发生关系;
- 查询要求“2024 年以后”,文档是否真的满足时间条件;
- 查询要求“支持 A 而不是 B”,文档是否明确表达了这个区别;
- 一段文档中只有某个局部短语与查询高度相关,其他内容是否造成整体向量偏移。
因此,双编码器擅长大规模初筛,但其相似度通常不是最终的精确相关性判断。
1.2 重排不能找回未召回的文档
设第一阶段返回候选集合:
重排器只能对 内的文档重新排序:
如果真正相关的文档 不在 中,那么无论重排模型多强,都无法把 排到最终结果中:
这条边界非常重要。重排器提高的是候选内部的排序质量,而不是第一阶段的召回上限。
因此,一个合理的 RAG 评测至少要拆成两个问题:
- 候选召回率 Recall@K:相关文档是否进入候选集合;
- 重排后的 Precision@k 或 nDCG@k:相关文档是否出现在靠前位置。
如果 Recall@K 很低,继续更换重排模型通常不能解决根因;应该检查分块、查询改写、混合检索、过滤条件或召回模型。
2. Cross Encoder:让查询和文档共同经过模型
2.1 定义和计算过程
Cross Encoder(交叉编码器)把查询和文档拼接为一个输入,由同一个 Transformer 联合编码:
模型输出一个相关性分数:
在 Transformer 的自注意力层中,查询 token 可以直接关注文档 token,文档 token 也可以关注查询 token。因此,模型能够计算更细粒度的词语、实体和句法交互。
一个简化的注意力公式是:
其中:
- 是查询向量;
- 是所有输入 token 的键和值;
- 是向量维度。
由于查询和文档 token 被放入同一个序列, 中会出现跨查询、跨文档的注意力项。这就是 Cross Encoder 能理解“查询中的这个词是否修饰文档中的那个实体”的原因。
2.2 为什么它通常比向量相似度更准确
假设有两个候选:
- 查询:
如何关闭生产环境中的自动部署 - 文档 A:
自动部署可以通过项目设置关闭。 - 文档 B:
生产环境部署流程包括自动构建、自动测试和自动发布。
双编码器可能因为“生产环境、自动部署、部署流程”等词的整体语义接近,而把文档 B 排得很高。Cross Encoder 则可以更明确地判断:
- 文档 A 包含“关闭”这一操作;
- 文档 B 只描述部署流程,没有回答“如何关闭”。
其优势不是简单地“理解更多词”,而是能够对查询和文档之间的关系进行条件化计算:
而不是分别计算两个独立表示后再比较:
2.3 Cross Encoder 的训练目标
若训练数据包含查询 、正例 和负例 ,可以使用 pairwise ranking loss:
其中 是 sigmoid 函数。这个损失鼓励正例分数高于负例。
也可以对一组候选使用 softmax:
这里的负例质量非常关键。随机负例通常太容易,模型学不到真正有区分度的边界。更有价值的负例包括:
- 第一阶段召回器经常排在前面的错误文档;
- 词面相似但结论相反的文档;
- 属于相同主题但不回答当前问题的文档;
- 时间、权限、产品版本不匹配的文档。
这些被称为 hard negatives(困难负例)。不过,困难负例也可能包含实际正例,训练前需要人工或规则复核,否则会把正确答案标成负例。
2.4 Cross Encoder 的计算代价
如果候选数量为 ,每个候选都要进行一次查询—文档联合编码。设:
- :查询 token 数;
- :文档 token 数;
- :拼接后的序列长度;
- :一次模型编码成本。
则总成本近似为:
Transformer 自注意力在朴素实现下对序列长度的注意力计算大致呈 关系,因此文档变长和候选数增加都会增加成本:
实际硬件上的耗时还会受到 batch size、GPU 利用率、padding、模型大小、量化方式和并发队列影响,所以不能仅凭这个公式预测毫秒数。但公式准确表达了两个方向:
- 候选数量 增加,推理次数增加;
- 单个文档变长,单次推理成本通常非线性增加。
2.5 输入截断是准确性和延迟的共同边界
Cross Encoder 通常有最大输入长度。若:
系统必须截断、滑窗或重新分块。
直接从文档尾部截断,可能丢掉标题、定义或结论;只保留前部,也可能丢掉真正回答问题的段落。更稳妥的方式是:
- 在入库时将文档切成较小、语义相对完整的 chunk;
- 查询时先召回 chunk,而不是把整篇文档交给重排器;
- 对特别长的 chunk,使用标题、父文档摘要和正文片段组合;
- 评测截断前后的 Recall@K 和重排指标。
一个常见误解是“把更多上下文一次性给 Cross Encoder 一定更准确”。实际上,过长上下文可能同时带来:
- 输入截断导致关键信息消失;
- 无关内容稀释相关信号;
- 更高的计算和显存成本;
- 多个主题混在同一 chunk 中,分数解释困难。
3. Late Interaction:不完全独立,也不完全联合
3.1 它位于双编码器和 Cross Encoder 之间
Late Interaction(晚交互)保留查询和文档的 token 级表示,但把交互推迟到编码完成之后。
查询被编码为 token 向量序列:
文档被编码为 token 向量序列:
以 ColBERT 风格的 MaxSim 为例,相关性分数为:
含义是:
- 对查询中的每个 token ;
- 找到文档中与它最相似的 token;
- 把这些最大相似度相加。
它与单向量相似度不同,因为查询中的不同 token 可以分别在文档的不同位置找到匹配。它也与 Cross Encoder 不同,因为查询和文档在交互前已经分别编码,交互阶段通常只使用向量相似度和聚合操作,而不是重新进行跨序列 Transformer 注意力。
3.2 一个完整算例
假设查询有三个 token:
两个候选文档的相似度矩阵如下。行是查询 token,列是文档 token:
对于文档 A:
文档 B 的相似度矩阵为:
则:
文档 B 对“生产环境”和“自动部署”可能有较强局部匹配,但对“关闭”没有可靠匹配,因此总分低于文档 A。
下面的 NumPy 示例可以直接运行,展示这一计算:
import numpy as np
# 行:查询 token;列:文档 token
sim_a = np.array([
[0.90, 0.20, 0.10],
[0.10, 0.88, 0.30],
[0.20, 0.30, 0.86],
])
sim_b = np.array([
[0.25, 0.30, 0.20],
[0.80, 0.20, 0.30],
[0.75, 0.25, 0.40],
])
def maxsim(similarity_matrix: np.ndarray) -> float:
# 每个查询 token 在文档中寻找最相似 token,再求和
return float(np.max(similarity_matrix, axis=1).sum())
print("document A:", maxsim(sim_a))
print("document B:", maxsim(sim_b))
预期输出:
document A: 2.64
document B: 1.85
这个示例省略了真实模型的 token 化、向量归一化和批量计算,但完整表达了 MaxSim 的聚合机制。
3.3 Late Interaction 的索引和在线路径
Late Interaction 的难点是:文档不再只有一个向量,而是一组 token 向量。离线阶段通常需要:
- 对每个 chunk 进行 token 化;
- 编码每个 token,得到 token embedding;
- 对向量做归一化、压缩或量化;
- 为 token 向量建立适合的近似检索结构;
- 在线接收查询 token 向量;
- 进行候选召回或候选打分;
- 对每个文档聚合 MaxSim 分数。
文档存储成本近似从单向量的:
增加到 token 级表示的:
其中:
- 是 chunk 数;
- 是向量维度;
- 是每个 chunk 的平均 token 数。
因此,Late Interaction 并不是“免费获得 Cross Encoder 精度”。它用更大的索引和更复杂的候选计算,换取比单向量检索更细粒度的匹配能力。
实际实现还会对标点、停用词或特殊 token 做处理,并使用向量压缩以降低存储和内存带宽。具体策略取决于模型和索引实现,不能把所有 Late Interaction 系统都等同于某一个固定模型或固定 API。
3.4 Late Interaction 的反例和边界
MaxSim 的聚合方式具有明显假设:查询中的每个 token 都可以独立寻找文档中的最佳匹配。这可能导致“局部词语都匹配,但整体关系错误”。
例如:
- 查询:
支持在 Linux 上运行的数据库 - 文档:
不支持在 Linux 上运行;仅支持 Windows。
如果查询 token 分别与文档中的“Linux”“运行”“数据库”高度匹配,MaxSim 可能给出较高分,但它未必充分理解“不支持”这一否定关系。
再如:
- 查询:
A 收购 B 的时间 - 文档:
B 收购 A 的时间
实体词和“收购”“时间”都匹配,但主客体关系反了。
这类问题说明:
- Late Interaction 不是语义逻辑的完整替代品;
- 需要在训练集中加入否定、主客体交换和条件不满足的 hard negatives;
- 对强关系、时间、数值和权限问题,可能仍需要 Cross Encoder、规则校验或结构化过滤。
4. 三种相关性计算方式的结构差异
可以把三种方式写成同一张表:
| 方法 | 文档离线表示 | 查询—文档交互时机 | 在线计算 | 主要优点 | 主要限制 |
|---|---|---|---|---|---|
| 双编码器 | 一个文档向量 | 最后用向量相似度 | 低 | 适合大规模 ANN | 交互粗粒度 |
| Late Interaction | 一组文档 token 向量 | 编码后逐 token 交互 | 中 | 保留局部匹配,优于单向量 | 索引大,关系建模有限 |
| Cross Encoder | 通常不独立缓存最终文档表示 | 编码过程中联合交互 | 高 | 相关性判断细致 | 每个候选都要联合推理 |
这里的“优于”不能理解为绝对排序。模型训练数据、领域、分块方式和评测任务都会影响结果。一个在通用问答数据上训练的 Cross Encoder,可能不如针对企业内部术语训练的 Late Interaction 模型。
更准确的系统描述是:
- 双编码器或 Late Interaction 负责扩大候选覆盖;
- Cross Encoder 负责在较小候选集合中进行精确判别;
- 生成模型只应接收经过过滤和排序的上下文。
5. 候选规模 :为什么不是越大越好
5.1 候选规模的定义
设第一阶段召回返回前 个候选:
之后重排器计算:
最后选取前 个上下文:
通常 。例如,第一阶段召回 50 个 chunk,Cross Encoder 对这 50 个候选打分,再将其中 5 个交给生成模型。
5.2 增大 的收益
假设相关文档在第一阶段召回排名中的位置是随机变量 ,则候选覆盖概率为:
随着 增加,Recall@K 单调不下降:
因此,当相关文档经常排在第 20 到第 50 位时,使用 的重排器根本看不到它们。
一个具体例子:
| 第一阶段排名 | 查询数量 | 其中包含相关文档的查询数 |
|---|---|---|
| Top 5 | 100 | 72 |
| Top 20 | 100 | 91 |
| Top 50 | 100 | 96 |
则:
如果最终只需要 5 个上下文,重排 Top 50 可能把位于第 20 位的相关文档提到前面。
5.3 增大 的代价
对于 Cross Encoder,候选数量几乎直接增加推理工作量:
其中:
- 是每个推理 batch 的候选数;
- 是处理一个 batch 的时间;
- 是等待推理资源的时间;
- 是数据传输和序列化时间。
例如,某系统一次 batch 最多处理 8 个候选,单个 batch 的模型耗时约为 12 ms。这里只做说明性计算:
- :约需 个 batch,模型部分约 12 ms;
- :约需 个 batch,模型部分约 48 ms;
- :约需 个 batch,模型部分约 96 ms。
这不是通用性能承诺,因为真实耗时还受动态 batching、padding、GPU 利用率和并发影响,但它说明了候选规模对延迟的基本方向。
候选太大还会造成第二类代价:最终上下文竞争。若直接将大量候选拼接给生成模型,则输入 token 数增加:
这会增加生成模型的首 token 延迟、输入 token 成本和上下文噪声。重排阶段的 和最终送入生成模型的 不是同一个参数,不能因为想扩大候选召回就把所有候选都放进 prompt。
5.4 候选规模的反例:更大的 可能降低最终质量
设重排器有误判概率,并且候选越多,越可能出现“高分但错误”的文档。若最终只取前 个,增加 会引入更多竞争项。
例如:
- 时,候选中包含 1 个相关文档,重排器将其排第 1;
- 时,额外出现许多术语高度相似但结论相反的文档;
- 重排器被这些 hard negatives 干扰,把真正相关文档排到第 8;
- 最终只取 ,相关文档反而被丢弃。
因此, 的收益依赖于重排器的判别能力。理想情况下:
但实际系统中二者可能发生权衡。不能只看 Recall@K,也不能只看重排后的 Top 1。
6. 延迟应该按数据流拆解
RAG 的端到端延迟可以近似拆为:
其中:
- :查询改写、查询向量编码或 token 编码;
- :租户、权限、时间、文档状态等过滤;
- :向量、关键词或混合检索;
- :Cross Encoder 或 Late Interaction 打分;
- :去重、截断、邻块扩展和 prompt 组装;
- :生成模型的首 token 和后续 token 延迟。
若各阶段串行,则:
若稠密检索、关键词检索和部分过滤可以并行,则:
但是,Cross Encoder 的重排通常必须等待候选集合形成,因此不能简单地和召回完全并行。
6.1 平均延迟不等于用户体验
生产系统通常还需要观察 p50、p95 和 p99。候选规模增加时,平均延迟可能只略有变化,但尾延迟会明显上升,原因包括:
- 某些请求的候选 chunk 更长;
- 动态 batch 等待时间增加;
- GPU 推理队列拥塞;
- 某些租户的过滤结果过多;
- 生成模型上下文长度出现长尾;
- 重试导致请求叠加。
如果一个服务要求 p95 小于某阈值,就不能只用平均 估算;应记录每次请求实际的:
- 召回候选数;
- 重排候选数;
- 每个候选 token 长度;
- batch size;
- 排队时间;
- 模型推理时间;
- 最终 prompt token 数;
- 生成 token 数。
6.2 并发和背压
重排服务往往是共享 GPU 或 CPU 资源。设同时到达的查询数为 ,每个查询平均需要处理 个候选,单候选服务能力为 ,则候选级工作量随 增加:
当到达速率接近服务能力时,队列等待时间会急剧增加。此时继续扩大 可能形成反馈:
重试又进一步增加负载。
因此,重排服务需要明确:
- 最大候选数;
- 单请求最大输入 token;
- batch 最大 token 数,而不只是最大样本数;
- 队列超时;
- 服务过载时的降级策略;
- 生成服务是否允许继续使用未重排候选。
一个安全的降级路径通常是:超时后使用已经完成的部分结果,或者回退到第一阶段排序;不应在权限检查未完成时返回任何文档内容。
7. 一个可观测的 RAG 重排流程
下面是一个与具体框架无关的端到端伪代码。它强调状态变化和边界,而不是某个特定 API:
def answer(query, tenant_id, user_id):
# 1. 查询处理
normalized_query = normalize_query(query)
# 2. 权限和租户约束必须进入检索条件
access_scope = load_access_scope(tenant_id, user_id)
# 3. 第一阶段召回:扩大候选覆盖
dense_hits = dense_retrieve(
normalized_query,
scope=access_scope,
top_k=100,
)
lexical_hits = lexical_retrieve(
normalized_query,
scope=access_scope,
top_k=100,
)
# 4. 合并、去重和基础过滤
candidates = merge_and_deduplicate(dense_hits, lexical_hits)
candidates = filter_active_and_authorized(candidates, access_scope)
# 5. 控制重排规模,避免无界增长
candidates = candidates[:80]
# 6. 精细重排
reranked = cross_encoder_rerank(
query=normalized_query,
documents=candidates,
max_document_tokens=400,
)
# 7. 组装最终上下文,而不是把所有候选都交给生成模型
context = select_context(
reranked,
max_chunks=6,
max_context_tokens=5000,
remove_near_duplicates=True,
)
# 8. 生成答案,并保留来源标识
return generate_with_citations(
query=normalized_query,
context=context,
)
每一步有不同的失败含义:
- 第一阶段召回为空:可能是索引故障、过滤过严或查询不匹配;
- 召回有结果但重排为空:可能是候选结构、模型输入或 token 截断错误;
- 重排成功但上下文为空:可能是去重、阈值或 token 预算配置问题;
- 上下文正确但答案错误:应检查生成模型是否遵循来源,而不是继续盲目调大 。
在日志中应至少保留文档 ID、版本 ID、分数、排名、过滤原因和模型版本,而不应默认记录未经脱敏的全文内容。权限边界要求尤其严格:重排器不能看到用户无权访问的文档正文,否则即使最终没有返回该文档,系统也已经发生了越权数据处理。权限过滤应尽可能在召回索引层完成,并在重排前再次校验。
8. 混合检索和重排的关系
单一向量检索对术语、编号、错误码、产品版本和精确短语可能不稳定。关键词检索则擅长词面匹配,但难以处理同义表达。因此常见架构会并行使用:
合并后可以使用 Reciprocal Rank Fusion(倒数排名融合):
其中:
- 是检索列表集合;
- 是文档 在列表 中的排名;
- 是防止头部排名权重过大的常数。
RRF 的作用是形成较好的候选集合,不等同于 Cross Encoder 重排。一个典型的分层流程是:
如果直接把不同检索器的原始分数相加,通常不可靠,因为向量余弦分数、BM25 分数和模型 logits 的数值尺度没有天然可比性。应先校准、归一化或使用排名融合。
9. 如何选择 Cross Encoder、Late Interaction 和候选规模
9.1 适合 Cross Encoder 的情况
Cross Encoder 更适合:
- 候选规模已经较小;
- 查询和文档之间需要精确关系判断;
- 领域中存在大量相似但结论不同的文档;
- 需要判断否定、条件、时间和数值;
- 可以接受额外的 GPU 或 CPU 推理成本。
常见做法是先召回 个候选,再只用 Cross Encoder 处理这些候选,而不是对整个语料库逐文档运行。
9.2 适合 Late Interaction 的情况
Late Interaction 更适合:
- 语料库较大,单向量表示损失明显;
- 希望在线保留 token 级匹配;
- 可以承担更大的索引存储和构建成本;
- 需要在较大的候选范围内提高局部语义匹配能力;
- 延迟预算不能支持对大量候选运行 Cross Encoder。
它也适合作为第一阶段或中间阶段检索器,再由 Cross Encoder 对更小的候选集做最后判别。
9.3 候选规模的选择方法
不应先凭经验固定 ,再观察系统是否变慢;应对多个 做离线和在线联合评测。例如测试:
分别记录:
- Recall@K;
- 重排后的 nDCG@5、MRR@5;
- 最终上下文命中率;
- 答案引用正确率;
- p50、p95、p99 延迟;
- 每次请求的推理 token 数;
- 单请求和每租户成本。
一个简单的决策过程是:
- 找到 Recall@K 开始趋于饱和的位置;
- 检查重排后的 Top 是否继续改善;
- 检查 增加是否显著恶化尾延迟;
- 在满足质量门槛的配置中选择成本最低者。
例如,如果 Recall@20 为 0.91,Recall@50 为 0.96,但最终答案引用正确率只提高 0.5 个百分点,而 p95 延迟增加一倍,那么 未必合理。相反,如果相关文档常位于第 30 到 50 位,且重排器能稳定把它们提升到前 5,则增加 可能值得。
10. 评测必须区分“找得到”和“排得准”
10.1 召回指标
对于每个查询,设人工标注的相关文档集合为 ,系统召回前 个集合为 ,则:
如果一个问题有多个可接受来源,分母不应机械地只设为一个;数据集需要定义相关性等级和可接受证据范围。
10.2 重排指标
若只关心前几个结果,可以使用 MRR:
其中 是第一个相关结果的排名。
若相关性有等级,例如:
- 2:直接回答问题;
- 1:部分有用;
- 0:无关;
则可使用 nDCG@k。它不仅考虑相关文档是否出现,还考虑是否出现在更靠前的位置。
10.3 RAG 还需要端到端指标
检索指标不能直接等价于答案质量。一个文档被排到第 1 名,不代表生成模型一定正确使用它。端到端评测还应检查:
- 答案是否被来源支持;
- 引用是否指向真正支持结论的 chunk;
- 是否混入权限外信息;
- 是否正确处理时间和版本;
- 多跳问题的多个必要证据是否同时进入上下文;
- 文档冲突时是否识别冲突而非随意拼接。
特别是多跳问题中,单一“最相关 chunk”可能不足。此时上下文选择需要覆盖多个证据,而不是简单取重排 Top 。
11. 常见误解和失败诊断
11.1 “用了 Cross Encoder,召回率就会提高”
错误。Cross Encoder 只能重新排序已有候选。诊断方法是同时查看:
和:
如果前者低,问题在召回或数据;如果前者高而后者低,才主要怀疑重排模型、训练数据、输入截断或分数使用方式。
11.2 “候选越多,答案越可靠”
错误。候选增加可能提高覆盖,也可能带来更多近似错误、上下文噪声和模型误判。尤其在最终 prompt 中无差别放入大量候选时,生成模型可能:
- 选择排名较低但措辞更强的错误段落;
- 把不同版本的结论混在一起;
- 受到相互矛盾的来源影响;
- 产生看似有引用但引用并不支持结论的答案。
11.3 “Late Interaction 就等于 Cross Encoder 的低成本版本”
不准确。Late Interaction 的 token 级相似度确实比单向量更细致,但它通常缺少 Cross Encoder 在联合注意力中建模复杂关系的能力。它们的训练目标、索引方式和错误模式不同。
11.4 “只调模型,不检查 chunk”
如果一个 chunk 同时包含多个主题,任何重排器都可能遇到困难。诊断时应检查:
- 相关答案是否跨 chunk;
- 标题是否被丢失;
- chunk 是否在句子中间截断;
- 文档版本是否混合;
- 代码、表格和自然语言是否使用了不合适的切分方式;
- 同一父文档的邻近 chunk 是否需要一起扩展。
RAG 指南通常把检索、过滤、上下文组织视为一体;重排器不能修复已经损坏的文档边界。
11.5 “分数阈值在所有查询上都一样”
不同查询的分数分布可能不同。一个短查询、一个长查询、一个包含罕见术语的查询,其分数不一定可直接比较。固定阈值可能导致:
- 某些查询没有任何结果;
- 某些查询保留过多低质量结果;
- 不同模型版本上线后阈值失效。
更可靠的做法是结合排名、分数分布、查询类型和离线校准评测。若使用模型 logits,不应把它们未经校准地解释为概率。
12. 生产系统中的版本、权限和成本
12.1 模型和索引必须成套版本化
文档 token embedding 由某个 Late Interaction 模型生成时,查询也必须使用兼容的查询编码器。更换模型后,旧文档向量通常不能直接与新查询向量混用。
应至少记录:
- embedding 模型版本;
- Late Interaction 模型版本;
- Cross Encoder 版本;
- 文档内容版本;
- chunk 规则版本;
- 索引构建时间;
- 过滤字段版本;
- 评测数据版本。
否则发生结果退化时,无法判断是模型变化、文档变化还是分块变化。
12.2 权限过滤不是重排分数
权限是硬约束,不是相关性特征。设用户可访问文档集合为 ,则检索和重排的有效空间应是:
而不是先在全库中取 Top ,再希望重排器把无权文档排到后面。无权文档即使相关性最高,也不得进入用户可见结果。
权限变更还会造成缓存问题:如果缓存键只有查询文本,那么同一个查询可能把一个用户的结果错误返回给另一个用户。缓存键至少需要包含租户、用户权限范围或权限快照版本。
12.3 成本来自多个维度
重排成本不只有模型调用费用,还包括:
- Late Interaction token 级索引的存储;
- 索引构建和增量更新;
- Cross Encoder 的在线算力;
- 生成模型新增的上下文 token;
- 超时和重试造成的额外计算;
- 日志、评测和回放存储。
因此可以使用不同成本层次的级联:
但每一级都需要用实际评测证明它带来的质量增益,不能因为结构更复杂就假设一定更好。
13. 一个可执行的调参实验设计
可以用固定评测集比较候选规模和重排器。每个查询都应有相关性标注,并保存第一阶段完整排名,而不是只保存最终结果。
实验变量例如:
- 召回器:稠密、关键词、混合;
- 候选数:;
- 重排器:无重排、Late Interaction、Cross Encoder;
- 最终上下文数:;
- 最大 chunk token 数;
- 是否加入邻近 chunk。
每个配置运行同一批查询,记录:
query_id
retriever_version
reranker_version
candidate_k
final_k
retrieval_recall_at_k
reranked_ndcg_at_5
answer_support_rate
p50_latency_ms
p95_latency_ms
input_tokens
reranker_compute_cost
评测时要特别保留“相关文档未进入候选”的案例。若只分析重排成功看到的候选,会产生幸存者偏差,把召回问题误判成重排问题。
最终配置应满足一个明确条件,例如:
然后在满足质量和延迟约束的候选中最小化成本,而不是单独追求某一个指标。
结语
Cross Encoder、Late Interaction 和双编码器的根本差异,在于查询—文档交互发生在哪里:
- 双编码器在各自编码后才进行单向量相似度比较;
- Late Interaction 在独立编码后进行 token 级交互;
- Cross Encoder 在联合 Transformer 编码过程中进行跨序列交互。
交互越细,通常越有机会提高相关性判断能力,但在线计算、索引成本或延迟也会增加。候选规模 则连接了召回质量与重排成本:
但同时:
所以重排系统的正确设计不是“选择最强模型”或“把候选调到最大”,而是先保证相关文档能进入候选,再用合适的交互结构改善排序,最后根据质量、p95 延迟、权限边界和单位请求成本确定候选预算。RAG 的检索质量最终取决于这条完整链路,而不是某一个重排模型的离线分数。
系列导航与关联阅读
- 系列入口:AI 工程完整学习路线:从机器学习与 Transformer 到 RAG、Agent 和生产治理
- 上一篇:混合检索:BM25、向量、过滤、融合排序和权重调优
- 下一篇:RAG 查询改写:扩展、分解、HyDE、多轮上下文和回退
官方资料
本文依据研究论文、标准组织与主流框架官方文档重新梳理;正文、示例与工程清单由 WR BLOG 编写。

评论
0 条讨论