本文翻译自 Can LLMs Be Computers?,原载于 Hacker News。
TL;DR
大语言模型(LLM)能够解决高难度的数学研究问题,但在涉及多步推理和长上下文的简单计算任务上却经常翻车。哪怕是两个数相乘或者解决小型数独,如果不借助外部工具,几乎都是不可能完成的任务。
但如果要让 LLM 本身像计算机一样可靠和高效,需要什么条件?
作者的答案是:在 Transformer 内部构建一台计算机。他们将任意 C 代码转换为模型可以直接执行的 token,在几秒钟内可靠执行数百万步。
下面是一个优化问题的演示——使用匈牙利算法(Hungarian algorithm)解决 10×10 最小费用完美匹配问题:
35,365 tok/s
126,716 tokens
7,688 lines/s
模型没有调用外部工具,而是直接通过 Transformer 权重执行程序,逐 token 生成执行轨迹(execution trace),在 CPU 上达到超过 30k tokens/秒的流式输出。
核心技术思想是一种新的执行轨迹解码路径,将模型的注意力查找从线性扫描转变为对数级时间查询,使得单次 Transformer 运行中可以完成数百万次正确的执行步骤。
为什么 LLM 不会计算
最先进的大语言模型能够解决令人印象深刻的高难度数学问题——在 IMO(国际数学奥林匹克)上达到金牌水平,甚至能够攻克开放性的数学和科学问题。
但与此同时,一个顽固的差距始终存在:即使是最先进的模型,在纯计算任务上仍然会犯错。它们在基础加法上会出错,没有外部帮助甚至连简单的数独都解不了。Sudoku-Bench 等基准测试清晰地展示了这种失败模式。
在实践中,我们用两类方法来弥合这个差距:
- 工具调用(Tool use):模型写代码,外部解释器执行,模型报告结果
- 智能体编排(Agentic orchestration):外层循环存储中间状态,分解任务,在短上下文中反复调用模型
这些方法非常有用,但它们揭示了一个重要的局限性:LLM 无法可靠地独立执行长程精确计算,所以我们经常把执行委托给外部工具或编排系统。
一个类比可以说明这种区别:人类不能飞。制造飞机并不能改变这个事实;它只是意味着我们造了一台替我们飞的机器。
今天的大语言模型处于同样的境地。当一个任务需要精确计算时,我们连接外部系统(解释器、代码运行器、智能体循环),让这些系统来做计算。
这意味着能力仍然在模型之外。模型本身存在根本性的缺陷:它无法执行它正在推理的计算,必须反复将任务转交给另一个系统。
因此,模型可以描述算法、推理它们、或编排运行它们的工具,但它自己无法执行这些步骤。一个不能计算的系统,无法真正内化什么是计算。
所以真正的问题不是模型能否谈论计算,甚至不是能否通过工具访问计算。真正的问题是:它能否在内部执行计算——可靠地、高效地、在极长的时间跨度上? 如果能做到,它就不再仅仅是计算的协调者,而是成为了一台计算机本身。
如何把 LLM 变成计算机
表面上看,没有什么问题。驱动 LLM 的 Transformer 架构非常强大。多项研究表明,不同变体的 Transformer 可以执行复杂计算,因为它们可以模拟图灵机(Turing machines),因此可以模拟任何有效计算机。
但理论上的通用性不等于实际执行。一个模型可能足够表达力来模拟计算机,但在实践中仍然是一台糟糕的计算机。经典的通用性结果通常依赖于这样的构造:真实机器中的简单操作对应于模拟模型中的一长串步骤。换句话说,仅凭表示能力并不能保证实际执行效率。
我们通过在 Transformer 内部实现一个现代 RAM 计算机来解决这个问题,而不是纯理论的计算模型。在我们的构造中,每条指令最多只映射到 5 个 token。
但这还不够。更深层的问题是解码过程本身。
Transformer 作为执行器有一个结构性缺陷:标准的自回归解码使每一步都与完整的累积历史交互。真正的计算机每条指令用大致恒定的工作量更新一个紧凑的状态。而 Transformer 一次生成一个 token,同时持续与不断增长的前缀交互。KV 缓存避免了重新计算过去的 key/value 投影,但解码仍然需要对缓存的前缀进行注意力操作,所以每步的工作量仍然与序列长度相关。
我们通过展示相同的 Transformer 架构为执行式轨迹提供高效解码方案来消除这个缺陷。关键的技术解锁是将查找头(lookup heads)限制为头维度 2,这启用了一条解码路径:在这个结构化执行器体制下,主导的检索/更新操作可以用对数时间计算,而不是完整的前缀大小注意力扫描。
这足够强大,让我们可以在 Transformer 内部执行任意程序达数百万步。
什么是「计算」?
如今,当模型需要计算时,它会写代码。比如计算 3 + 5,模型可能会输出:
python -c "print(3+5)"
然后生成暂停。一个外部工具调用机制拦截代码块,发送到沙箱解释器,将结果(8)注入回 token 流。模型恢复,现在知道了答案。
这行得通,但实际执行发生在模型之外。模型指定了计算,然后等待外部系统来执行。
我们的 Transformer 也会发出程序,但它不暂停等待外部工具,而是在同一个 Transformer 内部自己执行程序,一步一步。
为此,我们在 Transformer 权重中实现了一个 WebAssembly 解释器。WebAssembly 是一个低级指令集,专为快速、确定性执行而设计,是 C/C++ 等语言可以编译到的通用目标。要计算 3 + 5,模型会写:
{
i32.const 03 00 00 00
i32.const 05 00 00 00
i32.add 00 00 00 00
output 00 00 00 00
}
然后模型切换到快速解码模式,在同一个 Transformer 内一步步执行程序,生成 token 形式的执行轨迹:
03 00 00 00 commit(+1,sts=1,bt=0)
05 00 00 00 commit(+1,sts=1,bt=0)
08 00 00 00 commit(-1,sts=1,bt=0)
out(08)
halt
每一行都包含模型生成的 token。栈增长,add 触发,结果被输出,机器停止——全部在模型自己的输出流中完成,没有外部往返。
关键区别是:工具调用是不透明的——模型交出控制权,收到黑盒答案。模型内执行是透明的——每个中间步骤都出现在轨迹中,模型从未离开自己的解码循环。
更多演示:数独
数独是精确长程计算的另一个有用的压力测试。学习的神经方法在简单或随机数独上可以取得很强表现,但在难题上完全失败。一个常见的解释是,自回归模型根本不适合约束满足问题,因为它们逐 token 提交答案,无法修正早期错误。
但我们的完全自回归系统在这些基准上达到了 100% 的准确率。这表明真正的瓶颈不是自回归范式本身——而是解决困难数独需要非常长的执行轨迹,而标准注意力使长上下文生成成本过高。这正是我们的快速注意力路径所解决的问题。
我们的系统在 Transformer 内部执行一个完全正确的编译数独求解器。没有学习到的启发式算法替代,也不存在「模型建议了一个解」和「外部系统验证它」之间的差距。Transformer 一步一步执行求解器。
这个保证是通用的而非特定基准的:如果编译的求解器是正确的,Transformer 的执行也是正确的。实际上,这让模型甚至能解决著名的困难实例,比如 Arto Inkala 的数独(被称为世界上最难的数独),在 3 分钟内得到正确解。
33,558 tok/s
327,357 tokens
7,392 lines/s
计算如何被编码?
理解自回归 Transformer 的一个有用方式是把它看作一台活在自身历史中的机器。传统计算机有可编辑的内存,操作后更新。但在 Transformer 中没有这样的东西。
有一个固定的提示(输入或程序),然后有一个只增长的轨迹(模型生成的 token)。在每一步,模型只能通过少量查询(注意力头)「回头看」,然后必须再添加一个 token。通过这个过程,我们需要找到一种方式来编码一个工作的机器。
只追加的轨迹
要理解 Transformer 如何在内部执行程序,用一种稍微不寻常的方式思考计算是有帮助的。
想象一个笔记本,计算的每一步都写在下一行。一旦写好,之前的行就不能改变;笔记本只会增长。
- 前几行包含输入(提示)
- 每一行新记录计算的下一步
- 在每一步,你可以回头看之前的行,但不能编辑它们
这惊人地接近自回归 Transformer 的运作方式:提示是输入,生成的 token 形成不断增长的轨迹,每个新 token 都是在通过注意力查看少量位置后产生的。
一个计算能否被表示为只追加的轨迹,其中每一步只需要回头看少量次数?
答案是肯定的。在我们的系统中,模型显式生成这样的轨迹。它产生的 token 代表虚拟机的演化状态:指令指针、内存和栈操作、算术、控制流和输出。模型只需回头看相关的早期步骤就能重建当前状态。
关键解锁:指数级快速注意力
真正的计算机通过用近乎恒定的工作量更新紧凑状态(寄存器、栈、内存)来运行长程序。
标准 Transformer 通过生成 token 来「推进状态」,每个下一个 token 都通过注意力计算,而前缀永远在增长。KV 缓存重用之前计算的 keys/values,但它不消除基本扩展:在每一步,查询仍然需要与大小随生成 token 数增长的缓存交互。这就是为什么解码研究经常变成一个 IO 问题:瓶颈变成「让 KV 缓存足够快并对其评分」。
但无论 KV 缓存做多快,基本的扩展性仍然存在:第 t 步解码仍然与长度为 t 的前缀交互。这意味着每步工作量随轨迹长度线性增长,生成 t 个 token 的总成本呈二次增长。
我们的方法解决了这个二次爆炸,产生指数级更快的注意力查找。不是每步花费 Θ(t) 时间,我们的方法只需要 O(log t) 时间。下面是实际效果对比:
HullKVCache: 41,709 tok / 31,037 tok/s (6,747 lines/s) - 2.4s
KVCache: 1,714 tok / 612 tok/s (133 lines/s) - 258.9s total
在我们关心的工作负载(长执行轨迹,模型每步重复执行少量结构化查找)中,每步扩展性的差异会累积。
线性扫描解码器支付的成本随着轨迹增长而不断增长。基于凸包的解码器将每步成本保持为对数时间。在长时间跨度上,这改变了什么是可行的:在标准解码下不切实际地慢的计算变得可以运行数百万步。
快速路径:2D 注意力
我们通过重新构建问题来获得这个加速。我们不是试图在完全一般性上加速 Transformer,也不是想引入另一种架构。
相反,我们专注于普通 Transformer,但处于一个可处理的参数化下。我们专门针对头部维度较小的情况:2D。
这不意味着整个 Transformer 很小。你仍然可以有任意多的层数、任意多的头和任意大的嵌入。它只是意味着 Transformer 各层使用的嵌入被分解成更小的块,导致更多的头:n_heads = d_model / 2。
当然,这个限制对某些任务可能有代价,不是一切的魔法答案。虽然我们仍在探索这在实践中对训练大型模型有多大限制,但我们发现它仍然足够灵活,可以高效训练并捕获非常复杂的逻辑。
事实上,对于图灵完备性,2D 注意力就是你所需要的!它仍然足够灵活来表示整个 RAM 计算机,正如这篇博客所展示的。它是构建 Transformer 快速路径的关键使能器。虽然抽象推理和规划仍然是原始路径的一部分,但繁重的计算任务可以通过快速路径进行。
模型本身是一个完全标准的 PyTorch Transformer,没有什么异乎寻常的:
class VanillaTransformer(nn.Module):
def __init__(self, vocab, d_model=36, n_heads=18, n_layers=7, d_ffn=36):
super().__init__()
self.tok = nn.Embedding(vocab, d_model)
self.attn = nn.ModuleList([
nn.MultiheadAttention(d_model, n_heads, batch_first=True, bias=False)
for _ in range(n_layers)
])
self.ff_in = nn.ModuleList([nn.Linear(d_model, 2*d_ffn, bias=False) for _ in range(n_layers)])
self.ff_out = nn.ModuleList([nn.Linear(d_ffn, d_model, bias=False) for _ in range(n_layers)])
self.head = nn.Linear(d_model, vocab, bias=False)
def forward(self, idx):
T = idx.shape[1]
x = self.tok(idx) + pos_emb(T)
causal = torch.triu(torch.ones(T, T, device=idx.device, dtype=torch.bool), diagonal=1)
for attn, ff_in, ff_out in zip(self.attn, self.ff_in, self.ff_out):
y, _ = attn(x, x, x, attn_mask=causal, need_weights=False)
x = x + y
gate, val = ff_in(x).chunk(2, dim=-1)
x = x + ff_out(F.relu(gate) * val)
return self.head(x)
注意:d_model = 36 且 n_heads = 18,精确给出每个头 2D。架构使用 7 层、标准 nn.MultiheadAttention 和门控前馈网络。没有自定义注意力内核,没有稀疏掩码;只是普通的 PyTorch。唯一特别的是权重。
2D 注意力的几何视角
注意力机制的工作方式如下:
- 每个过去的 token 贡献一个 key 向量 k_j 和一个 value v_j
- 当前步形成一个 query 向量 q
- 为每个 key 计算相关性分数 q·k_j
- 头返回所有 key 的 value 之和,按其分数的 softmax 重新加权
在对我们的快速路径重要的特殊情况下:
- 每个 key 是 2D 的,k_j ∈ R²
- query q ∈ R² 可以被认为是平面上的一个方向
- 我们想要在该方向上最大化点积的点的值(hard-max attention)。如果有平局,计算平均值
尽管最大化查询是全局性的(对整个历史),存在高效的数据结构可以在点数的对数时间内回答这样的 2D 注意力查询。这正是计算几何中的经典「支撑点」查询:给定一个方向 q,找到凸包上在该方向最远的点。我们通过在 token 生成时维护这样的数据结构来加速解码,让我们高效搜索整个过去点集。
将头维度限制为 2 正是启用快速路径的原因:在推理期间,我们可以用一种结构替代暴力扫描(「对每个 key 评分」),在这种结构中,最大值可以只通过查看凸包中极小的一部分点来找到。
接下来是什么?
我们展示了 Transformer 可以成为一台计算机,这打开了软件和神经网络之间的新接口。一旦程序可以在模型自己的推理循环内高效运行,一个更大的设计空间就打开了。
更丰富的注意力机制
为简单起见,我们的构造使用 hard-max 注意力。但这不是根本性限制。虽然我们还不知道精确的 softmax 注意力是否能保持相同的效率,但用 k-sparse softmax 注意力来近似它很容易:检索 top-k keys 并只对它们执行 softmax。通过在嵌套凸包中存储点,这产生 O(k + log n) 的解码成本。
这意味着几何快速路径不限于我们的执行器构造。原则上,它可以加速任何具有 2D 头的 Transformer,用高效的几何检索替代完整线性扫描。同样的机制也通过 3D 凸包自然扩展到 3D 头,尽管更高维度很快变得不那么高效。关键问题是 2D 是否已经捕获了大部分加速,或者稍大的头是否解锁实质上更多的能力。
用 2D 头训练大型模型
这里使用的 2D 头参数化本质上并不小。总参数量可以保持与标准 Transformer 相当,因为模型可以简单地使用更多的头和层。真正的问题是这种模型在规模训练时会变得多么有能力的。
这个问题甚至超出了纯能力。这些模型可以在多种模式下发挥作用:作为与较慢、更通用模型配对的专用快速路径;作为单个系统内快速/慢速混合架构的一部分;或者作为推测执行模型快速提出 token,而常规注意力模型验证和接受它们。无论它们最终的能力上限如何,它们已经暗示了一个强大的系统原语,用于加速更大的模型。
程序到权重 & 超越梯度下降的训练
在我们当前的原型中,模型学习一个解释器,其行为编码在权重中。但我们为生成这些权重构建的编译机制可以走得更远。原则上,任意程序可以直接编译到 Transformer 权重中,完全绕过将它们表示为 token 序列的需要。
这将使权重本身成为软件的部署目标。模型不再仅仅学习类似软件的行为,而是可以字面上包含编译的程序逻辑作为其内部电路的一部分。
总结
这篇文章提出了一个令人兴奋的视角:让 LLM 不再只是「调用工具」,而是真正成为一台计算机。核心创新点:
- 在 Transformer 内部实现 WebAssembly 解释器,模型可以自己执行程序
- 2D 注意力头 + 凸包查询,将对数级查找变成现实,解决了长轨迹解码的效率问题
- 通用性保证:只要编译的求解器正确,Transformer 执行就正确
这项工作打开了「程序到权重」的全新方向——未来的模型可能直接在权重中包含编译好的程序逻辑,而不仅仅是学习到的行为。
对于中国开发者来说,这项研究特别值得关注,因为它展示了一条让 AI 模型获得可靠计算能力的新路径,而不是仅仅依赖外部工具调用。这对于需要精确计算的应用场景(如科学计算、金融建模、约束满足问题等)有重要意义。