sentence-transformers 多向量编码器评估指南:MultiVectorEncoder 的 MaxSim 评测体系与 NanoBEIR 实战
导读
本指南围绕 sentence-transformers 仓库中多向量编码器(MultiVectorEncoder,即 ColBERT 风格 late-interaction 模型)的评估体系展开,核心场景是检索质量(IR)、重排序(Reranking)、三元组排序与知识蒸馏跟踪等任务的离线评测。读完本文,你将掌握评估器基于 MaxSim 打分端到端运行的原理,能用 nano_beir.py 在 13 个 NanoBEIR 子集上快速评估预训练模型,并熟练配置 dataset_names、corpus_chunk_size、chunk_elements 等关键参数,同时了解全套评估器的数据格式与指标含义。
一、多向量编码器为什么需要专门的评估器
与 SentenceTransformer 将每个输入编码为单个向量不同,多向量编码器把每个输入编码为一串 token 向量(每个 token 一个向量),查询与文档的比较采用 ColBERT 风格的 MaxSim(late-interaction)打分:对每个查询 token,取其与文档所有 token 的最大相似度,再对所有查询 token 求和,即 sum_i max_j (a_i · b_j)。该公式的权威实现见 sentence_transformers/util/similarity.py 中的 maxsim 函数,它在 sentence_transformers/multi_vector_encoder 之外同样被多处复用。
正是这种"查询一组 token 向量 vs 文档一组 token 向量"的形态,决定了普通的余弦/点积评估器无法直接套用——它们定义在单向量之上,对非齐次(ragged)的 per-token 嵌入无法正确计算。sentence_transformers/multi_vector_encoder/evaluation/ 下的评估器把打分链路端到端封装好了:
- 编码阶段使用
encode_query与encode_document(而非统一的encode),因此模型的[Q]/[D]前缀、查询扩展(query expansion)以及文档 skiplist 都会在评估时生效; - 打分阶段使用模型自身的
similarity_fn_name("maxsim"或"meanmaxsim"); - 指标阶段在分数之上计算标准的检索/排序指标。
MultiVectorEncoder 只支持这两种相似度函数,见 sentence_transformers/multi_vector_encoder/model.py 中的 SUPPORTED_SIMILARITY_FN_NAMES = ("maxsim", "meanmaxsim")。两者的区别在于 maxsim 会随查询长度累积分数(归一化嵌入下约每个查询 token 贡献 1 分),而 meanmaxsim 将总分除以真实查询 token 数,把分数拉回余弦的 [-1, 1] 区间——后者用于以长度归一化打分训练的模型。
一个必须注意的约束:这些评估器不支持 truncate_dim。原因在源码 docstring 中写得很清楚——多向量 token 嵌入没有 Matryoshka 式截断,任何非 None 的 truncate_dim 都会直接抛出 ValueError(见 nano_beir.py 与 information_retrieval.py 的构造函数校验)。
二、运行评估脚本的通用流程
目录下的每个评估脚本都遵循统一的五步流程:
- 加载预训练的多向量模型(
MultiVectorEncoder(...)); - 准备评估数据集;
- 配置合适的评估器;
- 运行评估;
- 报告结果。
评估器与示例脚本的对应关系如下:
| 评估器 | 示例脚本 |
|---|---|
MultiVectorNanoBEIREvaluator |
examples/multi_vector_encoder/evaluation/nano_beir.py |
运行方式很简单:直接执行 Python 脚本即可,无需任何额外的前置数据处理步骤(NanoBEIR 子集由评估器自行加载)。
三、NanoBEIR 快速评测实战
3.1 什么是 NanoBEIR
NanoBEIR 是 BEIR)。
3.2 完整示例代码
以下是 nano_beir.py 的完整内容,评估 lightonai/LateOn 模型在全部 13 个 Nano-* 检索数据集上的 MaxSim 表现:
"""Evaluate a pretrained multi-vector model on NanoBEIR.
NanoBEIR is a fast benchmarking suite of 13 small BEIR subsets, useful for quickly comparing models
without running the full BEIR evaluation. This script loads a model from the Hub and runs all 13
Nano-* IR datasets with MaxSim scoring.
"""
from __future__ import annotations
from pprint import pprint
from sentence_transformers import MultiVectorEncoder
from sentence_transformers.multi_vector_encoder.evaluation import MultiVectorNanoBEIREvaluator
def main() -> None:
model = MultiVectorEncoder("lightonai/LateOn")
evaluator = MultiVectorNanoBEIREvaluator(batch_size=16)
results = evaluator(model)
print(f"Primary metric: {evaluator.primary_metric} = {results[evaluator.primary_metric]:.4f}")
pprint({k: v for k, v in results.items() if "ndcg@10" in k})
if __name__ == "__main__":
main()
要点拆解:
MultiVectorEncoder("lightonai/LateOn")从 Hub 加载预训练模型,等价于用任意多向量模型(如lightonai/GTE-ModernColBERT-v1,见评估器 docstring 中的示例)替换;MultiVectorNanoBEIREvaluator(batch_size=16)配置评估器,batch_size控制编码时每次处理的文本数;evaluator(model)触发端到端评估,返回一个dict[str, float];- 默认配置下
evaluator.primary_metric对应各子集主指标的均值聚合(aggregate_fn默认np.mean、aggregate_key默认"mean"),示例中的过滤条件"ndcg@10" in k会打印每个子集的 NDCG@10 以及聚合均值。
3.3 报告哪些指标
对每个子集,评估器报告 MRR@k、NDCG@k、Recall@k、Precision@k、Accuracy@k、MAP@k,并在最后跨子集聚合这些指标。各 k 值的默认配置(可覆盖)为:MRR@k = [10]、NDCG@k = [10]、Accuracy@k = [1, 3, 5, 10]、Precision/Recall@k = [1, 3, 5, 10]、MAP@k = [100]。
四、MultiVectorNanoBEIREvaluator 关键参数详解
结合 sentence_transformers/multi_vector_encoder/evaluation/nano_beir.py 的 docstring,以下参数最值得掌握:
| 参数 | 默认值 | 说明 |
|---|---|---|
dataset_names |
全部 13 个子集 | 限制评测范围,如 ["msmarco", "nq", "fiqa2018"]。13 个子集为:climatefever、dbpedia、fever、fiqa2018、hotpotqa、msmarco、nfcorpus、nq、quoraretrieval、scidocs、arguana、scifact、touche2020 |
dataset_id |
"sentence-transformers/NanoBEIR-en" |
指向具备相同布局(corpus / queries / qrels)的其他数据集,例如 NanoBEIR 集合中的翻译变体,用于非英语评估 |
corpus_chunk_size |
5000 |
每轮往返(round-trip)编码并打分的文档数量。越大则同时驻留内存的文档嵌入越多,但编码轮次越少 |
chunk_elements |
None |
MaxSim 打分中间结果的元素预算上限。调低可降低打分阶段内存占用 |
batch_size |
32 |
编码时的每批输入数量 |
mrr_at_k / ndcg_at_k |
[10] |
MRR 与 NDCG 的 k 值 |
accuracy_at_k |
[1, 3, 5, 10] |
Accuracy 的 k 值 |
precision_recall_at_k |
[1, 3, 5, 10] |
Precision 与 Recall 的 k 值 |
map_at_k |
[100] |
MAP 的 k 值 |
show_progress_bar |
False |
评估时是否显示进度条 |
write_csv |
True |
是否把每次调用(按 epoch/steps 一行)追加写入 CSV |
write_predictions |
False |
是否将每查询 top-k 预测写入 JSONL,可直接作为 ReciprocalRankFusionEvaluator 的输入 |
典型使用建议:训练过程中常用 dataset_names=["msmarco", "nq", "fiqa2018"] 这类子集做快速迭代评估,完整 13 子集留到训练收尾再跑。
4.1 chunk_elements 背后的内存原理
chunk_elements 直接透传给 similarity.py 中的 maxsim 函数,它约束的是补零后的 (chunk, d_tokens, dim) 文档张量与 4D 打分中间张量 (batch_q, chunk, q_tokens, d_tokens) 的总元素数。文档按预算贪心打包进块(逐块补零,因此单个超长文档只会撑大自己所在块),默认 None 时采用 maxsim 内置的 1 亿元素预算(最多约 400 MB,bf16/fp16 下减半)。在非常大的查询批量下,单文档兜底下限仍然可能很大,此时需要在外部对查询分片。
另外注意:MultiVectorNanoBEIREvaluator 构造时会把 corpus_chunk_size 与 chunk_elements 注入到每个子集内部构造的 IR 评估器(见源码 _ir_extra_kwargs 与 _load_dataset 的合并逻辑),因此这两个参数对全部子集统一生效。
五、其他 MaxSim 评估器:数据格式与指标
NanoBEIR 是本目录唯一带示例脚本的任务,但包内还内置了其他任务的 MaxSim 评估器(全部导出自 sentence_transformers/multi_vector_encoder/evaluation/init.py),每个类的 docstring 都附有可运行示例:
| 评估器 | 必需数据 |
|---|---|
MultiVectorInformationRetrievalEvaluator |
查询(qid => 问题文本)、语料(cid => 文档文本)、相关文档(qid => set[cid]) |
MultiVectorRerankingEvaluator |
形如 {'query': ..., 'positive': [...], 'negative': [...]} 的字典列表 |
MultiVectorTripletEvaluator |
(anchor, positive, negative) 三元组 |
MultiVectorDistillationEvaluator |
查询 + 候选文档 + teacher 分数 |
5.1 MultiVectorInformationRetrievalEvaluator:自建语料的检索评估
MultiVectorNanoBEIREvaluator 内部就是逐子集运行 MultiVectorInformationRetrievalEvaluator,因此它接受与上面相同的指标与内存选项(corpus_chunk_size、chunk_elements、各 *_at_k、write_predictions 等),用于你自己的语料。实现细节(见 information_retrieval.py):
- 未显式传入
score_functions时,打分函数在每次调用时根据model.similarity_fn_name动态解析(_model_score_functions),因此模型换用"meanmaxsim"时评估自动跟随;若显式传入chunk_elements,则会以functools.partial把它绑定到默认打分函数上; - 查询嵌入会被预补零并跨语料块复用(
embed_inputs中pad_sequence),每个块只重新编码文档,降低重复开销; - 自定义
score_functions时,若其中混入 XTR 打分(xtr_scores/XTRScores),会直接抛ValueError——XTR 做的是跨整个候选集的全局 top-k,与评估器"逐块打分语料"的机制不兼容,逐块取 top-k 会静默出错,因此源码主动拒绝; - 显式 prompt 与模型注册 prompt 不匹配时会发出
warning_once提示,避免显式 prompt 悄悄替换掉模型训练时的 marker prompt。
5.2 MultiVectorRerankingEvaluator:二阶重排
MultiVectorRerankingEvaluator 对每个查询的固定候选列表打分,报告 MAP、MRR@k、NDCG@k(at_k 默认 10)。这正是把多向量模型用作**二阶重排器(second-stage reranker)**的评测形态:一阶段检索器返回每个查询的候选(正例与干扰项混合),多向量模型再对其重打分排序。实现上(见 reranking.py),查询与文档分别经 encode_query / encode_document 非对称编码,打分默认回退到 model.similarity(会把单查询归一化为 one-query batch)。
5.3 MultiVectorTripletEvaluator:三元组排序准确率
MultiVectorTripletEvaluator 检查 anchor 对 positive 的分数高于对 negative 的次数比例,判定条件为 MaxSim(anchor, positive) > MaxSim(anchor, negative) + margin。anchor 经 encode_query 编码(带查询前缀与长度),positive / negative 经 encode_document 编码。
margin 有一个容易踩坑的细节:margin 字典必须按相似度类型(maxsim 或 meanmaxsim)分别指定(传入 float 则对两者同时生效),因为两种打分的量纲不同——maxsim 分数随查询长度累积(归一化嵌入下约每查询 token 一分),而 meanmaxsim 除以 token 数后落在余弦的 [-1, 1] 区间,两者需要不同的 margin 值。源码通过 MultiVectorEncoder.SUPPORTED_SIMILARITY_FN_NAMES 枚举生成全部受支持的成对打分函数,默认选用模型当前的 similarity_fn_name。
5.4 MultiVectorDistillationEvaluator:蒸馏过程跟踪
MultiVectorDistillationEvaluator 用 KL 散度(越低越好)与 Spearman 秩相关(越高越好,主指标)比较学生分数与 teacher 分数,用于跟踪知识蒸馏训练。它支持两种数据形态(见 distillation.py):
- 逐查询候选集(KD 训练格式):
documents为每查询一个 N 路候选列表,scores为对应的 2 维 teacher 分数。两个指标都按查询计算,直接对齐训练损失:KL 使用与MultiVectorDistillKLDivLoss相同的温度处理(temperature、student_temperature、teacher_temperature三个参数,KL 还会乘上学生温度平方),Spearman 为各查询秩相关的均值;若训练使用了非默认的similarity_fct(如 MeanMaxSim 打分),评估器也支持传入similarity_fct镜像训练设置,否则逐查询 KL 无法与训练损失对齐; - 扁平配对:每查询一个文档、1 维分数。此时逐查询分布无定义,KL 把整个数据集 softmax 成单个分布报告总散度(不做配对数量归一,因此不可与逐查询 KL 或 PyLate 直接比较),Spearman 则是全体配对的单一全局相关。
细节上:teacher 或学生分数为常量时秩相关无定义,对应查询会被跳过(全部跳过则报 0.0);因为 MaxSim 分数跨查询不可比(随查询长度累积),逐查询相关才是能跟踪损失的那个信号——这也是 Spearman 被设为主指标的原因。
六、评估实践要点小结
- 训练中快速迭代:用
dataset_names挑 3 个子集(如["msmarco", "nq", "fiqa2018"]),完整 13 子集留到训练结束; - 内存控制:打分内存优先调
chunk_elements(默认 1 亿元素预算、约 400 MB,bf16/fp16 减半);编码内存用corpus_chunk_size控制同时驻留的文档嵌入数; - 保持一致打分:若模型以
"meanmaxsim"(长度归一化)训练,评估器会自动按model.similarity_fn_name解析打分,无需额外配置;蒸馏评估则务必把temperature与训练损失对齐; - 非英语评测:把
dataset_id换成 NanoBEIR 集合中的翻译变体即可,无需改动其余代码; - 结果落盘:默认
write_csv=True会把每次调用(epoch/steps 一行)追加到 CSV;write_predictions=True输出的 JSONL 可作为稀疏检索融合评估器(ReciprocalRankFusionEvaluator)的输入做下游分析。
如需深入实现,可继续阅读 nano_beir.py(子集加载与 truncate_dim 校验)、information_retrieval.py(动态打分解析与逐块打分)、similarity.py(maxsim / meanmaxsim 的底层实现与内存预算逻辑),以及配套测试 tests/multi_vector_encoder/test_evaluators.py 验证各评估器的行为。