T
traeai
登录
返回首页
Hugging Face Blog

Ettin Reranker 系列模型发布

8.0Score
Ettin Reranker 系列模型发布

TL;DR · AI 摘要

Hugging Face发布基于ModernBERT编码器的Ettin Reranker系列,包含17M到1B参数六个CrossEncoder模型,采用蒸馏训练方法,在MTEB检索基准上达到同类最优性能,为检索增强生成(RAG)系统提供高效重排序方案。

核心要点

  • 发布6个CrossEncoder reranker模型(17M/32M/68M/150M/400M/1B参数),基于Ettin ModernBERT架构
  • 采用蒸馏训练策略:以mxbai-rerank-large-v2为教师模型,在混合数据集上训练
  • 在MTEB(eng, v2) Retrieval任务上,1B参数模型配合embeddinggemma-300m达到NDCG@10约58%的SOTA水平

结构提纲

按章节快速跳转。

  1. 发布六个基于Ettin ModernBERTCrossEncoder reranker模型,参数规模从17M到1B,采用蒸馏训练配方。

  2. Reranker是CrossEncoder模型,对(query, document)对进行联合编码输出相关性分数,比独立编码的embedding模型更准确但计算成本更高。

  3. 生产环境采用两阶段流程:先用embedding模型快速召回top-K候选,再用cross-encoder精确重排序,平衡成本与精度。

  4. 作为标准Sentence Transformers CrossEncoder模型使用,通过pipeline API或直接调用实现文档重排序。

  5. 使用Sentence Transformers v5.5.0的train-sentence-transformers Agent Skill,通过MSE蒸馏损失在混合预训练/微调数据上训练。

  6. 六个模型在MTEB(eng, v2) Retrieval任务上与embeddinggemma-300m配对测试,1B模型达到约58% NDCG@10,显著优于基线。

金句 / Highlights

值得收藏与分享的关键句。

  • Reranker让query和document通过每个transformer层相互注意力机制进行联合编码,比独立embedding模型更准确但计算成本更高。

    What is a reranker section

    ⬇︎ 下载 PNG𝕏 分享到 X
  • 生产环境的常见模式是retrieve-then-rerank:先用快速embedding模型低成本召回top-K候选,再用cross-encoder精确重排序。

    What is a reranker section

    ⬇︎ 下载 PNG𝕏 分享到 X
  • 1B参数模型在MTEB(eng, v2) Retrieval任务上配合google/embeddinggemma-300m达到约58%的NDCG@10。

    Results section

    ⬇︎ 下载 PNG𝕏 分享到 X
  • 训练使用MSE蒸馏损失,以mxbai-rerank-large-v2为教师模型,在混合预训练/微调数据上进行训练。

    Training section

    ⬇︎ 下载 PNG𝕏 分享到 X
  • 通过`hf skills add train-sentence-transformers`安装,并让AI编码代理在自定义数据上微调CrossEncoder模型。

    Training section

    ⬇︎ 下载 PNG𝕏 分享到 X
#Hugging Face#Reranker#CrossEncoder#ModernBERT#MTEB
打开原文

TL;DR

今天我发布了六个全新的 Sentence Transformers CrossEncoder 重排序模型,它们在各自参数量级上均达到 SOTA 水平。这些模型基于 Ettin ModernBERT 编码器构建,并同步开源了训练数据及完整的训练方案:

模型采用蒸馏方案训练:在 `cross-encoder/ettin-reranker-v1-data` 数据集上对 `mixedbread-ai/mxbai-rerank-large-v2` 的分数进行 pointwise MSE 回归。该数据集是 `lightonai/embeddings-pre-training` 的子集,并混合了 `lightonai/embeddings-fine-tuning` 的重排序子集。

Image 2: MTEB(eng, v2) Retrieval with embeddinggemma-300m + reranker

_我们的六个重排序模型与 `google/embeddinggemma-300m` 在 MTEB(eng, v2) Retrieval 任务上的组合效果。更多 embedding 模型组合请见 Results 章节。_

如果你刚接触重排序模型,想了解"为什么"使用它,请跳至 什么是重排序模型,为什么要与 embedding 模型配合使用?。如果你只想直接上手使用,请跳至 使用方法。如果你想训练自己的模型,请跳至 训练

我使用 Sentence Transformers v5.5.0 中新发布的 `train-sentence-transformers` Agent Skill 快速搭建了以下训练方案。可通过 hf skills add train-sentence-transformers [--global] [--claude] 安装,然后让你的 AI 编程助手(Claude Code、Codex、Cursor、Gemini CLI 等)在数据上微调 SentenceTransformerCrossEncoderSparseEncoder 模型。

目录

什么是重排序模型,为什么要与 embedding 模型配合使用?

重排序模型(又称 pointwise cross-encoder)是一种神经网络模型,接收 (query, document) 对并输出单一的相关性分数。与 embedding 模型不同——后者分别编码 query 和 document,再从两个向量计算相似度——重排序模型让两段文本在每一层 transformer 中相互 attention。这种联合编码更准确,但计算成本更高:模型需要为每个 (query, document) 对运行一次,而非每个文本运行一次。

由于 cross-encoder 在全量语料上运行成本过高,常见的生产模式是先检索后重排序:快速的 embedding 模型先召回 top-K 候选(低成本),然后 cross-encoder 仅对这 K 个结果进行高精度重排序。总成本保持可控,而最终排序效果接近全量 cross-encoder 遍历的结果。

Image 3: Embedding vs Reranker Models

本文中"reranker"和"cross-encoder"将交替使用。

使用方法

发布的模型是标准的 Sentence Transformers CrossEncoder 模型,只需 3 行代码即可使用:

code
from sentence_transformers import CrossEncoder

model = CrossEncoder("cross-encoder/ettin-reranker-32m-v1")
scores = model.predict([
    ("Where was Apple founded?", "Apple Inc. was founded in Cupertino, California in 1976 by Steve Jobs, Steve Wozniak, and Ronald Wayne."),
    ("Where was Apple founded?", "The Fuji apple is an apple cultivar developed in the late 1930s and brought to market in 1962."),
])
print(scores)
# [11.393298  2.968891]   <- 数值越大表示相关性越高

对于 query 和候选列表,也可以使用 rank 方法获取排序后的索引和分数:

code
ranked = model.rank(
    query="Which planet is known as the Red Planet?",
    documents=[
        "Venus is often called Earth's twin because of its similar size and proximity.",
        "Mars, known for its reddish appearance, is often referred to as the Red Planet.",
        "Jupiter, the largest planet in our solar system, has a prominent red spot.",
        "Saturn, famous for its rings, is sometimes mistaken for the Red Planet.",
    ],
    top_k=4,
    return_documents=True,
)
for r in ranked:
    print(f"({r['score']:.2f}): {r['text']}")
# (10.82): Mars, known for its reddish appearance, is often referred to as the Red Planet.
# (9.86): Saturn, famous for its rings, is sometimes mistaken for the Red Planet.
# (8.55): Jupiter, the largest planet in our solar system, has a prominent red spot.
# (6.21): Venus is often called Earth's twin because of its similar size and proximity.

你可以将 `cross-encoder/ettin-reranker-32m-v1` 替换为其他尺寸的模型,以在质量和速度之间进行权衡。得益于 ModernBERT 的长上下文预训练,所有六个模型都支持最多 8K token 的上下文(适用于长文档重排序)。

建议安装 `kernels` 并设置 model_kwargs={"dtype": "bfloat16", "attn_implementation": "flash_attention_2"} 以获得最高吞吐量。更多细节请参见下方的 Speed 章节,但总体而言,根据模型大小和序列长度,你可以预期相比默认加载有 1.7 倍到 8.3 倍的加速。

code
from sentence_transformers import CrossEncoder

model = CrossEncoder(
    "cross-encoder/ettin-reranker-32m-v1",
    model_kwargs={"dtype": "bfloat16", "attn_implementation": "flash_attention_2"},
)

端到端检索-重排序流水线

一个完整的示例,使用快速嵌入模型进行检索,再用重排序模型进行最终排序:

code
from sentence_transformers import SentenceTransformer, CrossEncoder

# 使用静态嵌入模型进行快速检索(CPU 上每查询亚毫秒级)
embedder = SentenceTransformer("sentence-transformers/static-retrieval-mrl-en-v1")
reranker = CrossEncoder("cross-encoder/ettin-reranker-68m-v1")

corpus = [
    "Apple Inc. was founded in Cupertino, California in 1976 by Steve Jobs, Steve Wozniak, and Ronald Wayne.",
    "The Fuji apple is an apple cultivar developed in the late 1930s.",
    "Steve Jobs introduced the iPhone in 2007 at Macworld.",
    "Macintosh computers were sold by Apple from 1984 onward.",
    # ... 生产环境中可能有数千或数百万条
]
query = "Where was Apple founded?"

# 步骤 1:编码 + 检索前 100 条
query_emb = embedder.encode_query(query, convert_to_tensor=True)
corpus_emb = embedder.encode_document(corpus, convert_to_tensor=True)
scores = embedder.similarity(query_emb, corpus_emb)[0]
top_k_idx = scores.topk(min(100, len(corpus))).indices.tolist()

# 步骤 2:重排序
top_k_docs = [corpus[i] for i in top_k_idx]
ranked = reranker.rank(query, top_k_docs, top_k=5, return_documents=True)
for r in ranked:
    print(f"({r['score']:.2f}): {r['text']}")
# (11.63): Apple Inc. was founded in Cupertino, California in 1976 by Steve Jobs, Steve Wozniak, and Ronald Wayne.
# (4.71): Steve Jobs introduced the iPhone in 2007 at Macworld.
# (1.96): The Fuji apple is an apple cultivar developed in the late 1930s.
# (1.49): Macintosh computers were sold by Apple from 1984 onward.

这与大多数现代搜索系统的架构相同。检索器决定哪些内容进入漏斗,重排序器决定哪些内容胜出。

架构细节

所有六个重排序器共享相同的架构,仅在骨干网络尺寸上有所不同。骨干网络是约翰霍普金斯大学 Ettin 套件中的六个 Ettin encoder 之一。这些是 ModernBERT 风格的模型,采用无填充注意力、RoPE 位置编码、GeGLU,以及 2T token 的开源许可证预训练,支持最多 8192 token 的上下文。

在每个编码器之上,重排序器使用一个 4 模块的分类头,其结构与 ModernBertForSequenceClassification 类似,但由 Sentence Transformers 的模块化组件构建而成。底层的 Transformer 是普通的 AutoModel 而非 AutoModelForSequenceClassification,这使我们能够对变长输入使用序列去填充以配合 Flash Attention 2。在中等文档序列长度下,相比 fp32+SDPA,根据模型大小可获得 1.7 倍到 8.3 倍的加速(完整基准测试请参见 Speed):

code
1. Transformer(FA2)
2. Pooling(cls)
3. Dense(H, H, bias=False, GELU)
4. LayerNorm(H)
5. Dense(H, 1, scores)

在我的消融实验中,CLS pooling 优于 mean pooling。这有些令人意外。ModernBERT 仅在每三层中使用全局注意力,其余三分之二使用局部窗口注意力,无法从远处位置关注到 CLS。从经验上看,少数的全局层携带了足够的信号,使 CLS 成为更优的 pooling 选择。

| 模型 | Backbone | 隐藏层大小 | 层数 | 参数量(含 head) | | --- | --- | --- | --- | --- | | `cross-encoder/ettin-reranker-17m-v1` | `jhu-clsp/ettin-encoder-17m` | 256 | 7 | 17.6M | | `cross-encoder/ettin-reranker-32m-v1` | `jhu-clsp/ettin-encoder-32m` | 384 | 10 | 32.8M | | `cross-encoder/ettin-reranker-68m-v1` | `jhu-clsp/ettin-encoder-68m` | 512 | 19 | 68.6M | | `cross-encoder/ettin-reranker-150m-v1` | `jhu-clsp/ettin-encoder-150m` | 768 | 22 | 150.9M | | `cross-encoder/ettin-reranker-400m-v1` | `jhu-clsp/ettin-encoder-400m` | 1024 | 28 | 401.6M | | `cross-encoder/ettin-reranker-1b-v1` | `jhu-clsp/ettin-encoder-1b` | 1792 | 28 | 1.00B |

全部六个模型均采用 Apache 2.0 许可证发布,与 Ettin encoder 保持一致。

实验结果

MTEB(eng, v2) 检索任务

我对每个发布的模型运行了完整的 `MTEB(eng, v2)` 检索基准测试(10 个任务,top-100 重排序),使用 MTEB 的两阶段重排序流程,将每个重排序器与六个涵盖速度/质量范围的 embedding 模型配对:

| Embedding 模型 | 激活参数量 | 仅检索器 NDCG@10 | | --- | --- | --- | | `sentence-transformers/static-retrieval-mrl-en-v1` | 0M | 0.3495 | | `sentence-transformers/all-MiniLM-L6-v2` | 23M | 0.4292 | | `BAAI/bge-small-en-v1.5` | 33M | 0.5149 | | `nomic-ai/nomic-embed-text-v1.5` | 137M | 0.5226 | | `google/embeddinggemma-300m` | 308M | 0.5463 | | `jinaai/jina-embeddings-v5-text-small-retrieval` | 596M | 0.5980 |

下方每张图表中的虚线(仅检索器)是需要超越的基准线。任何低于该线的结果都意味着重排序器平均而言对流程产生了负面影响:

| | | | --- | --- | | ![图 4: MTEB(eng, v2) 检索任务,使用 static-retrieval-mrl-en-v1 + 重排序器](https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/blog/ettin-reranker/mteb_ndcg10_static-retrieval-mrl-en-v1.png) | ![图 5: MTEB(eng, v2) 检索任务,使用 all-MiniLM-L6-v2 + 重排序器](https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/blog/ettin-reranker/mteb_ndcg10_all-MiniLM-L6-v2.png) | | ![图 6: MTEB(eng, v2) 检索任务,使用 bge-small-en-v1.5 + 重排序器](https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/blog/ettin-reranker/mteb_ndcg10_bge-small-en-v1.5.png) | ![图 7: MTEB(eng, v2) 检索任务,使用 nomic-embed-text-v1.5 + 重排序器](https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/blog/ettin-reranker/mteb_ndcg10_nomic-embed-text-v1.5.png) | | ![图 8: MTEB(eng, v2) 检索任务,使用 embeddinggemma-300m + 重排序器](https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/blog/ettin-reranker/mteb_ndcg10_embeddinggemma-300m.png) | ![图 9: MTEB(eng, v2) 检索任务,使用 jina-embeddings-v5-text-small-retrieval + 重排序器](https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/blog/ettin-reranker/mteb_ndcg10_jina-embeddings-v5-text-small-retrieval.png) |

完整结果表格(点击展开) 6 种 embedding 模型配对的平均 NDCG@10,按降序排列。我们的六个模型以粗体显示,教师模型 `mixedbread-ai/mxbai-rerank-large-v2` 以下划线标注。

| Reranker | 参数量 | MTEB(eng, v2) 检索 NDCG@10 | | --- | --- | --- | | `Qwen/Qwen3-Reranker-4B`† | 4.02B | 0.6367 | | `mixedbread-ai/mxbai-rerank-large-v2` | 1.54B | 0.6115 | | [`cross-encoder/ettin-reranker-1b-v1`](https://huggingface.co/cross-encoder/ettin-reranker-1b-v1) | 1.00B | 0.6114 | | [`cross-encoder/ettin-reranker-400m-v1`](https://huggingface.co/cross-encoder/ettin-reranker-400m-v1) | 401M | 0.6091 | | [`cross-encoder/ettin-reranker-150m-v1`](https://huggingface.co/cross-encoder/ettin-reranker-150m-v1) | 151M | 0.5994 | | `Qwen/Qwen3-Reranker-0.6B` | 596M | 0.5940 | | `mixedbread-ai/mxbai-rerank-base-v2` | 494M | 0.5920 | | [`cross-encoder/ettin-reranker-68m-v1`](https://huggingface.co/cross-encoder/ettin-reranker-68m-v1) | 68.6M | 0.5915 | | `jinaai/jina-reranker-m0` | 2.44B | 0.5856 | | `Alibaba-NLP/gte-reranker-modernbert-base` | 150M | 0.5843 | | [`cross-encoder/ettin-reranker-32m-v1`](https://huggingface.co/cross-encoder/ettin-reranker-32m-v1) | 32.8M | 0.5779 | | `ibm-granite/granite-embedding-reranker-english-r2` | 150M | 0.5656 | | [`cross-encoder/ettin-reranker-17m-v1`](https://huggingface.co/cross-encoder/ettin-reranker-17m-v1) | 17.6M | 0.5576 | | `BAAI/bge-reranker-v2-m3` | 568M | 0.5526 | | `zeroentropy/zerank-2-reranker`† | 4.02B | 0.5300 | | `BAAI/bge-reranker-large` | 560M | 0.5098 | | `cross-encoder/ms-marco-MiniLM-L6-v2` | 22.7M | 0.5082 | | `cross-encoder/ms-marco-MiniLM-L12-v2` | 33.4M | 0.5066 | | `mixedbread-ai/mxbai-rerank-large-v1` | 435M | 0.5063 | | `cross-encoder/ms-marco-MiniLM-L4-v2` | 19.2M | 0.4979 | | `mixedbread-ai/mxbai-rerank-xsmall-v1` | 70.8M | 0.4968 | | `BAAI/bge-reranker-base` | 278M | 0.4890 | | `mixedbread-ai/mxbai-rerank-base-v1` | 184M | 0.4865 |

† 限制为 max_seq_length=8192(基于 Qwen3 的 4B 重排序器在原生上下文长度下单张 H100 80GB 显存无法容纳)。原生上下文长度的评估结果可能更高。

NanoBEIR 完整结果表(点击展开) NanoBEIRBEIR 的一个快速 13 数据集子集,每个数据集使用 50 个查询,最多对应 5000 个文档。NanoBEIR 是训练期间 metric_for_best_model 所设置的指标(参见 评估),也是我用于指导实验的指标。

| Reranker | 参数量 | NanoBEIR 平均 NDCG@10 | | --- | --- | --- | | `mixedbread-ai/mxbai-rerank-large-v2` | 1.54B | 0.7318 | | [`cross-encoder/ettin-reranker-1b-v1`](https://huggingface.co/cross-encoder/ettin-reranker-1b-v1) | 1.00B | 0.7237 | | `jinaai/jina-reranker-m0` | 2.44B | 0.7197 | | [`cross-encoder/ettin-reranker-400m-v1`](https://huggingface.co/cross-encoder/ettin-reranker-400m-v1) | 401M | 0.7193 | | `mixedbread-ai/mxbai-rerank-base-v2` | 494M | 0.7162 | | [`cross-encoder/ettin-reranker-150m-v1`](https://huggingface.co/cross-encoder/ettin-reranker-150m-v1) | 151M | 0.7086 | | `Alibaba-NLP/gte-reranker-modernbert-base` | 150M | 0.7017 | | `BAAI/bge-reranker-v2-m3` | 568M | 0.6971 | | [`cross-encoder/ettin-reranker-68m-v1`](https://huggingface.co/cross-encoder/ettin-reranker-68m-v1) | 68.6M | 0.6915 | | `ibm-granite/granite-embedding-reranker-english-r2` | 150M | 0.6909 | | [`cross-encoder/ettin-reranker-32m-v1`](https://huggingface.co/cross-encoder/ettin-reranker-32m-v1) | 32.8M | 0.6825 | | [`cross-encoder/ettin-reranker-17m-v1`](https://huggingface.co/cross-encoder/ettin-reranker-17m-v1) | 17.6M | 0.6746 | | `mixedbread-ai/mxbai-rerank-large-v1` | 435M | 0.6488 | | `BAAI/bge-reranker-large` | 560M | 0.6379 | | `cross-encoder/ms-marco-MiniLM-L12-v2` | 33.4M | 0.6369 | | `cross-encoder/ms-marco-MiniLM-L6-v2` | 22.7M | 0.6312 | | `cross-encoder/ms-marco-MiniLM-L4-v2` | 19.2M | 0.6298 | | `mixedbread-ai/mxbai-rerank-base-v1` | 184M | 0.6231 | | `mixedbread-ai/mxbai-rerank-xsmall-v1` | 70.8M | 0.6136 | | `BAAI/bge-reranker-base` | 278M | 0.6027 |

我发布的最小模型——1700万参数版本——在参数量仅为其一半的情况下,在 MTEB 上以 +0.051 NDCG@10(0.5576 对比 0.5066)击败了 3300万参数的 ms-marco-MiniLM-L12-v2,在 NanoBEIR 上以 +0.038(0.6746 对比 0.6369)领先。3200万参数版本在 MTEB 上以 +0.025(0.5779 对比 0.5526)击败了 5.68亿参数的 BAAI/bge-reranker-v2-m3,参数量差距达 17 倍。如果你一直在检索-重排序流程中使用老旧的 MiniLM 重排序器作为默认选项,将其替换为我们的 1700万(或 3200万)参数版本是一个低风险的即插即用方案,在两个基准测试上都能获得明显的质量提升。

继续向上看,我们的 1.5亿参数版本是我在 MTEB 上测试过的 6亿参数以下最强的重排序器,以 +0.005(0.5994 对比 0.5940)险胜近期的 Qwen/Qwen3-Reranker-0.6B(5.96亿参数),并以 0.03 到 0.05 的优势击败所有 BAAI bge-reranker 变体。6800万参数版本也值得提及:以 0.5915 的成绩几乎与 Qwen3-Reranker-0.6B(0.5940)持平,而参数量仅为其九分之一。

在发布的模型中,我们的 10亿参数版本与其教师模型非常接近。在 MTEB 上与 15.4亿参数的 mxbai-rerank-large-v2 差距仅 0.0001(0.6114 对比 0.6115),在 NanoBEIR 上差距仅 0.008,尽管它是从一个比自身大 54% 的模型蒸馏而来。这种蒸馏有效地弥合了与教师模型的差距,这正是我在发布前希望看到的结果。

对比中整体最强的重排序器是 Qwen/Qwen3-Reranker-4B,MTEB 得分 0.6367,比我们的 10亿参数版本高 +0.025。要从当前方案缩小这一差距,可能需要从一个更强的教师模型进行蒸馏(我们的教师模型本身也低于 Qwen3-Reranker-4B)。对于大多数检索-重排序工作负载,我们的 10亿参数版本在参数量仅为四分之一的情况下(参见速度),是一个更实用的选择。

速度

对于重排序器而言,质量数字只是重要的一半。另一半是它的延迟是否能满足检索到向用户展示结果之间的预算。让我介绍一下我的测量结果。

我在单张 NVIDIA H100 80GB 上,将发布的六个模型与十三个公开重排序器(约 10亿参数以内的强基线)进行了基准测试。查询和文档来自 `sentence-transformers/natural-questions`,采用其自然文档长度分布:大多数 NQ 答案较短,有些较长。文档截断至 max_length=512,以避免让旧模型获得不公平优势。每个模型使用其最佳支持的注意力实现:在架构支持的情况下使用 Flash Attention 2(BERT、XLM-RoBERTa、ModernBERT、Qwen2),不支持时使用 SDPA,DeBERTa-v2 使用 eager(目前在 transformers 中既不支持 FA2 也不支持 SDPA)。

对于每个模型,自动批处理搜索从批大小 8 开始,翻倍直到 GPU 内存耗尽。在每个批大小下运行三次计时测试并取中位数吞吐量,这样单次运气不佳的运行不会拖累结果。报告的吞吐量采用获胜的批大小。

表 1. 每秒处理对数(pairs per second),全部为 bfloat16 精度。我们的六个重排序器以粗体显示。

| 模型 | 参数量 | 注意力 | 对/秒 | | --- | --- | --- | --- | | [`cross-encoder/ettin-reranker-17m-v1`](https://huggingface.co/cross-encoder/ettin-reranker-17m-v1) | 17M | FA2 | 7517 | | [`cross-encoder/ettin-reranker-32m-v1`](https://huggingface.co/cross-encoder/ettin-reranker-32m-v1) | 32M | FA2 | 6602 | | [`cross-encoder/ettin-reranker-68m-v1`](https://huggingface.co/cross-encoder/ettin-reranker-68m-v1) | 68M | FA2 | 4913 | | `cross-encoder/ms-marco-MiniLM-L4-v2` | 19M | FA2 | 4029 | | `cross-encoder/ms-marco-MiniLM-L6-v2` | 22M | FA2 | 3817 | | `cross-encoder/ms-marco-MiniLM-L12-v2` | 33M | FA2 | 3311 | | [`cross-encoder/ettin-reranker-150m-v1`](https://huggingface.co/cross-encoder/ettin-reranker-150m-v1) | 150M | FA2 | 3237 | | `BAAI/bge-reranker-base` | 278M | FA2 | 2858 | | `mixedbread-ai/mxbai-rerank-xsmall-v1` | 70M | eager | 2636 | | `mixedbread-ai/mxbai-rerank-base-v1` | 184M | eager | 1953 | | [`cross-encoder/ettin-reranker-400m-v1`](https://huggingface.co/cross-encoder/ettin-reranker-400m-v1) | 400M | FA2 | 1738 | | `BAAI/bge-reranker-large` | 560M | FA2 | 1659 | | `BAAI/bge-reranker-v2-m3` | 568M | FA2 | 1569 | | `Alibaba-NLP/gte-reranker-modernbert-base` | 150M | FA2 | 1418 | | `ibm-granite/granite-embedding-reranker-english-r2` | 150M | FA2 | 1404 | | [`cross-encoder/ettin-reranker-1b-v1`](https://huggingface.co/cross-encoder/ettin-reranker-1b-v1) | 1B | FA2 | 928 | | `mixedbread-ai/mxbai-rerank-large-v1` | 435M | eager | 867 | | `mixedbread-ai/mxbai-rerank-base-v2` | 494M | FA2 | 809 | | `mixedbread-ai/mxbai-rerank-large-v2` | 1.5B | FA2 | 387 |

我们的 17M 模型是整个对比中速度最快的重排序器,达到每秒 7517 对。这几乎是 ms-marco-MiniLM-L6-v2(3817)的两倍吞吐量,甚至比更小的 ms-marco-MiniLM-L4-v2(4029)还要快。正如你之前在 MTEB 表格中看到的,我们的 17M 模型也比所有 MiniLM 变体更准确。如果你目前正在运行 MiniLM 交叉编码器,切换到我们的 17M 只需修改一行代码,就能同时改善延迟和搜索质量。

我们的 150M 模型更是一个有趣的对比,因为恰好有两个直接的架构同级产品,参数量完全相同,都是 150M:`Alibaba-NLP/gte-reranker-modernbert-base``ibm-granite/granite-embedding-reranker-english-r2`。两者都基于相同的 ModernBERT-base 骨干网络。我们的 150M 模型每秒处理 3237 对,而两个同级产品分别为 1418 和 1404,存在 2.3 倍的速度差距。

三个 150M 模型都使用了 Flash Attention 2,但两个同级产品通过 AutoModelForSequenceClassification 加载,这会保持输入填充状态。因此,attention 本身运行的是 FA2 内核,但模型的其余部分仍在对没有任何贡献的填充 token 进行密集计算。我们的模块化 Transformer 模块(参见上文的 架构细节)将未填充的输入传播到整个模型,因此每一层只花费计算在真实 token 上。这就是获得部分 FA2 收益与获得全部收益之间的区别。

在表格底部,我们的 1B 模型达到每秒 928 对,比 1.54B 的教师模型 mxbai-rerank-large-v2(每秒 387 对)快 2.4 倍,同时 MTEB 分数相差不到 0.0001。该教师模型基于 Qwen2,每对都有 prompt 模板开销,因此蒸馏后的学生模型继承了教师的校准和判断能力,但跳过了所有运行时负担。说实话,这是本次发布中让我最满意的单个数字。

一个不幸的说明:基于 DeBERTa-v2 的 mxbai-rerank-{xsmall,base,large}-v1 系列最终比表格中其他模型慢得多,因为 DeBERTa-v2 目前在 transformers 中既不支持 Flash Attention 2 也不支持 SDPA。70M 的 mxbai-rerank-xsmall-v1 每秒运行 2636 对,约为我们 68M 模型吞吐量的一半,而参数量几乎相同。这些模型本身完全没问题,只是无法使用现代的 attention 内核。

消费级 GPU 上的相同基准测试(RTX 3090,24 GB) 如果你是在消费级显卡而非数据中心 GPU 上自托管,以下是 RTX 3090 上的相同吞吐量测试。与表 1 的基准设置相同:bfloat16,每个模型支持的最佳 attention,在最大可容纳批次下的三次试验中位数吞吐量。

| 模型 | 参数量 | 最佳 attention | 对/秒 | | --- | --- | --- | --- | | [`cross-encoder/ettin-reranker-17m-v1`](https://huggingface.co/cross-encoder/ettin-reranker-17m-v1) | 17M | FA2 | 9008 | | `cross-encoder/ms-marco-MiniLM-L4-v2` | 19M | FA2 | 5071 | | [`cross-encoder/ettin-reranker-32m-v1`](https://huggingface.co/cross-encoder/ettin-reranker-32m-v1) | 32M | FA2 | 4497 | | `cross-encoder/ms-marco-MiniLM-L6-v2` | 22M | FA2 | 4234 | | `cross-encoder/ms-marco-MiniLM-L12-v2` | 33M | FA2 | 2847 | | [`cross-encoder/ettin-reranker-68m-v1`](https://huggingface.co/cross-encoder/ettin-reranker-68m-v1) | 68M | FA2 | 1916 | | `mixedbread-ai/mxbai-rerank-xsmall-v1` | 70M | eager | 1677 | | `BAAI/bge-reranker-base` | 278M | FA2 | 1329 | | [`cross-encoder/ettin-reranker-150m-v1`](https://huggingface.co/cross-encoder/ettin-reranker-150m-v1) | 150M | FA2 | 982 | | `mixedbread-ai/mxbai-rerank-base-v1` | 184M | eager | 772 | | `ibm-granite/granite-embedding-reranker-english-r2` | 150M | FA2 | 598 | | `Alibaba-NLP/gte-reranker-modernbert-base` | 150M | FA2 | 586 | | `BAAI/bge-reranker-large` | 560M | FA2 | 448 | | `BAAI/bge-reranker-v2-m3` | 568M | FA2 | 436 | | [`cross-encoder/ettin-reranker-400m-v1`](https://huggingface.co/cross-encoder/ettin-reranker-400m-v1) | 400M | FA2 | 429 | | `mixedbread-ai/mxbai-rerank-large-v1` | 435M | eager | 266 | | `mixedbread-ai/mxbai-rerank-base-v2` | 494M | FA2 | 221 | | [`cross-encoder/ettin-reranker-1b-v1`](https://huggingface.co/cross-encoder/ettin-reranker-1b-v1) | 1B | FA2 | 189 | | `mixedbread-ai/mxbai-rerank-large-v2` | 1.5B | FA2 | 69 |

我们的 17M 模型仍以每秒 9008 对的速度位居表格首位,实际上高于其在 H100 上的数字,这表明在极小尺寸下原始计算并非瓶颈,H100 的额外算力无法转化为优势。表格中部有所变化,MiniLM 重排序器超过了我们的 32M 和 68M,而 1B 则落后于 mxbai-rerank-base-v2(189 对 221 对/秒)。我们的 150M 模型仍对两个基于 ModernBERT 的 150M 同级产品保持明显领先,教师替代的故事依然成立,我们的 1B 模型吞吐量是 1.5B mxbai-rerank-large-v2 的 2.7 倍(189 对 69 对/秒)。

CPU 上的相同基准测试(Intel Core i7-13700K)

| Model | Params | Best attn | pairs / second | | --- | --- | --- | --- | | [`cross-encoder/ettin-reranker-17m-v1`](https://huggingface.co/cross-encoder/ettin-reranker-17m-v1) | 17M | SDPA | 267.4 | | `cross-encoder/ms-marco-MiniLM-L4-v2` | 19M | SDPA | 206.2 | | `cross-encoder/ms-marco-MiniLM-L6-v2` | 22M | SDPA | 143.9 | | [`cross-encoder/ettin-reranker-32m-v1`](https://huggingface.co/cross-encoder/ettin-reranker-32m-v1) | 32M | SDPA | 92.5 | | `cross-encoder/ms-marco-MiniLM-L12-v2` | 33M | SDPA | 75.9 | | `mixedbread-ai/mxbai-rerank-xsmall-v1` | 70M | eager | 38.9 | | [`cross-encoder/ettin-reranker-68m-v1`](https://huggingface.co/cross-encoder/ettin-reranker-68m-v1) | 68M | SDPA | 31.2 | | `BAAI/bge-reranker-base` | 278M | SDPA | 19.2 | | `Alibaba-NLP/gte-reranker-modernbert-base` | 150M | SDPA | 14.7 | | `ibm-granite/granite-embedding-reranker-english-r2` | 150M | SDPA | 14.5 | | [`cross-encoder/ettin-reranker-150m-v1`](https://huggingface.co/cross-encoder/ettin-reranker-150m-v1) | 150M | SDPA | 14.0 | | `mixedbread-ai/mxbai-rerank-base-v1` | 184M | eager | 13.4 | | `BAAI/bge-reranker-large` | 560M | SDPA | 6.2 | | `BAAI/bge-reranker-v2-m3` | 568M | SDPA | 6.0 | | [`cross-encoder/ettin-reranker-400m-v1`](https://huggingface.co/cross-encoder/ettin-reranker-400m-v1) | 400M | SDPA | 5.2 | | `mixedbread-ai/mxbai-rerank-large-v1` | 435M | eager | 4.3 | | `mixedbread-ai/mxbai-rerank-base-v2` | 494M | SDPA | 3.5 | | [`cross-encoder/ettin-reranker-1b-v1`](https://huggingface.co/cross-encoder/ettin-reranker-1b-v1) | 1B | SDPA | 2.1 |

在 CPU 上,我们无法利用 bf16、Flash Attention 2 或 unpadding,因此延迟情况相对简单:参数量越大,模型越慢。17M 模型比 ms-marco-MiniLM-L6-v2 快得多(267.4 对 143.9 pairs/second),甚至比更小的 ms-marco-MiniLM-L4-v2 还要快(206.2)。不出所料,由于 unpadding 不再适用,我们的 150M 模型与另外两个 150M 的同类模型表现相当(14.0 对 14.5 和 14.7 pairs/second)。如果你受限于 CPU,我们的 17M 和 32M 是实用的选择。

为了解释速度的来源,下表使用相同的基准配置,对我们的六个模型进行了 fp32+SDPAbf16+SDPAbf16+FA2 的对比测试。FA2 列分为两部分:一部分是输入仍带有 padding(封装模型会看到的情况),另一部分是去除 padding 的输入(我们的模块化 Transformer 实际处理的方式)。最右列是启用 FA2 时我们模型默认使用的配置。

表 2. 六个发布版本在 max_length=512 条件下,针对自然 NQ 文档的精度与注意力机制消融实验。每个单元格显示 pairs/second,括号内为相对于 fp32+SDPA 的倍数,第二行为峰值 GPU 显存。最右列(加粗)是启用 FA2 时我们模型默认使用的配置。

| Model | Params | fp32+SDPA | bf16+SDPA | bf16+FA2 w. padding | bf16+FA2 w.o. padding | | --- | --- | --- | --- | --- | --- | | `cross-encoder/ettin-reranker-17m-v1` | 17M | 4402 (1.00x) 0.8 GB | 4523 (1.03x) 2.2 GB | 3744 (0.85x) 1.9 GB | 7517 (1.71x) 1.4 GB | | `cross-encoder/ettin-reranker-32m-v1` | 32M | 3307 (1.00x) 1.2 GB | 4357 (1.32x) 1.6 GB | 3040 (0.92x) 2.9 GB | 6602 (2.00x) 1.1 GB | | `cross-encoder/ettin-reranker-68m-v1` | 68M | 1364 (1.00x) 1.0 GB | 2861 (2.10x) 2.2 GB | 2003 (1.47x) 2.0 GB | 4913 (3.60x) 1.5 GB | | `cross-encoder/ettin-reranker-150m-v1` | 150M | 671 (1.00x) 1.6 GB | 1942 (2.90x) 1.8 GB | 1396 (2.08x) 3.1 GB | 3237 (4.83x) 1.4 GB | | `cross-encoder/ettin-reranker-400m-v1` | 400M | 266 (1.00x) 2.5 GB | 1113 (4.18x) 1.8 GB | 864 (3.25x) 2.7 GB | 1738 (6.53x) 2.2 GB | | `cross-encoder/ettin-reranker-1b-v1` | 1B | 112 (1.00x) 4.6 GB | 630 (5.60x) 2.8 GB | 522 (4.64x) 3.6 GB | 928 (8.26x) 4.5 GB |

bf16+FA2 w.o. padding 相对于 fp32+SDPA 基线的总加速比随模型规模急剧增长,从 17M 的 1.71x 提升到 1B 的 8.26x。其中大部分增长来自 bf16 本身:fp32+SDPAbf16+SDPA 这一步,17M 模型仅获得 1.03x 的加速,而 1B 模型获得了整整 5.60x 的加速,这也得益于降低的内存成本允许更大的 batch size。简而言之,bfloat16 是整体加速的最大单一贡献因素。

出乎意料的是,在输入仍然带有填充的情况下开启 FA2,实际上在每个尺寸上都比 bf16+SDPA 更慢。FA2 内核更倾向于无填充格式,当你输入带填充的数据时,需要承担格式转换的开销,同时还要在填充 token 上进行计算。因此,bf16+FA2 w. padding 这一列大致对应于你在 model_kwargs 中将 sdpa 替换为 flash_attention_2 而不改变模型加载其他设置时的测量结果。这就是表 1 中 gte-reranker-modernbert-basegranite-embedding-reranker-english-r2 所处的情况。

最后,从 bf16+FA2 w. paddingbf16+FA2 w.o. padding 可以带来 1.78 倍(1B)到 2.45 倍(68M)的额外吞吐量提升,同时还能显著降低峰值内存占用,从而支持更大的 batch size。

因此我的建议很简单:同时启用 bf16 和 FA2。六个 Ettin reranker 默认使用无填充输入,因为 架构细节 部分的模块化 Transformer 模块就是这样配置的。完整代码片段与上文 使用 部分相同:

python
from sentence_transformers import CrossEncoder

model = CrossEncoder(
    "cross-encoder/ettin-reranker-150m-v1",
    model_kwargs={
        "dtype": "bfloat16",
        "attn_implementation": "flash_attention_2",  # 见下方提示
    },
)

使用 pip install kernels 安装 FA2。它为广泛的 GPU 架构、CUDA 版本和操作系统提供预编译的内核。

对于其他 CrossEncoder 的一个注意事项:完整的加速仅适用于使用模块化 Transformer 构建的模型(如 Ettin reranker)。将同样的两个标志应用于通过 AutoModelForSequenceClassification 加载的 CrossEncoder,会使你落入表 2 中较慢的 bf16+FA2 w. padding 列。

训练

下面的训练脚本最初来自新的 `train-sentence-transformers` Agent Skill 的输出,该技能随 Sentence Transformers v5.5.0 发布。如果你使用 AI 编程助手(Claude Code、Codex、Cursor、Gemini CLI 等),可以安装该技能并要求它在你的数据上微调 SentenceTransformerCrossEncoderSparseEncoder 模型。该技能包含版本感知的基础模型选择、损失和评估器选择、难负例挖掘、蒸馏、LoRA、Matryoshka、多语言训练和静态嵌入的指导,以及每种模型类型的模板脚本。

code
hf skills add train-sentence-transformers --claude   # 符号链接到 .claude/skills/
hf skills add train-sentence-transformers --global   # 位于 ~/.agents/skills/

类似 _"在我的数据集上微调一个 cross-encoder reranker,挖掘难负例,并推送到我的 Hub 仓库"_ 的提示会生成一个可运行的脚本,你可以在此基础上迭代。我就是这样开始编写下面这个方案的。

所有六个 reranker 都使用相同的单阶段方案进行训练。只有学习率和每设备 batch size 随模型大小变化。完整训练脚本约 150 行,使用一个已发布的数据集。

该方案在一次跨模型尺寸的扫描后就收敛了。每个尺寸的学习率通过对最终训练数据约 15% 的子集进行小规模网格搜索来调优,得到的学习率可以直接迁移到完整数据运行而无需重新调优。除了学习率外,不需要针对每个尺寸进行其他调优。

蒸馏方案

大多数已发布的 reranker 方案使用人工标注的相关性三元组(一个查询、一个正例文档,以及可选的难负例)进行训练,采用对比、逐点、逐对或列表损失,如 `MultipleNegativesRankingLoss``BinaryCrossEntropyLoss``RankNetLoss``LambdaLoss`。例如,可以参见我之前的博客文章 使用 Sentence Transformers 训练和微调 Reranker 模型

但这种方法存在一些实际和理论上的缺点。首先,正例需要人工标注,成本高昂且难以跨多个领域扩展。其次,模型只能看到人工审核过的少量 (query, document) 对的标签。特别是在难负例挖掘之后,你会得到很多假负例,例如 Hard Negatives, Hard Lessons 中所示。第三,这种二元标注的性质与现实不符,现实中有些文档只是比其他文档更相关。

我在这里采取了不同的路线:从现有的强教师 reranker 进行逐点 MSE 蒸馏。设置足够简单,可以用三行描述:

  • 教师模型`mixedbread-ai/mxbai-rerank-large-v2`(15.4 亿参数)。
  • 损失函数`MSELoss`,作用于原始教师 logits(范围约 [−12, 22]),即不进行缩放。
  • 训练数据:约 1.43 亿个 (query, document, teacher_score) 三元组。

数据集

我已将训练数据作为单个 Hugging Face 数据集发布:`cross-encoder/ettin-reranker-v1-data`,由两个来源整合而成。每个来源保留为独立的 split,以确保数据来源透明:

  1. LightOn 预训练数据 (`lightonai/embeddings-pre-training`,未经筛选):32 个 split,涵盖广泛领域的文本相似性信号(MTP、FW-EDU、Reddit、PAQ、S2ORC、Amazon、Wikipedia、MS MARCO 等)。我对部分 split 的样本数量进行了限制,总计约 1.1 亿条 (query, document, similarity) 三元组。
  2. 来自 `lightonai/embeddings-fine-tuning` 的重打分检索数据:7 个 split(msmarcohotpotqatrivianqsquadv2fiqafever)。源数据集每个 query 最多包含 2048 个候选文档(最初使用 `Alibaba-NLP/gte-modernbert-base` 打分),我使用 `mixedbread-ai/mxbai-rerank-large-v2` 对其重打分,并上传为 `cross-encoder/lightonai-embeddings-fine-tuning-reranked-v1`。该数据集使用 Jang et al. 的分位数锚点方法,将每个 query 的 2048 个候选采样至 256 个(所有正例 + top-16 困难负例 + 约 239 个分位数锚点分层采样)。训练时,我从这 256 个中选取 64 个:32 个来自按分数排序的头部(正例加最难的负例),32 个为从教师模型排序更靠后位置采样的中等难度负例。具体排名位置详见 dataset card

总计:约 1.43 亿条 (query, document, score) 三元组,外加一个留出的 5K 行评估 split(quora 的尾部),用于训练过程中的评估损失。

训练参数

大多数超参数在各模型尺寸间保持一致:

code
CrossEncoderTrainingArguments(
    num_train_epochs=1,                    # 选择更多数据而非更多轮次
    per_device_train_batch_size=...,       # global_batch_size // world_size(见下表)
    gradient_accumulation_steps=1,
    learning_rate=...,                     # 各尺寸不同,见下表
    warmup_ratio=0.03,                     # 约 3% 线性 warmup,然后线性衰减(默认)
    bf16=True,                             # 全程使用 FA2 + bf16
    eval_strategy="steps",
    eval_steps=0.05,                       # 每 5% 训练步数运行 NanoBEIR
    save_strategy="steps",
    save_steps=0.05,
    save_total_limit=5,
    load_best_model_at_end=True,
    metric_for_best_model="eval_NanoBEIR_R100_mean_ndcg@10",
    seed=12,
)

只有学习率和全局 batch size 随模型尺寸变化。

| 尺寸 | 学习率 | 全局 batch size | | --- | --- | --- | | 17m | 2.4e-4 | 1024 | | 32m | 1.2e-4 | 512 | | 68m | 3e-5 | 256 | | 150m | 1.5e-5 | 192 | | 400m | 7e-6 | 256 | | 1b | 3e-6 | 512 |

global_batch_size = per_device_batch_size x world_size x gradient_accumulation_steps。在单节点 8 GPU 上,17m 的 1024 全局 batch 意味着 per_device=128;在 8 个节点上,则意味着 per_device=8。训练脚本通过 global_batch_size // world_size 计算 per_device_batch_size,因此同一脚本可在任意节点数下运行。全局 batch size 本可以设置得更一致,但我发现上述值效果良好,不愿仅为了一致性而重新调参。

评估

我在训练过程中监控 NanoBEIR 平均 NDCG@10(每 5% 步数评估一次),并将其作为 load_best_model_at_endmetric_for_best_model。NanoBEIR 速度快,因此每次训练运行可承受 20 次评估。训练完成后,我在完整 MTEB(eng, v2) Retrieval benchmark 上评估了最佳 checkpoint(按 NanoBEIR)和最后一个 checkpoint。最终发布的 checkpoint 是在 MTEB 上表现最好的那个。NanoBEIR 偏好的 checkpoint 在所有尺寸上都获胜,除了 68m,其最后一个 checkpoint 略强一些。

完整训练脚本

完整脚本(每个发布模型都使用此脚本训练)为单个文件。每次运行只改变 ENCODER_SIZE,其余全部自动处理:

code
from __future__ import annotations

import logging
import os
from pathlib import Path

import torch
import torch.nn as nn
from datasets import concatenate_datasets, get_dataset_config_names, load_dataset

from sentence_transformers import CrossEncoder
from sentence_transformers.base.modules import Dense
from sentence_transformers.cross_encoder import (
    CrossEncoderModelCardData,
    CrossEncoderTrainer,
    CrossEncoderTrainingArguments,
)
from sentence_transformers.cross_encoder.evaluation import CrossEncoderNanoBEIREvaluator
from sentence_transformers.cross_encoder.losses import MSELoss
from sentence_transformers.sentence_transformer.modules import LayerNorm, Pooling, Transformer

logging.basicConfig(level=logging.INFO, format="%(asctime)s %(message)s", datefmt="%H:%M:%S")
logging.getLogger("httpx").setLevel(logging.WARNING)

各规模配置。我使用以下全局(有效)batch size 扫描了学习率,

同时结合了 accum_steps

CONFIGS: dict[str, dict] = { "17m": {"base_model_name": "jhu-clsp/ettin-encoder-17m", "learning_rate": 2.4e-4, "global_batch_size": 1024}, "32m": {"base_model_name": "jhu-clsp/ettin-encoder-32m", "learning_rate": 1.2e-4, "global_batch_size": 512}, "68m": {"base_model_name": "jhu-clsp/ettin-encoder-68m", "learning_rate": 3e-5, "global_batch_size": 256}, "150m": {"base_model_name": "jhu-clsp/ettin-encoder-150m", "learning_rate": 1.5e-5, "global_batch_size": 192}, "400m": {"base_model_name": "jhu-clsp/ettin-encoder-400m", "learning_rate": 7e-6, "global_batch_size": 256}, "1b": {"base_model_name": "jhu-clsp/ettin-encoder-1b", "learning_rate": 3e-6, "global_batch_size": 512}, } ENCODER_SIZE = "17m"

def main() -> None: config = CONFIGS[ENCODER_SIZE] encoder_id = config["base_model_name"] learning_rate = config["learning_rate"] global_batch_size = config["global_batch_size"]

world_size = int(os.environ.get("WORLD_SIZE", 1)) per_device_batch_size = global_batch_size // world_size dataloader_workers = 0 if world_size > 8 else 4 run_name = f"ettin-reranker-{ENCODER_SIZE}-lr{learning_rate:.0e}"

1. 加载用于微调的模型,包含 model card 数据

该模型与 ModernBertForSequenceClassification 类似,但使用仅加载 AutoModel 的 "headless" Transformer。

这使得可以使用 FA2 进行 unpadding,而 AutoModelForSequenceClassification 无法实现这一点。

这显著加快了训练速度,同时大幅降低了内存使用。

torch.manual_seed(12) transformer = Transformer(encoder_id, model_kwargs={"attn_implementation": "flash_attention_2"}) transformer.model.config.num_labels = 1 embedding_dimension = transformer.get_embedding_dimension() pooling = Pooling(embedding_dimension=embedding_dimension, pooling_mode="cls") dense_inner = Dense( in_features=embedding_dimension, out_features=embedding_dimension, bias=False, activation_function=nn.GELU(), module_input_name="sentence_embedding", module_output_name="sentence_embedding", ) norm = LayerNorm(dimension=embedding_dimension) dense_score = Dense( in_features=embedding_dimension, out_features=1, bias=True, activation_function=nn.Identity(), module_input_name="sentence_embedding", module_output_name="scores", ) model = CrossEncoder( modules=[transformer, pooling, dense_inner, norm, dense_score], num_labels=1, activation_fn=nn.Identity(), model_card_data=CrossEncoderModelCardData( model_name=f"Ettin Reranker {ENCODER_SIZE} distilled from mxbai-rerank-large-v2", language="en", license="apache-2.0", ), ) actual_attn = getattr(model[0].model.config, "_attn_implementation", None) if not (actual_attn and "flash" in actual_attn.lower()): logging.warning(f"FA2 可能未激活 (attn_impl={actual_attn!r});训练速度会变慢。")

2. 加载数据集。每个配置对应一个来源子集(32 个 lighton + 7 个 rerank retrieval

领域)。留出的评估行位于 'quora' 配置的 'validation' 拆分中。

dataset_repo = "cross-encoder/ettin-reranker-v1-data" train_pieces = [] eval_dataset = None for config_name in get_dataset_config_names(dataset_repo): dataset = load_dataset(dataset_repo, config_name) train_pieces.append(dataset["train"]) if "validation" in dataset: eval_dataset = dataset["validation"] train_dataset = concatenate_datasets(train_pieces) print(train_dataset)

3. 定义损失函数

loss = MSELoss(model)

4. 指定训练参数

args = CrossEncoderTrainingArguments( output_dir=f"models/{run_name}", num_train_epochs=1, per_device_train_batch_size=per_device_batch_size, per_device_eval_batch_size=per_device_batch_size, gradient_accumulation_steps=1, learning_rate=learning_rate, warmup_ratio=0.03, bf16=True, eval_strategy="steps", eval_steps=0.05, save_strategy="steps", save_steps=0.05, save_total_limit=5, logging_steps=0.025, logging_first_step=True, load_best_model_at_end=True, metric_for_best_model="eval_NanoBEIR_R100_mean_ndcg@10", dataloader_num_workers=dataloader_workers, run_name=run_name, seed=12, )

5. 创建评估器

evaluator = CrossEncoderNanoBEIREvaluator( dataset_names=["msmarco", "nfcorpus", "nq", "fiqa2018", "touche2020", "scifact", "hotpotqa", "arguana", "fever", "dbpedia", "climatefever", "scidocs", "quoraretrieval"], batch_size=per_device_batch_size, always_rerank_positives=False, show_progress_bar=False, )

6. 创建训练器

trainer = CrossEncoderTrainer( model=model, args=args, train_dataset=train_dataset, eval_dataset=eval_dataset, loss=loss, evaluator=evaluator, )

7. 训练前评估

if trainer.is_world_process_zero(): with torch.autocast(device_type="cuda", dtype=torch.bfloat16): evaluator(model)

8. 训练

trainer.train()

9. 评估最终模型

if trainer.is_world_process_zero(): with torch.autocast(device_type="cuda", dtype=torch.bfloat16): evaluator(model)

10. 保存最终模型

final_dir = f"models/{run_name}/final" model.save_pretrained(final_dir)

if __name__ == "__main__": main()

code

对于多节点训练(17m/32m 之后的规模),使用 `torchrun` 启动相同的脚本:

单节点(17m, 32m):默认配置即可

python train.py

多节点 4n 配置用于 150m 模型,保持 global_batch_size=192:

torchrun --nproc_per_node=8 --nnodes=4 ... train.py

code

## [](https://huggingface.co/blog/ettin-reranker#conclusion) 结论

ettin-reranker-v1 系列采用单一简洁的训练方案,在每个已发布的参数量级(最高至 1B)上都达到了 SOTA 水平。通过 Pointwise MSE 蒸馏,将强大的 teacher 模型知识迁移到涵盖广泛领域和检索特定任务的混合数据上,模型规模从 17M 到 1B 参数都能清晰扩展,期间仅需调整学习率和每设备 batch size。

每个 ettin-reranker-v1 模型在 MTEB 和 NanoBEIR 上都以明显优势击败了 `ms-marco-MiniLM-L*-v2` 系列。`cross-encoder/ettin-reranker-150m-v1` 是我在 600M 以下参数范围内测试过的最强中端重排序模型;`cross-encoder/ettin-reranker-400m-v1` 的 MTEB 得分与 1.54B teacher 的差距仅为 0.0024;而 `cross-encoder/ettin-reranker-1b-v1` 与 teacher 的差距更是缩小到了 0.0001。

所有资源汇总如下:

*   **模型**:
    *   [`cross-encoder/ettin-reranker-17m-v1`](https://huggingface.co/cross-encoder/ettin-reranker-17m-v1)
    *   [`cross-encoder/ettin-reranker-32m-v1`](https://huggingface.co/cross-encoder/ettin-reranker-32m-v1)
    *   [`cross-encoder/ettin-reranker-68m-v1`](https://huggingface.co/cross-encoder/ettin-reranker-68m-v1)
    *   [`cross-encoder/ettin-reranker-150m-v1`](https://huggingface.co/cross-encoder/ettin-reranker-150m-v1)
    *   [`cross-encoder/ettin-reranker-400m-v1`](https://huggingface.co/cross-encoder/ettin-reranker-400m-v1)
    *   [`cross-encoder/ettin-reranker-1b-v1`](https://huggingface.co/cross-encoder/ettin-reranker-1b-v1)

*   **数据集**:[`cross-encoder/ettin-reranker-v1-data`](https://huggingface.co/datasets/cross-encoder/ettin-reranker-v1-data),包含约 1.43 亿条 `(query, document, label)` 三元组,分为 39 个命名子集,以便追溯每条数据的来源。
*   **训练脚本**:即上文 [完整训练脚本](https://huggingface.co/blog/ettin-reranker#overall-training-script) 中的约 150 行代码,六个模型均使用同一脚本训练。

如果你基于这些资源构建了新的应用,请告诉我!我非常期待看到大家的创意,如果你能用发布的数据训练出更好的重排序模型,那就更棒了。这个方案故意设计得简洁,部分原因就是为了给他人留出充足的改进空间。训练一个更强的 teacher 模型,同样的脚本就能持续产出更优秀的 student 模型。

## [](https://huggingface.co/blog/ettin-reranker#acknowledgements) 致谢

感谢 Ettin 团队(Orion Weller、Kathryn Ricci、Marc Marone、Antoine Chaffin、Dawn Lawrie 和 Benjamin Van Durme)[构建基础编码器](https://huggingface.co/blog/ettin),这些重排序模型正是建立在其之上;感谢 LightOn 团队(Antoine Chaffin、Raphael Sourty、Paulo Moura 和 Amélie Chatelain)[在训练数据收集方面的工作](https://huggingface.co/blog/lightonai/denseon-lateon);以及感谢 Mixedbread AI 团队(Xianming Li、Aamir Shakir、Rui Huang、Tsz-fung Andrew Lee、Julius Lipp、Benjamin Clavié 和 Jing Li)[在 teacher 模型方面的工作](https://arxiv.org/abs/2506.03487)。

## [](https://huggingface.co/blog/ettin-reranker#citation) 引用

如果你使用了 ettin-reranker-v1 系列或任何发布的资源,请引用本文:

@misc{aarsen2026ettin-reranker, title = "Introducing the Ettin Reranker Family", author = "Aarsen, Tom", year = "2026", publisher = "Hugging Face", url = "https://huggingface.co/blog/ettin-reranker", }

code

AI 可能会生成不准确的信息,请核实重要内容

Ettin Reranker 系列模型发布 | Hugging Face Blog | traeai