57B tokens 与 jq 重写:作者用 LLVM IR 让智能体重写 jq
57B tokens and nothin' on – a jq rewrite
作者 nialse 尝试让 AI 智能体直接在 LLVM IR 上重写 jq,为此先写了 POSIX 风格运行时 libmuffintop,产物名为 jeqy.ll。
Arthur C. Clarke 写道:“任何足够先进的技术都与魔法无异。”如果软件开发是魔法,那么它是在一个非常长的时间尺度上运作的,而诀窍就是永远耗时。1 一段时间以来,我一直在追逐前沿,尽管资源有限,只是个人或较小组织可用的资源。我的想法是,也许,只是也许,软件可以在你需要时立即出现。由机器自主实现,只需提供你需要什么或需要做什么的信息。从零开始创建软件。如果计算机能够根据提示编写真正的机器级代码呢?那么,这又将如何运作?抽象会是什么?我试图找出答案。第一个问题是让智能体持续工作。下一个问题是当它说完成时,弄清楚它实际构建了什么。而最终:魔法。
过去需要永远才能完成的诀窍
计算机曾经是一个进行计算的人。通常是一个团队,应用数学科学的进步,使其在某种程度上可用且有价值。那里有魔法可寻。当然,这需要非常长的时间。数天、数周、数年。2 而且成本非常高,因此这种创造必须带来价值,或者来自一位对科学家或研究有特殊兴趣、资源异常丰富的利益相关者的资助。纳皮尔于1614年发表了他的对数表,而他早在1594年就开始了这一旅程。多年的准备让任何人都能通过查表和加法快速完成乘法,至少是任何能买得起这本书的实体。3
当机械计算器,然后是电子计算器出现时,部分工作可以极大地加速。不是全部,但至少是基本操作,将心算的负担从人类肩上卸下。因此,计算机使工程师和其他人能够进行他们以前做梦也想不到的计算。它促成了众多领域的发现和研究。在宾夕法尼亚大学,用台式计算器计算弹道轨迹可能需要长达40小时。这使公司和经济体得以扩大规模,提高计算和核对账目的速度。机械出纳员带来的更快处理使单个员工能够服务更多客户,许多员工能够服务更多客户。它减轻了员工计算交易的大部分负担,拓宽了可被雇为出纳员的人群范围以及他们可能需要的教育背景。
在计算器之后,计算机以继电器、真空管以及后来的固态晶体管的形式重返舞台。它们使复杂的、协调的计算以及近乎即时地处理大量简单算术成为可能。微分分析仪将计算弹道轨迹的时间缩短到约半小时。ENIAC 可以在30秒内计算出一条轨迹。诀窍是一样的,但突然变得即时,计算也变得无形。4 大约七十年后,规模化已使从分钟或小时缩短到秒、毫秒和微秒。现在,每时每刻都有如此多的计算魔法在发生,我们几乎看不见它。它已经消失,这很重要,因为这就是整个软件开发领域接下来会发生的事情。
半生不熟的松饼
已经有一些用 AI 大规模重写源代码的案例。Jarred Sumner 描述了在 11 天内将 Bun 从 Zig 重写为 Rust,使用智能体并不断调整工作流程。PhotoCraft 自称是 Adobe Suite 的 Rust 重新实现,尽管其仓库仍将其标记为早期 alpha 版本。Anthropic 也报告称,用一个智能体团队在 Rust 中构建了一个 C 编译器。5 然而,运行软件的机器最流行的抽象似乎仍然是 Python、TypeScript/JavaScript 或 Rust 等高级语言,而不是 CPU 用于计算的实际指令,也不是操作系统看到的实际硬件。源代码描述了一种计算,编译器或解释器将其转换为机器可以执行的东西。如果这一步可以跳过呢?
即使没有高级抽象,仍然需要某种低级抽象。有些东西软件并不知道,而操作系统将其抽象掉了,比如硬件驱动程序、文件系统、网络协议等的实际实现。有时会有指令集的低级抽象,例如 Java JVM、Erlang BEAM 或 Python 虚拟机的字节码,它们抽象掉了 CPU 和操作系统。对于编译型语言,可以说最常用的低级抽象是 LLVM 中间表示(IR)结合某种无处不在的 libc 化身。
一个春天的早晨,或者甚至是一个冬天的早晨,一个奇妙的想法突然出现:也许,只是也许,未来计算的抽象就是 LLVM IR 本身。问题很快出现:我们能否实现,或者更确切地说,智能体能否直接以 LLVM IR 代码编写软件,而不使用来自高级语言的编译器?将问题视为一个从提示直接翻译到机器代码空间的函数。或者至少以某种或多或少跨平台的方式尽可能接近机器级代码。
只是有一点点小问题:高级语言共有的一个特点是,它们拥有庞大的代码库,将其与各种工具、服务和操作系统连接起来。基本上所有编译型语言都使用 libc 来访问它们所运行系统的抽象,但它们也提供对大量库或包的访问,无论是内置的还是第三方的。在这个实验中,软件所依赖的库也被替换了。是的,包括 libc 部分,以及连接到内核以执行最基本操作的低级接口。最简单,我不会称之为简单的抽象,可能是 POSIX 接口。然而,它在 ABI 意义上不是跨平台的。它标准化的是源代码级接口,而不是跨操作系统和指令集架构的单一二进制接口。6
仍在烤箱中烘焙,我在此呈上 libmuffintop7,一个用 LLVM IR 编写的类 POSIX 运行时环境。它有自己的固定宽度接口,并为 Linux x86-64、Linux AArch64 和 macOS ARM64 提供了宿主层。它不是完整的 libc,也不是标准的 C POSIX ABI。它不是最终产品。它的构建是为了服务于一个目的,基本上只服务于那个目的。它做了很多假设。最明显的假设是只有进程而没有线程,并且它只针对支持 64 位的系统。它也没有高层 libc 原语,凡是能避免的都避免了,而且没有 malloc 或 calloc,或者任何堆分配器。听起来很有趣,对吧?
有了这个抽象层,旅程开始了,看看软件是否真的能在这个架构上实现。于是进行了一系列测试,比如用 shim 从 C 编译 SQLite 以使用 libmuffin。令人惊讶的是,它居然成功了,并通过了测试。它的运行速度是原生速度的一半,但作为第一次尝试,这是个极好的消息。
通过测试
于是,带着一个刚出炉、或者至少是半熟的松饼,需要找到一个合适的开源项目,可以在 libmuffintop 之上用 LLVM IR 重新实现。前提是一个拥有大量测试的热门开源软件。从 LLM 那里征求了建议,最终选定了 jq 而不是 git,因为如果世界上有一个人你不想惹恼,那就是 Linus Torvalds。对于不熟悉 jq 的人来说,它是一个 JSON 查询命令行工具,基本上接受 JSON 输入并产生某种处理后的输出。它还包含自己的编程语言和对正则表达式的支持。这个重新实现被恰当地称为 jeqy,文件扩展名为 LLVM IR,即 jeqy.ll。是的,就像《化身博士》那样。8
将 jq 翻译成 LLVM IR 最明显的方法当然就是直接编译它,在编译器生成二进制代码之前捕获临时生成的 .ll 文件。所以这不是我们正在做的。我们不是在这里构建编译器。我们想要构建的是接受规范并生成机器代码的东西。至少是非常接近机器代码的东西,而且在这种情况下还是跨平台的。此外,或者说作为规范的一部分,我们提供了一个 oracle,即软件的实际实现,它总能针对任何输入产生正确的预期输出。说实话,当然仍然有编译在进行。.ll 文件不会神奇地变成可运行的二进制文件,而是经过编译和优化,然后才生成所选平台的实际代码。
最初的想法并不包括访问 jq 源代码。然而,懒惰的智能体偷看了它,并在其实现和架构完全不同时将其用作参考。9 这与 Bun 的 Zig 到 Rust 转换方式形成鲜明对比,后者专注于逐行、或者至少逐文件的翻译,包括 576 行的基本规则。10 对于 jeqy.ll,智能体可以自由选择自己的冒险和架构。这是一个更高的门槛,因为指导根本不存在,需要更强大的模型,能够同时规划要使用的抽象并编写代码。
在第一次尝试时,GPT-5 系列正流行。它在长时间坚持单一问题上存在巨大问题,而且在我看来,它对待工作的方式有点太像人类了。当面对如此复杂的任务时,它根本不知道如何推进。它常常直接停下,编造理由不再继续,不会在明显是长期任务的事情上投入持续的努力。所以在这个黑暗时代,大约半年前,技能和子代理被推出,使得编写监督者技能成为可能,而此前这需要编写自定义的测试框架。该技能协调流程的各个部分:分解问题,将其划分为可以交给子代理的任务,最重要的是,测试、结果验证以及重启失败的尝试。仅仅给出任务,由监督者驱动的代理就能突然运行数小时甚至数天,并且看似在取得进展。11
最终,这个过程完成了一个输出,可能不是 570 亿,但仍然是大约 250 亿个 token 和 673 小时的计算时间之后。但有一个问题。虽然结果通过了所有 jq 测试,但它只是通过了测试。实现过拟合,缺乏通用性,并且是针对特定测试用例量身定制的。答案被硬编码,代码中只实现了顺利路径。此时还没有留出测试。新手错误。把通过测试作为成功的定义是个坏主意。作为实验,代理被要求泛化并重写软件,但那是一次彻底的失败,从未实现。它陷得太深了。此时,代码基本上没有注释,也没有真正的结构。一次彻头彻尾的失败。
一轮,然后是其后的工作
GPT-5.6-Sol 在代理能力上真正上了一个台阶,监督者技能不再需要,但真正的震撼是用 GPT-6-Astra 重新运行该过程。它独自管理了长期任务和子代理。被指示在一轮内完成,并专注于通用性而非与预言机完美保真,它在 76 分钟内完成了任务。它不仅通过了所有测试,还通过了所有留出测试。全部只用了 6200 万个 token。太惊人了!
| 工作覆盖 | 总 token 数 | 记录的成本估算 | 记录的时间 | 状态 |
|---|---|---|---|---|
| GPT-5 | 25.30B | $5,104.02 | 672h 50m | 失败 |
| Astra | 194.44M | $16.94 | 8h 36m | 通过 |
| 注意:成本估算应持保留态度,因为结果差异巨大。 |
| 时间,UTC | 里程碑 | 固定用例通过 |
|---|---|---|
| 10:28 | 任务开始 | |
| 11:05 | 首次完整候选运行 | 726/881 |
| 11:14 | 第二次完整运行 | 830/881 |
| 11:26 | 诊断变更与一次回归 | 824/881 |
| 11:31 | 剩余两个用例 | 879/881 |
| 11:39 | 所有固定用例通过 | 881/881 |
| 11:44 | 全新构建检查并完成一轮 | 881/881 |
为了更深入地探究实现质量,构建了一个模糊测试器。不幸的是,OpenAI 不允许其代理这样做,这是本地开源模型的又一次胜利,这次是 Qwen-3.8。模糊测试揭示了不少边缘情况和限制,Astra 会根据反馈解决这些问题,并花费与它已经消耗的大致相同数量的 token。
仍然存在一些局限。已发布的代码现在具有 Unicode 感知的正则表达式,但其行为并不总是与 jq 一致。12 但这并非旨在成为完美的重新实现,我想如果有更多时间和 token,Astra 也能弄明白更多。检查代码时,LLVM IR 带有注释。它有结构。它有抽象。它与之前完全不同。而且看起来 Astra 可以在一个回合内从问题经由架构到代码。技术 indistinguishable from magic。
Andreas Påhlsson-Notini a@nial.se
附言 标题是对老板的致敬。13
- NASA、Arthur C. Clarke 及其关于足够先进技术的观察
- NASA/JPL,当计算机还是人类时
- John Napier 与对数的发明,1614 年
- 宾夕法尼亚大学,ENIAC 50 周年 ; 宾大周年纪念情况说明书
- Jarred Sumner,用 Rust 重写 Bun; PhotoCraft 仓库; Nicholas Carlini,用一队并行 Claude 构建 C 编译器
- POSIX.1-2024 简介 ; 可移植性理由
- libmuffintop
- jeqyll
- jeqyll/SOURCES.md
- Bun,阶段 A 移植指南
- Supervisor 技能
- jeqyll KNOWN_DEFICIENCIES.md
- Bruce Springsteen,57 Channels (And Nothin' On)
来源:Hacker News · nial.se