文章目录
- [1 -> 引言](#1 -> 引言)
- [2 -> 一段文字压缩成一个向量,会发生什么](#2 -> 一段文字压缩成一个向量,会发生什么)
- [3 -> Late Interaction 把一部分匹配工作留到查询时](#3 -> Late Interaction 把一部分匹配工作留到查询时)
- [4 -> 用几行代码看见"平均后信息丢失"](#4 -> 用几行代码看见“平均后信息丢失”)
- [5 -> 检索更细,代价主要落在索引和评分](#5 -> 检索更细,代价主要落在索引和评分)
- [6 -> 怎样确认它真的改善了知识库](#6 -> 怎样确认它真的改善了知识库)

1 -> 引言
做过知识库问答的人,往往遇到过一种特别难解释的错误:答案明明写在文档里,系统却说没有找到。换一种问法,又能搜出来。进一步查看检索结果,会发现系统返回的几段文字都"挺相关",只是恰好没有包含那个关键条件。
比如用户问"支持离线部署,并且有审计日志的版本是什么",检索结果可能只满足离线部署,也可能只介绍审计日志。生成模型拿到这些片段后,很容易把两个不同版本的信息拼在一起,给出一个不存在的答案。
因此,RAG 的质量问题不能全部归到大模型头上。它依赖前面的检索是否拿到了正确证据。本文从单向量表示的限制出发,解释多向量检索和 Late Interaction 的基本思想,并通过一个无需下载模型的小实验观察两种评分方式的区别。

2 -> 一段文字压缩成一个向量,会发生什么
常见语义检索会把查询和文档片段分别编码成固定长度向量,再比较相似度。向量不是关键词列表,而是模型学习到的连续数值表示。同一个意思的不同表达,有机会在这个空间里靠得很近。
这种方法的工程优势明显:每个片段只保存一个向量,容易建立索引,查询也相对直接。但一个片段可能同时包含多个实体、条件和例外,固定长度表示必须把它们合在一起。整体主题很相似,不代表每一个条件都对得上。
切小片段可以缓解信息混合,却不是无限有效。切得太碎,前提和结论可能被分开;切得太大,细节又容易被整体主题淹没。标题、表格结构、段落引用和文档版本,都可能影响最终的可检索性。
这也解释了为什么"换一个更大的 Embedding 模型"并不总能解决漏检。有时问题发生在解析阶段:表格行列被打散,图片中的条件没有提取,重复版本互相覆盖。进入向量模型之前,正确证据可能已经丢了。
排查顺序应该从原始文档开始:内容有没有被读取,片段是否完整,元数据是否正确,然后才是表示与排序。否则,检索模型只是替上游的数据问题背锅。
3 -> Late Interaction 把一部分匹配工作留到查询时
ColBERT 提出的一条路线,是让查询和文档分别保留多个上下文化 Token 向量,再在评分阶段进行细粒度匹配。这里的"上下文化"很重要:它不是把每个词变成一个永远固定的字典向量,而是让词的表示考虑所在语境。原始论文介绍了这种晚交互检索结构。
为什么叫"晚交互"?查询与文档可以先分别编码,文档表示还可以提前保存;两者在评分阶段才进行交互。它介于单向量比较与查询、文档联合进入一个完整模型的重排序之间,形成了不同的成本折中。
一种典型评分方式叫 MaxSim:对每个查询 Token,在文档 Token 中寻找最高相似度,再把这些最高值加起来。直观地说,查询中的多个线索分别寻找自己的最佳匹配位置。
2026 年 8 月,Sentence Transformers 增加了 MultiVectorEncoder 相关支持,让这类模型可以通过更统一的接口使用。这个进展主要降低工程接入成本,不意味着 Late Interaction 是刚刚发明的技术。Hugging Face 说明提供了具体接口和使用范围。
这种结构也有局限。最佳局部匹配的累加,并不自动理解严格的逻辑约束。一篇文档分别提到了两个条件,不代表它们对同一个对象同时成立;数字、否定、版本和时间范围仍可能匹配错误。多向量检索改善的是表示与相关性评分,不能代替业务规则检查。

4 -> 用几行代码看见"平均后信息丢失"
下面构造二维向量:查询关注两个不同方向。文档 A 分别保留这两个方向,文档 B 的两个表示都处在中间。将它们先平均,再做余弦比较,差别会被抹平;保留局部表示,评分则不同。
这是一组人为构造的几何例子,不来自真实模型。真实单向量编码器也不一定直接平均 Token。本实验只展示一种信息压缩现象,不能据此宣称多向量在所有任务中都更好。Python 3.10 及以上版本即可运行。
python
from math import sqrt
def dot(a, b):
return sum(x * y for x, y in zip(a, b))
def normalize(vector):
length = sqrt(dot(vector, vector))
if length == 0:
raise ValueError("zero vector")
return tuple(x / length for x in vector)
def pooled(vectors):
width = len(vectors[0])
return normalize(tuple(
sum(vector[i] for vector in vectors) / len(vectors)
for i in range(width)
))
def maxsim(query, document):
query = [normalize(v) for v in query]
document = [normalize(v) for v in document]
return sum(max(dot(q, d) for d in document) for q in query)
query = [(1.0, 0.0), (0.0, 1.0)]
documents = {
"A: separate signals": [(1.0, 0.0), (0.0, 1.0)],
"B: blended signals": [(1.0, 1.0), (1.0, 1.0)],
}
for name, document in documents.items():
single = dot(pooled(query), pooled(document))
multi = maxsim(query, document)
print(name, f"single={single:.3f}", f"maxsim={multi:.3f}")
assert abs(maxsim(query, documents["A: separate signals"]) - 2.0) < 1e-9
assert maxsim(query, documents["A: separate signals"]) > maxsim(
query, documents["B: blended signals"]
)
两个单向量分数都会接近 1。MaxSim 则分别为 2 和约 1.414。可以继续增加干扰向量,或者把查询改成只关注一个方向,观察分数怎样变化。
注意,MaxSim 的总分与查询 Token 数有关。长查询可能天然获得更大的累加值,不同模型的分数尺度也不一定相同。不能看到一个结果是 12 分、另一个是 8 分,就跨查询断言前者"更可信"。
5 -> 检索更细,代价主要落在索引和评分
多向量最直接的成本是需要存储更多表示。假设有十万个片段,每个片段平均保留 200 个 Token 向量,每个向量 128 维,使用每个数值 2 字节的表示,仅向量原始数据就约为 5.12 GB,即约 4.77 GiB。
同样数量的片段,如果每段只保存一个 768 维、每数值 2 字节的向量,原始数据约为 153.6 MB。这些都是按假设计算的教学值,不包含元数据、索引结构、缓存和压缩,不能当成某个数据库的实际占用。
研究与工程系统会用量化、压缩、候选筛选等方式减少成本。ColBERTv2 就讨论了通过残差压缩等方法降低多向量表示开销。论文中的方法与具体实现有关,不能把某一种压缩比例直接套到所有模型。
因此,现实中常见的选择是分阶段检索:先用较便宜的方法召回一批候选,再用更精细的方法排序。候选数量越少,后续成本越可控,但前一阶段漏掉的正确答案,后面的排序器也无法凭空找回来。
| 方案 | 适合关注的问题 | 需要承担的代价 |
|---|---|---|
| 关键词检索 | 型号、专有名词、明确词项 | 对改写和语义变化敏感 |
| 单向量检索 | 语义相近的表达 | 细粒度条件可能混合 |
| 多向量检索 | 多个局部线索匹配 | 索引与评分开销增加 |
| 联合编码重排序 | 对候选做更细的相关性判断 | 通常需要逐对计算 |
表中的方法可以组合,不是四选一。型号和语义混合的查询,往往值得保留关键词与向量两条召回路径。
6 -> 怎样确认它真的改善了知识库
不要从演示里挑几道效果好的问题下结论。先建立一份覆盖真实需求的小型评测集,每个问题标注哪些文档或片段能够支撑答案。标注时允许多个有效证据,避免把一个正确但不同的来源误判为错误。
评估至少分三层。第一层看召回:正确证据有没有进入前 K 个结果。第二层看排序:正确结果是否出现在用户或生成模型实际会读取的位置。第三层看回答:最终结论是否忠于证据,是否附带有效引用。
指标名称也要说准确。若某问题有四个已标注的相关片段,前五个结果找回了两个,那么 Recall@5 是 2/4;"前五个里至少有一个正确结果"则更接近 Hit@5。两者回答的问题不同,不能在实验表里混用。如果标注并不完整,也需要说明漏标可能影响评估。
还可以给失败结果分类。完全没有匹配到主题,可能是解析、语言或表示问题;主题相关却缺条件,可能需要更好的细粒度匹配;正确文档已经召回却排得很后,问题主要在排序;证据正确而最终回答错误,则应检查生成阶段。这样能把优化集中在真正有问题的环节。
例如,知识库同时保存标准版和企业版的说明。用户问企业版的离线审计功能时,标准版文档即使词语更相似,也不应该优先成为依据。明确的版本元数据与过滤规则,可能比再换一个更大的检索模型更直接。同样,已经废弃的文档仍被当成最新事实,也不是相似度算法能够独自解决的问题。
还要记录延迟和索引体积。一个方案把召回率提高了一点,却让成本超过业务预算,并不一定值得采用。对频繁更新的知识库,还需测试增量写入、删除和版本替换的行为。
测量时尽量保留相同的文档解析与切分结果,让不同检索器面对同一批材料。如果同时更换解析器、片段大小、Embedding 和重排序器,就算最终变好了,也很难知道收益来自哪里。逐项替换虽然看起来慢一些,却更容易形成可以复用的经验。
权限是另一条不能靠相关性替代的边界。用户无权访问的文档,不应先检索出来再希望模型"不要说"。应在检索流程中实施访问控制,并保证缓存与引用同样遵守权限。
最后,检索到的文本是证据,不是指令。文档里出现"忽略前面的规则"之类内容,不能改变系统的行为边界。知识库越开放,这种区分越重要。
多向量检索的价值,在于给了我们一种更细的匹配工具。它值得尝试,但真正决定 RAG 质量的,仍然是一整条链路:文档处理是否正确,问题是否可检索,证据是否完整,排序是否合理,回答是否受到证据约束。先找出漏检发生在哪一层,再选择技术,通常比一上来更换所有组件有效。
感谢各位大佬支持!!!
互三啦!!!