2026 年 7 月 30 日,Transformers 为 T5 启用了 PyTorch 的缩放点积注意力(SDPA)。作为副作用,UMT5 的解码器可以关注未来的 token。在训练过程中,它可以窥探它本应预测的答案。没有发生崩溃,但这个 bug 在 main 分支上停留了近两个月。
当我们后来重放这些提交时,一个在 CPU 上运行的小型 UMT5 模型在每个提交上都标记了该问题。这篇文章就是关于这个模型以及围绕它构建的测试。
为什么 bug 会进入 main 分支
Transformers 支持超过 750 种模型类型,其测试套件非常庞大:main 分支上有 88,000+ 个 CPU 测试,每日运行中有 200,000+ 个 GPU 测试。 模型测试只是其中一部分:我们还测试管道、训练、分布式执行、量化和注意力内核。

集成测试加载真实的检查点并在真实硬件上运行模型的 API。如果在每个 PR 上都运行所有这些内容,意味着需要整天让大量 GPU 处于忙碌状态,这并不切实际。当计划内的运行发现回归问题时,它可能已经合并到 main 中了。
我们的方法是缩小模型。我们构建一个夹具(fixture):一个由真实检查点的配置制成的小型模型,层更少且更窄,但包含它拥有的每种类型的层,并且其 tokenizer 和 processor 文件保持不变。它只发布一次,然后重复使用。我们在廉价的 CPU 运行器上对其运行“快速集成测试”。在我们的 CPU 研究 中,实验性的 UMT5 夹具使用了 66 MB 的权重;源检查点是 1,227 MB。
这也涵盖了非常大的模型:有些模型无法放入我们标准的 GPU 运行器中,但它们的夹具可以在 CPU 上运行。
模型集成人员已经构建了微小的测试模型(Transformers 的 tiny-random-* 检查点)。我们想要一个通用的配方,以及超出加载模型并运行前向传播的检查。UMT5 bug 会顺利通过前向传播测试。
夹具不会取代完整的集成测试,但在 PR 上,它们给了我们一个在 bug 到达 main 之前捕获它们的机会。
介绍模型集成契约 (Model Integration Contracts)
transformers-mic 是打包这些测试的一种提议标准。模型集成契约 (MIC) 包含三个部分:
- 一个夹具:上述缩小后的模型,发布一次并固定版本
- 一个使用路径:人们使用该模型所做某事的可运行 Markdown 示例,例如生成文本或运行训练步骤
- 基线:运行该示例时记录的输出,经过人工审查
如果在夹具上运行使用路径仍然产生基线,则契约成立。
契约涵盖一种架构和一个使用路径,而不是单个检查点:兼容的检查点可以共享一个夹具,而每个检查点保留自己的预期结果。我们将它们发布在 huggingface-mic。
例如,SmolLM 契约从诸如“The capital of France is”之类的提示符进行生成,并将其 token ID 与记录的答案进行比较。加载模型和 tokenizer 后,生成代码简化为一个提示符如下:
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 上的 车队数据集 将每个 MIC 固定到一个冻结的版本,以及用于记录其基线的包版本。每次 CI 运行都会记录所使用的车队版本,以便我们稍后重现。
在 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 演示 包含了测量数据和重现细节。
如何缩小模型
最初的配方很简单:使维度变小并保留更少的层。这对于混合架构来说还不够。如果两层都使用滑动注意力,而全注意力层消失,那么保留两层就没有用处。
现在的配方有四个步骤:
- 缩小配置。 从真实检查点开始,减少隐藏大小、注意力头数和深度。保留不同的层类型,包括滑动注意力和全注意力的混合,或者密集层和 MoE 层的混合。某些架构需要额外的规则来保持其组件的兼容性。我们还查看模型自身的 Transformers 测试:
test_modeling_<model>.py 中的 ModelTester 已经手动选择了一个小配置,因此对于我们的通用规则不知道名称的任何宽度,我们采用其尺寸。
- 保持预处理不变。 保留词汇表、特殊令牌和输入维度,并按字节复制 tokenizer 和 processor 文件。加载和预处理是我们希望测试的一部分。
- 构建并验证权重。 尝试使用种子随机权重并更改输入以检查模型是否有响应。如果没有,尝试在契约的小批量示例上对其进行训练。重新加载保存的模型并检查输出、损失和梯度是否有限。拒绝失败的构建。
- 冻结并记录。 保存接受的权重,并在已知良好的 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 ''
它在你的机器上起草契约,构建夹具并记录其基线,要求你审查每一步,并在上传任何内容之前停止。对于新模型,草稿来自通过 Inference Providers 的 LLM,费用记入你自己的账户 (--bill-to '')。CLI 文档 涵盖了各个步骤。
我们使用代理跨模型家族构建 MIC。当它们遇到边缘情况时,我们在模型专家的帮助下修复配方本身,而不是修复那个特定的夹具。已发布的模型和剩余的差距位于 覆盖空间。
随机 vs 过拟合
使用随机权重时,我们的 Whisper 夹具对音频的变化几乎没有响应。它运行了,但这使其成为音频路径的糟糕测试。
因此,我们在一些音调与诸如“red”和“green”等单词配对的数据上对其进行了训练。我们并没有教它语音识别,只是让音频变化在其输出中可见。在我们的 权重初始化研究 中,随机 Whisper 夹具捕获了 24 个注入故障中的 18 个。过拟合夹具捕获了全部 24 个。
这给了我们两种构建权重的方法:
- 从随机权重和固定种子开始。
- 在一些示例上过拟合,例如“The capital of France is” => “Paris”。
Whisper 的结果并不能确定哪种更好。那些是合成故障,我们还没有找到仅由过拟合版本捕获的真实回归。两个版本都错过了真实 Whisper 检查点暴露出的辅助解码 bug。
因此,当随机权重通过检查时,我们保留随机权重;当我们需要更好的灵敏度或可读答案时,我们进行过拟合。训练只需几秒钟,但它是一个可能无法收敛的额外步骤,也是更多需要维护的东西。比较 包含了两者的结果。
开放性问题
截至 2026 年 11 月 1 日,Transformers CI 中的 MIC 作业 报告失败但不阻止 PR。是否应该阻止取决于我们尚未测量的因素:
- 它在 PR 上捕获了多少真实 bug,而不仅仅是在重放中?
- 它多久会让我们去追逐虚假警报?
- 基线审查需要多少工作量?
我们也不知道有多少 bug,比如 Whisper 辅助解码的那个,只有在真实权重下才会出现,或者过拟合是否能捕获随机权重遗漏的真实 bug。
覆盖率仍然是部分的,跨越模型家族和用户使用的 API。当回归问题溜走时,我们可以添加缺失的使用路径并在损坏和修复的版本上重放它。只要覆盖了现有测试,我们就保留它们。
最后,我们希望将原始的 Hub 模型页面链接到它们的 MIC,以便使用检查点的开发人员或代理可以从在该检查点、其环境和其预期输出上测试的示例开始。
想帮忙吗?从 覆盖空间 中选择一个未覆盖的模型,尝试 tmic 工作流,并贡献一个使用路径或审查生成的契约。
查看原文
On July 30, 2026, Transformers enabled PyTorch's scaled dot-product attention (SDPA) for T5. As a side effect, UMT5's decoder could attend to future tokens. During training, it could peek at the answer it was supposed to predict. Nothing crashed, and the bug stayed in main for almost two months.
When we later replayed those commits, a small UMT5 model running on CPU flagged it on every one of them. This post is about that model and the tests built around it.
Why bugs reach main
Transformers supports over 750 model types, and its test suite is big: 88,000+ CPU tests on main, plus 200,000+ GPU tests in a daily run. Model tests are only part of it: we also test pipelines, training, distributed execution, quantization and attention kernels.

The integration tests load real checkpoints and exercise the model's API on real hardware. Running all of that on every PR would mean keeping a lot of GPUs busy all day long, which isn't practical. By the time a scheduled run finds a regression, it may already be in main.
Our approach is to shrink the model instead. We build a fixture: a small model made from the real checkpoint's configuration, with fewer and narrower layers but every kind of layer it has, and its tokenizer and processor files unchanged. It is published once, then reused. We run "fast integration tests" against it on a cheap CPU runner. In our CPU study, the experimental UMT5 fixture used 66 MB of weights; the source checkpoint is 1,227 MB.
This also covers very large models: some don't fit on our standard GPU runners, but their fixtures run on CPU.
Model integrators already build tiny test models (Transformers' tiny-random-* checkpoints). We wanted a common recipe, and checks that go beyond loading the model and running a forward pass. The UMT5 bug would have passed a forward pass just fine.
Fixtures won't replace full integration tests, but on PRs they give us a chance to catch bugs before they reach main.
Introducing Model Integration Contracts
transformers-mic is a proposed standard for packaging these tests. A Model Integration Contract (MIC) has three parts:
- a fixture: the shrunk model described above, published once and pinned
- a usage path: a runnable Markdown example of something people do with the model, such as generating text or running a training step
- baselines: the outputs recorded when running that example, reviewed by a human
The contract holds if running the usage path on the fixture still produces the baselines.
A contract covers an architecture and a usage path, not a single checkpoint: compatible checkpoints can share a fixture, while each keeps its own expected results. We publish them in huggingface-mic.
For example, the SmolLM contract generates from prompts such as "The capital of France is" and compares its token IDs with a recorded answer. After loading the model and tokenizer, the generation code is, simplified to one prompt:
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))
It also compares training loss, checks that gradients are finite and verifies that an optimizer step changes the weights. See the full usage.md.
A fleet dataset on the Hub pins each MIC to a frozen revision, along with the package versions used to record its baselines. Each CI run records the fleet revision it used, so we can reproduce it later.
On a PR, we pick MICs based on the files the patch changes.
The UMT5 regression
We replayed frozen MICs over five months of Transformers commits. The small UMT5 fixture flagged the regression from its introduction on July 30, 2026 until its fix on September 28. Label log-probabilities differed from the reviewed baseline by 1.22 and training loss by about 0.20.
One detail mattered: the contract used SDPA, the default attention path. Running only the eager attention variant missed the bug. A fixture only helps if it goes through the same code as the real checkpoint.
We then reverted the fix on a proof branch in Transformers' CI. The check failed with the same differences, and the selected T5 contract's runner step took 13.4 seconds on CPU, excluding setup.
The replay results and the CI demo have the measurements and reproduction details.
How models are shrunk
The first recipe was simple: make the dimensions smaller and keep fewer layers. That wasn't enough for mixed architectures. Keeping two layers is no use if both use sliding attention and the full-attention layers disappear.
The recipe now has four steps:
- Shrink the configuration. Start from a real checkpoint and reduce hidden sizes, attention heads and depth. Keep the different layer kinds, including mixtures of sliding and full attention or dense and MoE layers. Some architectures need extra rules to keep their components compatible. We also look at the model's own Transformers tests: the
ModelTester in test_modeling_<model>.py already picks a small config by hand, so we take its sizes for any width our generic rule doesn't know by name.
- Keep preprocessing unchanged. Preserve vocabulary, special tokens and input dimensions, and copy tokenizer and processor files byte for byte. Loading and preprocessing are part of what we want to test.
- Build and validate the weights. Try seeded random weights and change the inputs to check that the model responds. If it doesn't, try training it on the contract's small batch of examples. Reload the saved model and check that outputs, loss and gradients are finite. Reject failed builds.
- Freeze and record. Save the accepted weights and measure repeatable outputs on a known-good Transformers build. Review the baselines for each supported platform and precision, then publish a pinned snapshot. CI reuses it; it does not rebuild the fixture for every PR.
We also keep the original weights file format. Otherwise we would skip the conversion code that runs when a user loads the real checkpoint. The full recipe has more detail.
The Llama 4 Scout shrink report shows what this looks like on a big model. 108.6 billion parameters become 44.7 million. The vision stack goes from 34 layers to 2 and the language stack from 48 to 4, keeping the checked layer kinds and MoE routing style.
Building MICs with agents
Our CLI, tmic, creates the contract and builds the fixture. For a new model, an LLM drafts the usage code and manifest, and CPU and GPU runs measure the candidate baselines. The LLM drafts; it doesn't approve. Changed expectations require human review.
To try it on your machine, use your source checkpoint in place of 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 ''
It drafts the contract, builds the fixture and records its baselines on your machine, asking you to review each step, and stops before uploading anything. For a new model, the draft comes from an LLM through Inference Providers, billed to your own account (--bill-to ''). The CLI docs cover the individual steps.
We used agents to build MICs across model families. When they hit an edge case, we fixed the recipe itself rather than the one fixture, with help from model experts. The published models and remaining gaps are in the coverage space.
Random vs Overfit
With random weights, our Whisper fixture barely responded to changes in the audio. It ran, but that made it a poor test of the audio path.
So we trained it on a few tones paired with words such as "red" and "green". We weren't teaching it speech recognition, just making audio changes visible in its outputs. In our weight-initialization study, the random Whisper fixtures caught 18 of 24 injected faults. The overfit fixtures caught all 24.
That gives us two ways to build the weights:
- Start with random weights and a fixed seed.
- Overfit on a few examples, like "The capital of France is" => "Paris".
The Whisper result doesn't settle which is better. Those were synthetic faults, and we haven't found a real regression caught only by the overfit version. Both versions missed an assisted-decoding bug that the real Whisper checkpoint exposed.
So we keep random weights when they pass the checks, and overfit when we need better sensitivity or readable answers. Training takes seconds, but it is one more step that can fail to converge, and one more thing to maintain. The comparison has the results for both.
Open questions
As of November 1, 2026, the MIC job in Transformers CI reports failures without blocking PRs. Whether it should block depends on things we haven't measured yet:
- How many real bugs does it catch on PRs, not just in replays?
- How often does it send us chasing false alarms?
- How much work do baseline reviews take?
We also don't know how many bugs, like the Whisper assisted-decoding one, only show up with real weights, or whether overfitting catches real bugs that random weights miss.
Coverage is still partial, across model families and the APIs people use. When a regression slips through, we can add the missing usage path and replay it on the broken and fixed versions. We'll keep existing tests until their coverage is accounted for.
Finally, we'd like to link original Hub model pages to their MICs, so a developer or an agent using a checkpoint could start from an example tested on that checkpoint, its environment and its expected outputs.
Want to help? Pick an uncovered model from the coverage space, try the tmic workflow, and contribute a usage path or review a generated contract.
原文版权归 Hugging Face Blog 及原作者所有,本站转载仅供阅读。如需删除,请联系 support@aisa.one。