焊死的导线和现算的导线 左边写着一个查找表的配置 0x96 和 GPT-3 的 175B × 16 bit。右边是一片方格状的 FPGA 查找表阵列,导线横平竖直;阵列向右渐渐变成一排词元圆点,圆点之间用粗细不一的弧线相连,表示注意力每一步现算出来的连线。 一个查找表的配置 0x96 一个大模型的配置 175B × 16 bit

一位全加器是数字电路的第一课:输入两个比特 a、b 和低位进来的进位 c,输出这一位的和 s 和往高位送的进位。把它烧进 FPGA,最后落到芯片上的是两张小小的真值表,写成十六进制只有两个数:

s = 0x96 # 1001 0110:a、b、c 里有奇数个 1 时为 1
cout = 0xE8 # 1110 1000:a、b、c 里至少两个 1 时为 1

同一个全加器也可以用两个“神经元”来做:每个神经元把输入加权求和,超过阈值就输出 1。这时候写下的不是比特,而是 9 个实数,比如 [1, 1, 1], −1.5 和 [1, 1, 1, −2], −0.5。Transformer 说到底也是这样一个东西:一张固定的计算图,加上几十亿、几千亿个这样的实数。

这样一比,问题就来了。FPGA 是一片可以随意配置的电路,Transformer 跑一步也是一个电路,两者看起来是一回事,那它们到底哪里相同、哪里不同? FPGA 从 1985 年就有了,比 Transformer 早了三十多年,单片上的逻辑单元也从几十个涨到了上千万个,为什么智能没有在 FPGA 上“涌现”出来? 我们先把两种电路各自怎么配置讲清楚,再一层层往下找它们真正的差别。

一、两种电路各自怎么配置

FPGA:查找表和导线

FPGA(现场可编程门阵列)里没有固定的与门、或门,只有成片的查找表(LUT,look-up table)和它们之间的可编程导线。一个 k 输入的查找表就是一块 2k2^k 位的小存储器:k 个输入拼成一个地址,读出那一位就是输出。所以 4 输入 LUT 有 16 个配置位,可以实现任意一个 4 输入的布尔函数;现在的主流芯片用 6 输入 LUT,64 个配置位。导线也一样,每个交叉点上的开关接通还是断开,都由一个配置位决定。

把一个设计放进 FPGA 的过程是:工程师用 Verilog 之类的硬件描述语言写出电路,综合工具把它翻译成查找表,布局布线工具决定每张表放在芯片哪个位置、走哪几根线,最后生成比特流(bitstream),也就是所有配置位的总和。上电时比特流写进芯片,电路就成形了。

第一块 FPGA 是 1985 年 Xilinx 的 XC2064,只有 64 个可配置逻辑块,每块是一个 4 输入的逻辑函数1。2023 年 AMD 发布的 VP1902 有 846 万个 LUT2。

Transformer:固定的计算图和权重

Transformer 每预测一个词元(token),都要把同一张计算图走一遍:词嵌入把符号变成向量,注意力从前面的位置取信息,MLP 在每个位置上做非线性变换,输出头给每个候选打分3。这张图里有哪些乘法、加法、softmax,连接方式是什么,在训练之前就定死了;会变的只有图里的权重,也就是那几千亿个实数。

从这个角度看,Transformer 的一次前向计算就是一个电路,权重就是它的配置。跑在 GPU 上时,这个电路是用矩阵乘法“模拟”出来的;也可以真把它做进硬件,后面会看到,确实有人把它放进了 FPGA。

同一个全加器,两种配置

把开头那个全加器画出来,两种配置的样子就很清楚了。

同一个一位全加器的两种配置 左边是 FPGA 的两张查找表:输入 c、b、a 的 8 种组合各对应一个和 s 与进位 cout,合起来是 0x96 和 0xE8 两个配置字节,共 16 个比特。右边是两个阈值神经元:进位神经元权重 1、1、1,偏置负 1.5;和神经元权重 1、1、1、负 2,偏置负 0.5,共 9 个实数。以 a=1、b=1、c=0 为例,两边都得到 s=0、进位=1。 FPGA · 两张查找表 神经网络 · 两个阈值神经元 地址 c b a s cout 0 0 0 000 1 0 0 110 2 0 1 010 3 0 1 101 4 1 0 010 5 1 0 101 6 1 1 001 7 1 1 111 从地址 7 读到 0:s = 0x96,cout = 0xE8,共 16 bit 进位神经元 cout 权重[1, 1, 1]接 a、b、c 偏置−1.5 输出[a+b+c ≥ 1.5] 和神经元 s 权重[1, 1, 1, −2]最后一个接 cout 偏置−0.5 输出[a+b+c−2·cout ≥ 0.5] 例:a=1, b=1, c=0,和为 2,cout=1,s=0;共 9 个实数 两边的骨架(按地址读表、加权求和再比阈值)都是固定的,换一个功能只需换这串数。 不同的是:左边的数只能是 0 或 1,右边的数可以连续地调。
图 1 · 同一个一位全加器:FPGA 记住 16 个比特,阈值神经元记住 9 个实数

左边的查找表就是把真值表原样存下来:地址 3(c=0、b=1、a=1)那一行,s 是 0,进位是 1。右边的阈值神经元是另一种存法:进位神经元把三个输入加起来,超过 1.5 就输出 1;和神经元再减去两倍进位,剩下的超过 0.5 就输出 1。

两种写法得到的是同一个函数,所以电路本身只是骨架,功能全在那串配置里。这是两者最根本的共同点。那么,除了这一点,它们在“能表示什么”上还有多像?

二、相同之处:骨架固定,功能在配置里

都能表示任意函数

查找表网络能表示任意布尔函数,这几乎是定义:最笨的办法是把整张真值表存进一大片 LUT 里。阈值神经元也一样,图 1 用两个神经元做出了全加器,而全加器串起来就是任意位数的加法器;与、或、非都能用一个阈值神经元实现,所以任意布尔电路都能一个门一个门地换成神经元。

反过来,神经网络里的乘加和比较,本身也是数字电路,FPGA 当然能实现。两边谁也不比谁“多”一种能力,差别只在需要多大的电路。

一步之内能算多深

再细一点,可以问“一步”能算多少。FPGA 的一次计算是一片组合逻辑,深度由设计者决定,只受时钟频率约束;如果加上寄存器和时钟,让电路每一拍更新一次状态,它就是一台完整的有限状态机,CPU 本身就能用 FPGA 实现。

Transformer 的一次前向只有固定的几十层。理论计算机科学里有一个干净的结论:如果数值精度是输入长度的对数级,一次前向可以用常数深度的阈值电路来模拟,属于复杂度类 TC⁰4。也就是说,单看一步,Transformer 是一种层数固定、但每层可以很宽的阈值电路,和图 1 右边那种神经元电路是同一类东西。

Transformer 的“循环”来自自回归:每一步写出一个词元,接到输入末尾,再算下一步。允许它先写一串中间步骤(也就是思维链)以后,表示能力会随步数增长:线性步数可以识别所有正则语言,多项式步数恰好对应 P5。

两种电路能表示什么
FPGATransformer
基本单元4–6 输入查找表,任意布尔函数加权求和 + 非线性(注意力、MLP)
一步之内任意深度的组合逻辑,受时钟频率约束固定层数;对数精度下属于常数深度阈值电路 TC⁰
加上循环寄存器 + 时钟:任意有限状态机,可以做成 CPU自回归把输出写回输入:线性步数覆盖正则语言,多项式步数对应 P
实现对方可以跑神经网络推理:Brainwave 在 Stratix 10 上批大小 1 时 39.5 TFLOPS训练好的网络可以整个编译成查找表(LogicNets、LUTNet)

只要电路够大,谁都能模拟谁;单看“能表示什么”,FPGA 不比 Transformer 弱,一步之内甚至更强。

表 1 · 表示能力不是两者的分水岭

它们可以互相实现

表 1 最后一行不是理论上的可能,而是已经做成的工程。微软的 Project Brainwave 把神经网络推理放在数据中心的 FPGA 上,权重全部放进片上存储,在 Stratix 10 上批大小为 1 时达到 39.5 TFLOPS 的有效算力,用在必应搜索里6。反方向,LUTNet 直接训练 FPGA 查找表里的内容7,LogicNets 先训练一个每个神经元只有很少输入的网络,再把每个神经元整个换成一张真值表,编译成 FPGA 电路8。

到这里,一个直觉上的答案就被排除了:FPGA 时代没有出现智能,不是因为 FPGA“表示不了”智能需要的计算。既然能表示,那差别在哪里? 回到图 1 最后那句话:两边的数,一边只能是 0 和 1,一边可以连续地调。这串数是谁写的、怎么写出来的,才是下一节的问题。

三、配置由谁来写

现象:FPGA 上的每一位都是人决定的

一个 FPGA 设计里的每一个配置位,归根到底都来自工程师写下的 Verilog。综合和布局布线工具会替人做大量优化,但它们只是把人已经想清楚的功能翻译成比特,不会自己发明功能。工程师得先知道全加器是什么,才写得出 assign s = a ^ b ^ c;。

而且这条路很慢。一个大设计跑一遍布局布线要几个小时:在 Virtex-7 2000T 上,一个大设计用 Vivado 跑了 5 小时、占了 9 GB 内存,旧工具 ISE 要 13 小时9。改一行代码,等半天才能在板子上看到结果。

所以 FPGA 能做的事,被设计者的理解力框住了:电路不会比写它的人更聪明。要出现“没人设计过、自己长出来”的能力,配置就不能由人来写。

第一次尝试:让进化来写

上世纪九十年代,真有人这样试过。1996 年,萨塞克斯大学的 Adrian Thompson 用遗传算法直接“进化”FPGA 的配置10。他用的是 Xilinx XC6216,只取芯片一角 10×10 共 100 个格子,每格 18 个配置位,整个“基因”是 1800 个比特。任务是分辨两种方波:输入 1 kHz 时输出一个电平,输入 10 kHz 时输出另一个电平,而且不给时钟、不给任何外部元件。

做法很朴素:随机生成 50 个配置,逐个写进真实的芯片,用示波器和积分器给它们的表现打分;分高的留下来交叉、变异,生成下一代。

Thompson 1996 年的进化实验 上方是一条 0 到 5000 代的时间轴,标出最好个体的表现:第 0 代输出恒定 5 伏;第 650 代对 1 千赫基本稳住,对 10 千赫仍在照抄输入;第 1400 代两种频率开始分开;第 2800 代只剩 10 千赫时的快速毛刺;第 3500 代示波器上看着完美;第 4100 代偶发尖峰消除;取第 5000 代为最终电路。下方四张卡片:基因 1800 比特;种群 50,约 25 万次评估;每次评估 5 秒,全程两到三周;有用的只有 32 格,有的格子不连到输出却不能去掉。 最好个体在示波器上的表现(代数) 这次实验的账 0恒定 +5V,毫无区分 14001k 多为高、10k 多为低 3500示波器上看着完美 5000取为最终电路 6501k 基本稳住,10k 仍照抄 2800只剩 10k 时的毛刺 4100偶发尖峰消除 基因 1800 bit10×10 格 × 每格 18 bit 种群 50 · 约 5000 代约 25 万次评估 每次评估约 5 秒全程 2–3 周 有用的只有 32 格有的不连到输出却不能去掉
图 2 · 进化确实“长”出了没人设计过的电路,但 1800 个比特就用了 25 万次评估、两三周

结果是成功的,而且很奇特。最后的电路只用了 32 个格子,没有时钟,却能分辨周期差了五个数量级的信号(每个格子的延迟只有几纳秒,信号周期是 0.1 和 1 毫秒)。其中几个格子跟输出之间没有任何导线相连,可一旦把它们固定住,电路就失灵;Thompson 推测它们是通过电源线或电磁耦合在起作用。这个电路还很怕温度:用一盏 60 瓦的灯泡烤芯片,或者在凉风习习的晚上打开实验室所有窗户,频率响应就会平移,分辨就不再完美。

这次实验正好说明了让机器写配置的两面。好的一面是,它真的写出了人写不出来的东西,这正是“涌现”的味道。坏的一面是代价:1800 个比特的配置,就要 25 万次评估、两三周时间;而且进化利用的是这一块芯片的物理细节,换一块芯片、换一个温度就不一定成立。

为什么这么慢? 照这个速度,GPT-3 那样几万亿比特的配置是不可能靠进化写出来的。

梯度下降:参数怎样一步步收敛

要说清楚这一点,得先说清楚“训练”和“收敛”是怎么回事。

训练一个网络,先要把“它现在错得有多厉害”变成一个数,叫损失(loss)。做加法时,可以数一数输出的比特错了几个;实际训练更常用交叉熵,它不只看对错,还看模型对正确答案有几分把握,所以“差一点就对了”和“错得离谱”会得到不同的分数。

接下来可以打一个比方:把所有参数当成坐标,损失当成高度,整张网络的所有可能配置就是一片起伏的地形,训练就是在这片地形上往低处走。梯度是脚下这一点在每个方向上的坡度,它告诉你:这个参数调大一点,损失是升还是降、变化多快。梯度下降每一步做两件事:先算出坡度,再朝下坡的方向挪一小步,步子的大小叫学习率。一步一步走下去,损失降到一个很低的位置,参数也不再怎么变,这就叫收敛。

坡度怎么算?网络是一层套一层的函数,按微积分里的链式法则,最后的损失对某个参数的导数,等于从输出往回、沿途每一层“输入变一点、输出变多少”的连乘。反向传播就是把这件事从输出层往回一次做完,代价大约只是一次前向计算的两倍,就能得到所有参数各自的导数11。参数从 1800 个变成 1800 亿个,每一步的代价跟着线性增长,但每一步仍然同时告诉每个参数该往哪边走。

Transformer 能收敛,靠的是三样东西凑在一起。

每个零件都可导。矩阵乘法、softmax、归一化、GELU 之类的激活函数,都是光滑的函数,输入变一点,输出就跟着变一点;ReLU 只在 0 这一个点上有折角,不影响大局。注意力也不例外:softmax 给出的权重是 0.96、0.01 这样的连续值,而不是“选中 / 不选中”,所以“该看哪里”本身也能被梯度慢慢调准。

坡度能传到底。几十层的网络,连乘一长串导数,很容易乘到接近 0(梯度消失)或者大得离谱(梯度爆炸)。Transformer 每个部件外面都有一条残差连接,输出等于输入加上部件算出来的增量,梯度可以沿着这条直通的路一路传回最前面;归一化把每层的数值拉回固定范围,避免越乘越大。

参数多,路就多。直觉上,几千亿维的地形应该坑坑洼洼,走到哪个小坑里就出不来;但实践中,参数远多于“恰好够用”时,能把训练数据拟合好的配置非常多,彼此连成大片的低地,从随机起点出发大多都能走到其中一处。这是大量训练经验的观察,理论上还没有完全说清楚。还要注意,“收敛”指的是走到一处足够低的地方,而不是找到唯一的最优解:同一个模型换个随机种子训练两次,参数完全不同,表现却差不多。

原因:比特上没有坡度

FPGA 的配置位只能是 0 或 1。把上面的地形换到比特上,就不再是连绵的山坡,而是一个个孤立的点:每个比特只有两个位置,中间没有“往右挪一点”可言。

这件事可以直接量出来。我们搭了一张由 4 输入查找表组成的小网络学 3 位加法(下一小节的实验用的也是它),再给它做一个“软”版本:每张查找表的 16 个比特各换成一个实数 θ,经过 sigmoid 变成 0 到 1 之间的“软比特”,θ 大于 0 就对应比特 1,小于 0 对应比特 0。软版本用梯度训练到第 20 步、还没收敛的时候,挑出其中一个配置项,把它从 −6 扫到 6,分别看软网络的损失和取回比特后的硬电路错了多少(图 3)。软查找表上,损失随它平滑变化,当前位置的坡度清楚地指向右边;硬查找表上,这个配置项小于 0 时电路错 86 个输出位,大于 0 时错 70 个,两边都是平的。

同一个配置项:软查找表有坡度,硬查找表没有 3 位加法网络训练到第 20 步时,把其中一个配置项从负 6 扫到 6。左边是软查找表的损失,从 0.562 平滑降到 0.535,当前位置 1.59 处斜率为负,往右调损失下降。右边是硬查找表错了多少个输出位:小于 0 时 86 个,大于 0 时 70 个,两段都是平的,只在 0 处跳一下。 软查找表 · 损失(交叉熵) 硬查找表 · 错了几个输出位(共 256) 0.53 0.54 0.55 0.56 -6 -3 0 3 6 当前 θ = 1.59 坡往右下:θ 调大一点,损失就小一点 60 70 80 90 -6 -3 0 3 6 当前:错 70 个 错 86 个 两段都是平的:往哪边挪一点,结果都不变 左右是同一张网络里的同一个配置项。软的那张告诉你往哪边调;硬的那张只能整个翻过去试试看(虚线)。
图 3 · 把一个配置位放松成实数,损失就有了坡度;留在 0 和 1 上,坡度处处是 0

硬的那张图上,坡度在每一处都是 0,只在 0 处有一个跳变,那里导数根本不存在。梯度下降站在任何一点上,看到的都是“往哪边走都一样”,一步也迈不出去。剩下的办法只有试:把这一位整个翻过去,重新评估一次,看看变好没有。

而试的代价随规模增长得极快。变异搜索每做一次评估,得到的只是一个分数:这一组随机翻转合起来让整体变好了还是变差了,至于是哪一位起了作用、哪一位帮了倒忙,分数里看不出来。1800 个比特有 218002^{1800} 种组合;配置越长,随机翻转碰巧改对、又没碰坏别处的机会越小,越到后期越难。

这里要分清两件事。FPGA 芯片本身完全可以执行梯度下降,Brainwave 就是在 FPGA 上跑神经网络;收敛不了的,是 FPGA 自己的比特流。而且普通 FPGA 的比特流还不能随便乱改,有的配置会让两个驱动源接到同一根线上;Thompson 特意选了 XC6216,正是因为它的任何配置都不会损坏芯片10。这不是 FPGA 的硬件缺陷,而是“配置是比特”这件事本身决定的。

实验:同一张查找表网络,两种写法

为了看看这个差别放大以后有多大,我们用同一类“FPGA 式”网络做了个小实验12。网络由 4 输入查找表组成,每张表 16 个配置位;导线随机连好以后就不再改,每张表从原始输入和上一层的输出里随机挑 4 根线,就像布局布线已经做完。任务是 n 位无符号加法:输入 2n 个比特,输出 n+1 个比特,把所有 22n2^{2n} 种输入都算对才算成功。网络宽 16n 张表、深 n+2 层,2 位加法有 1584 个配置位,4 位加法有 5200 个。

同一张网络,我们用两种办法去写它的配置。

  • 变异搜索,相当于简化版的 Thompson:每一轮把当前配置复制 64 份,每份随机翻转大约 2 个比特,挑出得分最高的一份;只要不比当前差,就替换掉当前配置。
  • 梯度下降:用图 3 里那种软查找表,输入也允许是 0 到 1 之间的小数,查找表的输出就是 16 个软比特按输入加权的平均。这样整张网络对配置可以求导,用 Adam 优化;每 10 步把实数按正负取回成比特,放回真正的硬电路上检查是不是全对。
同一张查找表网络,两种写法(各 3 个随机种子)
任务配置位梯度下降:多少步全对变异搜索:20 万次评估后
2 位加法158450、30、30 步全对,分别用了 4673、10945、2945 次
3 位加法313680、80、90 步95.3%、96.9%、96.9% 的题全对
4 位加法52001270、1530 步;第三个 3000 步时 97.7%54.7%、65.6%、52.0%
5 位加法77764000 步时 94.2%、97.6%,没有全对(2 个种子)没有跑(4 位已经差得很远)

梯度一步要算一次前向和一次反向,大约相当于 3 次评估。3 位加法上,我们又给变异搜索加到 200 万次评估,它在第 153 万次时全对。

表 2 · 配置位从 1584 涨到 5200,变异搜索从几千次评估就能写对,变成 20 万次也只对一半多

结果(表 2)和 Thompson 的经验一致,而且差距随规模拉得很快。2 位加法的配置只有 1584 位,两种办法都能写对,变异搜索用几千到一万次评估,梯度用三五十步。到 3 位加法,梯度仍然只要 80 来步,变异搜索 20 万次评估后停在 95% 到 97%,我们把预算加到 200 万次,它在第 153 万次评估时才全对,差不多是梯度的六千倍。4 位加法上,梯度要一千多步,变异搜索 20 万次以后只有一半多的题全对。

4 位加法:梯度下降与变异搜索的学习曲线 横轴是前向计算次数(对数),纵轴是 256 道题全对的比例。梯度下降在约 3810 次前向(1270 步)时达到 100%;变异搜索在 60 万次评估后停在 59.8%。 4 位加法 · 5200 个配置位 · 256 道题全对的比例 0% 25% 50% 75% 100% 10 100 1000 1万 10万 100万 前向计算次数(梯度一步按 3 次算) 梯度下降:1270 步全对 变异搜索:60 万次后 59.8%
图 4 · 同一张查找表网络:梯度在约 4000 次前向内写对了 5200 个配置位,变异搜索用了 60 万次评估还差四成

图 4 把 4 位加法的过程画了出来。变异搜索前几千次评估涨得很快,因为随机配置里错的地方很多,随便翻几位都可能改好;越往后,对的位越多,随机翻转碰到“该翻的那一位”而不碰坏别处的机会越小,曲线就趴下了。梯度下降每一步都知道 5200 个数各自该往哪边调,不靠运气。

这个实验也有它的边界。梯度这一边并不是无敌的:5 位加法时,两个种子跑完 4000 步分别有 94.2% 和 97.6% 的题全对,没有完全写对;规模再大就要换更好的网络结构和训练方法,这正是深度学习这些年一直在做的事。我们的网络是随机布线、只有几千个配置位的玩具,不能拿来推算真实 FPGA 上的情况;它要说明的只是方向:同样是写一串配置,离散的比特只能靠试,连续的实数可以靠算。

解决:把比特放松成实数

这个实验里的“软查找表”并不是我们的发明,它正是近几年把 FPGA 和神经网络接起来的那条路。可微逻辑门网络把每个逻辑门放松成 16 种二输入门的概率混合,用梯度训练,训练完再给每个门挑最可能的那一种13;它的后续工作在 CIFAR-10 图像分类上做到了 86.29% 的准确率,用了大约 6100 万个逻辑门14。前面提到的 LUTNet、LogicNets 也都是先在连续空间里训练,再落成查找表。权重只有 ±1 的二值神经网络也是同样的套路:训练时在背后保留一份实数权重,前向用它的符号,反向时假装“取符号”这一步的导数是 1,让梯度穿过去,更新的是那份实数15。

也就是说,今天要让 FPGA 上的配置“自己学出来”,办法是先借用神经网络的连续参数和梯度,学完再离散化。真正写配置的,是梯度下降,FPGA 只是最后承载结果的地方。

配置能被梯度写出来,这只是第一步。下一个问题是:就算配置能学,FPGA 那种烧写后就焊死的导线,是不是学习的合适形状?

四、导线是焊死的还是现算的

现象:按内容取数,固定电路要为每个位置备一套

看一个最简单的“理解”任务:给一张表,北京对应京,上海对应沪,广东对应粤,四川对应川;问“上海”,答“沪”。语言里这种事无处不在:读到“她”时,要回头找前文里哪个名字是她;读到“这个函数”,要找前面定义的是哪个函数。该从哪里取信息,取决于内容本身。

按内容取数:固定导线与注意力 左边是固定电路:北京、上海、广东、四川四个槽位,每个后面接一个“等于上海吗”的比较器,比较结果都接到一个选择器上,选出“沪”;每多一个槽位就要多一套比较器和一路选择。右边是注意力:查询向量和四个键做点积,得到分数负 0.3、4.1、0.2、负 0.6,经过 softmax 变成权重 0.01、0.96、0.02、0.01,按权重把值加起来,结果约等于“沪”;同一组投影矩阵适用于任意多个槽位。 固定导线:每个槽位一套比较器 注意力:权重每一步现算 北京 → 京 上海 → 沪 广东 → 粤 四川 → 川 = 上海? = 上海? = 上海? = 上海? 选择器 沪 槽位数在烧写时定死 多一个槽位,多一套比较器 和一路选择 查询 q = WQ · 上海 键 kᵢq·kᵢ权重 北京−0.30.01 上海4.10.96 广东0.20.02 四川−0.60.01 Σ 权重 × vᵢ ≈ 沪 数值示意 左边的“比较什么、从哪几个位置选”都写在配置里;右边只写了 WQ、WK、WV 三个矩阵。 槽位从 4 个变成 4000 个,右边的参数一个不多,算的次数跟着变多。
图 5 · 固定电路也能按内容选,但每条可能的路都要预先铺好;注意力把“比较”做成了所有位置共用的一组参数

FPGA 当然能做这件事,图 5 左边就是标准做法:每个槽位后面接一个比较器,比较结果送进选择器。选择本身是运行时按数据决定的,这一点和注意力一样。差别在于有哪些候选、怎么比较,全都要在烧写时铺成实实在在的电路:槽位数定死了,多一个槽位就多一套比较器和一路选择;“相等”这个比较标准也是写死的,要换成“意思相近”,就得重新设计电路。

注意力:导线由数据当场算出来

注意力做的是同一件事,但换了一种存法。当前位置算一个查询向量 q,每个位置算一个键 k 和一个值 v,q 和每个 k 做点积得到分数,softmax 变成权重,再按权重把 v 加起来3。可以把这组权重看成这一步临时接上的导线:权重 0.96 就是一根粗线,0.01 就是几乎断开。

关键在于 WQW_Q、WKW_K、WVW_V 三个矩阵对所有位置共用。配置里不需要列出“第 3 个位置和第 7 个位置之间要不要接线”,只需要写下“什么样的查询和什么样的键算相近”。这个“相近”本身也是学出来的:训练会把“她”和前文的人名放到点积大的方向上去。至于序列有多长,那是运行时的事,参数一个不用多,代价是要做的乘法跟着变多。

这对“学习”很重要。如果连接方式要写进配置,学习者就得对所有可能的连接一一试错,配置的长度随位置数增长;把连接变成数据的函数以后,要学的只是一个比较规则,而且它是连续的、可以求导的。

边写边读:把中间结果写回输入

导线现算还有一个后果。Transformer 每写出一个词元,都会把它接到输入末尾,下一步可以像读输入一样读它。比如用 Transformer 做十进制加法时,它不需要一根专门传进位的导线,而是把上一位的两个数字再看一遍,跟自己刚写下的那一位比一比,就能推出有没有进位。思维链让表示能力随步数增长,靠的也是这个:中间结果写在序列里,后面的步骤用注意力按需取回5。

FPGA 也有存储,寄存器和片上存储器一样能保存中间结果;但读哪一格、写哪一格,同样是电路里写死的地址逻辑。Transformer 的“草稿纸”和它读输入用的是同一套注意力,读哪里也是现算的。

到这里,我们看到了两个结构上的差别:配置能不能求导,导线是不是现算。可就算有了这两样,1990 年代也不可能一下子做出 GPT。还差的是什么? 是规模。

五、规模:差了多少个数量级

配置有多大,是怎么写出来的
对象年份配置有多大配置怎么写出来
XC2064198564 个 4 输入逻辑块工程师手写
Thompson 的进化电路19961800 bit遗传算法,约 25 万次评估、2–3 周
本文的查找表网络20261584–7776 bit梯度几十到几千步;变异搜索写对 3 位加法要 153 万次评估
VP19022023846 万个 6 输入 LUT,查找表内容约 5.4 亿 bit(不含布线)工程师写 Verilog,工具综合、布局布线
GPT-320201750 亿个参数,按 16 bit 存约 2.8 万亿 bit梯度下降,约 3000 亿个 token,3.14×10²³ 次浮点运算

VP1902 的 5.4 亿 bit 是按 846 万 × 64 估算的,只算查找表本身;GPT-3 的配置比它多约 5000 倍。

表 3 · 配置的规模从几千比特到几万亿比特,能写到这么大的只有梯度下降

把几样东西的配置摆在一起(表 3),差距是按数量级算的。最大的 FPGA,查找表内容大约 5.4 亿比特,已经全靠工程师和工具一层层写出来;GPT-3 的 1750 亿个参数按 16 比特存是 2.8 万亿比特16,多出约 5000 倍,没有任何人知道其中某一个数为什么是现在这个值。

写出这么大的配置,需要的不只是算法,还有两样东西。一是数据:GPT-3 读了大约 3000 亿个 token16,这么多可用的文本要到互联网普及很久以后才有。二是算力:训练 GPT-3 用了大约 3.14×10²³ 次浮点运算17。拿 Brainwave 那块 Stratix 10 的 39.5 TFLOPS 来算,一块 FPGA 不停地跑也要 250 年左右;1990 年代的芯片还要再慢好几个数量级。

而“涌现”恰好是规模的产物。Wei 等人把它定义为“小模型上没有、大模型上出现”的能力,许多能力在训练计算量达到 10²² 到 10²⁴ 次浮点运算这个区间时才明显高于随机水平17。也有人认为,所谓的“突然出现”很大程度上是评价指标造成的:用只看答案对错的指标,曲线就像突然跳起来;换成连续的指标,能力其实是平滑增长的18。这两种看法争的是“涌现”是不是突变,但都同意一点:这些能力出现在规模很大的时候,小模型上看不到。

所以就算 1996 年有人用梯度和注意力去训练 FPGA,配置规模、数据和算力也都差了好几个数量级,看不到涌现是意料之中的。那么回到开头的问题,把这几条放在一起,答案是什么?

六、为什么 FPGA 时代没有出现涌现

先把时间线摆出来。FPGA 和神经网络其实是同时代的东西,并不存在“先有 FPGA 时代、后有 Transformer 时代”。

FPGA 和神经网络的两条时间线 上面一条是 FPGA:1985 年 XC2064 有 64 个逻辑块,1996 年 Thompson 进化出 1800 比特的电路,2018 年 Brainwave 在 FPGA 上跑神经网络,2022 年出现可微逻辑门网络。下面一条是神经网络:1986 年反向传播,2012 年 AlexNet,2017 年 Transformer,2020 年 GPT-3。2018 年和 2022 年两条线之间有虚线相连:FPGA 开始承载神经网络,可微逻辑门让梯度来写查找表。 FPGA:配置由人写 神经网络:配置由梯度写 1985 XC2064 64 个逻辑块 1996 进化电路 1800 bit,2–3 周 2018 Brainwave 2022 可微逻辑门 FPGA 跑神经网络 1986 反向传播 2012 AlexNet · GPU 2017 Transformer 2020 GPT-3 两条线并行了三十多年;交汇时,FPGA 上的配置也改由梯度来写(虚线)。
图 6 · FPGA 和神经网络是同时代的;智能出现在梯度能写的那条线上

把前面几节合起来,答案可以分成三层。

第一层:智能不在电路里,在配置里。 FPGA 是一种承载配置的硬件,和 GPU 是同一类东西。问“FPGA 上为什么没有涌现”,就像问“GPU 上为什么在 2012 年之前没有涌现”:硬件一直在那里,缺的是一份够好的配置。FPGA 的四十年里,几乎所有配置都是工程师写的,而人写出来的电路不会超出写它的人对问题的理解。

第二层:唯一能自动写配置的办法,在 FPGA 的比特上走不远。 进化是那个年代最自然的尝试,Thompson 的实验也确实长出了没人设计过的电路;但离散的比特没有梯度,搜索的代价随配置长度急剧上升,我们的小实验在几千比特的规模上就看到了这一点。能把配置写到几万亿比特的只有梯度下降,而梯度下降要求配置是连续的。反向传播 1986 年就有了11,它天然属于神经网络那条线,不属于 FPGA。

第三层:就算有了连续参数,还要有合适的结构和足够的规模。 注意力让“连哪里”变成数据的函数,一组参数能用在任意长度的输入上;而涌现出现在配置、数据和算力都很大的时候。等到这几样凑齐,跑训练的硬件已经是 GPU 了:它天生适合大块的矩阵乘法,改一行代码几秒钟就能重新跑,而 FPGA 改一次设计要等几个小时的布局布线。Hooker 把这种“一条研究路线能不能赢,取决于它是否碰巧适合当时的硬件”的现象叫作硬件彩票19,神经网络抽中的是 GPU。

所以更准确的说法是:FPGA 时代并不缺可配置的电路,缺的是一个能从数据里自动写出几万亿个配置数的方法,以及让这个方法有效的连续参数、可学习的连接方式、海量数据和算力。这些条件凑齐的地方是 GPU 上的神经网络。FPGA 后来也接上了这条线:Brainwave 把训练好的网络放到 FPGA 上跑,可微逻辑门让梯度来写查找表,但写配置的始终是梯度,不是 FPGA 自己。

七、回到开头

回到开头那两个十六进制数。0x96 和 0xE8 是人写的:工程师先知道全加器是什么,再把它的真值表交给工具。神经元那 9 个实数 [1, 1, 1], −1.5 和 [1, 1, 1, −2], −0.5,其实也是我们手写的,它们和 0x96 一样“不聪明”。

这一点可以在 Transformer 自己身上看得很清楚。AdderBoard 排行榜上有一个只有 6 个参数的 Transformer,能把十位数加法做对,它的 6 个参数全是人推出来的,本质上是一个用 Transformer 零件搭出来的加法电路,和 FPGA 上手写的加法器没有什么两样;同一个榜上训练出来的模型最少要 36 个参数,那是梯度下降自己找到的解(详见《做加法最少要几个参数》)。

所以 Transformer 和 FPGA 的差别,不在“它是不是电路”,而在这串配置数能不能由梯度从数据里写出来,以及写到多大。手写的配置,不管放在 FPGA 上还是 Transformer 上,都不会涌现;梯度写的配置,放到 FPGA 上照样能跑。

小结

  1. 两者相同在哪? 都是“固定骨架 + 一串配置数”:FPGA 的配置是查找表和导线开关的比特,Transformer 的配置是权重。都能表示任意函数,也能互相实现:FPGA 能跑神经网络,训练好的网络能编译成查找表。
  2. 表示能力是不是差别? 不是。Transformer 一次前向是常数深度的阈值电路,FPGA 加上寄存器和时钟是任意状态机,一步之内 FPGA 甚至更强。
  3. 配置由谁来写?为什么 Transformer 的参数能收敛? FPGA 上是人,Transformer 上是梯度。收敛就是沿着损失的坡度一步步往下走:Transformer 的每个零件都可导,残差和归一化让坡度传得下去,参数多让低处连成一片。FPGA 的比特只有 0 和 1,损失对它的坡度处处是 0,只能整位翻过去试;Thompson 的进化电路 1800 个比特就要 25 万次评估,我们的实验里变异搜索在几千比特的网络上就追不上梯度下降。把比特放松成实数以后,梯度一样能写查找表。
  4. 导线有什么不同? FPGA 的连接在烧写时铺好,每条可能的路都写在配置里;注意力的连接每一步由数据算出,参数和序列长度无关,比较规则本身也能学。
  5. 规模差多少? 最大 FPGA 的查找表内容约 5.4 亿比特,GPT-3 约 2.8 万亿比特,还要 3000 亿 token 和 3×10²³ 次浮点运算;涌现只在这样的规模上出现。
  6. 为什么 FPGA 时代没有涌现? 因为智能在配置里而不在电路里;那个年代能自动写配置的方法走不远,能走远的梯度下降属于神经网络,等数据和算力凑齐时它已经跑在 GPU 上了。

如果想自己试一试,实验代码只有一百来行,依赖 PyTorch,train_grad 和 train_evo 分别是梯度和变异两种写法,在笔记本上几分钟就能跑完 2 到 4 位加法12。

参考资料

Footnotes

  1. Chip Hall of Fame: Xilinx XC2064 FPGAIEEE Spectrumspectrum.ieee.org ↗1985 年的第一块 FPGA,64 个可配置逻辑块,每块是一个 4 输入的逻辑函数。https://spectrum.ieee.org/chip-hall-of-fame-xilinx-xc2064-fpga ↩

  2. AMD Introduces World's Largest FPGA-Based Adaptive SoC for Emulation and PrototypingAMD · 2023-06-27ir.amd.com ↗Versal Premium VP1902:1850 万个系统逻辑单元、846 万个 LUT。“最大”是厂商自己的说法。https://ir.amd.com/news-events/press-releases/detail/1141/amd-introduces-worlds-largest-fpga-based-adaptive-soc ↩

  3. Attention Is All You NeedAshish Vaswani 等 · NeurIPS 2017arxiv.org ↗https://arxiv.org/abs/1706.03762 ↩ ↩2

  4. The Parallelism Tradeoff: Limitations of Log-Precision TransformersWilliam Merrill, Ashish Sabharwal · TACL · 2023arxiv.org ↗对数精度的 Transformer 一次前向可以用常数深度的阈值电路模拟,属于 uniform TC⁰。https://arxiv.org/abs/2207.00729 ↩

  5. The Expressive Power of Transformers with Chain of ThoughtWilliam Merrill, Ashish Sabharwal · ICLR 2024arxiv.org ↗允许生成中间步骤后,线性步数可以识别全部正则语言,多项式步数恰好对应 P(各自带有归一化方式的前提条件)。https://arxiv.org/abs/2310.07923 ↩ ↩2

  6. Serving DNNs in Real Time at Datacenter Scale with Project BrainwaveEric Chung 等 · IEEE Micro · 2018microsoft.com ↗微软在 Intel Stratix 10 FPGA 上跑神经网络推理,批大小为 1 时有效算力 39.5 TFLOPS,权重全部放在片上存储里。https://www.microsoft.com/en-us/research/uploads/prod/2018/03/mi0218_Chung-2018Mar25.pdf ↩

  7. LUTNet: Learning FPGA Configurations for Highly Efficient Neural Network InferenceErwei Wang, James J. Davis, Peter Y. K. Cheung, George A. Constantinides · IEEE Transactions on Computers · 2020arxiv.org ↗直接训练 FPGA 查找表里的内容,把查找表当作神经网络的算子。https://arxiv.org/abs/1910.12625 ↩

  8. LogicNets: Co-Designed Neural Networks and Circuits for Extreme-Throughput ApplicationsYaman Umuroglu 等 · FPL 2020arxiv.org ↗先训练稀疏、低比特的网络,再把每个神经元整个换成一张真值表,编译成 FPGA 网表。https://arxiv.org/abs/2004.03021 ↩

  9. FPGA Design Suite Generates Global Minimum LayoutElectronic Designelectronicdesign.com ↗Virtex-7 2000T 上的一个大设计,Vivado 布局布线用了 5 小时、9 GB 内存,旧工具 ISE 要 13 小时。https://www.electronicdesign.com/technologies/embedded/digital-ics/fpga/article/21789745/fpga-design-suite-generates-global-minimum-layout ↩

  10. An Evolved Circuit, Intrinsic in Silicon, Entwined with PhysicsAdrian Thompson · ICES 1996 · 1996cse-robotics.engr.tamu.edu ↗在 XC6216 的 10×10 格上用遗传算法进化出音调分辨电路。文中的代数、评估时间、32 个有效格、温度实验都取自这篇论文的第 3–5 节。https://cse-robotics.engr.tamu.edu/dshell/cs625/Thompson96Evolved.pdf ↩ ↩2

  11. Learning representations by back-propagating errorsDavid E. Rumelhart, Geoffrey E. Hinton, Ronald J. Williams · Nature 323 · 1986doi.org ↗https://doi.org/10.1038/323533a0 ↩ ↩2

  12. 本文的实验代码与调研笔记/mnt/project-files/transformer-fpga-research/:experiment/lutnet.py 是查找表网络和两种训练方法,run_scale.py、run_more.py 是实验入口,输出在 logs/。PyTorch 2.14.1 CPU 版,4 核。 ↩ ↩2

  13. Deep Differentiable Logic Gate NetworksFelix Petersen, Christian Borgelt, Hilde Kuehne, Oliver Deussen · NeurIPS 2022arxiv.org ↗把每个逻辑门放松成 16 种二输入门的概率混合,用梯度训练,训练完再取最可能的那个门。https://arxiv.org/abs/2210.08277 ↩

  14. Convolutional Differentiable Logic Gate NetworksFelix Petersen 等 · NeurIPS 2024arxiv.org ↗同一思路做到 CIFAR-10 上 86.29%,用了约 6100 万个逻辑门。https://arxiv.org/abs/2411.04732 ↩

  15. Binarized Neural NetworksItay Hubara, Matthieu Courbariaux, Daniel Soudry, Ran El-Yaniv, Yoshua Bengio · NeurIPS 2016arxiv.org ↗权重和激活都是 ±1 的网络;训练时保留实数权重,用直通估计(straight-through estimator)让梯度穿过取符号。凭领域知识整理,发文前可再核对。https://arxiv.org/abs/1602.02830 ↩

  16. Language Models are Few-Shot LearnersTom B. Brown 等 · NeurIPS 2020arxiv.org ↗GPT-3:1750 亿参数,训练用了约 3000 亿个 token。https://arxiv.org/abs/2005.14165 ↩ ↩2

  17. Emergent Abilities of Large Language ModelsJason Wei 等 · TMLR · 2022arxiv.org ↗附录里 GPT-3 175B 的训练计算量记为 3.14×10²³ FLOPs;涌现能力定义为“小模型上没有、大模型上出现”。https://arxiv.org/abs/2206.07682 ↩ ↩2

  18. Are Emergent Abilities of Large Language Models a Mirage?Rylan Schaeffer, Brando Miranda, Sanmi Koyejo · NeurIPS 2023arxiv.org ↗认为很多“突然出现”是评价指标不连续造成的,换成连续指标后曲线是平滑的。https://arxiv.org/abs/2304.15004 ↩

  19. The Hardware LotterySara Hooker · arXiv · 2020arxiv.org ↗一个研究方向能不能赢,很大程度上取决于它是否碰巧适合当时的硬件和软件。https://arxiv.org/abs/2009.06489 ↩