跳到正文
Hugging Face Blog·· 10 小时前精选AI 评分61

Hugging Face 提出 Transformers Model Integration Contracts,用小型 fixture 模型在 CPU 上拦截回归

Transformers Model Integration Contracts

AI 导读

Hugging Face 提出 transformers-mic 标准,通过发布一次的小型 fixture 模型、可运行的用法示例和人工审核的基线输出,在 PR 的 CPU CI 中检测 Transformers 模型回归。

推荐理由

文章给出用缩小的 fixture 模型在 CPU 上于 PR 阶段拦截回归的可复用方法,含完整构建配方与实测数据。

正文 · AI 翻译

2026 年 7 月 30 日,Transformers 为 T5 启用了 PyTorch 的缩放点积注意力(SDPA)。一个副作用是,UMT5 的解码器能够关注未来的 token。在训练时,它可以偷看到本应预测的答案。没有任何东西崩溃,这个 bug 在 main 分支上待了将近两个月。

当我们后来重放那些提交时,一个在 CPU 上运行的小型 UMT5 模型在每一个提交上都标记出了问题。本文就是关于这个模型以及围绕它构建的测试。

为什么 bug 会进入 main

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

Transformers testing: 88,000+ CPU tests on main and 200,000+ GPU tests daily. Hardware includes CPUs, NVIDIA GPUs, AMD GPUs, Intel XPUs and Gaudi 3. PRs run selected tests.

集成测试会加载真实的检查点(checkpoint),并在真实硬件上调用模型的 API。在每个 PR 上运行所有这些测试,意味着要让大量 GPU 整天繁忙,这并不现实。等到定时运行发现回归时,它可能已经进入 main 了。

我们的做法是缩小模型。我们构建一个夹具(fixture):一个由真实检查点的配置生成的小模型,层数更少、维度更窄,但保留它拥有的每一种层,并且其分词器和处理器文件保持不变。它只发布一次,然后反复使用。我们在廉价的 CPU 运行器上针对它运行“快速集成测试”。在我们的 CPU 研究中, 实验性的 UMT5 夹具使用了 66 MB 的权重;而源检查点有 1,227 MB。

这也覆盖了非常大的模型:有些模型放不进我们的标准 GPU 运行器,但它们的夹具可以在 CPU 上运行。

模型集成者已经在构建微型测试模型(Transformers 的 tiny-random-* 检查点)。我们想要一个通用的配方,以及超越“加载模型并运行一次前向传播”的检查。UMT5 这个 bug 本可以轻松通过前向传播测试。

夹具不会取代完整的集成测试,但在 PR 上,它们让我们有机会在 bug 进入 main 之前抓住它们。

引入 Model Integration Contract

transformers-mic 是一个用于打包这些测试的拟议标准。一份 Model Integration Contract(MIC,模型集成契约)包含三个部分:

  • 一个夹具:上述缩小后的模型,发布一次并被固定版本
  • 一条使用路径:一个可运行的 Markdown 示例,展示人们对模型做的事情,例如生成文本或运行一步训练
  • 若干基线:运行该示例时记录的输出,由人工审核

如果在夹具上运行使用路径仍能产出基线结果,契约就成立。

一份契约覆盖一种架构和一条使用路径,而不是单个检查点:兼容的检查点可以共享一个夹具,同时各自保留自己的预期结果。我们在 huggingface-mic 中发布它们。

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

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 数据集 将每份 MIC 固定到一个冻结的 revision,以及记录其基线时使用的软件包版本。每次 CI 运行都会记录它使用的 fleet revision,以便我们之后可以复现。

在 PR 上,我们会根据补丁所更改的文件来选取 MIC。

UMT5 回归

我们对 Transformers 五个月的提交重放了冻结的 MIC。小型 UMT5 测试用例从 2026 年 7 月 30 日该回归被引入起就将其标记出来,直到 9 月 28 日被修复为止。 标签的对数概率与已评审的基线相差 1.22,训练损失相差约 0.20。

有一个细节很关键:该契约使用的是 SDPA,即默认的注意力路径。如果只运行 eager 注意力变体,就会漏掉这个 bug。一个测试用例只有在与真实检查点走相同的代码路径时才有帮助。

随后我们在 Transformers 的 CI 中的一个验证分支上回退了该修复。检查以同样的差异失败,所选 T5 契约的运行步骤在 CPU 上耗时 13.4 秒(不含环境搭建)。

重放结果 和 CI 演示 包含测量数据和复现细节。

模型是如何被缩小的

第一个配方很简单:把维度改小,减少层数。这对混合架构来说并不够。如果保留下来的两层都用滑动注意力,而全注意力层消失了,那保留两层就毫无用处。

现在的配方有四个步骤:

  1. 缩小配置。从真实的检查点出发,减小隐藏层尺寸、注意力头数和深度。保留不同种类的层,包括滑动注意力与全注意力的混合,或稠密层与 MoE 层的混合。某些架构需要额外的规则来保持其组件的兼容性。我们还会参考模型在 Transformers 中的自带测试:test_modeling_<model>.py 中的 ModelTester 已经手工挑选了一个小配置,所以对于我们的通用规则无法按名称识别的宽度,我们就采用它的尺寸。
  2. 保持预处理不变。保留词表、特殊 token 和输入维度,并逐字节复制 tokenizer 和 processor 文件。加载和预处理也是我们想要测试的一部分。
  3. 构建并验证权重。尝试带种子的随机权重,并修改输入以检查模型是否有响应。如果没有,尝试在契约的小批量示例上对其进行训练。重新加载保存的模型,检查输出、损失和梯度是否为有限值。拒绝构建失败的产物。
  4. 冻结并记录。保存通过验证的权重,并在一个已确认良好的 Transformers 构建上测量可复现的输出。对每个支持的平台和精度评审基线,然后发布一个固定的快照。CI 复用该快照;不会为每个 PR 重新构建测试用例。

我们还保留原始权重的文件格式。否则我们就会跳过用户加载真实检查点时运行的转换代码。 完整配方 有更多细节。

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

用智能体构建 MIC

我们的 CLI,即 tmic,负责创建契约并构建测试用例。对于新模型,LLM 会起草用法代码和清单,CPU 和 GPU 运行会测量候选基线。LLM 负责起草;它不做批准。变更的预期需要人工评审。

要在你的机器上试用,请用你的源检查点替换 org/model:

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 ''

它会在你的机器上起草契约、构建测试夹具并记录其基准线,要求你审查每一步,并在上传任何内容之前停止。对于新模型,草案由 LLM 通过 Inference Providers 生成,费用记入你自己的账户(--bill-to '')。CLI 文档介绍了各个步骤。

我们使用智能体跨模型家族构建 MIC。当它们遇到边缘情况时,我们在模型专家的帮助下修复的是配方本身,而不是单个夹具。已发布的模型和尚未覆盖的部分见覆盖范围空间。

随机权重 vs 过拟合

使用随机权重时,我们的 Whisper 夹具对音频变化几乎没有反应。它能运行,但因此难以很好地测试音频路径。

于是我们用几个音调配以 "red" 和 "green" 之类的词语对它进行了训练。我们并不是在教它语音识别,只是让音频变化在其输出中可见。在我们的权重初始化研究中,随机 Whisper 夹具捕获了 24 个注入故障中的 18 个。过拟合夹具捕获了全部 24 个。

这给了我们两种构建权重的方法:

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

Whisper 的结果并不能说明哪种方式更好。那些是合成故障,我们还没有发现只被过拟合版本捕获的真实回归。两个版本都漏掉了一个辅助解码 bug,而真实的 Whisper checkpoint 暴露了该 bug。

因此,当随机权重能通过检查时我们就使用随机权重,需要更好的灵敏度或可读的答案时才进行过拟合。训练只需几秒钟,但这多了一步可能无法收敛的环节,也多了一项需要维护的内容。对比中包含两者的结果。

开放问题

截至 2026 年 11 月 1 日,Transformers CI 中的 MIC 任务会报告失败但不会阻塞 PR。是否应该阻塞取决于一些我们尚未衡量的事情:

  • 它在 PR 上(而不仅仅是在重放中)能捕获多少真实 bug?
  • 它多久会让我们追查一次误报?
  • 基准线审查需要多少工作量?

我们也不知道有多少 bug(比如 Whisper 的辅助解码 bug)只在使用真实权重时才显现,也不确定过拟合是否能捕获随机权重漏掉的真实 bug。

覆盖范围在模型家族和人们使用的 API 方面仍不完整。当有回归漏网时,我们可以添加缺失的使用路径,并在损坏版本和修复版本上重放它。我们会保留现有测试,直到它们的覆盖范围得到核算。

最后,我们希望将 Hub 上的原始模型页面与其 MIC 关联起来,这样使用某个 checkpoint 的开发者或智能体就可以从针对该 checkpoint 测试过的示例及其环境和预期输出入手。

想帮忙吗?从覆盖范围空间中选一个尚未覆盖的模型,试试tmic 工作流,贡献一条使用路径,或者审查一份生成的契约。

来源: Hugging Face Blog · huggingface.co