RRF 融合后还要不要重排:先画候选召回上限
RRF 能把不同检索通道的排名合到一起但它不能把没召回的文档变出来。重排器也是一样。很多“换了重排模型还是答错”的问题实际发生在候选池形成之前。10 月 8 日Qdrant 与 Pento 发布了 miniCOIL EN-ES 的双语稀疏检索研究预览尝试让英语与西班牙语的概念共享稀疏槽位。这提醒我们稀疏与稠密检索的差异不只是两组分数范围语言、词项和模型训练方式都会影响漏召回的类型。该预览没有提供中文业务的效果保证。官方文章先检查“有没有”再检查“排第几”对于一条有人工相关性标注的查询先算候选池是否覆盖正确文档再检查最终 Top-k 的顺序。若标注答案是文档 E而两路候选只含 A、B、C、D任何重排分数都无法修复这个遗漏。RRF 对某个文档的贡献是1 / (k rank)其中 rank 从 1 开始。各通道内部应该先去重不然重复 ID 会被当成多份证据。k、每路深度、融合后的截断深度需要一起实验没有一个参数能对所有查询都处在帕累托前沿。下面只实现确定性的排名融合。它没有生成随机模型分数也不在输出上伪装成 Cross-Encoder 推理。图中 E 位于候选池外它表示召回遗漏。不要把这张示意图理解成某个模型的性能结果代码中的顺序完全由两份人工给定的列表决定。from collections import defaultdict def rrf(rankings, k60): if k 0: raise ValueError(k must be nonnegative) scores defaultdict(float) for ranking in rankings: unique list(dict.fromkeys(ranking)) for rank, doc_id in enumerate(unique, 1): scores[doc_id] 1.0 / (k rank) return sorted(scores, keylambda d: (-scores[d], d)) def candidate_recall(candidates, relevant): if not relevant: raise ValueError(query has no relevance judgment) return len(set(candidates) relevant) / len(relevant) rankings [[A, B, B, C], [C, A, D]] fused rrf(rankings) assert len(fused) 4 and fused[0] A assert rrf([[A, A]]) rrf([[A]]) assert candidate_recall(fused, {E}) 0.0 assert E not in fused print(fusedA,C,B,D; missed candidate E remains unrecoverable)例子跑通以后还要验证什么这段代码已在本地 macOS、CPython 3.9.6 下运行实际输出为fusedA,C,B,D; missed candidate E remains unrecoverable。它验证的是例子中的数据约束与分支不包含线上服务的吞吐或延迟测量。未运行 BM25 索引、miniCOIL、向量库或 Cross-Encoder未测 GPU 或并发性能。接入真实系统后需要分别记录每路深度、候选并集召回率、融合召回率、重排质量和排队耗时。改一个参数时其他条件与评测查询应固定不要同时换模型、向量与分块后把收益全归给 RRF。延伸阅读miniCOIL EN-ES2026 年 10 月 8 日的研究预览说明双语稀疏表示的尝试与限制。Qdrant Hybrid Queries查阅融合与多阶段查询语义实际 API 仍需按部署版本验证。