﻿---
title: "Transformer 和 FPGA 都是电路：为什么智能没有在 FPGA 上涌现"
description: "FPGA 是一片可以重新配置的电路，Transformer 跑一步也是一个固定的电路，两者都是“固定骨架 + 一串配置数”。我们从同一个一位全加器在两边的写法出发，先看它们相同在哪：都能表示任意函数，FPGA 的表示能力甚至更强；再看不同在哪：配置是人写的比特还是梯度写的实数，导线是烧写时焊死的还是每一步现算的，配置的规模差了几个数量级；解释 Transformer 的参数为什么能靠梯度下降收敛、FPGA 的比特为什么不行，并用一个小实验比较变异搜索和梯度下降去“写”同一张查找表网络；最后回答 FPGA 的四十年里为什么没有出现智能涌现。"
pubDate: 2026-10-25
updatedDate: 2026-10-26
tags: ["AI", "Transformer", "FPGA", "电路"]
hero: "./hero.svg"
clc: "TP183"
refs:
  - id: xc2064
    title: "Chip Hall of Fame: Xilinx XC2064 FPGA"
    url: https://spectrum.ieee.org/chip-hall-of-fame-xilinx-xc2064-fpga
    site: IEEE Spectrum
    note: "1985 年的第一块 FPGA，64 个可配置逻辑块，每块是一个 4 输入的逻辑函数。"
  - id: vp1902
    title: "AMD Introduces World's Largest FPGA-Based Adaptive SoC for Emulation and Prototyping"
    url: https://ir.amd.com/news-events/press-releases/detail/1141/amd-introduces-worlds-largest-fpga-based-adaptive-soc
    site: AMD
    date: 2023-06-27
    note: "Versal Premium VP1902：1850 万个系统逻辑单元、846 万个 LUT。“最大”是厂商自己的说法。"
  - id: thompson
    title: "An Evolved Circuit, Intrinsic in Silicon, Entwined with Physics"
    url: https://cse-robotics.engr.tamu.edu/dshell/cs625/Thompson96Evolved.pdf
    author: Adrian Thompson
    site: ICES 1996
    date: "1996"
    note: "在 XC6216 的 10×10 格上用遗传算法进化出音调分辨电路。文中的代数、评估时间、32 个有效格、温度实验都取自这篇论文的第 3–5 节。"
  - id: tc0
    title: "The Parallelism Tradeoff: Limitations of Log-Precision Transformers"
    url: https://arxiv.org/abs/2207.00729
    author: William Merrill, Ashish Sabharwal
    site: TACL
    date: "2023"
    note: "对数精度的 Transformer 一次前向可以用常数深度的阈值电路模拟，属于 uniform TC⁰。"
  - id: cot
    title: "The Expressive Power of Transformers with Chain of Thought"
    url: https://arxiv.org/abs/2310.07923
    author: William Merrill, Ashish Sabharwal
    site: ICLR 2024
    note: "允许生成中间步骤后，线性步数可以识别全部正则语言，多项式步数恰好对应 P（各自带有归一化方式的前提条件）。"
  - id: brainwave
    title: "Serving DNNs in Real Time at Datacenter Scale with Project Brainwave"
    url: https://www.microsoft.com/en-us/research/uploads/prod/2018/03/mi0218_Chung-2018Mar25.pdf
    author: Eric Chung 等
    site: IEEE Micro
    date: "2018"
    note: "微软在 Intel Stratix 10 FPGA 上跑神经网络推理，批大小为 1 时有效算力 39.5 TFLOPS，权重全部放在片上存储里。"
  - id: lutnet
    title: "LUTNet: Learning FPGA Configurations for Highly Efficient Neural Network Inference"
    url: https://arxiv.org/abs/1910.12625
    author: Erwei Wang, James J. Davis, Peter Y. K. Cheung, George A. Constantinides
    site: IEEE Transactions on Computers
    date: "2020"
    note: "直接训练 FPGA 查找表里的内容，把查找表当作神经网络的算子。"
  - id: logicnets
    title: "LogicNets: Co-Designed Neural Networks and Circuits for Extreme-Throughput Applications"
    url: https://arxiv.org/abs/2004.03021
    author: Yaman Umuroglu 等
    site: FPL 2020
    note: "先训练稀疏、低比特的网络，再把每个神经元整个换成一张真值表，编译成 FPGA 网表。"
  - id: difflogic
    title: "Deep Differentiable Logic Gate Networks"
    url: https://arxiv.org/abs/2210.08277
    author: Felix Petersen, Christian Borgelt, Hilde Kuehne, Oliver Deussen
    site: NeurIPS 2022
    note: "把每个逻辑门放松成 16 种二输入门的概率混合，用梯度训练，训练完再取最可能的那个门。"
  - id: convdifflogic
    title: "Convolutional Differentiable Logic Gate Networks"
    url: https://arxiv.org/abs/2411.04732
    author: Felix Petersen 等
    site: NeurIPS 2024
    note: "同一思路做到 CIFAR-10 上 86.29%，用了约 6100 万个逻辑门。"
  - id: backprop
    title: "Learning representations by back-propagating errors"
    url: https://doi.org/10.1038/323533a0
    author: David E. Rumelhart, Geoffrey E. Hinton, Ronald J. Williams
    site: Nature 323
    date: "1986"
  - id: bnn
    title: "Binarized Neural Networks"
    url: https://arxiv.org/abs/1602.02830
    author: Itay Hubara, Matthieu Courbariaux, Daniel Soudry, Ran El-Yaniv, Yoshua Bengio
    site: NeurIPS 2016
    note: "权重和激活都是 ±1 的网络；训练时保留实数权重，用直通估计（straight-through estimator）让梯度穿过取符号。凭领域知识整理，发文前可再核对。"
  - id: vivado
    title: "FPGA Design Suite Generates Global Minimum Layout"
    url: https://www.electronicdesign.com/technologies/embedded/digital-ics/fpga/article/21789745/fpga-design-suite-generates-global-minimum-layout
    site: Electronic Design
    note: "Virtex-7 2000T 上的一个大设计，Vivado 布局布线用了 5 小时、9 GB 内存，旧工具 ISE 要 13 小时。"
  - id: attention
    title: "Attention Is All You Need"
    url: https://arxiv.org/abs/1706.03762
    author: Ashish Vaswani 等
    site: NeurIPS 2017
  - id: gpt3
    title: "Language Models are Few-Shot Learners"
    url: https://arxiv.org/abs/2005.14165
    author: Tom B. Brown 等
    site: NeurIPS 2020
    note: "GPT-3：1750 亿参数，训练用了约 3000 亿个 token。"
  - id: emergent
    title: "Emergent Abilities of Large Language Models"
    url: https://arxiv.org/abs/2206.07682
    author: Jason Wei 等
    site: TMLR
    date: "2022"
    note: "附录里 GPT-3 175B 的训练计算量记为 3.14×10²³ FLOPs；涌现能力定义为“小模型上没有、大模型上出现”。"
  - id: mirage
    title: "Are Emergent Abilities of Large Language Models a Mirage?"
    url: https://arxiv.org/abs/2304.15004
    author: Rylan Schaeffer, Brando Miranda, Sanmi Koyejo
    site: NeurIPS 2023
    note: "认为很多“突然出现”是评价指标不连续造成的，换成连续指标后曲线是平滑的。"
  - id: lottery
    title: "The Hardware Lottery"
    url: https://arxiv.org/abs/2009.06489
    author: Sara Hooker
    site: arXiv
    date: "2020"
    note: "一个研究方向能不能赢，很大程度上取决于它是否碰巧适合当时的硬件和软件。"
  - id: notes
    title: "本文的实验代码与调研笔记"
    note: "`/mnt/project-files/transformer-fpga-research/`：`experiment/lutnet.py` 是查找表网络和两种训练方法，`run_scale.py`、`run_more.py` 是实验入口，输出在 `logs/`。PyTorch 2.14.1 CPU 版，4 核。"
---

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

```text
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 输入的查找表就是一块 $2^k$ 位的小存储器：k 个输入拼成一个地址，读出那一位就是输出。所以 4 输入 LUT 有 16 个配置位，可以实现任意一个 4 输入的布尔函数；现在的主流芯片用 6 输入 LUT，64 个配置位。导线也一样，每个交叉点上的开关接通还是断开，都由一个配置位决定。

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

第一块 FPGA 是 1985 年 Xilinx 的 XC2064，只有 64 个可配置逻辑块，每块是一个 4 输入的逻辑函数[^xc2064]。2023 年 AMD 发布的 VP1902 有 846 万个 LUT[^vp1902]。

### Transformer：固定的计算图和权重

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

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

### 同一个全加器，两种配置

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

<figure class="dd dd-figure">
<div class="dd-scroll">
<svg class="dd-svg" viewBox="0 0 800 400" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="tf-adder-title tf-adder-desc">
<title id="tf-adder-title">同一个一位全加器的两种配置</title>
<desc id="tf-adder-desc">左边是 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。</desc>
<defs>
<marker id="tf-adder-arrow" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto"><path class="dd-head" d="M1 1 L7 4 L1 7"/></marker>
</defs>
<rect class="dd-frame" width="800" height="400" rx="10"/>
<rect class="dd-zone" x="16" y="16" width="376" height="320" rx="8"/>
<rect class="dd-zone" x="408" y="16" width="376" height="320" rx="8"/>
<text class="t-zone" x="32" y="44">FPGA · 两张查找表</text>
<text class="t-zone" x="424" y="44">神经网络 · 两个阈值神经元</text>
<!-- LUT card -->
<rect class="dd-card" x="32" y="60" width="344" height="240" rx="6"/>
<path class="dd-card-head" d="M33 92 V67 Q33 61 39 61 H369 Q375 61 375 67 V92 Z"/>
<text class="t-name" x="48" y="81">地址 c b a</text>
<text class="t-name" x="208" y="81">s</text>
<text class="t-name" x="288" y="81">cout</text>
<text class="t-mono" x="48" y="109">0  0 0 0</text><text class="t-val" x="208" y="109">0</text><text class="t-val" x="288" y="109">0</text>
<line class="dd-hair" x1="32" y1="118" x2="376" y2="118"/>
<text class="t-mono" x="48" y="135">1  0 0 1</text><text class="t-val" x="208" y="135">1</text><text class="t-val" x="288" y="135">0</text>
<line class="dd-hair" x1="32" y1="144" x2="376" y2="144"/>
<text class="t-mono" x="48" y="161">2  0 1 0</text><text class="t-val" x="208" y="161">1</text><text class="t-val" x="288" y="161">0</text>
<line class="dd-hair" x1="32" y1="170" x2="376" y2="170"/>
<text class="t-acc t-mono" x="48" y="187">3  0 1 1</text><text class="t-acc t-mono" x="208" y="187">0</text><text class="t-acc t-mono" x="288" y="187">1</text>
<line class="dd-hair" x1="32" y1="196" x2="376" y2="196"/>
<text class="t-mono" x="48" y="213">4  1 0 0</text><text class="t-val" x="208" y="213">1</text><text class="t-val" x="288" y="213">0</text>
<line class="dd-hair" x1="32" y1="222" x2="376" y2="222"/>
<text class="t-mono" x="48" y="239">5  1 0 1</text><text class="t-val" x="208" y="239">0</text><text class="t-val" x="288" y="239">1</text>
<line class="dd-hair" x1="32" y1="248" x2="376" y2="248"/>
<text class="t-mono" x="48" y="265">6  1 1 0</text><text class="t-val" x="208" y="265">0</text><text class="t-val" x="288" y="265">1</text>
<line class="dd-hair" x1="32" y1="274" x2="376" y2="274"/>
<text class="t-mono" x="48" y="291">7  1 1 1</text><text class="t-val" x="208" y="291">1</text><text class="t-val" x="288" y="291">1</text>
<text class="t-say" x="32" y="324">从地址 7 读到 0：s = 0x96，cout = 0xE8，共 16 bit</text>
<!-- neuron cards -->
<rect class="dd-card" x="424" y="60" width="344" height="110" rx="6"/>
<path class="dd-card-head" d="M425 92 V67 Q425 61 431 61 H761 Q767 61 767 67 V92 Z"/>
<text class="t-name" x="440" y="81">进位神经元</text>
<text class="t-mono" x="712" y="81">cout</text>
<text class="t-mono" x="440" y="109">权重</text><text class="t-val" x="528" y="109">[1, 1, 1]</text><text class="t-note" x="640" y="109">接 a、b、c</text>
<line class="dd-hair" x1="424" y1="118" x2="768" y2="118"/>
<text class="t-mono" x="440" y="135">偏置</text><text class="t-val" x="528" y="135">−1.5</text>
<line class="dd-hair" x1="424" y1="144" x2="768" y2="144"/>
<text class="t-mono" x="440" y="161">输出</text><text class="t-val" x="528" y="161">[a+b+c ≥ 1.5]</text>
<line class="dd-edge" x1="736" y1="170" x2="736" y2="196" marker-end="url(#tf-adder-arrow)"/>
<rect class="dd-card" x="424" y="198" width="344" height="110" rx="6"/>
<path class="dd-card-head" d="M425 230 V205 Q425 199 431 199 H761 Q767 199 767 205 V230 Z"/>
<text class="t-name" x="440" y="219">和神经元</text>
<text class="t-mono" x="728" y="219">s</text>
<text class="t-mono" x="440" y="247">权重</text><text class="t-val" x="528" y="247">[1, 1, 1, −2]</text><text class="t-note" x="648" y="247">最后一个接 cout</text>
<line class="dd-hair" x1="424" y1="256" x2="768" y2="256"/>
<text class="t-mono" x="440" y="273">偏置</text><text class="t-val" x="528" y="273">−0.5</text>
<line class="dd-hair" x1="424" y1="282" x2="768" y2="282"/>
<text class="t-mono" x="440" y="299">输出</text><text class="t-val" x="528" y="299">[a+b+c−2·cout ≥ 0.5]</text>
<text class="t-say" x="424" y="324">例：<tspan class="t-acc">a=1, b=1, c=0</tspan>，和为 2，cout=1，s=0；共 9 个实数</text>
<text class="t-say" x="32" y="364">两边的骨架（按地址读表、加权求和再比阈值）都是固定的，换一个功能只需换这串数。</text>
<text class="t-say" x="32" y="388">不同的是：左边的数只能是 0 或 1，右边的数可以连续地调。</text>
</svg>
</div>
<figcaption>图 1 · 同一个一位全加器：FPGA 记住 16 个比特，阈值神经元记住 9 个实数</figcaption>
</figure>

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

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

## 二、相同之处：骨架固定，功能在配置里

### 都能表示任意函数

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

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

### 一步之内能算多深

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

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

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

<figure class="dd dd-figure dt-figure">
<div class="dt-panel">
<div class="dt-title" id="tf-power-title">两种电路能表示什么</div>
<div class="dt-scroll">
<table class="dt" aria-labelledby="tf-power-title">
<colgroup><col style="width:18%"><col><col></colgroup>
<thead>
<tr><th scope="col"></th><th scope="col">FPGA</th><th scope="col">Transformer</th></tr>
</thead>
<tbody>
<tr><th scope="row">基本单元</th><td>4–6 输入查找表，任意布尔函数</td><td>加权求和 + 非线性（注意力、MLP）</td></tr>
<tr><th scope="row">一步之内</th><td>任意深度的组合逻辑，受时钟频率约束</td><td>固定层数；对数精度下属于常数深度阈值电路 TC⁰</td></tr>
<tr><th scope="row">加上循环</th><td>寄存器 + 时钟：任意有限状态机，可以做成 CPU</td><td>自回归把输出写回输入：线性步数覆盖正则语言，多项式步数对应 P</td></tr>
<tr><th scope="row">实现对方</th><td>可以跑神经网络推理：Brainwave 在 Stratix 10 上批大小 1 时 39.5 TFLOPS</td><td>训练好的网络可以整个编译成查找表（LogicNets、LUTNet）</td></tr>
</tbody>
</table>
</div>
<p class="dt-note">只要电路够大，谁都能模拟谁；单看“能表示什么”，FPGA 不比 Transformer 弱，一步之内甚至更强。</p>
</div>
<figcaption>表 1 · 表示能力不是两者的分水岭</figcaption>
</figure>

### 它们可以互相实现

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

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

## 三、配置由谁来写

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

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

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

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

### 第一次尝试：让进化来写

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

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

<figure class="dd dd-figure">
<div class="dd-scroll">
<svg class="dd-svg" viewBox="0 0 800 360" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="tf-evo-title tf-evo-desc">
<title id="tf-evo-title">Thompson 1996 年的进化实验</title>
<desc id="tf-evo-desc">上方是一条 0 到 5000 代的时间轴，标出最好个体的表现：第 0 代输出恒定 5 伏；第 650 代对 1 千赫基本稳住，对 10 千赫仍在照抄输入；第 1400 代两种频率开始分开；第 2800 代只剩 10 千赫时的快速毛刺；第 3500 代示波器上看着完美；第 4100 代偶发尖峰消除；取第 5000 代为最终电路。下方四张卡片：基因 1800 比特；种群 50，约 25 万次评估；每次评估 5 秒，全程两到三周；有用的只有 32 格，有的格子不连到输出却不能去掉。</desc>
<rect class="dd-frame" width="800" height="360" rx="10"/>
<rect class="dd-zone" x="16" y="16" width="768" height="184" rx="8"/>
<rect class="dd-zone" x="16" y="216" width="768" height="128" rx="8"/>
<text class="t-zone" x="32" y="44">最好个体在示波器上的表现（代数）</text>
<text class="t-zone" x="32" y="244">这次实验的账</text>
<line class="dd-edge" x1="48" y1="120" x2="752" y2="120"/>
<line class="dd-edge-acc" x1="625" y1="120" x2="752" y2="120"/>
<circle class="dd-dot" cx="48" cy="120" r="5"/>
<circle class="dd-dot" cx="140" cy="120" r="5"/>
<circle class="dd-dot" cx="245" cy="120" r="5"/>
<circle class="dd-dot" cx="442" cy="120" r="5"/>
<circle class="dd-dot" cx="541" cy="120" r="5"/>
<circle class="dd-fill-acc" cx="625" cy="120" r="6"/>
<circle class="dd-dot" cx="752" cy="120" r="5"/>
<text class="t-mono" x="48" y="72">0</text><text class="t-note" x="48" y="90">恒定 +5V，毫无区分</text>
<text class="t-mono" x="245" y="72">1400</text><text class="t-note" x="245" y="90">1k 多为高、10k 多为低</text>
<text class="t-mono" x="487" y="72" text-anchor="middle">3500</text><text class="t-note" x="487" y="90" text-anchor="middle">示波器上看着完美</text>
<text class="t-mono" x="752" y="72" text-anchor="end">5000</text><text class="t-note" x="752" y="90" text-anchor="end">取为最终电路</text>
<text class="t-mono" x="140" y="152">650</text><text class="t-note" x="140" y="170">1k 基本稳住，10k 仍照抄</text>
<text class="t-mono" x="442" y="152" text-anchor="middle">2800</text><text class="t-note" x="442" y="170" text-anchor="middle">只剩 10k 时的毛刺</text>
<text class="t-acc t-mono" x="625" y="152">4100</text><text class="t-note" x="625" y="170">偶发尖峰消除</text>
<path class="dd-hair" d="M487 98 V110 H541"/>
<!-- stat cards -->
<rect class="dd-card" x="32" y="260" width="172" height="68" rx="6"/>
<text class="t-name" x="48" y="286">基因 1800 bit</text><text class="t-note" x="48" y="312">10×10 格 × 每格 18 bit</text>
<rect class="dd-card" x="220" y="260" width="172" height="68" rx="6"/>
<text class="t-name" x="236" y="286">种群 50 · 约 5000 代</text><text class="t-note" x="236" y="312">约 25 万次评估</text>
<rect class="dd-card" x="408" y="260" width="172" height="68" rx="6"/>
<text class="t-name" x="424" y="286">每次评估约 5 秒</text><text class="t-note" x="424" y="312">全程 2–3 周</text>
<rect class="dd-card" x="596" y="260" width="172" height="68" rx="6"/>
<text class="t-name" x="612" y="286">有用的只有 32 格</text><text class="t-note" x="612" y="312">有的不连到输出却不能去掉</text>
</svg>
</div>
<figcaption>图 2 · 进化确实“长”出了没人设计过的电路，但 1800 个比特就用了 25 万次评估、两三周</figcaption>
</figure>

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

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

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

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

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

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

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

坡度怎么算？网络是一层套一层的函数，按微积分里的链式法则，最后的损失对某个参数的导数，等于从输出往回、沿途每一层“输入变一点、输出变多少”的连乘。**反向传播**就是把这件事从输出层往回一次做完，代价大约只是一次前向计算的两倍，就能得到所有参数各自的导数[^backprop]。参数从 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 个，两边都是平的。

<figure class="dd dd-figure">
<div class="dd-scroll">
<svg class="dd-svg" viewBox="0 0 800 360" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="tf-slope-title tf-slope-desc">
<title id="tf-slope-title">同一个配置项：软查找表有坡度，硬查找表没有</title>
<desc id="tf-slope-desc">3 位加法网络训练到第 20 步时，把其中一个配置项从负 6 扫到 6。左边是软查找表的损失，从 0.562 平滑降到 0.535，当前位置 1.59 处斜率为负，往右调损失下降。右边是硬查找表错了多少个输出位：小于 0 时 86 个，大于 0 时 70 个，两段都是平的，只在 0 处跳一下。</desc>
<defs><marker id="tf-slope-arrow-acc" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto"><path class="dd-head-acc" d="M1 1 L7 4 L1 7"/></marker></defs>
<rect class="dd-frame" width="800" height="360" rx="10"/>
<rect class="dd-zone" x="16" y="16" width="376" height="288" rx="8"/>
<rect class="dd-zone" x="408" y="16" width="376" height="288" rx="8"/>
<text class="t-zone" x="32" y="44">软查找表 · 损失（交叉熵）</text>
<text class="t-zone" x="424" y="44">硬查找表 · 错了几个输出位（共 256）</text>
<line class="dd-hair" x1="72" y1="256.0" x2="368" y2="256.0"/>
<text class="t-note" x="64" y="260.0" text-anchor="end">0.53</text>
<line class="dd-hair" x1="72" y1="203.4" x2="368" y2="203.4"/>
<text class="t-note" x="64" y="207.4" text-anchor="end">0.54</text>
<line class="dd-hair" x1="72" y1="150.9" x2="368" y2="150.9"/>
<text class="t-note" x="64" y="154.9" text-anchor="end">0.55</text>
<line class="dd-hair" x1="72" y1="98.3" x2="368" y2="98.3"/>
<text class="t-note" x="64" y="102.3" text-anchor="end">0.56</text>
<text class="t-note" x="72.0" y="276" text-anchor="middle">-6</text>
<text class="t-note" x="146.0" y="276" text-anchor="middle">-3</text>
<text class="t-note" x="220.0" y="276" text-anchor="middle">0</text>
<text class="t-note" x="294.0" y="276" text-anchor="middle">3</text>
<text class="t-note" x="368.0" y="276" text-anchor="middle">6</text>
<line class="dd-hair" x1="220.0" y1="72" x2="220.0" y2="256"/>
<path class="dd-edge" d="M72.0 86.1 L74.5 86.1 L76.9 86.2 L79.4 86.2 L81.9 86.3 L84.3 86.4 L86.8 86.5 L89.3 86.6 L91.7 86.7 L94.2 86.8 L96.7 86.9 L99.1 87.0 L101.6 87.2 L104.1 87.4 L106.5 87.6 L109.0 87.8 L111.5 88.0 L113.9 88.2 L116.4 88.5 L118.9 88.8 L121.3 89.2 L123.8 89.5 L126.3 89.9 L128.7 90.4 L131.2 90.9 L133.7 91.4 L136.1 92.0 L138.6 92.6 L141.1 93.3 L143.5 94.1 L146.0 94.9 L148.5 95.9 L150.9 96.9 L153.4 98.0 L155.9 99.1 L158.3 100.4 L160.8 101.8 L163.3 103.3 L165.7 105.0 L168.2 106.7 L170.7 108.6 L173.1 110.6 L175.6 112.8 L178.1 115.1 L180.5 117.5 L183.0 120.1 L185.5 122.8 L187.9 125.7 L190.4 128.6 L192.9 131.8 L195.3 135.0 L197.8 138.4 L200.3 141.8 L202.7 145.4 L205.2 149.0 L207.7 152.6 L210.1 156.3 L212.6 160.0 L215.1 163.7 L217.5 167.4 L220.0 171.0 L222.5 174.6 L224.9 178.1 L227.4 181.5 L229.9 184.8 L232.3 188.0 L234.8 191.0 L237.3 194.0 L239.7 196.7 L242.2 199.4 L244.7 201.9 L247.1 204.3 L249.6 206.5 L252.1 208.5 L254.5 210.5 L257.0 212.3 L259.5 214.0 L261.9 215.5 L264.4 217.0 L266.9 218.3 L269.3 219.5 L271.8 220.6 L274.3 221.7 L276.7 222.6 L279.2 223.5 L281.7 224.3 L284.1 225.0 L286.6 225.7 L289.1 226.3 L291.5 226.8 L294.0 227.3 L296.5 227.8 L298.9 228.2 L301.4 228.6 L303.9 228.9 L306.3 229.2 L308.8 229.5 L311.3 229.8 L313.7 230.0 L316.2 230.2 L318.7 230.4 L321.1 230.6 L323.6 230.8 L326.1 230.9 L328.5 231.0 L331.0 231.2 L333.5 231.3 L335.9 231.4 L338.4 231.4 L340.9 231.5 L343.3 231.6 L345.8 231.7 L348.3 231.7 L350.7 231.8 L353.2 231.8 L355.7 231.9 L358.1 231.9 L360.6 231.9 L363.1 232.0 L365.5 232.0 L368.0 232.0"/>
<line class="dd-edge-acc" x1="259.3" y1="214.0" x2="296.5" y2="227.8" marker-end="url(#tf-slope-arrow-acc)"/>
<circle class="dd-fill-acc" cx="259.3" cy="214.0" r="5"/>
<text class="t-acc t-mono" x="251" y="200" text-anchor="end">当前 θ = 1.59</text>
<text class="t-note" x="72" y="292">坡往右下：θ 调大一点，损失就小一点</text>
<line class="dd-hair" x1="464" y1="256.0" x2="760" y2="256.0"/>
<text class="t-note" x="456" y="260.0" text-anchor="end">60</text>
<line class="dd-hair" x1="464" y1="204.9" x2="760" y2="204.9"/>
<text class="t-note" x="456" y="208.9" text-anchor="end">70</text>
<line class="dd-hair" x1="464" y1="153.8" x2="760" y2="153.8"/>
<text class="t-note" x="456" y="157.8" text-anchor="end">80</text>
<line class="dd-hair" x1="464" y1="102.7" x2="760" y2="102.7"/>
<text class="t-note" x="456" y="106.7" text-anchor="end">90</text>
<text class="t-note" x="464.0" y="276" text-anchor="middle">-6</text>
<text class="t-note" x="538.0" y="276" text-anchor="middle">-3</text>
<text class="t-note" x="612.0" y="276" text-anchor="middle">0</text>
<text class="t-note" x="686.0" y="276" text-anchor="middle">3</text>
<text class="t-note" x="760.0" y="276" text-anchor="middle">6</text>
<line class="dd-hair" x1="612.0" y1="72" x2="612.0" y2="256"/>
<line class="dd-edge" x1="464.0" y1="123.1" x2="609.5" y2="123.1"/>
<line class="dd-edge" x1="614.5" y1="204.9" x2="760.0" y2="204.9"/>
<line class="dd-edge dd-dash" x1="612.0" y1="123.1" x2="612.0" y2="204.9"/>
<circle class="dd-fill-acc" cx="651.3" cy="204.9" r="5"/>
<text class="t-acc t-mono" x="651" y="229" text-anchor="middle">当前：错 70 个</text>
<text class="t-mono" x="538" y="111" text-anchor="middle">错 86 个</text>
<text class="t-note" x="464" y="292">两段都是平的：往哪边挪一点，结果都不变</text>
<text class="t-say" x="32" y="332">左右是同一张网络里的同一个配置项。软的那张告诉你往哪边调；硬的那张只能整个翻过去试试看（虚线）。</text>
</svg>
</div>
<figcaption>图 3 · 把一个配置位放松成实数，损失就有了坡度；留在 0 和 1 上，坡度处处是 0</figcaption>
</figure>

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

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

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

### 实验：同一张查找表网络，两种写法

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

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

- **变异搜索**，相当于简化版的 Thompson：每一轮把当前配置复制 64 份，每份随机翻转大约 2 个比特，挑出得分最高的一份；只要不比当前差，就替换掉当前配置。
- **梯度下降**：用图 3 里那种软查找表，输入也允许是 0 到 1 之间的小数，查找表的输出就是 16 个软比特按输入加权的平均。这样整张网络对配置可以求导，用 Adam 优化；每 10 步把实数按正负取回成比特，放回真正的硬电路上检查是不是全对。

<figure class="dd dd-figure dt-figure">
<div class="dt-panel">
<div class="dt-title" id="tf-exp-title">同一张查找表网络，两种写法（各 3 个随机种子）</div>
<div class="dt-scroll">
<table class="dt" aria-labelledby="tf-exp-title">
<colgroup><col style="width:14%"><col style="width:14%"><col class="hl"><col></colgroup>
<thead>
<tr><th scope="col">任务</th><th scope="col">配置位</th><th scope="col" class="acc">梯度下降：多少步全对</th><th scope="col">变异搜索：20 万次评估后</th></tr>
</thead>
<tbody>
<tr><th scope="row">2 位加法</th><td class="mono num">1584</td><td class="mono acc">50、30、30 步</td><td class="mono good">全对，分别用了 4673、10945、2945 次</td></tr>
<tr><th scope="row">3 位加法</th><td class="mono num">3136</td><td class="mono acc">80、80、90 步</td><td class="mono warn">95.3%、96.9%、96.9% 的题全对</td></tr>
<tr><th scope="row">4 位加法</th><td class="mono num">5200</td><td class="mono acc">1270、1530 步；第三个 3000 步时 97.7%</td><td class="mono bad">54.7%、65.6%、52.0%</td></tr>
<tr><th scope="row">5 位加法</th><td class="mono num">7776</td><td class="mono">4000 步时 94.2%、97.6%，没有全对（2 个种子）</td><td>没有跑（4 位已经差得很远）</td></tr>
</tbody>
</table>
</div>
<p class="dt-note">梯度一步要算一次前向和一次反向，大约相当于 3 次评估。3 位加法上，我们又给变异搜索加到 200 万次评估，它在第 153 万次时全对。</p>
</div>
<figcaption>表 2 · 配置位从 1584 涨到 5200，变异搜索从几千次评估就能写对，变成 20 万次也只对一半多</figcaption>
</figure>

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

<figure class="dd dd-figure">
<div class="dd-scroll">
<svg class="dd-svg" viewBox="0 0 800 360" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="tf-curve-title tf-curve-desc">
<title id="tf-curve-title">4 位加法：梯度下降与变异搜索的学习曲线</title>
<desc id="tf-curve-desc">横轴是前向计算次数（对数），纵轴是 256 道题全对的比例。梯度下降在约 3810 次前向（1270 步）时达到 100%；变异搜索在 60 万次评估后停在 59.8%。</desc>
<rect class="dd-frame" width="800" height="360" rx="10"/>
<rect class="dd-zone" x="16" y="16" width="768" height="328" rx="8"/>
<text class="t-zone" x="32" y="44">4 位加法 · 5200 个配置位 · 256 道题全对的比例</text>
<line class="dd-hair" x1="96" y1="264" x2="752" y2="264"/>
<text class="t-note" x="84" y="268" text-anchor="end">0%</text>
<line class="dd-hair" x1="96" y1="216" x2="752" y2="216"/>
<text class="t-note" x="84" y="220" text-anchor="end">25%</text>
<line class="dd-hair" x1="96" y1="168" x2="752" y2="168"/>
<text class="t-note" x="84" y="172" text-anchor="end">50%</text>
<line class="dd-hair" x1="96" y1="120" x2="752" y2="120"/>
<text class="t-note" x="84" y="124" text-anchor="end">75%</text>
<line class="dd-hair" x1="96" y1="72" x2="752" y2="72"/>
<text class="t-note" x="84" y="76" text-anchor="end">100%</text>
<line class="dd-hair" x1="96" y1="72" x2="96" y2="264"/>
<text class="t-note" x="96" y="284" text-anchor="middle">10</text>
<line class="dd-hair" x1="227" y1="72" x2="227" y2="264"/>
<text class="t-note" x="227" y="284" text-anchor="middle">100</text>
<line class="dd-hair" x1="358" y1="72" x2="358" y2="264"/>
<text class="t-note" x="358" y="284" text-anchor="middle">1000</text>
<line class="dd-hair" x1="490" y1="72" x2="490" y2="264"/>
<text class="t-note" x="490" y="284" text-anchor="middle">1万</text>
<line class="dd-hair" x1="621" y1="72" x2="621" y2="264"/>
<text class="t-note" x="621" y="284" text-anchor="middle">10万</text>
<line class="dd-hair" x1="752" y1="72" x2="752" y2="264"/>
<text class="t-note" x="752" y="284" text-anchor="middle">100万</text>
<text class="t-note" x="424" y="308" text-anchor="middle">前向计算次数（梯度一步按 3 次算）</text>
<path class="dd-edge" d="M372.5 241.5 L412.0 230.2 L435.1 222.0 L451.5 218.2 L464.2 216.0 L474.6 213.0 L483.3 210.8 L491.0 204.8 L497.7 203.2 L503.7 199.5 L509.1 195.8 L514.1 195.8 L518.6 194.2 L522.8 193.5 L526.8 193.5 L530.4 192.0 L533.9 191.2 L537.2 191.2 L540.2 189.0 L543.2 183.0 L545.9 180.0 L548.6 178.5 L551.1 178.5 L553.6 177.0 L555.9 175.5 L558.1 174.8 L560.3 173.2 L562.3 173.2 L564.3 172.5 L566.3 172.5 L568.1 171.8 L569.9 171.8 L571.7 170.2 L573.4 170.2 L575.0 170.2 L576.7 170.2 L578.2 170.2 L579.7 169.5 L581.2 169.5 L582.7 168.8 L584.1 168.8 L585.4 168.8 L586.8 168.0 L588.1 167.2 L589.4 166.5 L590.6 166.5 L591.8 166.5 L593.0 166.5 L594.2 166.5 L595.4 165.8 L596.5 165.8 L597.6 165.0 L598.7 165.0 L599.8 165.0 L600.8 165.0 L601.8 165.0 L602.8 162.8 L603.8 162.8 L604.8 162.8 L605.8 162.8 L606.7 162.8 L607.6 162.8 L608.5 162.8 L609.4 162.8 L610.3 162.8 L611.2 162.8 L612.0 162.8 L612.9 162.8 L613.7 162.8 L614.5 162.8 L615.4 162.8 L616.1 162.8 L616.9 162.8 L617.7 162.8 L618.5 162.8 L619.2 162.8 L620.0 162.8 L620.7 162.8 L621.4 162.8 L622.2 162.8 L622.9 162.8 L623.6 162.8 L624.2 162.8 L624.9 162.8 L625.6 162.8 L626.3 162.8 L626.9 162.8 L627.6 162.8 L628.2 162.8 L628.9 162.8 L629.5 162.8 L630.1 162.8 L630.7 162.8 L631.3 162.8 L631.9 162.8 L632.5 162.8 L633.1 162.8 L633.7 162.8 L634.3 162.8 L634.9 162.8 L635.4 162.8 L636.0 162.8 L636.6 162.8 L637.1 162.8 L637.6 162.8 L638.2 162.8 L638.7 162.8 L639.3 162.8 L639.8 162.0 L640.3 162.0 L640.8 162.0 L641.3 162.0 L641.8 161.2 L642.3 161.2 L642.8 161.2 L643.3 161.2 L643.8 161.2 L644.3 161.2 L644.8 161.2 L645.3 161.2 L645.7 161.2 L646.2 161.2 L646.7 161.2 L647.1 161.2 L647.6 161.2 L648.0 161.2 L648.5 161.2 L648.9 161.2 L649.4 161.2 L649.8 161.2 L650.3 161.2 L650.7 161.2 L651.1 161.2 L651.5 161.2 L652.0 161.2 L652.4 161.2 L652.8 161.2 L653.2 161.2 L653.6 161.2 L654.0 161.2 L654.4 161.2 L654.8 161.2 L655.2 161.2 L655.6 160.5 L656.0 160.5 L656.4 160.5 L656.8 160.5 L657.2 159.8 L657.6 159.0 L658.0 159.0 L658.3 159.0 L658.7 159.0 L659.1 159.0 L659.5 159.0 L659.8 159.0 L660.2 159.0 L660.6 159.0 L660.9 159.0 L661.3 159.0 L661.6 159.0 L662.0 159.0 L662.4 159.0 L662.7 159.0 L663.1 159.0 L663.4 159.0 L663.7 159.0 L664.1 159.0 L664.4 159.0 L664.8 159.0 L665.1 159.0 L665.4 159.0 L665.8 159.0 L666.1 159.0 L666.4 159.0 L666.8 159.0 L667.1 159.0 L667.4 159.0 L667.7 159.0 L668.0 159.0 L668.4 159.0 L668.7 159.0 L669.0 159.0 L669.3 159.0 L669.6 159.0 L669.9 159.0 L670.2 159.0 L670.5 159.0 L670.8 159.0 L671.1 159.0 L671.4 159.0 L671.7 159.0 L672.0 159.0 L672.3 159.0 L672.6 159.0 L672.9 159.0 L673.2 159.0 L673.5 158.2 L673.8 158.2 L674.1 158.2 L674.4 158.2 L674.6 158.2 L674.9 158.2 L675.2 158.2 L675.5 158.2 L675.8 158.2 L676.0 158.2 L676.3 158.2 L676.6 158.2 L676.9 158.2 L677.1 158.2 L677.4 158.2 L677.7 157.5 L677.9 157.5 L678.2 156.8 L678.5 156.8 L678.7 156.8 L679.0 156.8 L679.3 156.8 L679.5 156.8 L679.8 156.8 L680.1 156.8 L680.3 156.8 L680.6 156.8 L680.8 156.8 L681.1 156.8 L681.3 156.8 L681.6 156.8 L681.8 156.8 L682.1 156.8 L682.3 156.8 L682.6 156.8 L682.8 156.8 L683.1 156.8 L683.3 156.8 L683.6 156.8 L683.8 156.8 L684.0 156.8 L684.3 156.8 L684.5 156.8 L684.7 156.8 L685.0 156.8 L685.2 156.8 L685.5 156.8 L685.7 156.8 L685.9 156.8 L686.2 156.8 L686.4 156.8 L686.6 156.8 L686.8 156.8 L687.1 156.0 L687.3 156.0 L687.5 156.0 L687.8 156.0 L688.0 156.0 L688.2 156.0 L688.4 156.0 L688.6 156.0 L688.9 156.0 L689.1 156.0 L689.3 156.0 L689.5 156.0 L689.7 156.0 L690.0 156.0 L690.2 156.0 L690.4 156.0 L690.6 156.0 L690.8 156.0 L691.0 156.0 L691.2 156.0 L691.5 156.0 L691.7 156.0 L691.9 156.0 L692.1 156.0 L692.3 156.0 L692.5 156.0 L692.7 156.0 L692.9 156.0 L693.1 156.0 L693.3 156.0 L693.5 156.0 L693.7 156.0 L693.9 156.0 L694.1 156.0 L694.3 156.0 L694.5 156.0 L694.7 156.0 L694.9 156.0 L695.1 156.0 L695.3 156.0 L695.5 156.0 L695.7 156.0 L695.9 156.0 L696.1 156.0 L696.3 156.0 L696.5 156.0 L696.7 156.0 L696.9 156.0 L697.1 156.0 L697.3 156.0 L697.5 156.0 L697.7 156.0 L697.8 156.0 L698.0 156.0 L698.2 156.0 L698.4 156.0 L698.6 156.0 L698.8 156.0 L699.0 156.0 L699.1 156.0 L699.3 156.0 L699.5 156.0 L699.7 156.0 L699.9 156.0 L700.1 156.0 L700.2 156.0 L700.4 156.0 L700.6 156.0 L700.8 156.0 L701.0 156.0 L701.1 156.0 L701.3 156.0 L701.5 156.0 L701.7 156.0 L701.8 156.0 L702.0 156.0 L702.2 155.2 L702.4 155.2 L702.5 155.2 L702.7 154.5 L702.9 154.5 L703.1 154.5 L703.2 154.5 L703.4 154.5 L703.6 154.5 L703.8 154.5 L703.9 154.5 L704.1 154.5 L704.3 154.5 L704.4 154.5 L704.6 154.5 L704.8 154.5 L704.9 154.5 L705.1 154.5 L705.3 153.8 L705.4 153.8 L705.6 153.8 L705.8 153.8 L705.9 153.8 L706.1 153.8 L706.2 153.8 L706.4 153.8 L706.6 153.8 L706.7 153.8 L706.9 153.8 L707.1 153.8 L707.2 153.8 L707.4 153.8 L707.5 153.8 L707.7 153.8 L707.9 153.8 L708.0 153.8 L708.2 153.8 L708.3 153.8 L708.5 153.8 L708.6 153.8 L708.8 153.8 L709.0 153.8 L709.1 153.8 L709.3 153.8 L709.4 153.8 L709.6 153.8 L709.7 153.8 L709.9 153.8 L710.0 153.8 L710.2 153.8 L710.3 153.8 L710.5 153.8 L710.6 153.8 L710.8 153.8 L710.9 153.8 L711.1 153.8 L711.2 153.8 L711.4 153.8 L711.5 153.8 L711.7 153.8 L711.8 153.8 L712.0 153.8 L712.1 153.8 L712.3 153.8 L712.4 153.8 L712.6 153.8 L712.7 153.8 L712.9 153.8 L713.0 153.8 L713.1 153.8 L713.3 153.8 L713.4 153.8 L713.6 153.8 L713.7 153.8 L713.9 153.8 L714.0 153.8 L714.1 153.8 L714.3 153.8 L714.4 153.8 L714.6 153.8 L714.7 153.0 L714.8 153.0 L715.0 153.0 L715.1 153.0 L715.3 153.0 L715.4 153.0 L715.5 153.0 L715.7 153.0 L715.8 153.0 L716.0 153.0 L716.1 153.0 L716.2 153.0 L716.4 153.0 L716.5 153.0 L716.6 153.0 L716.8 153.0 L716.9 153.0 L717.0 153.0 L717.2 153.0 L717.3 152.2 L717.4 152.2 L717.6 152.2 L717.7 152.2 L717.8 152.2 L718.0 152.2 L718.1 150.0 L718.2 150.0 L718.4 149.2 L718.5 149.2 L718.6 149.2 L718.8 149.2 L718.9 149.2 L719.0 149.2 L719.2 149.2 L719.3 149.2 L719.4 149.2 L719.5 149.2 L719.7 149.2 L719.8 149.2 L719.9 149.2 L720.1 149.2 L720.2 149.2 L720.3 149.2 L720.4 149.2 L720.6 149.2 L720.7 149.2 L720.8 149.2 L720.9 149.2 L721.1 149.2 L721.2 149.2 L721.3 149.2 L721.4 149.2 L721.6 149.2 L721.7 149.2 L721.8 149.2 L721.9 149.2 L722.1 149.2 L722.2 149.2 L722.3 149.2 L722.4 149.2 L722.6 149.2 L722.7 149.2 L722.8 149.2 L722.9 149.2"/>
<path class="dd-edge-acc" style="stroke-width:2.4" d="M158.6 252.0 L198.1 248.2 L221.2 216.0 L237.6 167.2 L250.3 157.5 L260.7 138.8 L269.5 118.5 L277.1 111.8 L283.8 107.2 L289.8 96.8 L295.2 91.5 L300.2 85.5 L304.7 83.2 L309.0 80.2 L312.9 79.5 L316.6 78.0 L320.0 78.0 L323.3 78.0 L326.4 78.0 L329.3 78.0 L332.1 78.0 L334.7 78.0 L337.3 76.5 L339.7 76.5 L342.0 75.0 L344.2 75.0 L346.4 75.0 L348.5 75.0 L350.5 75.0 L352.4 75.0 L354.3 75.0 L356.1 73.5 L357.8 73.5 L359.5 73.5 L361.2 73.5 L362.8 73.5 L364.3 73.5 L365.9 73.5 L367.3 73.5 L368.8 73.5 L370.2 73.5 L371.6 73.5 L372.9 73.5 L374.2 73.5 L375.5 73.5 L376.8 73.5 L378.0 73.5 L379.2 73.5 L380.4 73.5 L381.5 73.5 L382.6 73.5 L383.7 73.5 L384.8 73.5 L385.9 73.5 L386.9 73.5 L388.0 73.5 L389.0 73.5 L390.0 73.5 L390.9 73.5 L391.9 73.5 L392.8 73.5 L393.8 73.5 L394.7 73.5 L395.6 73.5 L396.5 73.5 L397.3 73.5 L398.2 73.5 L399.0 73.5 L399.9 73.5 L400.7 73.5 L401.5 73.5 L402.3 73.5 L403.1 73.5 L403.8 73.5 L404.6 73.5 L405.4 73.5 L406.1 73.5 L406.8 73.5 L407.6 73.5 L408.3 73.5 L409.0 73.5 L409.7 73.5 L410.4 73.5 L411.1 73.5 L411.7 73.5 L412.4 73.5 L413.1 73.5 L413.7 73.5 L414.4 73.5 L415.0 73.5 L415.6 73.5 L416.2 73.5 L416.9 73.5 L417.5 73.5 L418.1 73.5 L418.7 73.5 L419.3 73.5 L419.8 73.5 L420.4 72.8 L421.0 72.8 L421.6 72.8 L422.1 72.8 L422.7 72.8 L423.2 72.8 L423.8 72.8 L424.3 72.8 L424.9 72.8 L425.4 72.8 L425.9 72.8 L426.4 72.8 L426.9 72.8 L427.5 72.8 L428.0 72.8 L428.5 72.8 L429.0 72.8 L429.5 72.8 L429.9 72.8 L430.4 72.8 L430.9 72.8 L431.4 72.8 L431.9 72.8 L432.3 72.8 L432.8 72.8 L433.3 72.8 L433.7 72.8 L434.2 72.8 L434.6 72.0"/>
<circle class="dd-fill-acc" cx="434.6" cy="72.0" r="5"/>
<text class="t-acc t-mono" x="445" y="90">梯度下降：1270 步全对</text>
<circle class="dd-dot" cx="722.9" cy="149.2" r="5"/>
<text class="t-mono" x="715" y="197" text-anchor="end">变异搜索：60 万次后 59.8%</text>
</svg>
</div>
<figcaption>图 4 · 同一张查找表网络：梯度在约 4000 次前向内写对了 5200 个配置位，变异搜索用了 60 万次评估还差四成</figcaption>
</figure>

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

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

### 解决：把比特放松成实数

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

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

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

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

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

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

<figure class="dd dd-figure">
<div class="dd-scroll">
<svg class="dd-svg" viewBox="0 0 800 400" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="tf-route-title tf-route-desc">
<title id="tf-route-title">按内容取数：固定导线与注意力</title>
<desc id="tf-route-desc">左边是固定电路：北京、上海、广东、四川四个槽位，每个后面接一个“等于上海吗”的比较器，比较结果都接到一个选择器上，选出“沪”；每多一个槽位就要多一套比较器和一路选择。右边是注意力：查询向量和四个键做点积，得到分数负 0.3、4.1、0.2、负 0.6，经过 softmax 变成权重 0.01、0.96、0.02、0.01，按权重把值加起来，结果约等于“沪”；同一组投影矩阵适用于任意多个槽位。</desc>
<defs>
<marker id="tf-route-arrow" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto"><path class="dd-head" d="M1 1 L7 4 L1 7"/></marker>
<marker id="tf-route-arrow-acc" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto"><path class="dd-head-acc" d="M1 1 L7 4 L1 7"/></marker>
</defs>
<rect class="dd-frame" width="800" height="400" rx="10"/>
<rect class="dd-zone" x="16" y="16" width="376" height="320" rx="8"/>
<rect class="dd-zone" x="408" y="16" width="376" height="320" rx="8"/>
<text class="t-zone" x="32" y="44">固定导线：每个槽位一套比较器</text>
<text class="t-zone" x="424" y="44">注意力：权重每一步现算</text>
<!-- left edges -->
<line class="dd-edge" x1="136" y1="82" x2="160" y2="82" marker-end="url(#tf-route-arrow)"/>
<line class="dd-edge-acc" x1="136" y1="130" x2="160" y2="130" marker-end="url(#tf-route-arrow-acc)"/>
<line class="dd-edge" x1="136" y1="178" x2="160" y2="178" marker-end="url(#tf-route-arrow)"/>
<line class="dd-edge" x1="136" y1="226" x2="160" y2="226" marker-end="url(#tf-route-arrow)"/>
<line class="dd-edge" x1="256" y1="82" x2="284" y2="82" marker-end="url(#tf-route-arrow)"/>
<line class="dd-edge-acc" x1="256" y1="130" x2="284" y2="130" marker-end="url(#tf-route-arrow-acc)"/>
<line class="dd-edge" x1="256" y1="178" x2="284" y2="178" marker-end="url(#tf-route-arrow)"/>
<line class="dd-edge" x1="256" y1="226" x2="284" y2="226" marker-end="url(#tf-route-arrow)"/>
<line class="dd-edge-acc" x1="328" y1="248" x2="328" y2="268" marker-end="url(#tf-route-arrow-acc)"/>
<!-- left cards -->
<rect class="dd-card" x="32" y="64" width="104" height="36" rx="6"/><text class="t-mono" x="48" y="87">北京 → 京</text>
<rect class="dd-card" x="32" y="112" width="104" height="36" rx="6"/><text class="t-acc t-mono" x="48" y="135">上海 → 沪</text>
<rect class="dd-card" x="32" y="160" width="104" height="36" rx="6"/><text class="t-mono" x="48" y="183">广东 → 粤</text>
<rect class="dd-card" x="32" y="208" width="104" height="36" rx="6"/><text class="t-mono" x="48" y="231">四川 → 川</text>
<rect class="dd-card" x="164" y="64" width="92" height="36" rx="6"/><text class="t-mono" x="176" y="87">= 上海?</text>
<rect class="dd-card" x="164" y="112" width="92" height="36" rx="6"/><text class="t-acc t-mono" x="176" y="135">= 上海?</text>
<rect class="dd-card" x="164" y="160" width="92" height="36" rx="6"/><text class="t-mono" x="176" y="183">= 上海?</text>
<rect class="dd-card" x="164" y="208" width="92" height="36" rx="6"/><text class="t-mono" x="176" y="231">= 上海?</text>
<rect class="dd-card" x="288" y="64" width="80" height="184" rx="6"/><text class="t-name" x="328" y="160" text-anchor="middle">选择器</text>
<rect class="dd-card" x="288" y="272" width="80" height="36" rx="6"/><text class="t-acc t-mono" x="328" y="295" text-anchor="middle">沪</text>
<text class="t-note" x="32" y="284">槽位数在烧写时定死</text>
<text class="t-note" x="32" y="304">多一个槽位，多一套比较器</text>
<text class="t-note" x="32" y="324">和一路选择</text>
<!-- right -->
<rect class="dd-card" x="424" y="60" width="344" height="36" rx="6"/>
<text class="t-mono" x="440" y="83">查询 q = W<tspan baseline-shift="sub" font-size="9">Q</tspan> · <tspan class="t-acc">上海</tspan></text>
<rect class="dd-card" x="424" y="108" width="344" height="136" rx="6"/>
<path class="dd-card-head" d="M425 140 V115 Q425 109 431 109 H761 Q767 109 767 115 V140 Z"/>
<text class="t-name" x="440" y="129">键 kᵢ</text><text class="t-name" x="568" y="129">q·kᵢ</text><text class="t-name" x="664" y="129">权重</text>
<text class="t-mono" x="440" y="157">北京</text><text class="t-val" x="568" y="157">−0.3</text><text class="t-val" x="664" y="157">0.01</text>
<line class="dd-hair" x1="424" y1="166" x2="768" y2="166"/>
<text class="t-acc t-mono" x="440" y="183">上海</text><text class="t-acc t-mono" x="568" y="183">4.1</text><text class="t-acc t-mono" x="664" y="183">0.96</text>
<line class="dd-hair" x1="424" y1="192" x2="768" y2="192"/>
<text class="t-mono" x="440" y="209">广东</text><text class="t-val" x="568" y="209">0.2</text><text class="t-val" x="664" y="209">0.02</text>
<line class="dd-hair" x1="424" y1="218" x2="768" y2="218"/>
<text class="t-mono" x="440" y="235">四川</text><text class="t-val" x="568" y="235">−0.6</text><text class="t-val" x="664" y="235">0.01</text>
<line class="dd-edge-acc" x1="596" y1="244" x2="596" y2="268" marker-end="url(#tf-route-arrow-acc)"/>
<rect class="dd-card" x="424" y="272" width="344" height="36" rx="6"/>
<text class="t-mono" x="440" y="295">Σ 权重 × vᵢ ≈ <tspan class="t-acc">沪</tspan></text>
<text class="t-note" x="700" y="295">数值示意</text>
<text class="t-say" x="32" y="364">左边的“比较什么、从哪几个位置选”都写在配置里；右边只写了 W<tspan baseline-shift="sub" font-size="9">Q</tspan>、W<tspan baseline-shift="sub" font-size="9">K</tspan>、W<tspan baseline-shift="sub" font-size="9">V</tspan> 三个矩阵。</text>
<text class="t-say" x="32" y="388">槽位从 4 个变成 4000 个，右边的参数一个不多，算的次数跟着变多。</text>
</svg>
</div>
<figcaption>图 5 · 固定电路也能按内容选，但每条可能的路都要预先铺好；注意力把“比较”做成了所有位置共用的一组参数</figcaption>
</figure>

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

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

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

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

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

### 边写边读：把中间结果写回输入

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

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

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

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

<figure class="dd dd-figure dt-figure">
<div class="dt-panel">
<div class="dt-title" id="tf-scale-title">配置有多大，是怎么写出来的</div>
<div class="dt-scroll">
<table class="dt" aria-labelledby="tf-scale-title">
<colgroup><col style="width:22%"><col style="width:10%"><col><col class="hl"></colgroup>
<thead>
<tr><th scope="col">对象</th><th scope="col">年份</th><th scope="col">配置有多大</th><th scope="col" class="acc">配置怎么写出来</th></tr>
</thead>
<tbody>
<tr><th scope="row">XC2064</th><td class="mono">1985</td><td>64 个 4 输入逻辑块</td><td>工程师手写</td></tr>
<tr><th scope="row">Thompson 的进化电路</th><td class="mono">1996</td><td class="mono">1800 bit</td><td>遗传算法，约 25 万次评估、2–3 周</td></tr>
<tr><th scope="row">本文的查找表网络</th><td class="mono">2026</td><td class="mono">1584–7776 bit</td><td>梯度几十到几千步；变异搜索写对 3 位加法要 153 万次评估</td></tr>
<tr><th scope="row">VP1902</th><td class="mono">2023</td><td>846 万个 6 输入 LUT，查找表内容约 5.4 亿 bit（不含布线）</td><td>工程师写 Verilog，工具综合、布局布线</td></tr>
<tr><th scope="row">GPT-3</th><td class="mono">2020</td><td>1750 亿个参数，按 16 bit 存约 2.8 万亿 bit</td><td class="acc">梯度下降，约 3000 亿个 token，3.14×10²³ 次浮点运算</td></tr>
</tbody>
</table>
</div>
<p class="dt-note">VP1902 的 5.4 亿 bit 是按 846 万 × 64 估算的，只算查找表本身；GPT-3 的配置比它多约 5000 倍。</p>
</div>
<figcaption>表 3 · 配置的规模从几千比特到几万亿比特，能写到这么大的只有梯度下降</figcaption>
</figure>

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

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

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

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

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

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

<figure class="dd dd-figure">
<div class="dd-scroll">
<svg class="dd-svg" viewBox="0 0 800 320" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="tf-time-title tf-time-desc">
<title id="tf-time-title">FPGA 和神经网络的两条时间线</title>
<desc id="tf-time-desc">上面一条是 FPGA：1985 年 XC2064 有 64 个逻辑块，1996 年 Thompson 进化出 1800 比特的电路，2018 年 Brainwave 在 FPGA 上跑神经网络，2022 年出现可微逻辑门网络。下面一条是神经网络：1986 年反向传播，2012 年 AlexNet，2017 年 Transformer，2020 年 GPT-3。2018 年和 2022 年两条线之间有虚线相连：FPGA 开始承载神经网络，可微逻辑门让梯度来写查找表。</desc>
<defs>
<marker id="tf-time-arrow-acc" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto"><path class="dd-head-acc" d="M1 1 L7 4 L1 7"/></marker>
</defs>
<rect class="dd-frame" width="800" height="320" rx="10"/>
<rect class="dd-zone" x="16" y="16" width="768" height="116" rx="8"/>
<rect class="dd-zone" x="16" y="148" width="768" height="116" rx="8"/>
<text class="t-zone" x="32" y="44">FPGA：配置由人写</text>
<text class="t-zone" x="32" y="176">神经网络：配置由梯度写</text>
<line class="dd-edge" x1="48" y1="96" x2="768" y2="96"/>
<line class="dd-edge" x1="48" y1="228" x2="768" y2="228"/>
<path class="dd-edge-acc dd-dash" d="M646 222 V104" marker-end="url(#tf-time-arrow-acc)"/>
<path class="dd-edge-acc dd-dash" d="M717 222 V104" marker-end="url(#tf-time-arrow-acc)"/>
<circle class="dd-dot" cx="66" cy="96" r="5"/>
<circle class="dd-dot" cx="259" cy="96" r="5"/>
<circle class="dd-fill-acc" cx="646" cy="96" r="5"/>
<circle class="dd-fill-acc" cx="717" cy="96" r="5"/>
<circle class="dd-dot" cx="83" cy="228" r="5"/>
<circle class="dd-dot" cx="540" cy="228" r="5"/>
<circle class="dd-dot" cx="629" cy="228" r="5"/>
<circle class="dd-dot" cx="682" cy="228" r="5"/>
<text class="t-mono" x="66" y="76">1985 XC2064</text>
<text class="t-note" x="66" y="120">64 个逻辑块</text>
<text class="t-mono" x="259" y="76">1996 进化电路</text>
<text class="t-note" x="259" y="120">1800 bit，2–3 周</text>
<text class="t-acc t-mono" x="638" y="76" text-anchor="end">2018 Brainwave</text>
<text class="t-acc t-mono" x="776" y="76" text-anchor="end">2022 可微逻辑门</text>
<text class="t-note" x="638" y="120" text-anchor="end">FPGA 跑神经网络</text>
<text class="t-mono" x="83" y="208">1986 反向传播</text>
<text class="t-mono" x="532" y="208" text-anchor="end">2012 AlexNet · GPU</text>
<text class="t-mono" x="621" y="252" text-anchor="end">2017 Transformer</text>
<text class="t-mono" x="690" y="252">2020 GPT-3</text>
<text class="t-say" x="32" y="292">两条线并行了三十多年；交汇时，FPGA 上的配置也改由梯度来写（虚线）。</text>
</svg>
</div>
<figcaption>图 6 · FPGA 和神经网络是同时代的；智能出现在梯度能写的那条线上</figcaption>
</figure>

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

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

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

**第三层：就算有了连续参数，还要有合适的结构和足够的规模。** 注意力让“连哪里”变成数据的函数，一组参数能用在任意长度的输入上；而涌现出现在配置、数据和算力都很大的时候。等到这几样凑齐，跑训练的硬件已经是 GPU 了：它天生适合大块的矩阵乘法，改一行代码几秒钟就能重新跑，而 FPGA 改一次设计要等几个小时的布局布线。Hooker 把这种“一条研究路线能不能赢，取决于它是否碰巧适合当时的硬件”的现象叫作**硬件彩票**[^lottery]，神经网络抽中的是 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 个参数，那是梯度下降自己找到的解（详见[《做加法最少要几个参数》](/blog/adderboard-tiny-transformer/)）。

所以 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 位加法[^notes]。

## 参考资料
