文章

Bi-Encoder 与 Cross-Encoder:从架构差异理解检索与精排

从文档向量能否复用、问题与文档何时交互,理解 Bi-Encoder、Cross-Encoder 和 Late Interaction 的取舍,以及它们适合的检索与精排场景。

Bi-Encoder 与 Cross-Encoder:从架构差异理解检索与精排
  1. 先看三种打分路径
  2. Bi-Encoder:分别编码,让文档向量可以复用
  3. Cross-Encoder:把问题和文档放在一起判断
  4. Late Interaction:保留多个向量,把细粒度匹配放到最后
  5. 架构怎样决定适用场景

在上百万篇手机资料里搜索“某款手机的续航怎么样”,逐篇带着问题读太慢,提前把文档压缩成语义向量又可能丢掉细节。Bi-Encoder 与 Cross-Encoder 的区别,就在于问题和文档何时交互,以及哪些计算可以提前完成。

先看三种打分路径

下面这张 vLLM 官方 Score Types 图把三种路径放在了一起:左侧分别编码后比较单向量,中间联合编码后打分,右侧分别编码后比较多个向量。

vLLM 三种打分架构:左侧 Bi-Encoder 分别编码后比较单向量,中间 Cross-Encoder 联合编码后打分,右侧 Late Interaction 分别保留 Token 向量再做 MaxSim 匹配 来源:vLLM Scoring Usages,原始 SVG 保存于本站,按项目 Apache-2.0 许可引用。

从下往上读图:黄色和蓝色表示问题与文档,模型先把文本变成内部表示。文本会拆成 Token,即模型处理的文本单位,可以是词或词的片段;模型为这些单位计算包含上下文信息的向量。图中的 LLM 在这里负责产生表示,不意味着必须生成一段回答。

中间的 Pooling 层把这些表示整理成打分所需的形式:Sequence Pooling 得到整段输入的单个向量,Token Pooling 保留 Token 级的多个向量;顶部的 Score Function 再将表示变成分数。沿着这三层看,关键区别是:两段文本是在模型内部一起处理,还是各自编码后再比较。 这决定了文档表示能否提前算好、反复使用。

Bi-Encoder:分别编码,让文档向量可以复用

同一篇文档会被不同用户反复搜索。要避免每次重新读它,可以提前生成一份固定长度的数字向量,也就是 Embedding;搜索时再把问题变成向量,比较两者的相似度。

把文本转换成表示的网络称为 Encoder(编码器)。Bi- 表示两路:问题和文档分别编码,之后才比较向量。两路可以共享同一个模型的参数,不一定是两个不同模型;Bi 也不是“双向注意力”的意思。Sentence-BERT 原论文展示的就是共享权重的两路编码。

这可以概括为:

\[s(q,d)=\operatorname{sim}\bigl(f_q(q),f_d(d)\bigr)\]

其中,$q$ 是问题,$d$ 是文档;$f_q$、$f_d$ 分别输出一个 $m$ 维向量,$\operatorname{sim}$ 用余弦相似度或点积等方式得到分数。关键在于:文档编码 $f_d(d)$ 不依赖当前问题 $q$。

因此,文档向量可以提前计算、建立索引,供许多查询复用。在线搜索主要承担新问题的编码和向量检索,这正是 Bi-Encoder 适合大型知识库搜索的原因。

代价也来自独立编码。一篇同时介绍电池、相机和屏幕的评测,必须在不知道用户关心什么时就形成表示,某些细节可能被压缩掉。改问拍照后,问题向量会变,结果也可以变;保持不变的是文档向量。

Cross-Encoder:把问题和文档放在一起判断

当搜索已经缩小到几十篇候选,就可以多花一些计算,检查每篇文章究竟能不能回答当前问题。

Cross-Encoder 把问题与候选文档联合输入,让两段文本在编码时就发生交互,再输出相关性分数。Cross 指的正是这种跨文本交互。Sentence Transformers 官方架构说明用这条路径与前面的独立编码作对照。

例如,一篇手机评测同时介绍续航测试、夜景表现和充电功能。问续航时,模型可以结合问题关注电池与测试时长;问拍照时,则结合问题利用相机与成像信息。它的内部表示会受到这一次文本配对的影响。

这很像“带着问题读文档,在心里高亮相关内容”。不过,高亮只是比喻:典型模型不会先明确挑出几句话,再只对这些句子打分,而是在多层计算中整合信息。以 BERT 实现为例,拼接后的两段文本通过 Self-Attention 交互,并不需要一个独立的 Cross-Attention 模块。BERT 原论文

这种交互有助于检查局部细节,但需要为每个问题—文档对计算。如果全库有一百万篇,就要处理一百万对;先筛到 100 篇,则只需联合判断这 100 对。这些配对可以批量处理,并不要求逐条发起请求。

还有一个直观边界:问“支持无线充电吗”,文档写“不支持无线充电”,仍然高度相关。相关性看它是否有助于回答问题,不要求答案是肯定的。

Late Interaction:保留多个向量,把细粒度匹配放到最后

单向量压缩可能损失细节,联合编码又需要反复计算。若希望复用文档表示,同时保留更多局部信息,可以采用 Late Interaction(晚交互)。

以 ColBERT 为例,问题和文档仍分别编码,但保留的是 Token 级的多个向量,而不是把整段文本汇聚成一个向量。每个向量已经包含各自文本内部的上下文。

编码结束后,才让两组向量做细粒度匹配。典型的 MaxSim 做法是:对每个问题 Token,找出与它最相似的文档 Token,再把这些最大相似度加起来。这里匹配的是语义向量,不要求字面相同。

晚交互仍能预计算文档表示,代价是存储和匹配更多向量。它既可用于精排,也可结合多向量索引做检索。MaxSim 不会让文档重新编码,因此它与 Cross-Encoder 的联合编码仍有本质区别;也不能仅凭“晚交互”这个名字,就认定其速度或质量一定处于另外两者之间。

架构怎样决定适用场景

选择哪种方式,取决于候选规模、表示能复用多少次,以及任务需要多细的匹配。

场景常见选择为什么适合
大型知识库、商品或文章搜索Bi-Encoder 召回文档向量预计算,索引支持高效近邻搜索
文本聚类、相似内容发现Bi-Encoder Embedding每段文本都有可复用的向量,方便分组和比较
已召回候选的精排Cross-Encoder候选较少,可以投入联合计算检查具体相关性
只搜索十几个段落或判断已知文本对可直接用 Cross-Encoder配对数量有限,未必需要先建立向量索引
希望保留局部匹配能力,又要预计算文档Late Interaction多向量保留更细表示,编码后再匹配,但需承担额外存储与计算

大型知识库常把前两种架构组合成 Retrieve & Re-Rank:先召回,再精排。Sentence Transformers 官方流程展示的就是这种分工:

sequenceDiagram
    participant A as 检索服务
    participant B as Bi-Encoder
    participant V as 文档与向量索引
    participant C as Cross-Encoder

    Note over A,V: 离线:资料入库或更新
    A->>B: 文档文本
    B-->>A: 文档向量
    A->>V: 保存向量与原文

    Note over A,C: 在线:处理一个新问题
    A->>B: 问题文本
    B-->>A: 问题向量
    A->>V: 搜索近邻
    V-->>A: 候选文档原文
    A->>C: 问题与候选组成的文本对
    C-->>A: 每对文本的相关性分数
    A->>A: 排序并选取结果

精排接收的是问题与候选原文,可以重新检查细节;它只能调整候选顺序,不能找回未被召回的文档。召回也可以使用关键词或混合检索,不必绑定 Bi-Encoder。

vLLM 把三种路径都纳入 scoring,也说明了一个实用区别:“输入两段文本、输出分数”并不能证明底层是 Cross-Encoder;Rerank 描述的是重新排序的任务,架构描述的是分数怎样算出来。 模型质量还取决于训练与数据,分数的范围和含义也应按具体模型理解,不能一律当作概率。

如果还需要生成答案,就把选出的证据交给生成模型。向量用于检索,分数用于排序,生成模型负责组织回答;三者在 RAG 工作流中协作。相关知识的整体关系见AI 知识地图。

本文由作者按照 CC BY 4.0 进行授权