从零构建现代搜索引擎(E02):多向量与 Late Interaction
主线第 18 篇把整篇文档压缩成一个 512 维向量。这个"池化"操作不可避免地丢失信息:一篇同时讨论"线程池配置"和"垃圾回收调优"的长文档,其单向量是两个主题的模糊平均——搜任何一个主题都不会得到高分,搜两个主题的交集反而可能命中。
ColBERT(Khattab & Zaharia, 2020)提出了不同的方案:不池化,保留每个 token 的独立向量,在检索时做 token 级的细粒度匹配。
单向量的信息瓶颈
把一篇 200 个 token 的文档压缩成 1 个向量,信息压缩比是 200:1。对短文档(标题、摘要)这个损失可以接受;对长文档(技术文章、API 文档),关键细节可能被淹没。
具体场景:
1 | |
三个查询的相似度差距很小——单向量无法区分文档中哪个部分与哪个查询最相关。
ColBERT 的 MaxSim 机制
ColBERT 的做法:保留文档中每个 token 的独立向量,检索时计算 query token 与 document token 之间的细粒度匹配。
编码
1 | |
每个 token 向量的维度通常是 128(比单向量的 512 低,但数量多)。
MaxSim 计算
对 query 中的每个 token,找 document 中与它最相似的 token,取最大相似度:
1 | |
手算示例:
1 | |
关键特性:每个 query token 独立找到文档中最匹配的位置。“Concurrent"匹配到了"Concurrent”,“HashMap"匹配到了"HashMap”——即使它们在文档中不相邻。
存储成本
多向量的代价是存储量大幅增加:
| 方案 | 每文档向量数 | 维度 | 字节/文档 | 100 文档 | 10K 文档 |
|---|---|---|---|---|---|
| 单向量 float32 | 1 | 512 | 2,048 | 200 KB | 20 MB |
| 单向量 int8 | 1 | 512 | 512 | 50 KB | 5 MB |
| ColBERT float32 | ~200 | 128 | 102,400 | 10 MB | 1 GB |
| ColBERT int8 | ~200 | 128 | 25,600 | 2.5 MB | 250 MB |
200 个 token 的文档,ColBERT float32 的存储是单向量的 50 倍。
ColBERTv2 的压缩方案
ColBERTv2(Santhanam et al., 2022)用残差压缩大幅降低存储:
- 对所有 token 向量做 k-means 聚类(如 k=65536)
- 每个 token 向量只存:聚类 ID(2 字节)+ 残差的 2-bit 量化(128d × 2bit = 32 字节)
- 每个 token 向量从 512 字节压缩到 ~34 字节
压缩后 200 token 文档的存储:200 × 34 = 6,800 字节 ≈ 单向量 float32 的 3.3 倍。代价从 50 倍降到 3 倍,可以接受。
检索策略
多向量检索有两条路线:
路线 A:作为 Reranker
最实际的方案——用 ColBERT 做 reranker,只在候选集上计算 MaxSim:
1 | |
优势:
- 只存储候选文档的 token 向量,或在线计算
- 不需要全量 token 向量索引
- 与现有流水线兼容
劣势:
- 召回上界受限于第一阶段——如果 BM25 + dense 漏掉了文档,ColBERT 也救不回来
- 在线计算 MaxSim 的延迟取决于候选数量
路线 B:作为 Retriever
把所有 token 向量建 HNSW 索引,检索时每个 query token 独立搜索最近邻:
1 | |
优势:
- 召回不受 BM25/dense 限制,理论上更完整
- 对 token 级匹配的查询效果好
劣势:
- 索引规模巨大(N 文档 × T token/文档 个向量)
- 每个 query token 都要做一次 HNSW 搜索,延迟是 query token 数的倍数
对于本系列的 100 篇小语料,路线 A 更合适。路线 B 在大规模生产环境中才有意义。
Lucene 的 Late Interaction 支持
Lucene 10.3 引入了 late-interaction reranking 的基础能力,但没有原生的 ColBERT MaxSim 评分器。实现路径:
存储多向量
1 | |
这种方式每个 token 一个字段——字段数量不固定,管理复杂。
更实际的做法:自定义 Collector
1 | |
把 token 向量序列化为一个二进制字段存储,检索时反序列化后计算 MaxSim。这种方案绕过了 Lucene 向量搜索的限制,代价是无法利用 HNSW 加速。
成本效益对比
把 ColBERT 与主线已有的 cross-encoder reranker(第 21 篇)放在一起比较:
| 维度 | Cross-Encoder | ColBERT MaxSim |
|---|---|---|
| 计算方式 | query-doc 拼接过完整 Transformer | query/doc 分别编码,点积 |
| 延迟(20 候选) | ~200ms(串行) | ~5ms(向量运算) |
| 延迟(100 候选) | ~1000ms | ~25ms |
| 离线编码 | 不可能(依赖 query) | doc 端可离线 |
| 质量 | 最高(全交互) | 略低(只有 MaxSim 交互) |
| 存储 | 无额外存储 | 每文档额外 ~7 KB(压缩后) |
ColBERT 的优势在延迟:100 候选的 MaxSim 计算只需 25ms,而 cross-encoder 需要 1 秒。在延迟预算紧张的场景,ColBERT 是 cross-encoder 的高效替代。
在质量上,cross-encoder 因为看到了 query 和 doc 的完整交互,通常优于 ColBERT 1-3 个 nDCG 点。如果延迟预算允许,cross-encoder 仍是更好的选择。
练习
- 手算一个 3-token query 和 5-token document 之间的 MaxSim 分数
- 计算 ColBERT 在 100 文档、1000 文档、10000 文档规模下的存储量(float32 和 int8)
- 设计实验:在 Top-50 候选上分别用 cross-encoder 和 ColBERT MaxSim 重排,对比 nDCG@10 和延迟
延伸阅读
- Khattab & Zaharia, “ColBERT: Efficient and Effective Passage Search via Contextualized Late Interaction over BERT”, SIGIR 2020
- Santhanam et al., “ColBERTv2: Effective and Efficient Retrieval via Lightweight Late Interaction”, NAACL 2022
- Lucene 10.3 Release Notes(late-interaction reranking 支持)






