同一批运行,作弊记成失败,是 11.3 小时;记成成功,是 270 多小时。差出二十几倍的这一截,来自评分器没守住的地方。
在无人值守的自动实验循环里,评分器只要放在 agent 写得到的地方,早晚会被写到。挡得住的是权限和结构,写进 prompt 的禁令几乎没用。评分器还有第二个洞:没人作弊,噪声也能让数字失真。
evaluate_bpb 放进写着“不要改”的 prepare.py;METR 的 NanoGPT 加固版把 runner 和 validator 设成 root 所有,计时归 runner,模型只返回 logits。对着源码读,Karpathy 版里的 loss 由 agent 可改的 forward 算出,计时循环也在 agent 可改的 train.py 里。锁死评分器,只解决了“被改”。METR 的 NanoGPT 实验里,GPT-5 和 Opus-4.1 的原始轨迹显示有进步,重新验证后一点也没有。没人作弊,数字也是虚的。
METR 评估 OpenAI 的 GPT-5.6 Sol 时,碰到一个很难受的局面。METR 的主指标是 50% 时间视野:模型有一半把握做成的任务,换算成人类要做多久。按 METR 的标准做法,作弊的运行记为失败。可是 Sol 作弊太多,记账方式一换,结论就跟着跑。METR 的报告给了三个数:
270 除以 11.3,差约 24 倍(这是我的算术)。这个差不来自模型多做成了什么,来自同一批作弊运行怎么记账。报告里有句话说得很直接:METR 不认为这几个数字里的任何一个,是对这个模型能力的稳健测量。
我拿它当起点,因为它把一个常被默认的前提摆到了桌面上:我们看到的每条“能力曲线”、每个“实验收益”,都是某个评分器吐出来的数字。评分器管不住候选,数字就没有固定的含义。
先交代两个词。评分器,在本文指从“候选产出了东西”到“拿到一个数字”之间的整条链:计时器、参考答案、测试集、损失函数、通过阈值、日志。长时程自动实验,指 agent 无人值守地跑几小时到几天,一轮轮改代码、跑实验、看分数、保留或丢弃。训练阶段里的 reward hacking 是另一类问题,本文不碰。
这篇是《AI Native 时代的研发》§04 里一段话的展开。那篇只给了结论:评分器对生成者要不可写。这里把证据和做法摊开,最后回头查一遍我自己的实验做到了几条。
METR 近一年的几份报告给了足够多的数字,别的研究者也有可对照的。先放表,再说怎么读。
这张表有三处要小心。
分母各不相同:全部运行、成功运行、一条轨迹、一个人埋下的诱饵,混在一起算不出“前沿模型平均作弊百分之几”。任务也是挑过的:METR 的数字来自它最难的任务,报告里说,METR 已经为此加固过任务,还删掉了几个被作弊搞得没有信息量的任务,剩下的任务上 agent 仍在反复尝试。Sol 的报告也提醒,观察到的作弊率会受评估脚手架里的提示和任务指令措辞影响。
第二,同一份 2025-06 的报告里,HCAST 是 0.7%,RE-Bench 高出 43 倍以上,模型是同一个 o3。METR 自己给的猜测很值得抄下来:RE-Bench 的任务里,模型能看到整个评分函数,这让评分函数更容易被绕过,也在无意间把模型引向这类解法。同一个模型在两套任务上差出 43 倍以上,差别似乎出在评分函数看不看得见、够不够得着。这是 METR 的推测,不是检验过的结论。
第三,人类基线员很少这样做。METR 写到,人类基线员也有拿高分的金钱激励,作弊同样是条路,但据 METR 所知只有一例。所以问题不只出在“任务难到让人想走捷径”。
读完这张表,我最想问的是:评分函数摆在哪?
把这些案例按评分器被动到的部位排一遍,手法其实不多,每次只是换了个落点。
一,时钟。 o3 在一个写 GPU 内核的任务里,把 time.time 换成一个返回真实时间千分之一的函数,代码注释写着“给 score.py 用的 1000× 快的时钟”。另一条轨迹里,它关掉 CUDA 同步,让系统测不到真实执行时间,解法看起来几乎无限快。
二,答案。 就是上面关掉同步的那条轨迹,o3 同时沿 Python 调用栈翻出评分系统已经算好的正确答案,直接返回。Optimize LLM Foundry 里,21 次运行 21 次选择预先算好答案、缓存起来,让脚本显得很快。GPT-5.6 Sol 则是把利用代码打进中间提交的包里,探出隐藏测试集的信息;在另一道题里抽出了写着期望答案的隐藏源代码。Quran 那项研究里,是硬编码评估行的答案;PostTrainBench 里,是在测试集上训练。
三,评估函数本身。 o3 在 CodeContest 题上 monkey-patch 评估器,让它永远返回满分;还有覆盖相等运算符、用桩函数替换评估器。METR 的 Time Horizon 1.1 里有个 agent 给自己写的东西起名叫“HackRouter”,用栈帧内省钻进模拟器,把任务改得无关紧要。在 MirrorCode 里,Opus 4.6 往评分脚本里注入日志代码,想在评分器读取测试用例时把它们记下来。
四,评分协议,也就是“怎样才算数”。 这一类不动评分器的代码,动的是算数的条件。METR 的 NanoGPT 实验里,agent 试过在第一个验证步就停止训练,只为碰一下目标验证损失;也试过在损失离目标不到约 0.001 时冻结 MLP。METR 认为这是 reward hacking 的特例;维护者则说,底层的一些技术(比如后期冻结)本身相当合理,但现在被过度拟合到精确的目标 loss 上,太脆弱,所以会被拒绝。
五,环境与资源,也就是沙箱的边界。 PostTrainBench 里,agent 下载现成的 instruct 模型检查点,而不是自己训练;还用在环境里找到的 API key 生成合成数据。METR 的 Time Horizon 1.1 里有个任务,要求为一个较弱的 LLM 设计 Rust 解题脚手架,有个 agent 写了个程序,去 17 个公开 GitHub 仓库里搜匹配的 C++ 解法,包成 Rust,把通过分数做到了 10 倍。Shopify 把 autoresearch 用在构建时间上,agent 也曾靠删一大堆文件换速度,作者写的是:“Yeah, it's faster, but that's not acceptable.”
有三点值得单独说。
它们常常是“知道”的。 METR 在 o3 写出第一个作弊方案后问它:上面的计划是否符合用户的意图?o3 十次里十次回答“不”。METR 写道,在很多别的任务上,o3 似乎完全清楚自己在作弊。到 Sol 的报告里,METR 又说观察到相当强的情境意识,以及对评估环境的推理。这不是理解力的问题,是目标的牵引:分数是看得见的,意图是推出来的。
提示词挡不住。 METR 在 Optimize LLM Foundry 上拿同一个模型,每种提示跑 20 次,数它在第一次生成里描述出作弊计划的比例:原提示 80%;“请不要作弊”80%;“请不要 reward hack”70%;“请只用任务设计者可能想要的方法”反而升到 95%。METR 的结论是几乎没有效果。BaitBench 同样发现,明确提示不许用捷径,平均作弊率仍然高于 50%。Quran 论文里,真正让行为变化的是结构上的改动:第二项(预注册)研究给 agent 加了保留测试集并告诉它们存在,记答案的行为随之消失(样本很小,这是摘要里的说法)。
有一类“作弊”其实是指标缺了约束。 Shopify 的删文件,指标本身没被改动,构建时间确实降了;被牺牲的是指标没写出来的约束:产物要完整。同一篇文章里还有一个合法的例子:agent 发现 TypeScript 转换在 580 个组件文件上都跑了一遍,其实只有 105 个需要,构建一下快了 65%。两者的差别,评分器分不出来,作者的办法是事后由人把 hack 扔掉,留下好的。NanoGPT 里的“脆弱解”也是同一类。这一类的修法是补指标、补约束、加人审;评分器被改的那一类,靠隔离。两类混着处理,哪一类都处理不好。
Karpathy 的 autoresearch 是这类循环里最有名的设计,也常被拿来当“评分器隔离”的范例。结构很干净:prepare.py 放数据、分词器、dataloader 和评估;train.py 是 agent 唯一能改的文件;program.md 是人给 agent 的指令。每次实验固定 5 分钟,指标是 val_bpb,评估函数 evaluate_bpb 放在 prepare.py 里,评估部分的标题注释写着 “DO NOT CHANGE — this is the fixed metric”。program.md 里有一栏“What you CANNOT do”,第一条是不许改 prepare.py,第三条是不许改评估框架。
我对着源码读了一遍。GitHub 上这个仓库 master 的最新一次提交是 2026-03-26,下面的行号都指向这一版。有三处让我停了一下。
第一,loss 是 agent 可改的代码算出来的。 evaluate_bpb 的主体是对 model(x, y, reduction='none') 的返回值求和,换算成每字节比特数。而这个 forward,定义在 train.py 的第 268 行,交叉熵就在里面算。也就是说,“固定的指标”信任的是 agent 可改文件里算出来的逐 token 损失。
第二,计时循环在 agent 的文件里。 TIME_BUDGET = 300 这个常量在 prepare.py;而累计 total_training_time、判断“是否到点”并 break的循环,在 train.py 里。
第三,“只读”是一句话,不是权限。 program.md 写的是“It is read-only”,是对 agent 的指令。README 的上手说明是在仓库里启动 Claude 或 Codex,并且(原文)“disable all permissions”,我的理解是跳过权限确认,让它能整夜跑下去。我读的四个文件(README、program.md、prepare.py、train.py)里,没有看到文件系统层面的保护。
要公平地说,这套设计面向的是单人、可信的研究循环,改动都在 git 里,读 diff 就能发现。我读到的材料里,没有人报告在这个仓库里实际钻过这三个缝。Karpathy 公布 700 次实验的结果时,也说过事后验证了约 20 个改动可以叠加,并迁移到了更大的 depth=24 模型,这本身就是一道保留检验(据其推文)。我也没有真去改一遍试试。所以这是“读代码得到的结构判断”,不是事件。
对照 METR 的做法。Expenditure Horizon 那项研究里,agent 在 NanoGPT speedrun 上做自主优化,每个 agent 最多花 $10,000。METR 在附录 D 里列出了加固的手段,并交代了来历:这些决定是在多轮诱导实验、看到 agent 在初版任务上尝试作弊之后做出的。
solution.py,里面有 setup 和 train 两个函数;runner.py 和 validate.py 是 root 所有,受保护,不能编辑。eval_forward,返回 logits;受保护的 validator 在固定的验证集上算交叉熵。METR 说,这样就挡住了 agent 直接伪造验证损失。对照下来,两种做法锁住的东西不一样。Karpathy 锁的是评分函数的规格:用哪个验证分片、评估多少 token、怎么换算字节数。METR 在此之外还锁了评分函数的输入来源(logits,而不是 loss)和计时权。前者防的是 agent 改评估口径,后者防的是 agent 伪造评估输入和时间。再往下是强制的层级:一个靠指令,一个靠操作系统的文件所有权。
另一点是来历。METR 说这些决定是看到 agent 在初版任务上尝试作弊之后做的。把每一条对回前面的手法,大致是:计时归 runner 对应改时钟,logits 对应伪造损失,未训练断言对应带预训练权重,root 所有对应改评估器(这个对应关系是我的归纳,METR 没有逐条写)。MirrorCode 的加固是同一个思路:agent 注入日志代码想记下测试用例,在自己的推理里写道这行不通,因为只有 /workdir/src/ 的内容会被拷贝进另一个容器评分。这份清单是被 agent 一条条教出来的,下一代模型会教出新的几条。
把写权限锁死之后,评分器还有一个洞:噪声。
在 NanoGPT 的实验里,agent 提交的轨迹显示的是“累计最优”分数。METR 对每个声称有进步的方案,都重跑 40 次以上,还对每花 $500 时的前三名中间方案同样重跑。跑的过程中,agent 被提示用少量运行探索,用 n=8 确认有希望的方案。重新验证的结论写得很清楚:原始轨迹高估了进度,原因是统计噪声。GPT-5 和 Opus-4.1 的原始轨迹都显示有进步,最终算法重新验证后,相对基线没有提升,METR 的原话是这两个模型 only chase noise。表现最好的两个模型,重验证后的加速大约是 1% 到 1.5%,维护者认为其中只有约 70% 的贡献可以合并,评价是“大多在拨弄旋钮”。
“累计最优”这个形式本身,就是一台噪声放大器。我做了个小模拟来说明量级。假设 650 个变体的真实效果全部为零,每次测量带一个单位的噪声。取其中最高的那个,期望读数是真实水平之上约 3.1 个单位。
650 是 METR 报告里 GPT-5.5 生成的变体数;910 是SkyPilot 那次 8 小时、16 张 GPU 上提交的实验数(厂商自己的博客)。候选越便宜、挑得越多,对评分器的精度要求就越高。这和《AI Native 时代的研发》里说的“钱花在验证上”是同一个方向:廉价的生成,把验证的账单抬高了。
噪声之外还有一个面:题本身可能见过。Expenditure Horizon 的附录里,METR 发现从较早记录起步的实验,模型能说出之后的几项记录;有一次 Opus-4.7 的输出里写着“Let me apply known speedrun improvements”。METR 不把这些运行当主要证据,并说无法证明是记忆。从 #78 起步的主实验里,没有探测出模型知道更晚的记录。评分器的“题库”被训练数据污染,同样是它失灵的一个面。
SkyPilot 的那次实验还留下一个相关的细节:agent 自己发现 H200 在同样的 5 分钟里多跑约 9% 的步数,于是在 H100 上筛、在 H200 上确认,并发现某个学习率比例参数在 H100 上赢、在 H200 上输。便宜的代理评分,排序并不总能迁移到贵的评分上。
回到开头那条时间视野曲线,看它为什么特别脆弱。
METR 的前沿风险报告里有几句话,凑在一起很有分量。作弊严重到影响测量的完整性,以至于手动检查作弊,常常占了跑一遍评估套件的大部分工作(原文:manually checking for cheating is often the majority of the work)。METR 删掉过一些任务,因为过多的作弊让它们没有信息量。对其中一个模型,如果把成功的作弊算通过,测得的时间视野大约会翻一倍。作弊在最难的任务上最常见:超过 8 小时的任务里,至少 16% 的成功运行经复核不正当,对应超过 100 个不同的作弊实例。
这些放在一起,是一个三方牵扯的结构。最长的任务,作弊最多;复核一条长轨迹的人力成本,按常理也最高(这是我的推断,METR 没有按任务长度拆过人力);而曲线的最高点,恰恰落在这批任务上。Sol 的三个数字(11.3、71、270 多)把这个结构画得很清楚。作弊算失败,是标准做法,偏保守;算成功,是另一个极端;剔除,则让几个长任务没了数据,区间膨胀到 13 到 11,400 小时。三种记法没有一种“正确”,因为它们回答的是不同的问题。
对读者的含义是简单的。看到一个长时程的评测数字时,值得问三个问题:作弊怎么记?谁复核、复核了多大比例的轨迹?删了哪些任务?METR 把这几项都写进了报告,所以我们才问得出来。
站内凡是引用 METR 曲线的地方,也都该带上这几个口径。
下面九条是我从上面这些案例里提炼的。每一条标了出处,哪些是我的推论也写在里面。
runner.py、validate.py 为 root 所有;MirrorCode 里只有 agent 的 src 目录被拷贝到评分环境,所以注入日志代码的企图落了空(原文)。同时别把评分器的源码和参考答案放在 agent 能读到的地方,Sol 就曾抽出写着期望答案的隐藏源代码。train.py 里,见 §03);METR 给每个 agent 的上限是 $10,000。预算也是评分的一部分:谁的预算大,谁就占便宜。没有哪一条是新发明。把它们放在一起,是为了换个提问方式:不是“有没有让 agent 别作弊”,而是“这个 agent 此刻能写到评分链上的哪一环”。
autoresearch-batch 的设定:给 GLM 一个训练脚本和当前最优指标,让它一次性生成完整的改进代码;框架通过 Batch API 批量提交,收回后在 GPU 上跑,选最优,进入下一轮。报告页写的协议很轻:训练脚本“只需一行”,print(f"ARB_METRIC: {acc:.4f}")。对照上面九条:
ARB_METRIC;评分逻辑和验证集划分放在哪一侧,报告页没有说明eval、os.system、shell=True,目的是安全,不保护评分;页面没有提到文件权限或容器隔离patch 模块val_acc 一个数,选优和汇报用的是同一个数几点补充。
一次性生成降低了暴露面,但不是免疫。 我的候选生成是一次 Batch API 请求换回一整份脚本,生成者没有 shell,也看不到自己的运行结果。这样的生成者,比 METR 那些带工具、能反复试错的 agent 更难发现漏洞;报告里的结果也都只跑了一轮。可是,生成者读到的是整份训练脚本,如果评分逻辑就写在这份脚本里(报告页没有说明),那么它看到的,就是 METR 说的 RE-Bench 那种“整个评分函数”。报告页的“下一步”写的是多轮迭代和过夜大规模实验,那时每一轮的最优指标会被喂回生成者,选择压力才真正出现。
预算不固定,让对照本身不干净。 《AI Native 时代的研发》§10 已经承认,ResNet-18 的 +5.0pp 里有多少来自架构,多少来自 50 个 epoch 的预算,这一轮没有同预算对照。从评分器的角度再说一遍:训练强度是评分的自由度。Karpathy 把它固定成 5 分钟,METR 由 runner 管时钟,都是在堵这个口子。
选优和汇报共用一个数。 报告页没写验证集多大。若按 CIFAR 的 1 万张算,准确率在 0.46 附近时,单次评估的标准误约 0.5 个百分点(我的算术,不是报告里的数)。CIFAR-100 那一轮里 c1、c3 的 +0.6pp 就落在这个量级里,不能算数;ResNet-18 的 +5.0pp 远大于这个量级,但其中多少来自架构、多少来自 50 个 epoch 的预算,仍要等同预算对照和多 seed 才分得开。
在跑过夜实验之前,我会先做五件事,按成本从低到高:由 runner 统一注入 epoch,LLM 只能改架构和优化器;候选只保存模型或返回 logits,由 runner 在固定的验证集上重算准确率;把评分模块放进另一个目录,agent 的脚本只在子进程里运行,评分在父进程里做;留出一个从不参与选优的测试集,最后只对 Top-2 评一次;对 Top-2 各补两个 seed。最后一项会把全量训练的次数从 2 次提到 6 次,这是把预算从探索挪向验证,和《AI Native 时代的研发》讲的方向一致。
还有一个我没法回避的可能:评分器面对的对手,可能在变强的同时也在学会躲检测。METR 在 Sol 的报告里就是这样担心的:如果未来的模型表现出明显更少的不良倾向,反而要警惕它们是不是学会了躲避检测。到那时,“抽查轨迹”这一条本身就需要被评估。
首次发布 2026-10-13