单一向量检索撑不起生产级 RAG。语义相似度对”iPhone 15 Pro Max”这类精确串、法条编号、错误码天然不敏感,召回阶段就漏了,后面再好的模型也救不回来。检索与排序这两层增强,本质是分工:前者负责把该在的文档捞进来,后者负责把最该看的排到最前。

召回层:关键词与向量的加权融合
混合检索的做法是并行跑两路召回,再合并打分。关键词一路用 BM25,它从 TF-IDF 演化而来,对词频做饱和处理,并按文档长度归一化,专治精确匹配;语义一路用 Sentence-BERT 之类的双塔模型产出向量相似度,负责”夏天穿的鞋”到”透气网面运动鞋”这种泛化。
两路得分量纲不同,BM25 可能落在 0 到几十的区间,余弦相似度在 0 到 1 之间,直接相加没有意义。工程上先做 Min-Max 归一化压到同一区间,再按权重线性融合:
综合得分 = α · BM25_norm + (1 - α) · Semantic_norm
α 的取值靠验证集调,网格搜索或贝叶斯优化都行。电商场景常见 0.4 到 0.6,语义略占上风。融合后取 Top-50 左右作为下一阶段输入,没必要留太多,后面重排序吃不下。
如果底层用的是 Elasticsearch 8.x,不必自己写融合逻辑。它的检索 API 支持在同一个请求里并发执行 BM25 查询和 kNN 查询,用 RRF(Reciprocal Rank Fusion)合并结果,关键参数是 rank_constant(默认 60)和 rank_window_size。RRF 只依赖排名不依赖原始分数,天然绕开归一化问题,和线性加权是两条路线,选一条即可,别混用。

精排层:让查询和文档真正”对话”
召回给的是粗排结果,Top-50 里”沾边”和”命中”混在一起。重排序换用交叉编码器(Cross-Encoder),把查询和文档拼成 [CLS] Query [SEP] Document [SEP] 一次性喂进 Transformer。自注意力直接在两者之间建立细粒度关联——查询里的”续航”能对上文档里的”电池容量””单次充电使用时长”,这是双塔模型各自编码时做不到的。
取 [CLS] 位置的隐状态接一个 MLP,输出 0 到 1 的相关性概率,按此重排。模型选择上,bge-reranker 开源、中等显卡可跑,通用场景够用;mxbai-rerank-v2 支持更长上下文,适合长文档;Cohere Rerank 是付费 API,多语言表现稳。候选量控制在 Top-50 上下,经验上已覆盖九成以上核心相关文档,再往上加只增成本不增收益。

部署时真正会卡住的地方
批处理是提速的常规手段,一次送 10 个候选进模型,但显存是硬约束。常见报错是 torch.cuda.OutOfMemoryError,根因通常是候选文档过长导致单批 token 总量失控,而非 batch size 本身设大了。处理办法:先把 batch size 降到 4 或 2 试探,同时对超长文档做分块,各块单独打分后取最大值作为该文档的最终得分——注意是取最大不是取平均,平均会稀释掉真正相关的那一段。
另一个坑是截断。多数 Cross-Encoder 的 max_length 在 512 左右,长文档尾部被静默切掉,相关性判断直接失真。上线前用一组已知答案的查询做校验:对每条查询确认目标文档重排后进入 Top-3,命中率低于预期就先查截断配置,再查模型选型。
两层增强的协同与边界
混合检索拓宽候选覆盖面,避免语义路漏掉精确串;重排序修正顺序,把核心文档顶上去。两者叠加相比纯语义检索,事实准确率通常有 30% 到 50% 的提升空间,但这不是无条件成立的。
代价要算清楚。Cross-Encoder 是逐对推理,延迟比双塔高一个量级,在线问答对首字延迟敏感时,Top-50 全量重排可能直接超预算,折中方案是只对 Top-20 重排,或改用蒸馏后的小模型。强时效查询(”今天的汇率””本周发布”)也不适合纯语义召回,得靠元数据过滤兜底——在检索和排序链路里挂上发布时间、领域标签做前置筛选,比事后调 α 更有效。

调优方向
权重不必全局固定。按查询类型分流:事实型查询把 α 调高偏向 BM25,语义型查询反过来,效果比单一权重稳。模型蒸馏把 Cross-Encoder 的交互能力迁移到更快的双塔结构,是压延迟的主流思路。元数据增强则解决时效和专业性问题,让”2025 年 AI 趋势”这类查询优先命中当年文档。


实现顺序建议:先把混合召回跑通并用校验集确认召回率,再引入重排序并单独度量排序准确率,最后才调融合权重和模型选型。一步到位同时改三层,出了问题定位不了。
ID:7766