双塔、交叉编码器与后期交互
双塔(bi-encoder)把 query 与文档各自编码成向量,能预计算、能建索引,但看不到两者的交互;交叉编码器(cross-encoder)把两者拼在一起打分,准但每对都要现算,所以不能用来检索全库。 ColBERT 式后期交互是第三条路:token 级向量 + 延后打分。
也叫:双塔 · bi-encoder · 交叉编码器 · cross-encoder · ColBERT · 后期交互 · late interaction
原理拆解:双塔 vs 交叉编码器出自 T2-6
- Bi-Encoder 双塔query / doc 各自编码 → 算余弦相似度
- ColBERT 后期交互doc 存一组 token 级向量,细粒度匹配
- Cross-Encoder 精排
[query + doc]→ 全交叉注意力 → 分数
| 阶段 | 谁来算 | 候选数 | 代价 |
|---|---|---|---|
| 第一阶段 · 召回 | 双塔,doc 向量已离线算好 | 百万 → 50~100 | 百万级语料上毫秒级 |
| 第二阶段 · 精排 | 交叉编码器,query 来了才能算 | 50~100 → 5~10 | 重排 50 条多几十到几百毫秒 |
| 假如全库跑精排 | 交叉编码器,无法离线预计算 | 100 万篇全打分 | 跑 100 万次模型推理,延迟与成本不可行 |
完整回答分三层:机制差异(独立编码 vs 联合编码)→ 工程后果(可预计算 vs 不可预计算)→ 架构结论(两阶段是成本约束下的必然)。能答到第三层的人不多。
双塔的代价说具体点:每篇文档被压成一个固定向量,压缩时丢掉的信息再也找不回来 —— 它只判断「这两段话大体上像不像」,判断不了「这段话到底回不回答得了这个问题」。
这是本篇的核心,也是追问必到之处。
双塔(Bi-Encoder)——你的 embedding 模型
query 和 document 各自独立编码成向量,再算余弦相似度:
query ──► [编码器] ──► 向量A ─┐
├──► 余弦相似度
doc ──► [编码器] ──► 向量B ─┘关键特性:doc 的向量可以离线预先算好存起来。查询时只需编码 query 一次,剩下的就是纯向量检索——所以能在百万级语料上做到毫秒级。
代价:query 和 doc 在编码时从未见过对方。每篇文档被压缩成一个固定向量,压缩过程中丢掉的信息再也找不回来。它只能判断「这两段话大体上像不像」,判断不了「这段话到底回不回答得了这个问题」。
交叉编码器(Cross-Encoder)——Rerank 模型
把 query 和 document 拼在一起送进模型,让注意力机制在两者之间充分交互,直接输出一个相关性分数:
[query + doc] ──► [编码器 + 全交叉注意力] ──► 相关性分数
关键特性:没有压缩、没有近似,模型能看到「问题里的『垫付』和文档里的『先行支付』是一回事」这种细粒度对应关系。精度显著高于双塔。
代价:没法预计算。因为分数依赖 query-doc 这一「对」,query 来了才能算。要给 100 万文档打分,就得跑 100 万次模型推理。
于是答案自明了
为什么不直接用 cross-encoder 检索全库? 因为它的计算量正比于候选数量,且无法离线预计算。全库跑一遍在延迟和成本上完全不可行。所以工程上必须两阶段:用便宜的双塔把范围从百万缩到一百,再用昂贵的交叉编码器在这一百里精挑。
这个回答的完整版本包含三层:机制差异(独立编码 vs 联合编码)→ 工程后果(可预计算 vs 不可预计算)→ 架构结论(两阶段是成本约束下的必然)。能答到第三层的人不多。
第三条路:ColBERT 式后期交互
介于两者之间:文档预先编码成一组 token 级向量(不压成单个向量),查询时做细粒度的 token 匹配。精度接近 cross-encoder,又保留了部分预计算能力。代价是存储开销大得多。知道它的存在和定位即可,属于加分项。
以上节选自T2-6 Rerank:为什么要两阶段检索,读全文能看到前后语境。