跨不同指标对 Transformer 架构版本进行基准测试
这是一篇由人类撰写的、聚焦 AI 智能体的博客文章。
编码智能体正越来越多地代替我们与软件协作:描述一个任务,智能体就会选择库、编写调用、运行它们,并调试自己的错误。当库本身成为障碍时,它会愉快地绕过它,从头重写逻辑。这为库开发引入了一个新概念:代码不仅应该正确且快速,还应该设计成能让智能体有效驱动。一个笨拙的 API 或过时的文档会让我们开发者感到烦恼,但现在它也会让智能体走上一条更长、更昂贵的路径。
大多数基准测试只看最终答案。而我们想要的是整个过程:不仅看智能体是否做对了,还要看它花了多少功夫才达到目标,以及这些功夫在不同模型、库版本和任务之间如何变化。我们以 Transformers 作为案例研究,精确测量了这些指标。
在这里,我们将介绍一个专注于答案发现过程的工具特定基准测试,并提供一种此类测试框架的简单实现。该实现完全运行在由 pi 编码智能体驱动的开源模型上,并将模型、版本和任务的完整组合分散到 Hugging Face Jobs 上,以确保每次运行都使用相同的硬件环境。
但是,如何为智能体优化软件呢?
我们坚信以下两条软件原则:
- 未经测试,即不可用
- 没有文档,即不存在
在面向智能体优化的工具领域,这一点同样适用,而且这一次,这两条原则直接相互关联。
你希望你的工具对智能体而言是“存在”的:它需要是可发现的。API 必须清晰,文档必须详尽。它们的结构需要能让智能体快速访问有用的文件和示例。如果你希望你的工具能为智能体所用,那么你就应该针对智能体使用场景进行测试。
为智能体使用场景测试软件
我们将以 transformers 为例贯穿这篇博文:智能体使用它来解决机器学习任务(文本分类、图像加注、音频转录),而不是为其贡献代码;不过,该测试框架被设计为能与任何可通过命令行操作的工具配合使用。
我们对 transformers 的直觉是,通过几项改动就能大幅简化其使用方式:一个命令行界面、一个技能包,以及自包含的、针对特定任务的示例。这正是最近应用于 hf 命令行界面的相同方案——该界面已被重新设计为针对智能体进行优化,智能体使用的模型 token 减少了 1.3 到 1.8 倍(最高可达 6 倍)。我们想知道这种优势是否具有普适性,以及它是否也能对 transformers 有所帮助。
直觉是一种强大的工具,但在我们向 transformers 这样广泛使用的代码库提交增加数千行代码的拉取请求之前,我们希望获得更多证据。我们着手衡量成功的标准是什么。
并非所有成功都同等重要
两个智能体都能为情感分类任务生成正确的标签,但其中一个:
- 编写一个 40 行的 Python 脚本,导入 transformers,调试一个形状错误,重新运行两次,最后打印出答案;
而另一个
- 输入 `transformers classify --model ... --text "..."`,一次调用就完成了。
两者都达到了 POSITIVE(0.9999),以下是智能体在此特定任务上实际采取的两条路径:
# Task: classify the sentiment of "I absolutely loved the movie, it was fantastic!"
- # one agent: pipe a script into python and parse the output
- python - <<'PY'
- from transformers import AutoTokenizer, AutoModelForSequenceClassification
- import torch
- import torch.nn.functional as F
-
- model = AutoModelForSequenceClassification.from_pretrained("distilbert/distilbert-base-uncased-finetuned-sst-2-english")
- tokenizer = AutoTokenizer.from_pretrained("distilbert/distilbert-base-uncased-finetuned-sst-2-english")
- inputs = tokenizer("I absolutely loved the movie, it was fantastic!", return_tensors="pt")
- with torch.no_grad():
- logits = model(**inputs).logits
- probs = F.softmax(logits, dim=1)
- idx = torch.argmax(probs, dim=1).item()
- print(model.config.id2label[idx], probs[0][idx].item())
- PY
+ # the other agent: one command
+ transformers classify \
+ --model distilbert/distilbert-base-uncased-finetuned-sst-2-english \
+ --text "I absolutely loved the movie, it was fantastic!"
两种方法都得到了相同的结果。但它们在成本、延迟、模型 token 使用量和失败率方面有着截然不同的表现。
如果你的评估只检查最终字符串,你就无法看到这些差异,也无法判断你对库所做的改动(命令行界面改进、更好的错误消息、技能包)是否真正帮助了智能体。
我们使用这个测试框架的目标是评估智能体执行给定任务需要做多少工作,以及库的改动是否能提升性能。
我们如何进行评估?
简单说明一下我们将如何在此评估智能体。
我们在三种变体(或称“层级”)下运行每个任务;这是智能体处理 transformers 的三种不同方式:
bare pip install transformers, and nothing else
clone the full transformers source, checked out in the working directory
skill a packaged Skill: the CLI's docs + task examples, loaded in context
这些并非嵌套关系:技能不包含克隆(它提供的是精选文档,而非源代码树),两者之间也不存在严格的包含关系,各自为智能体提供不同类型的帮助。正如我们即将看到的,模型在克隆任务上的表现有时会优于技能任务。
还有几个选择:
- 目前我们只关注能够提供精确匹配的确定性任务,因为它们为实验提供了极佳的基础。对于其他任务,采用模型作为评判者及其他方案显然是下一步的方向。
- 每次运行都是一个独立的 Hugging Face 任务:每个(模型 × 版本 × 任务)组合对应一个任务,因此整个扫描过程在相同的硬件上并行执行,从而在大规模测试中保持公平性。
- 结果和追踪数据会存入 Hugging Face Bucket:速度快,无需版本管理,且能处理极高的写入并发。
应该以哪些模型作为基准进行对比?
驱动智能体的模型并非都同等优秀,它们的差异会改变你在运行时应关注的重点。
大型开源模型
一方面,你有最大、能力最强的开源模型。在相当常见的任务上,这些模型最终应该能给出正确答案。对于它们而言,任务完成率接近 100%,这已无法告诉你太多关于工具的信息;更相关的基准是智能体完成任务所付出的努力:消耗了多少轮次、多少 token 和多少秒,以及它们走的是清晰的路径还是使用了已弃用的 API。
本地模型
本地模型在规模上差异很大,其能力也是如此。与大型模型相比,“匹配率”等指标更具相关性,因为你可以看到模型大小/能力如何影响你在特定工具上的结果。
这个测试框架不仅为库维护者提供了如何改进仓库以适配智能体交互的指导,还有助于评估不同智能体和模型在用户关心的任务上的表现。
该框架从多个维度对每次运行进行评分,以便你可以针对每类模型提出真正重要的问题:
- 匹配率:最终答案是否包含预期结果(按任务区分,支持不区分大小写的子串匹配/正则匹配/精确匹配,所有信息均在报告中明确列出);
- 中位时间与中位 token 数(区分新生成、缓存与已生成)。
- 运行错误百分比:包含一个防护机制,用于标记那些未产生任何输出(0 个输出 token、无工具调用、无回答)的运行,从而避免静默失败被伪装成“0”;
- 标记采纳:工具定义的行为标记;下文将解释其含义。
所有这些内容都会汇总到一份可直接查阅的报告中:
实时报告:概览、覆盖范围与结果,全部在客户端侧完成。
由于它捕获了每次运行的智能体原生轨迹,数字仅仅是个开始:你可以逐条命令精确查看智能体执行了哪些操作。这些轨迹可通过 Hub 的智能体轨迹查看器进行分享:
在 Hub 的智能体轨迹查看器中渲染的一次运行:MiniMax-M2.7 执行“回答问题”任务。在 Hub 上打开此轨迹 ↗
在展示结果之前,先快速回顾一下实验设置。每次运行会改变四个变量:驱动智能体的模型、该模型所运行的 transformers 版本、任务,以及层级(裸模型 / 克隆 / 技能)。如前所述,我们对两类不同的模型采用不同的评估指标。
大型开源模型:固定模型,改变版本
由于大型开源模型通常能得出正确结果,因此真正衡量的是它为此付出的努力。它用了十轮交互还是一轮?它是否因为信任过时的文档而遵循了你已弃用的 API 路径?它是否遇到了你未曾预见的错误?
自然的实验方法是固定一个强大的模型,然后改变工具的版本:我们测试了 transformers 的多个连续 git 版本,从 v5.8.0 和 v5.9.0 等已发布标签,到引入 CLI 和技能的具体提交。我们想观察模型对智能体施加的负载是上升还是下降。我们使用该测试框架对 transformers 进行了检验,以确认添加专用的 CLI 和技能是否确实减轻了智能体的工作负担。
对于测试中使用的三个大型模型,所有任务的平均耗时表明,技能提交版本使得智能体处理任务所花费的时间更少:
按层级划分的各版本中位耗时:技能提交(绿点)速度最快。
另一方面,在我们克隆仓库的实验中,可以看到由于引入 CLI 和示例的提交,模型 token 消耗量显著增加,我们稍后会看到这一点。
按层级划分的每次修订中新增 token 中位数:一旦 CLI 进入仓库,克隆变体的数值就会跃升。
阅读克隆变体的追踪记录就能明白原因。该提交添加了一个命令,但它同时也将 CLI 的实现和一组 `cli/agentic/*.py` 使用示例直接放入了仓库。
在克隆变体中,智能体面前有一个完整的 transformers 代码库,大约三分之一的运行会先去读取新的代码表面(`/cli/` 目录树和示例脚本)以了解接口,然后再调用它。这使得输入中位数从约 4k token 上升到约 6.4k token。
那么这两个图表就是同一权衡的两个方面:该提交为大型模型节省了时间(它们直接使用 CLI 而不是调试 Python),代价是消耗更多 token(它们读取了教会它们使用 CLI 的代码)。这是在合并 PR 之前值得了解的一个权衡。
不过,有一个有利于 CLI 的注意事项尚未被基准测试覆盖:读取它的成本会随着连续运行而被分摊。我们的设置是为一次性实验而构建的。每次运行都是一个全新的智能体,从头重新发现 CLI,因此每次都要支付发现成本。在实际使用中,智能体只学习一次接口,然后在同一会话中一个接一个地完成任务,从而将成本分摊到多次请求中。我们在这里测量到的 token 增加更接近最坏情况,而非用户日常会看到的情况。
小型模型:固定修订版本,改变模型
开放模型让我们能够对这里最重要的变量进行精细控制:大小、配置、量化、提供商、训练,以及任何模型之间可能存在的差异。同时,这也是一个好的工具接口最能发挥作用的地方:一个被要求“在裸环境中使用 transformers 做 X 任务”的小型模型,可能会猜测一个已在几个版本前更改过的 API,可能会进行不必要的工具调用,并且可能得到错误的答案。
因此,这里的实验与上述相反:固定修订版本,遍历模型。这有助于观察哪些模型真正完成了任务,不仅看 token 数量和时间,还要看哪些模型无法可靠地处理工具调用。我们的直觉是,模型越小,工具使用和任务本身的难度就越大;我们在一系列不同规模的模型上运行了测试框架,正是为了验证这一点:
按层级划分的各模型匹配率:技能层级提升了较大模型的表现,但降低了较小模型的表现。
这似乎也与摄入的 token 数量相关
按层级划分的各模型新增 token 中位数。
关于公平比较的说明:当覆盖范围不均衡时(只完成了简单任务的模型看起来速度很快),简单地对各任务取平均值会产生误导。该报告设有"仅共享任务"切换选项(可跨模型和/或修订版本),以便进行同类比较,同时还有覆盖范围热力图,可以精确查看哪些任务 × 修订版本 × 模型组合实际运行过。
调整工具:标记与结果
这里涉及两件事:如何超越智能体是否成功这一层面,去观察它做了什么以及如何做到的;以及我们从测试框架中提取出的首批结果。
什么是标记?
匹配率、token 数量和时间能告诉你一次运行的成本,但无法揭示底层发生了什么。
这就是我们引入标记概念的原因。标记是一个命名模式,配置文件(即小型逐工具插件,用于教导测试框架如何构建和驱动给定库)会将其与运行过程进行匹配。
它是一个单行标签,用于标记你关心的行为,并与智能体运行的 shell 命令、编写的代码、读取的文件或最终答案进行比对。一次运行可能触发多个标记,也可能一个都不触发;报告会显示每个标记在每个模型和每个修订版本中的触发频率。
对于 transformers,我们声明了几个标记,但这里只关注最相关的两个:
- cli:智能体调用了 transformers 命令行工具(例如 `transformers classify …`),而不是编写 Python 代码。
- pipeline:智能体使用了高级的 `pipeline(...)` Python API。
这些是我们观察变化是否真正改变了智能体行为的关键指标。有趣的是,模型越大,就越倾向于利用新的上下文而非依赖自身记忆;因此,它们会更多地使用新引入的命令行界面。
各模型按层级对命令行的采用情况:只有技能层级会主动使用它,而且模型越大,使用频率越高。
命令行界面的采用是全新的:该命令行仅通过一次提交引入,不在任何模型的训练数据中,且文档说明非常简略。效果很明显:正是那个包含了命令行文档的技能变体,真正主动使用了它,使用率达到 55.3%。
命令行界面加技能提交是否有帮助?
对比不同模型规模下的提交效果,命令行加技能对较大模型有帮助:在技能层级上,Kimi 和其他大型智能体会主动使用命令行,并以更少的交互轮次完成任务。(在克隆层级上,它们会先花费更多输入 token 来读取新的命令行代码,正如我们之前所见,因此优势体现在时间和轮次上,而非原始 token 数量。)
不同版本下的 Kimi-K2.6、GLM-5.1 和 MiniMax-M2.7
但在某些较小模型的设置中,这似乎反而损害了性能。一个合理的解释是,小模型依赖记忆中的 API 模式,会复现它们在训练数据中见过的 `pipeline(...)` 代码片段。新的概念对它们来说是一个更容易出错的更大范围。你可以直接在测试框架上观察到这一点:匹配率降低、重试次数增加、命令行标记几乎未被触发。这在 Qwen3-4B 模型上尤为明显:技能变体几乎未改变其匹配率,但其成本分布却受到了显著影响。
这几乎全部来自克隆层级。代码检出现在包含了命令行的实现和 `cli/agentic/*.py` 示例,而 4B 智能体大量读取了这些内容:其中位数新 token 数从约 2.4k 跃升至约 23k,时间和输出也急剧增加,但准确率毫无提升。
不同版本下的 Qwen3-4B。命令行加技能提交使得成本分布大幅扩散,在克隆层级上,智能体大量读取了新引入的命令行源代码(新 token 数增加了约 10 倍),但匹配率毫无提升。(重复 token 数保持稳定:此设置未使用提示词缓存。)
不过,有时技能会直接破坏正确性。查看追踪记录可以发现,例如对于 Qwen3-14B:添加技能后,其整体匹配率从 67%(裸模型)下降到了 43%,而在最简单的任务上,这种崩溃非常明显:情感分类任务从克隆版本的 100% 降到了带技能版本的 0%。
Qwen3-14B 在情感分类任务上的表现,按层级划分:克隆版本(蓝色)在各修订版本中均保持 100%,但技能版本(绿色)在 CLI + 技能修订版本中崩溃到了 0%。
查看追踪记录,模型误将 CLI 当成了一个可以直接调用的工具(就像智能体框架中的工具,例如网络搜索)。技能并非可执行工具:它是加载到智能体上下文中的文档,而 transformers CLI 始终只能通过 shell(使用 bash)运行;因此这种方式行不通。
Qwen3-14B 读取了技能,在其 56 次技能运行中,有 39 次要么发出了 `transformers(command="classify", ...)` 工具调用(一个从未注册过的工具),要么在其可用的 read/bash/edit/write 工具中找不到类似工具,从而得出结论认为无法运行模型并放弃。无论哪种情况,它都没有退回到在克隆版本中取得 100% 分数的单行 `pipeline(...)` 代码,而是宣布任务不可行。
Qwen3-14B 在情感分类任务(技能版本)上的表现:它推理认为 read/bash/edit/write 无法运行模型,然后放弃。
这正是我们构建该测试框架所要捕捉的问题:同样一个改动,在加速大型模型的同时,却破坏了小型模型,这起初让我们觉得有些反直觉,而且我们很可能就这样直接发布了。给维护者的启示:面向智能体的 API 应该跨不同模型规模进行评估,因为一项新的能力可能会减少强模型的工作量,同时却给小型模型增加歧义。这也暗示了一种修复方法:与其手动编写技能并在事后检查,不如针对较弱的模型预先生成并验证技能。
这正是 Upskill 所做的:只有当一项技能确实能帮助小型模型时,它才会将强模型的解决方案转化为技能。
亲自尝试
该测试框架是一个 CLI 工具,名为 `agent-eval`。安装它,运行一个测试套件,在 HF Jobs 上将其分发到多个模型 × 修订版本组合,然后将报告发布为一个 Hugging Face Space。
仅限受信任的本地使用。该测试框架会运行一个具有绕过权限的编码智能体,并执行你指向的任何版本中的代码,而追踪记录可能包含提示词、输出结果以及本地路径。在将其指向非你编写的代码或分享结果之前,请先查阅 SECURITY.md 文件。
完整且持续更新的设置与使用说明位于 README 文件中。
结语
检查最终答案能告诉你一个智能体是否可以使用你的库。但它无法告诉你成本:即所花费的轮次、模型 token、错误以及它达成目标所走的路径。这个测试框架可以衡量这些指标,涵盖你所选的不同版本和模型。
在 transformers 上,它发现了一个我们原本会凭信心发布的问题:CLI + 技能(Skill)有助于最大的开源模型,却损害了最小的模型。在合并之前,这一点值得了解!
它基于配置文件,并且设计为可适配的:将其指向你自己的库,定义几个任务及其预期答案,即可获得同样的报告。代码和任务在仓库中,追踪记录在 Hub 上。如果你在自己的项目中使用了它,请告诉我们!
致谢
这个测试框架完全建立在 pi(Mario Zechner 开发的编码智能体 CLI)之上:它驱动了所有开源模型的运行,并且只需要一个 HF_TOKEN 即可服务一个模型,这正使得对开源模型的全面扫描变得切实可行。
感谢我们扫描过的模型背后的模型构建者和推理提供商。总体而言,它们的表现远高于原始基线所暗示的水平。
开源