Hugging Face 推出模型集成契约以防范回归缺陷

原标题:Transformers Model Integration Contracts
68AI 研判分

Hugging Face 发布“模型集成契约”(TMIC)机制,旨在解决 Transformers 库中因测试覆盖不足导致的隐蔽回归问题。该方案通过为每个模型定义标准化的行为契约与自动化测试路径,确保如 SDPA 启用后引发的注意力掩码错误等关键逻辑在合并前被捕获。此举显著提升了拥有 750+ 模型支持的开源生态的工程稳定性与维护效率。

报道正文

本文目录8 个章节

返回文章列表

Transformers 模型集成契约(Model Integration Contracts)

发布于 2026 年 11 月 3 日 在 GitHub 上更新 点赞 1

文本
 [Tarek Ziadé tarekziade 关注](https://huggingface.co/tarekziade)       

2026 年 7 月 30 日,Transformers 为 T5 启用了 PyTorch 的缩放点积注意力机制(SDPA)。这一改动产生了一个副作用:UMT5 的解码器能够注意到未来的 token。在训练过程中,它可以“偷看”本应预测的答案。程序没有崩溃,这个 bug 在主分支(main)中潜伏了将近两个月。

当我们后来重放这些提交时,一个在 CPU 上运行的小型 UMT5 模型在每一次重放中都标记出了该问题。这篇文章将介绍这个模型以及围绕它构建的测试。

为什么 Bug 会进入主分支

Transformers 支持超过 750 种模型类型,其测试套件非常庞大:主分支上有 88,000+ 个 CPU 测试,每日运行还有 200,000+ 个 GPU 测试。 模型测试只是其中的一部分:我们还测试 pipeline、训练、分布式执行、量化和注意力内核。

Transformers 测试概况:主分支上有 88,000+ 个 CPU 测试,每日有 200,000+ 个 GPU 测试。硬件包括 CPU、NVIDIA GPU、AMD GPU、Intel XPU 和 Gaudi 3。PR 仅运行选定的测试。

集成测试加载真实的检查点(checkpoints),并在真实硬件上调用模型的 API。如果在每个 PR 上都运行所有这些测试,意味着需要让大量 GPU 全天保持忙碌状态,这并不切实际。等到计划任务发现回归问题时,bug 可能已经进入主分支了。

我们的方法是缩小模型规模。我们构建一个 fixture:基于真实检查点的配置生成一个小模型,层数更少且更窄,但包含原模型拥有的所有类型的层,同时保持其 tokenizer 和 processor 文件不变。该 fixture 只需发布一次,随后即可重复使用。我们在廉价的 CPU runner 上针对它运行“快速集成测试”。在我们的 CPU 研究中,实验性的 UMT5 fixture 权重占用 66 MB;而源检查点大小为 1,227 MB。

这也涵盖了超大模型:有些模型无法在我们的标准 GPU runner 上运行,但它们的 fixture 可以在 CPU 上运行。

模型集成方早已在构建微型测试模型(例如 Transformers 的 tiny-random-* checkpoints)。我们想要一套通用的配方,以及超越“加载模型并运行一次前向传播”的深度检查。UMT5 的那个 bug 完全能通过前向传播测试。

Fixtures 无法取代完整的集成测试,但在 PR 阶段,它们能让我们有机会在 bug 合入 main 分支之前将其捕获。

引入模型集成契约 (Model Integration Contracts)

transformers-mic 是为此类测试打包提出的一项标准。一个模型集成契约 (MIC) 包含三个部分:

  • fixture:上述提到的缩小版模型,发布一次并固定版本
  • usage path:一个可运行的 Markdown 示例,展示人们如何使用该模型,例如生成文本或执行训练步骤
  • baselines:运行该示例时记录的输出结果,需经人工审核

如果在 fixture 上运行 usage path 仍能产生相同的 baselines,则契约成立。

一个契约涵盖一种架构和一个使用路径,而非单个 checkpoint:兼容的 checkpoints 可以共享同一个 fixture,但各自保留其预期的结果。我们在 huggingface-mic 中发布这些契约。

例如,SmolLM 契约会从诸如 "The capital of France is" 这样的提示词中进行生成,并将其 token IDs 与记录的答案进行比较。在加载模型和 tokenizer 后,简化为单个提示词的生成代码如下:

PYTHON
inputs = tokenizer("The capital of France is", return_tensors="pt")
outputs = model.generate(**inputs, max_new_tokens=4, do_sample=False)
new_tokens = outputs[:, inputs["input_ids"].shape[1]:]
print(tokenizer.batch_decode(new_tokens, skip_special_tokens=True))

它还会比较训练损失,检查梯度是否为有限值,并验证优化器步骤是否改变了权重。请参阅完整的 usage.md。

Hub 上的一个 fleet dataset 将每个 MIC 锁定到一个冻结的 revision,以及用于记录其 baselines 的软件包版本。每次 CI 运行都会记录所使用的 fleet revision,以便我们后续复现。

在 PR 中,我们根据补丁修改的文件来选择相应的 MICs。

UMT5 回归问题

我们在五个月的 Transformers 提交历史中重放了冻结的 MIC。小型 UMT5 fixture 标记出了从 2026 年 7 月 30 日引入该回归到 9 月 28 日修复期间的状态。标签对数概率(label log-probabilities)与已审核基线的差异为 1.22,训练损失(training loss)的差异约为 0.20。

有一个细节至关重要:该契约使用的是 SDPA,即默认的注意力路径。如果仅运行 eager attention 变体,就会漏掉这个 bug。只有当 fixture 经过与真实 checkpoint 相同的代码路径时,它才能发挥作用。

随后,我们在 Transformers CI 的一个证明分支上回滚了该修复。检查以相同的差异失败,且选定的 T5 契约 runner 步骤在 CPU 上耗时 13.4 秒(不含 setup)。

重放结果和 CI 演示提供了测量数据和复现细节。

模型如何被缩小

最初的方案很简单:减小维度并保留更少的层。但这对于混合架构来说是不够的。如果保留的两层都使用滑动注意力(sliding attention),而全注意力(full-attention)层消失了,那么保留这两层毫无意义。

现在的方案包含四个步骤:

  1. 缩减配置。 从真实的 checkpoint 出发,减小隐藏层维度、注意力头数和模型深度。保留不同类型的层结构,包括滑动窗口注意力与全量注意力的混合,以及稠密层与 MoE(混合专家)层的组合。某些架构需要额外的规则来确保其组件的兼容性。我们还会参考模型自身的 Transformers 测试代码:test_modeling_<model>.py 中的 ModelTester 已经手动选取了一个小型配置,因此对于通用规则未明确命名的宽度参数,我们直接采用该测试中设定的尺寸。
  2. 保持预处理不变。 保留词表、特殊 token 和输入维度,并逐字节复制 tokenizer 和 processor 文件。加载和预处理流程是我们希望测试的一部分。
  3. 构建并验证权重。 尝试使用带种子的随机权重,并修改输入以检查模型是否有响应。如果无响应,则尝试在合约定义的小批量示例上进行训练。重新加载保存的模型,并检查输出、损失值和梯度是否为有限值。拒绝失败的构建。
  4. 冻结并记录。 保存已接受的权重,并在已知良好的 Transformers 版本上测量可复现的输出。审查每个支持平台和精度下的基线数据,然后发布一个固定版本的快照。CI 会复用该快照;它不会为每个 PR 重新构建 fixture。

我们还保留了原始的权重文件格式。否则,我们将跳过用户加载真实 checkpoint 时执行的转换代码。完整方案中有更多细节。

Llama 4 Scout 缩减报告展示了在大模型上的实际效果。1086 亿参数被缩减至 4470 万。视觉栈从 34 层减至 2 层,语言栈从 48 层减至 4 层,同时保留了受检的层类型和 MoE 路由风格。

使用 Agent 构建 MIC

我们的 CLI 工具 tmic 用于创建合约并构建 fixture。对于新模型,LLM 负责起草使用代码和 manifest,CPU 和 GPU 运行则用于测量候选基线。LLM 仅负责起草,不负责批准。变更预期结果需要人工审查。

要在你的机器上试用,请将 org/model 替换为你的源 checkpoint:

SH
pip install "transformers-mic[runtime]" "hf-doc-builder @ git+https://github.com/huggingface/doc-builder.git"
hf auth login
tmic run org/model --local-only --fixtures-only --no-history --bill-to ''

它会起草合约、构建 fixture 并在你的机器上记录其基线,要求你逐步审查,且在上传任何内容之前停止。对于新模型,草稿通过 Inference Providers 由 LLM 生成,费用计入你自己的账户(--bill-to '')。CLI 文档涵盖了各个具体步骤。

我们使用智能体跨不同模型家族构建 MIC。当遇到边缘情况时,我们在模型专家的帮助下修复配方本身,而不是单独修复某个 fixture。已发布的模型和剩余缺口可在 coverage space 中查看。

随机权重 vs 过拟合

使用随机权重时,我们的 Whisper fixture 对音频变化的响应微乎其微。虽然它能运行,但这使其成为音频路径的一个糟糕测试用例。

因此,我们在几个音调与“red”和“green”等词语的配对数据上对其进行了训练。我们并非在教它语音识别,只是让音频变化在其输出中变得可见。在我们的权重初始化研究中,随机权重的 Whisper fixtures 捕获了 24 个注入故障中的 18 个。而过拟合的 fixtures 捕获了全部 24 个。

这为我们提供了两种构建权重的方法:

  • 从随机权重和固定种子开始。
  • 在少量示例上过拟合,例如 "The capital of France is" => "Paris"。

Whisper 的结果并未定论哪种方法更好。那些是合成故障,我们尚未发现仅被过拟合版本捕获的真实回归问题。两个版本都漏掉了一个辅助解码 bug,而真实的 Whisper checkpoint 暴露了该 bug。

因此,当随机权重能通过检查时,我们保留它们;当需要更高的灵敏度或可读的答案时,我们采用过拟合。训练只需几秒钟,但这增加了一个可能无法收敛的步骤,也增加了维护负担。对比结果包含了两种方法的详细结果。

待解决问题

截至 2026 年 11 月 1 日,Transformers CI 中的 MIC 任务报告失败但不阻塞 PR。是否应该将其设为阻塞项,取决于一些我们尚未测量的因素:

  • 它在真实的 PR 中实际捕获了多少个 bug,而不仅仅是在回放测试中?
  • 它让我们因误报而疲于奔命的频率有多高?
  • 基线审查需要耗费多少工作量?

我们也不清楚有多少 bug(例如 Whisper 辅助解码中的那个)只有在加载真实权重时才会显现,或者过拟合是否捕获了随机权重所遗漏的真实 bug。

目前,在模型系列和用户使用的 API 方面,覆盖率仍然不完整。当有回归问题漏网时,我们可以补充缺失的使用路径,并在损坏版本和修复版本上进行回放测试。我们会保留现有测试,直到其覆盖范围得到充分评估。

最后,我们希望将原始的 Hub 模型页面与其 MICs 关联起来,这样使用检查点(checkpoint)的开发者或智能体就可以从一个在该检查点上经过测试的示例开始,了解其运行环境和预期输出。

想参与贡献吗?从覆盖空间中选择一个未覆盖的模型,尝试 tmic 工作流,并贡献一条使用路径或审查一个生成的契约。