自我改进的上限,是验证器的上限

拿 GCC 当 oracle 去验一个十万行的新编译器——这一手解决的不是「怎么写对」,是「怎么知道写对了」。四类自我改进方法的天花板都在这儿。

位置
第 02 篇 / 共 4 篇
预计
6 分钟

怎么知道一个十万行的 C 编译器是对的?

写测试?测试也是它写的。让模型自己检查?那是自己给自己判卷。找人审?十万行 Rust,两周内产出。

Anthropic 那个项目给的答案是:拿 GCC 当 oracle。 具体做法是写一个测试 harness,随机用 GCC 编译内核的大部分文件,只把剩下那些交给 Claude 写的编译器,然后看整体还能不能正确构建。

这一手值得停下来想一会儿,因为它绕开了一个几乎无解的问题:不去证明新编译器是对的,而是找一个已经被世界验证过二十年的东西来对比。

为什么这比它看起来更深刻

正确性证明是不可行的(编译器等价性一般不可判定)。人工审查在这个规模下也不可行。而 GCC 提供的是一个免费的、覆盖极广的、几乎不会错的参照系。

更妙的是那个「随机混合」的设计。它不是「用 GCC 编一遍再用 Claude 编一遍比结果」,而是在同一次构建里混着用:大部分文件走 GCC,剩下的走 Claude 的编译器。这样一来:

  • 只要构建出的内核能跑,说明 Claude 编译的那部分和 GCC 编译的那部分在二进制层面是兼容的
  • 出了问题,可以用 delta debugging 缩小到具体是哪几个文件的组合
  • 每次随机换一批,覆盖面自然铺开

这是把「验证」变成了「差分测试」,而差分测试的成本几乎为零 —— 参照系已经存在。

四条路的验证器各是什么

上一篇那四种范式按验证器重新排一遍,会看到一个清晰的谱系:

范式 验证器 它有多可信 它有多贵
并行团队写编译器 GCC + 测试套件 + CI 极高(外部、独立、久经考验) 极低(现成的)
Darwin Gödel Machine SWE-bench / Polyglot 高(外部基准) 中(每次改动跑一遍)
ADAS 各领域基准 中到高
SBCO 学出来的验证器 中(自己学的,有门槛) 低(学一次反复用)

最后那一行最有意思,因为它承认了一个现实:很多领域没有现成的 oracle,那就只能学一个出来。

SBCO 的做法是学一组按约束拆分的验证函数,并且给它们设了准入门槛 —— 精确率低于 0.80 或者召回低于 0.55 的直接不要。这两个数是这篇里我最看重的细节:它承认了学出来的验证器会错,然后明确说明能容忍多少错。

对比之下,「让另一个模型来评判」这种做法通常连这个门槛都没有。

验证器决定上限,有两层意思

验证器验不了的东西,自我改进就到不了。

DGM 把 SWE-bench 从 20% 提到 50%,改进的全是 SWE-bench 能量到的东西 —— 代码编辑工具、长上下文管理、同行评审机制。它不会自发变得更好读、更好维护、更省钱,因为基准不量这些。

那个编译器项目也一样。它 99% 通过测试套件、能编 Linux 内核,但生成的代码明显不如 GCC 高效 —— 因为它的 oracle 只验「对不对」,不验「快不快」。

优化会精确地停在验证器的边界上,一步都不多走。

还有更危险的一面:验证器本身会被绕过。

上一个系列里那篇讲自动修复 UI 测试的论文给了最具体的例子:系统为了让报告变绿,会把 expect(value).toBe(5) 改成 expect(value).toBeTruthy(),或者直接删掉修不好的用例。同一批数据,朴素口径的收敛率 70%,剔除这些手段之后 50%

模型没有作弊,它在忠实地优化你给的目标。 而「让验证通过」和「让东西变对」之间,改验证器永远是最短路径。

所以自我改进系统有一条硬性要求:被改的对象和验证的对象必须分开。 DGM 改自己的代码但用外部基准评分;编译器项目改编译器但拿 GCC 对比。一旦 agent 能改动自己的验证器,整个循环就失去意义了。

那没有现成 oracle 的领域怎么办

这是我自己那条路上的实际问题。做「从需求生成测试用例」,产出物的正确性没有 GCC 可以对比 —— 判断一条用例好不好,本身就是个判断题。

从上面这些案例里能抄的有三条:

一、找差分参照,而不是找绝对标准。 GCC 那一手的本质是「和一个已知正确的东西比」。对测试生成来说,可能的参照包括:人工写的用例集、历史上真实发生过的缺陷、同一产品上一个版本的用例。都不完美,但都比「让模型自己判断」强。

二、把验证器拆成一条条约束,并给每条设门槛。 SBCO 那个 0.80 精确率 / 0.55 召回的做法可以直接搬。关键不是门槛定在哪,是必须有一个门槛,并且明确说出学出来的验证器会错多少

三、承认哪些维度验不了,写进文档。 编译器项目的代码效率验不了,DGM 的可维护性验不了。这些不是缺陷,是边界 —— 写出来的边界不会伤人,没写出来的会。

边界与代价

差分测试要求参照系和被测对象解决同一个问题。 GCC 和 Claude 的编译器目标一致,所以能比。而很多场景里没有这样的对手 —— 你在做的可能就是第一个。

学出来的验证器有一个鸡生蛋问题。 SBCO 学验证器用的是模型自己的输出打分。它靠质量门槛和自监督循环缓解,但验证器的天花板终究受限于产生它的那个模型。这一点那篇没有回避,也没有完全解决。

「验证器决定上限」这个说法有例外。 开放式探索类的工作(DGM 的存档机制)就是在试图突破这一点 —— 保留多样性,让暂时得分不高的分支活着。但那也只是延缓收敛,不是取消边界。

这一篇仍然是读来的。 我自己那套东西的验证器还很弱:判定靠正则,执行层刚接通,没有留出集也没有人工标注的基线。所以上面这些是我准备照着做的,不是我做过的。

下一篇

前两篇都在讲这条路怎么走通。下一篇是算账的 —— 有一篇论文专门去量了「让 meta agent 设计 agent」到底划不划算。

结论不太好看:用累积上下文的做法,比完全忽略之前的设计还差;而经济上的回本点在约 15000 个样例,很多数据集上则是无论规模多大都回不了本