﻿---
title: "能不能用 FPGA 做一块 Transformer 专用推理电路"
description: "一块 H100 只给一个用户生成文字时，不到 0.3% 的乘法器在干活。既然 FPGA 能搭出任意电路，能不能专门为 Transformer 搭一块，让推理更快、更省电、更便宜？我们先看 decode 时芯片到底在等什么，再看专用电路能省在哪里；接着看已经做出来的 FPGA 推理流水线赢在哪、又被什么卡住；最后用一个综合实验看把权重“焊进电路”能省多少面积，以及为什么这条路最后通向 ASIC。"
pubDate: 2026-10-25
tags: ["AI", "Transformer", "FPGA", "推理", "芯片"]
hero: "./hero.svg"
clc: "TP183"
refs:
  - id: h100
    title: "NVIDIA H100 Tensor Core GPU"
    url: https://www.nvidia.com/en-us/data-center/h100/
    site: NVIDIA
    note: "H100 SXM：80 GB HBM3，带宽 3.35 TB/s，BF16 稠密算力约 989 TFLOPS（官网标的 1979 是稀疏数字），功耗最高 700 W。"
  - id: positron
    title: "Positron Atlas"
    url: https://www.bittware.com/products/positron-atlas/
    site: BittWare
    note: "Positron 的第一代推理服务器，用 Intel Agilex 7 M 系列 FPGA（带 HBM）。厂商称对 H100 有 3.5 倍的性价比和能效；演示里 Llama 3.1 8B 在 H100 上约 140 token/s。都是厂商数字。"
  - id: horowitz
    title: "Computing's Energy Problem (and what we can do about it)"
    url: https://ieeexplore.ieee.org/document/6757323
    author: Mark Horowitz
    site: ISSCC 2014
    note: "正文第 5、6 节：处理器每条指令约 70 pJ 的开销，一次运算只要几 pJ；缓存取数 20 pJ；DRAM 一次访问 1–2 nJ；专用硬件比处理器高效 2–3 个数量级。45 nm 工艺。"
  - id: flightllm
    title: "FlightLLM: Efficient Large Language Model Inference with a Complete Mapping Flow on FPGAs"
    url: https://arxiv.org/abs/2401.03868
    author: Shulin Zeng 等
    site: FPGA 2024
    note: "Alveo U280 上跑 LLaMA2-7B，权重平均 3.5 bit、N:M 稀疏，225 MHz，HBM 利用率 65.9%；能效是 V100S 的 6.0 倍、优化后 A100 的 3.8 倍，性价比是 V100S 的 1.8 倍（U280 按约 8000 美元算）。"
  - id: spatial
    title: "Understanding the Potential of FPGA-Based Spatial Acceleration for Large Language Model Inference"
    url: https://arxiv.org/abs/2312.15159
    author: Hongzheng Chen 等
    site: ACM TRETS
    date: "2024"
    note: "同样在 U280 上，GPT 的 decode 比 A100 快 1.9 倍、能效高 5.7 倍。"
  - id: u280
    title: "Alveo U280 Data Center Accelerator Card Data Sheet (DS963)"
    url: https://docs.amd.com/r/en-US/ds963-u280
    site: AMD
    note: "8 GB HBM2，460 GB/s；约 130 万个 LUT、9024 个 DSP、2016 块 BRAM、960 块 UltraRAM，片上存储约 41 MB（TerEffic 论文里写约 42 MB）；最大功耗 225 W。"
  - id: v80
    title: "AMD Alveo V80 Compute Accelerator"
    url: https://www.amd.com/en/products/accelerators/alveo/v80/a-v80-p64g-pq-g.html
    site: AMD
    note: "32 GB HBM2e，约 820 GB/s；约 260 万个 LUT、10848 个 DSP，片上存储 673 Mb（约 84 MB）；TDP 190 W，标价 9495 美元。"
  - id: tereffic
    title: "TerEffic: Highly Efficient Ternary LLM Inference on FPGA"
    url: https://arxiv.org/abs/2502.16473
    author: Chenyang Yin 等
    date: "2025"
    note: "三值权重（每个 −1/0/+1，5 个打包成 8 bit，平均 1.6 bit）。两块 U280 把 3.7 亿参数模型全部放在片上，单批 16300 token/s；功耗 35.8 W 来自 Vivado 功耗报告，不是实测。HBM 版跑 27 亿参数模型 727 token/s，约为 A100 的 3 倍。"
  - id: groq
    title: "A Review on Proprietary Accelerators for Large Language Models"
    url: https://arxiv.org/abs/2503.09650
    date: "2025"
    note: "综述里 Groq LPU 的数据：每片 230 MB SRAM，片内带宽 80 TB/s；跑 Llama 70B 这类模型要几百片。Groq 演示 Llama 2 70B 用了 576 片。"
  - id: cerebras
    title: "Cerebras Wafer-Scale Engine 3"
    url: https://www.cerebras.ai/chip
    site: Cerebras
    note: "一整片晶圆做成的芯片，片上 SRAM 44 GB。"
  - id: kuon
    title: "Measuring the Gap Between FPGAs and ASICs"
    url: https://www.eecg.utoronto.ca/~jayar/pubs/kuon/kuontcad06.pdf
    author: Ian Kuon, Jonathan Rose
    site: IEEE TCAD
    date: "2007"
    note: "90 nm 工艺：只用 LUT 时 FPGA 面积是 ASIC 的约 35 倍，大量用硬核乘法器和存储块时可降到 4.7 倍；速度慢 3.4–4.6 倍；动态功耗约 14 倍。"
  - id: taalas
    title: "AI inference cast in silicon: Taalas announces HC1 chip"
    url: https://heise.de/-11185112
    site: heise online
    date: "2026-02"
    note: "Llama 3.1 8B 直接做进 TSMC N6 工艺的芯片：815 mm²、530 亿晶体管，3 bit / 6 bit 混合格式，约 200 W 一张卡；厂商称每用户约 17000 token/s。只能跑这一个基础模型，可以加 LoRA。"
  - id: asimov
    title: "Positron jumps up to the big league investment circle"
    url: https://www.jonpeddie.com/news/positron-jumps-up-to-the-big-league-investment-circle/
    site: Jon Peddie Research
    note: "Positron 从第一代 FPGA 系统 Atlas 转向自研 ASIC Asimov，并说以后保持“FPGA 先行、ASIC 跟进”的节奏。"
  - id: notes
    title: "本文的实验代码与调研笔记"
    note: "`/mnt/project-files/fpga-transformer-research/`：`experiment/batch_bw.py` 是批处理实验，`gen_dot.py`、`run_synth.sh`、`check_dot.py` 是综合实验，`cost_model.py` 是成本估算，输出在 `logs/`。yosys 0.33、Icarus Verilog、numpy 2.5，4 核 Xeon。"
---

用 GPU 跑一个 80 亿参数的模型，比如 Llama 3.1 8B，只给一个人用，一块 H100 每秒大约能吐出 140 个 token[^positron]。生成一个 token，模型要做大约 160 亿次浮点运算（每个参数一次乘法一次加法），一秒 140 个就是 2.2 万亿次。H100 的标称算力是每秒 989 万亿次[^h100]。

两个数一除，不到 0.3%。也就是说，这块三万美元的卡，在给一个人写字的时候，绝大部分乘法器是闲着的。

这就让人想问：GPU 是为各种计算设计的通用芯片，如果我们只跑 Transformer，能不能为它专门搭一块电路？FPGA 正好能搭出任意电路，而且不用去流片。这样一块专用电路，会不会更快、更省电、更便宜？它又会卡在哪里？

## 一、推理时芯片在等什么

### 现象：算力用不满

先弄清楚那 99.7% 的乘法器为什么闲着。大模型生成文字分两段：先把用户的整段输入一次性算完（prefill），然后一个 token 一个 token 地往外写（decode）。我们和模型聊天时看到的“逐字往外蹦”，就是 decode。

decode 每写一个 token，都要让这个 token 依次穿过模型的每一层，每一层都要用到这一层的全部权重。80 亿个参数，每个用 16 位浮点存，一共 16 GB。所以**每生成一个 token，芯片就要把 16 GB 的权重从显存里完整地读一遍**，而读进来的每个参数只参与一次乘法和一次加法。

<figure class="dd dd-figure">
<div class="dd-scroll">
<svg class="dd-svg" viewBox="0 0 800 280" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="ft-flow-title ft-flow-desc">
<title id="ft-flow-title">decode 一个 token 时，数据从显存流到计算单元</title>
<desc id="ft-flow-desc">左边是显存里的 16 GB 权重，每生成一个 token 要全部读一遍；中间是 3.35 TB/s 的带宽；右边是 989 TFLOPS 的计算单元。读权重要 4.8 毫秒，计算只要 0.016 毫秒，乘法器忙碌的时间约 0.3%。</desc>
<defs>
<marker id="ft-flow-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="280" rx="10"/>
<rect class="dd-zone" x="16" y="16" width="300" height="248" rx="8"/>
<text class="t-zone" x="32" y="44">显存 · HBM3 80 GB</text>
<rect class="dd-zone" x="484" y="16" width="300" height="248" rx="8"/>
<text class="t-zone" x="500" y="44">计算单元 · H100 SXM</text>
<line class="dd-edge-acc" x1="300" y1="132" x2="496" y2="132" marker-end="url(#ft-flow-arrow-acc)"/>
<text class="t-acc t-mono" x="400" y="122" text-anchor="middle">3.35 TB/s</text>
<text class="t-note" x="400" y="152" text-anchor="middle">每个 token 一趟</text>
<rect class="dd-card" x="32" y="64" width="268" height="136" rx="6"/>
<path class="dd-card-head" d="M38 64 H294 A6 6 0 0 1 300 70 V96 H32 V70 A6 6 0 0 1 38 64 Z"/>
<text class="t-name" x="48" y="85">权重</text>
<text class="t-mono" x="284" y="85" text-anchor="end">Llama 3.1 8B</text>
<text class="t-mono" x="48" y="113">参数量</text><text class="t-val" x="284" y="113" text-anchor="end">80 亿</text>
<line class="dd-hair" x1="32" y1="122" x2="300" y2="122"/>
<text class="t-mono" x="48" y="139">精度</text><text class="t-val" x="284" y="139" text-anchor="end">FP16 · 2 字节</text>
<line class="dd-hair" x1="32" y1="148" x2="300" y2="148"/>
<text class="t-mono" x="48" y="165">大小</text><text class="t-acc t-mono" x="284" y="165" text-anchor="end">16 GB</text>
<line class="dd-hair" x1="32" y1="174" x2="300" y2="174"/>
<text class="t-mono" x="48" y="191">每个 token</text><text class="t-val" x="284" y="191" text-anchor="end">全部读一遍</text>
<rect class="dd-card" x="500" y="64" width="268" height="136" rx="6"/>
<path class="dd-card-head" d="M506 64 H762 A6 6 0 0 1 768 70 V96 H500 V70 A6 6 0 0 1 506 64 Z"/>
<text class="t-name" x="516" y="85">生成 1 个 token</text>
<text class="t-mono" x="752" y="85" text-anchor="end">批大小 1</text>
<text class="t-mono" x="516" y="113">乘加</text><text class="t-val" x="752" y="113" text-anchor="end">16 GFLOP</text>
<line class="dd-hair" x1="500" y1="122" x2="768" y2="122"/>
<text class="t-mono" x="516" y="139">读权重用时</text><text class="t-acc t-mono" x="752" y="139" text-anchor="end">4.8 ms</text>
<line class="dd-hair" x1="500" y1="148" x2="768" y2="148"/>
<text class="t-mono" x="516" y="165">计算用时</text><text class="t-val" x="752" y="165" text-anchor="end">0.016 ms</text>
<line class="dd-hair" x1="500" y1="174" x2="768" y2="174"/>
<text class="t-mono" x="516" y="191">乘法器忙碌</text><text class="t-val" x="752" y="191" text-anchor="end">约 0.3%</text>
<text class="t-say" x="32" y="232">上限：3.35 TB/s ÷ 16 GB ≈ 209 token/s</text>
<text class="t-say" x="500" y="232">芯片每读 1 字节能算 295 次</text>
<text class="t-say" x="500" y="252">decode 只用得上 1 次</text>
</svg>
</div>
<figcaption>图 1 · 批大小为 1 时，生成一个 token 的时间几乎全花在读权重上，乘法器 99% 以上的时间在等数据</figcaption>
</figure>

图 1 把账算了出来。H100 的显存带宽是 3.35 TB/s，读完 16 GB 要 4.8 毫秒；而 160 亿次运算，按 989 TFLOPS 算只要 0.016 毫秒。这块芯片的“算力 ÷ 带宽”约为 295，意思是每从显存读进 1 个字节，它有能力做 295 次运算；decode 却只给了它 1 次。所以单用户 decode 的速度上限由带宽决定：3.35 TB/s ÷ 16 GB ≈ 209 token/s，实际能做到的一百二三十到一百四十，就是带宽用到了六七成。

### 原因：读一遍权重，只服务了一个 token

问题不在乘法器不够快，而在**读进来的每个权重只用了一次**。这件事有一个直接的推论：如果同一时刻有很多用户，每人都要生成下一个 token，那么读一遍权重，就可以让这些 token 一起用。读权重的时间不变，产出却翻了很多倍。这就是推理服务里的“批处理”（batching）。

我们在一台 4 核的 CPU 机器上模拟了这件事[^notes]。取一个 512 MB 的权重矩阵（远大于 CPU 缓存），分别乘上 1 个、2 个……512 个 token 的激活向量，看完成一次要多久。

<figure class="dd dd-figure dt-figure">
<div class="dt-panel">
<div class="dt-title" id="ft-batch-title">同一个 512 MB 的权重矩阵，一次乘上 B 个 token（4 核 Xeon，numpy）</div>
<div class="dt-scroll">
<table class="dt" aria-labelledby="ft-batch-title">
<colgroup><col style="width:22%"><col><col class="hl"><col></colgroup>
<thead>
<tr><th scope="col">批大小 B</th><th scope="col">一次用时</th><th scope="col" class="acc">等效 token/s</th><th scope="col">等效读权重带宽</th></tr>
</thead>
<tbody>
<tr><th scope="row">1</th><td class="mono num">12.8 ms</td><td class="mono num acc">78</td><td class="mono num">41.8 GB/s</td></tr>
<tr><th scope="row">4</th><td class="mono num">26.5 ms</td><td class="mono num acc">151</td><td class="mono num">20.3 GB/s</td></tr>
<tr><th scope="row">16</th><td class="mono num">28.7 ms</td><td class="mono num acc">558</td><td class="mono num">18.7 GB/s</td></tr>
<tr><th scope="row">64</th><td class="mono num">47.5 ms</td><td class="mono num acc">1347</td><td class="mono num">11.3 GB/s</td></tr>
<tr><th scope="row">256</th><td class="mono num">134.5 ms</td><td class="mono num acc">1903</td><td class="mono num">4.0 GB/s</td></tr>
</tbody>
</table>
</div>
<p class="dt-note">B=1 时 numpy 走的是矩阵 × 向量的专门路径，带宽用得最好；B≥2 换成矩阵 × 矩阵的路径，所以 B=2 到 16 的用时差不多，都是 26–29 ms。</p>
</div>
<figcaption>表 1 · 批大小从 4 涨到 16，用时几乎不变，产出翻了近 4 倍：读一遍权重的成本被多个 token 分摊了</figcaption>
</figure>

表 1 里最值得看的是 B 从 4 到 16 那一段：一次的用时从 26.5 毫秒只涨到 28.7 毫秒，产出却从每秒 151 个 token 涨到 558 个。再往后，计算量追上来了，用时才开始明显增加，到 256 时 CPU 已经主要在算而不是在等。GPU 上是同样的规律，只是拐点更靠后：H100 每读 1 字节能算 295 次，批要到一两百才会被算力卡住。

这就是 GPU 服务大模型的基本做法：把很多用户的请求拼成一批，让那 99% 闲着的乘法器忙起来。可是批处理只解决了“很多人同时用”的情况。对一个用户来说，他的下一个 token 必须等上一个 token 算完，批再大也帮不了他的延迟。

**那么，如果不靠批处理，而是从电路本身下手，能把这一个用户的 token 做得更快、更省吗？**

## 二、专用电路能省在哪里

### 通用处理器为“通用”付的钱

要回答这个问题，先得知道一块芯片的能量花在了哪里。Horowitz 在 2014 年 ISSCC 的主题报告里，把 45 nm 工艺下各种操作的能耗摆在一起比较[^horowitz]，结果如图 2。

<figure class="dd dd-figure">
<div class="dd-scroll">
<svg class="dd-svg" viewBox="0 0 800 280" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="ft-energy-title ft-energy-desc">
<title id="ft-energy-title">一次操作的能耗（45 nm）</title>
<desc id="ft-energy-desc">对数刻度。一次加法或乘法几 pJ，一级缓存取数约 20 pJ，处理器执行一条指令的额外开销约 70 pJ，从 DRAM 取一次数据 1000 到 2000 pJ。后两项用强调色标出。</desc>
<rect class="dd-frame" width="800" height="280" rx="10"/>
<rect class="dd-zone" x="16" y="16" width="768" height="248" rx="8"/>
<text class="t-zone" x="32" y="44">一次操作要花多少能量 · 45 nm · 对数刻度</text>
<line class="dd-hair" x1="240" y1="60" x2="240" y2="206"/>
<line class="dd-hair" x1="370" y1="60" x2="370" y2="206"/>
<line class="dd-hair" x1="500" y1="60" x2="500" y2="206"/>
<line class="dd-hair" x1="630" y1="60" x2="630" y2="206"/>
<line class="dd-hair" x1="760" y1="60" x2="760" y2="206"/>
<text class="t-note" x="240" y="224" text-anchor="middle">1 pJ</text>
<text class="t-note" x="370" y="224" text-anchor="middle">10 pJ</text>
<text class="t-note" x="500" y="224" text-anchor="middle">100 pJ</text>
<text class="t-note" x="630" y="224" text-anchor="middle">1 nJ</text>
<text class="t-note" x="760" y="224" text-anchor="middle">10 nJ</text>
<text class="t-name" x="32" y="87">一次加法或乘法</text>
<rect class="dd-bar" x="240" y="70" width="91" height="24" rx="3"/>
<text class="t-mono" x="341" y="87">几 pJ</text>
<text class="t-name" x="32" y="123">一级缓存取一次数</text>
<rect class="dd-bar" x="240" y="106" width="169" height="24" rx="3"/>
<text class="t-mono" x="419" y="123">20 pJ</text>
<text class="t-name" x="32" y="159">执行一条指令的开销</text>
<rect class="dd-fill-acc" x="240" y="142" width="240" height="24" rx="3"/>
<text class="t-acc t-mono" x="490" y="159">70 pJ</text>
<text class="t-name" x="32" y="195">从 DRAM 取一次数</text>
<rect class="dd-fill-acc" x="240" y="178" width="429" height="24" rx="3"/>
<text class="t-acc t-mono" x="679" y="195">1–2 nJ</text>
<text class="t-say" x="32" y="250">强调色两项：一项为“可编程”付，一项为“数据在芯片外面”付。专用电路主要省这两笔。</text>
</svg>
</div>
<figcaption>图 2 · 真正做运算的那一下只要几 pJ，取指令和从片外取数据要贵一到三个数量级</figcaption>
</figure>

真正做乘加的那一下，只要几 pJ。可是在通用处理器上，为了执行这一下，要先取一条指令、译码、调度、读写寄存器，这些加起来约 70 pJ，是运算本身的十几倍以上。数据如果不在手边，要从 DRAM 取，一次就是 1 到 2 nJ，又贵了几百倍。Horowitz 由此得出一个结论：专用硬件比处理器高效两到三个数量级，而省下来的主要不是运算，而是**指令**和**数据搬运**。

GPU 已经比 CPU 好很多：一条指令驱动几十个线程，Tensor Core 一条指令做一整块矩阵乘，指令开销被摊薄了。但前两节的账告诉我们，decode 时的大头是图 2 最下面那一行：每个 token 都要从显存搬 16 GB。

### 专用电路的三根杠杆

把这些放在一起，一块只为 Transformer 设计的电路，能在三个地方动手：

- **去掉指令。** 计算的顺序在设计电路时就定好了，数据从一个单元直接流到下一个单元，不再取指、译码、调度。
- **位宽按需定。** GPU 的乘法器只有几种固定位宽。专用电路可以给权重用 4 bit、3 bit，甚至只用 −1、0、+1 三个值，每个权重占的字节少了，读一遍权重的时间就短了，乘法器也跟着变小。
- **数据不出芯片。** 一层算完的中间结果直接送进下一级，不写回显存；如果片上存储够大，连权重都放在片上，彻底不用每个 token 去片外搬一趟。

FPGA 正好能把这三件事都做出来：它的查找表能搭出任意位宽的运算单元，布线能把单元连成固定的流水线，芯片上还散布着几千块小存储。**那么，真在 FPGA 上把 Transformer 搭成一条流水线，效果怎么样？**

## 三、在 FPGA 上搭一条推理流水线

### 已经做出来的：FlightLLM

这件事已经有人做了，而且做得相当完整。FlightLLM 是发表在 FPGA 2024 上的一项工作，在一块 AMD Alveo U280 加速卡上跑 LLaMA2-7B[^flightllm]。U280 是一张数据中心用的 FPGA 卡，带 8 GB HBM，带宽 460 GB/s，大约八千美元[^u280]。

它基本就是把上一节的三根杠杆都用上了。权重压到平均 3.5 bit，并做了 N:M 结构化稀疏（每 M 个权重里只留 N 个非零）；注意力也用块稀疏，只算一部分位置。计算组织成一条按层走的流水线，GPU 上拆成好几个 kernel 的步骤，在这里是几个相邻的专用单元，激活在单元之间直接传（图 3）。

<figure class="dd dd-figure">
<div class="dd-scroll">
<svg class="dd-svg" viewBox="0 0 800 372" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="ft-pipe-title ft-pipe-desc">
<title id="ft-pipe-title">同一层在 GPU 和 FPGA 上的走法</title>
<desc id="ft-pipe-desc">上半部分是 GPU：QKV 矩阵乘、注意力、输出投影、FFN 四个 kernel，各自从显存读权重和激活、再把结果写回显存。下半部分是 FPGA 流水线：HBM 里的 3.5 bit 权重流进解压单元，再进稀疏乘加阵列和 softmax/归一化单元，激活在单元之间直接传，不回 HBM。</desc>
<defs>
<marker id="ft-pipe-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="ft-pipe-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="372" rx="10"/>
<rect class="dd-zone" x="16" y="16" width="768" height="184" rx="8"/>
<text class="t-zone" x="32" y="44">GPU · 批大小 1：一层拆成几个 kernel</text>
<line class="dd-edge" x1="111" y1="128" x2="111" y2="100" marker-end="url(#ft-pipe-arrow)"/>
<line class="dd-edge" x1="135" y1="96" x2="135" y2="124" marker-end="url(#ft-pipe-arrow)"/>
<line class="dd-edge" x1="291" y1="128" x2="291" y2="100" marker-end="url(#ft-pipe-arrow)"/>
<line class="dd-edge" x1="315" y1="96" x2="315" y2="124" marker-end="url(#ft-pipe-arrow)"/>
<line class="dd-edge" x1="471" y1="128" x2="471" y2="100" marker-end="url(#ft-pipe-arrow)"/>
<line class="dd-edge" x1="495" y1="96" x2="495" y2="124" marker-end="url(#ft-pipe-arrow)"/>
<line class="dd-edge" x1="651" y1="128" x2="651" y2="100" marker-end="url(#ft-pipe-arrow)"/>
<line class="dd-edge" x1="675" y1="96" x2="675" y2="124" marker-end="url(#ft-pipe-arrow)"/>
<rect class="dd-card" x="48" y="60" width="150" height="36" rx="6"/>
<text class="t-name" x="123" y="83" text-anchor="middle">QKV 矩阵乘</text>
<rect class="dd-card" x="228" y="60" width="150" height="36" rx="6"/>
<text class="t-name" x="303" y="83" text-anchor="middle">注意力</text>
<rect class="dd-card" x="408" y="60" width="150" height="36" rx="6"/>
<text class="t-name" x="483" y="83" text-anchor="middle">输出投影</text>
<rect class="dd-card" x="588" y="60" width="150" height="36" rx="6"/>
<text class="t-name" x="663" y="83" text-anchor="middle">FFN</text>
<rect class="dd-card" x="48" y="128" width="690" height="36" rx="6"/>
<text class="t-mono" x="64" y="151">HBM · 权重（16 bit）+ 每一步的中间激活</text>
<text class="t-say" x="32" y="188">每个 kernel 单独启动，读完写回再交给下一个；A100 上带宽只用到 28.6%（优化后 57.4%）</text>
<rect class="dd-zone" x="16" y="216" width="768" height="140" rx="8"/>
<text class="t-zone" x="32" y="244">FPGA（FlightLLM，U280）· 一层是一条流水线</text>
<line class="dd-edge-acc" x1="152" y1="286" x2="176" y2="286" marker-end="url(#ft-pipe-arrow-acc)"/>
<line class="dd-edge-acc" x1="340" y1="286" x2="364" y2="286" marker-end="url(#ft-pipe-arrow-acc)"/>
<line class="dd-edge-acc" x1="508" y1="286" x2="532" y2="286" marker-end="url(#ft-pipe-arrow-acc)"/>
<rect class="dd-card" x="32" y="268" width="120" height="36" rx="6"/>
<text class="t-mono" x="92" y="291" text-anchor="middle">HBM 权重流</text>
<rect class="dd-card" x="180" y="268" width="160" height="36" rx="6"/>
<text class="t-mono" x="260" y="291" text-anchor="middle">解压 3.5 bit→int8</text>
<rect class="dd-card" x="368" y="268" width="140" height="36" rx="6"/>
<text class="t-mono" x="438" y="291" text-anchor="middle">N:M 稀疏乘加</text>
<rect class="dd-card" x="536" y="268" width="160" height="36" rx="6"/>
<text class="t-mono" x="616" y="291" text-anchor="middle">softmax / 归一化</text>
<text class="t-say" x="32" y="336">只有权重从 HBM 流进来，激活在相邻单元之间直接传；HBM 带宽用到 65.9%</text>
</svg>
</div>
<figcaption>图 3 · GPU 上一层要分几趟进出显存，FPGA 上一层是一条直通的流水线，带宽利用率因此更高</figcaption>
</figure>

图 3 只是示意，GPU 这边的算子融合（比如 FlashAttention）也能把一部分中间结果留在片上，但在批大小为 1 时，GPU 的带宽确实用得不好：FlightLLM 测到 A100 只用到 28.6%，换上优化过的推理框架是 57.4%；它自己在 U280 上是 65.9%。

结果是这样的：和 V100S 比，FlightLLM 每焦耳能生成的 token 多 6.0 倍；和优化过的 A100 比，多 3.8 倍。按“每美元每秒能生成多少 token”算，比 V100S 好 1.8 倍。另一篇在 U280 上做 GPT 推理的工作也得到相近的结论：decode 比 A100 快 1.9 倍，能效高 5.7 倍[^spatial]。做 FPGA 推理服务器的 Positron 公司，在 Agilex 7 FPGA 上做出了产品，自称比 H100 有 3.5 倍的性价比和能效[^positron]。

所以第一个问题的答案是肯定的：**为 Transformer 搭的 FPGA 流水线，在单用户 decode 上确实能比 GPU 省几倍电，单 token 也更便宜。**

### 不足：比的是谁

但仔细看这些对比，会发现两个问题。

一是 GPU 那一边用的是 16 位权重，FPGA 这一边是 3.5 bit。读一遍权重的字节数差了四倍多，而 decode 的速度正是由读多少字节决定的。如果 GPU 也把权重压到 4 bit，差距会小很多：FlightLLM 自己列出的 A100 加 INT4 的对照是每秒 196.8 个 token，而它在更新的 Versal VHK158 上是 92.5 个。

二是这些对比几乎都在批大小为 1 的条件下做。前面看到，批处理是 GPU 服务大模型的基本做法，那才是数据中心里的常态。

**把这两点都算进去，FPGA 还便宜吗？**

## 四、带宽这道墙

### 原因：FPGA 卡的显存带宽小得多

decode 的速度上限是“带宽 ÷ 权重字节数”，那就先比带宽。U280 的 HBM 是 460 GB/s，新一代的 Alveo V80 是 820 GB/s[^v80]，H100 是 3.35 TB/s，差了 4 到 7 倍。FPGA 再怎么把带宽用满，也追不上带宽本身的差距；它能赢，靠的是把权重压得更小，而 GPU 也能压。

批处理一来，差距就更大了。FPGA 卡上的乘法器（DSP）只有一万个上下，批稍微大一点就被算力卡住；H100 每读 1 字节能算 295 次，批到一两百都还在“等带宽”的那一段，产出几乎随批大小线性增长。

### 估算：每百万 token 多少钱

我们按“每一步的用时 = 读权重的时间和计算时间里较大的那个”做了一个粗略的估算[^notes]，模型是 Llama 3.1 8B。成本只算硬件按三年摊销加上满功耗的电费（0.1 美元/度），H100 按三万美元，FPGA 用厂商标价；带宽利用率 GPU 取 45%–60%、FPGA 取 65%，算力利用率 GPU 取 50%、FPGA 取 70%。这些都是假设，表 2 看数量级就好。

<figure class="dd dd-figure dt-figure">
<div class="dt-panel">
<div class="dt-title" id="ft-cost-title">Llama 3.1 8B decode 的屋顶线估算（只算权重，不算 KV cache）</div>
<div class="dt-scroll">
<table class="dt" aria-labelledby="ft-cost-title">
<colgroup><col style="width:30%"><col><col><col class="hl"></colgroup>
<thead>
<tr><th scope="col">设备 · 权重精度</th><th scope="col">token/s</th><th scope="col">焦耳/token</th><th scope="col" class="acc">美元/百万 token</th></tr>
</thead>
<tbody>
<tr class="dt-group"><th colspan="4" scope="rowgroup">批大小 1：一个用户</th></tr>
<tr><th scope="row">H100 · FP16</th><td class="mono num">126</td><td class="mono num">5.6</td><td class="mono num bad">2.68</td></tr>
<tr><th scope="row">H100 · INT4</th><td class="mono num">377</td><td class="mono num">1.9</td><td class="mono num acc">0.89</td></tr>
<tr><th scope="row">U280 · 3.5 bit</th><td class="mono num">87</td><td class="mono num">2.6</td><td class="mono num acc">1.05</td></tr>
<tr><th scope="row">V80 · 4 bit</th><td class="mono num">133</td><td class="mono num">1.4</td><td class="mono num acc">0.79</td></tr>
<tr class="dt-group"><th colspan="4" scope="rowgroup">批大小 64：很多用户同时用</th></tr>
<tr><th scope="row">H100 · FP16</th><td class="mono num">8040</td><td class="mono num">0.087</td><td class="mono num acc">0.042</td></tr>
<tr><th scope="row">H100 · INT4</th><td class="mono num">24120</td><td class="mono num">0.029</td><td class="mono num good">0.014</td></tr>
<tr><th scope="row">U280 · 3.5 bit</th><td class="mono num">178</td><td class="mono num">1.3</td><td class="mono num bad">0.51</td></tr>
<tr><th scope="row">V80 · 4 bit</th><td class="mono num">285</td><td class="mono num">0.67</td><td class="mono num bad">0.37</td></tr>
</tbody>
</table>
</div>
<p class="dt-note">功耗按标称最大值算，FPGA 的实际功耗通常低得多，所以 FPGA 那几行的焦耳/token 偏保守。FPGA 的算力只按 DSP 每拍一次乘加估算，批到 8 左右就饱和了。</p>
</div>
<figcaption>表 2 · 一个用户时，FPGA 能和 16 位的 GPU 拉开差距，但和同样压到 4 bit 的 GPU 差不多；用户一多，GPU 每 token 的成本低一个数量级</figcaption>
</figure>

表 2 的上半部分和 FlightLLM 的结论吻合：一个用户、GPU 用 16 位权重时，FPGA 每百万 token 便宜一半以上，能耗也低一半。可是一旦 GPU 也用 4 bit，两边就差不多了。下半部分才是数据中心真正的账：64 个用户同时用，H100 读一遍权重能产出 64 个 token，每百万 token 的成本降到一两分美元，FPGA 卡在自己的算力上，比它贵二三十倍。

这样看，“FPGA 专用电路降低推理成本”这个说法要加上条件。**在一个用户、对延迟敏感、或者功耗受限的场合（边缘设备、机柜供电紧张的机房），它能赢；在大批量的云端服务里，GPU 靠批处理把读权重的成本摊薄了，FPGA 赢不了。**

到这里，FPGA 的专用电路其实只用上了三根杠杆里的前两根：去掉指令、压低位宽。权重还在片外的 HBM 里，每个 token 照样要读一遍，带宽墙一点没动。**如果把权重整个搬进芯片里呢？**

## 五、把权重搬进芯片

### 片上存储有多快

FPGA 芯片上散布着几千块小存储：U280 有 2016 块 BRAM 和 960 块 UltraRAM[^u280]，每一块都有自己的读写口，每个时钟周期都能同时出数。按块数粗略算，片上的总带宽在 10 TB/s 量级，是它那 460 GB/s HBM 的二十多倍，而且读片上存储的能耗比读片外低一两个数量级（回看图 2）。

TerEffic（2025）就是这么做的[^tereffic]。它用三值权重：每个权重只能是 −1、0 或 +1，5 个权重打包成 8 bit，平均 1.6 bit。乘法也就不需要乘法器了，乘以 +1 是原样，乘以 −1 是取负，乘以 0 是丢掉，用查找表搭一个选择器就够。这样两块 U280 能把一个 3.7 亿参数的模型全部放进片上存储，单用户每秒生成 16300 个 token，是前面 7B 模型速度的上百倍。

### 不足：片上装不下大模型

问题是，16300 token/s 的是一个 3.7 亿参数的小模型，而且要两块卡。每块 U280 的片上存储大约 41 MB，按 1.6 bit 一个权重，只能装两亿出头。图 4 把几种芯片的片上容量和几个模型的大小放在一起。

<figure class="dd dd-figure">
<div class="dd-scroll">
<svg class="dd-svg" viewBox="0 0 800 340" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="ft-cap-title ft-cap-desc">
<title id="ft-cap-title">片上存储容量和模型大小</title>
<desc id="ft-cap-desc">对数刻度，单位 MB。芯片：U280 片上约 41 MB，V80 约 84 MB，Groq LPU 一片 230 MB，Cerebras WSE-3 一整片晶圆 44 GB。模型：3.7 亿参数三值模型 74 MB，Llama 3.1 8B 四比特 4 GB，Llama 3 70B 四比特 35 GB。</desc>
<rect class="dd-frame" width="800" height="340" rx="10"/>
<rect class="dd-zone" x="16" y="16" width="768" height="308" rx="8"/>
<text class="t-zone" x="32" y="44">能放下多少权重 · 对数刻度</text>
<line class="dd-hair" x1="260" y1="56" x2="260" y2="272"/>
<line class="dd-hair" x1="380" y1="56" x2="380" y2="272"/>
<line class="dd-hair" x1="500" y1="56" x2="500" y2="272"/>
<line class="dd-hair" x1="620" y1="56" x2="620" y2="272"/>
<line class="dd-hair" x1="740" y1="56" x2="740" y2="272"/>
<text class="t-note" x="260" y="290" text-anchor="middle">10 MB</text>
<text class="t-note" x="380" y="290" text-anchor="middle">100 MB</text>
<text class="t-note" x="500" y="290" text-anchor="middle">1 GB</text>
<text class="t-note" x="620" y="290" text-anchor="middle">10 GB</text>
<text class="t-note" x="740" y="290" text-anchor="middle">100 GB</text>
<text class="t-name" x="32" y="77">U280 片上存储</text>
<rect class="dd-bar" x="260" y="64" width="74" height="18" rx="3"/>
<text class="t-mono" x="342" y="77">41 MB</text>
<text class="t-name" x="32" y="105">V80 片上存储</text>
<rect class="dd-bar" x="260" y="92" width="111" height="18" rx="3"/>
<text class="t-mono" x="379" y="105">84 MB</text>
<text class="t-name" x="32" y="133">Groq LPU 一片</text>
<rect class="dd-bar" x="260" y="120" width="163" height="18" rx="3"/>
<text class="t-mono" x="431" y="133">230 MB</text>
<text class="t-name" x="32" y="161">Cerebras WSE-3 整片晶圆</text>
<rect class="dd-bar" x="260" y="148" width="437" height="18" rx="3"/>
<text class="t-mono" x="705" y="161">44 GB</text>
<line class="dd-hair" x1="32" y1="180" x2="768" y2="180"/>
<text class="t-name" x="32" y="207">3.7 亿参数 · 三值</text>
<rect class="dd-fill-acc" x="260" y="194" width="104" height="18" rx="3"/>
<text class="t-acc t-mono" x="372" y="207">74 MB</text>
<text class="t-name" x="32" y="235">Llama 3.1 8B · 4 bit</text>
<rect class="dd-fill-acc" x="260" y="222" width="312" height="18" rx="3"/>
<text class="t-acc t-mono" x="580" y="235">4 GB</text>
<text class="t-name" x="32" y="263">Llama 3 70B · 4 bit</text>
<rect class="dd-fill-acc" x="260" y="250" width="425" height="18" rx="3"/>
<text class="t-acc t-mono" x="693" y="263">35 GB</text>
<text class="t-say" x="32" y="312">FPGA 片上只装得下几亿参数的小模型；80 亿参数要么读片外 HBM，要么摊到几十上百块芯片上</text>
</svg>
</div>
<figcaption>图 4 · 片上存储和模型之间差了两到三个数量级，只有整片晶圆大小的芯片才装得下几十亿参数</figcaption>
</figure>

8B 模型压到 4 bit 也有 4 GB，是 V80 片上容量的近 50 倍。这不是 FPGA 独有的难题：专门走“全 SRAM”路线的 Groq，每片也只有 230 MB，演示 Llama 2 70B 用了 576 片[^groq]；Cerebras 干脆把一整片晶圆做成一块芯片，才有 44 GB 的片上存储[^cerebras]。权重放在片上，速度确实快，代价是要很多芯片一起扛，每片都要买、都要供电。

片上存储装不下，是因为存储块本身占地方。**那如果不“存”权重，而是把权重直接做成电路的一部分呢？**

## 六、把权重焊进电路

### 实验：权重可改和权重写死，面积差多少

到目前为止，权重都是“数据”：放在存储里，算的时候读出来，送进乘法器。可推理时权重根本不变，那么完全可以把它当成常数，直接写进电路。乘以一个已知的常数，电路能简单很多：乘以 0 的那根线可以整个删掉，乘以 +1 就是一根导线，乘以 −1 只要一个取负；乘以 37 这样的整数，也可以化成几次移位和加法。

为了看看到底能省多少，我们用开源综合工具 yosys 做了个实验[^notes]。电路是一个 64 元点积，激活是 8 位整数；权重分成 8 位、4 位、三值三种，每种又分两种写法：一种是权重从端口送进来（可以随时换，相当于通用电路），一种是写死成常数（换一组随机权重各综合三次）。全部综合到 Xilinx 7 系列的 6 输入查找表上，不用 DSP，只看用了多少个查找表。每种写法都先用 Icarus Verilog 随机仿真了 200 组输入，结果全对。

<figure class="dd dd-figure dt-figure">
<div class="dt-panel">
<div class="dt-title" id="ft-synth-title">64 元点积综合到 Xilinx 7 系列，用了多少个查找表（LUT6，不用 DSP）</div>
<div class="dt-scroll">
<table class="dt" aria-labelledby="ft-synth-title">
<colgroup><col style="width:22%"><col><col class="hl"><col style="width:16%"></colgroup>
<thead>
<tr><th scope="col">权重</th><th scope="col">从端口送进来（可改）</th><th scope="col" class="acc">写死成常数（3 组随机权重）</th><th scope="col">省了</th></tr>
</thead>
<tbody>
<tr><th scope="row">8 位整数</th><td class="mono num">14529</td><td class="mono acc">4915、5113、4483</td><td class="mono num">约 3 倍</td></tr>
<tr><th scope="row">4 位整数</th><td class="mono num">7271</td><td class="mono acc">2267、2513、2508</td><td class="mono num">约 3 倍</td></tr>
<tr><th scope="row">三值 −1/0/+1</th><td class="mono num">2618</td><td class="mono acc good">793、884、844</td><td class="mono num">约 3 倍</td></tr>
</tbody>
</table>
</div>
<p class="dt-note">开源 yosys 综合出来的面积比厂商工具 Vivado 大，绝对值只作参考，看同一列、同一行之间的比例。</p>
</div>
<figcaption>表 3 · 位宽从 8 位降到三值省了 5 倍多，权重从可改变成写死又省了 3 倍，两样叠起来差 17 倍</figcaption>
</figure>

表 3 里有两条线索。竖着看，位宽越小越省：8 位可改要 14529 个查找表，三值可改只要 2618 个，因为乘法器变成了选择器。横着看，写死的版本都只有可改版本的三分之一左右。图 5 拿四个输入画出了三值的情况。

<figure class="dd dd-figure">
<div class="dd-scroll">
<svg class="dd-svg" viewBox="0 0 800 300" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="ft-hw-title ft-hw-desc">
<title id="ft-hw-title">三值权重：可改的点积和写死的点积</title>
<desc id="ft-hw-desc">左边：x0 到 x3 各接一个选择器，由 2 位权重寄存器决定选 +x、−x 还是 0，再进一个 3 个加法器的加法树。右边：权重写死为 +1、0、−1、+1，x1 的线被删掉，x2 只接一个取负，x0 和 x3 直连，加法树只剩 2 个加法器。</desc>
<defs>
<marker id="ft-hw-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="ft-hw-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="300" rx="10"/>
<rect class="dd-zone" x="16" y="16" width="376" height="268" rx="8"/>
<text class="t-zone" x="32" y="44">权重可改：每个输入一个选择器</text>
<rect class="dd-zone" x="408" y="16" width="376" height="268" rx="8"/>
<text class="t-zone" x="424" y="44">权重写死：w = [+1, 0, −1, +1]</text>
<line class="dd-edge" x1="84" y1="82" x2="100" y2="82" marker-end="url(#ft-hw-arrow)"/>
<line class="dd-edge" x1="84" y1="130" x2="100" y2="130" marker-end="url(#ft-hw-arrow)"/>
<line class="dd-edge" x1="84" y1="178" x2="100" y2="178" marker-end="url(#ft-hw-arrow)"/>
<line class="dd-edge" x1="84" y1="226" x2="100" y2="226" marker-end="url(#ft-hw-arrow)"/>
<line class="dd-edge" x1="254" y1="82" x2="276" y2="82" marker-end="url(#ft-hw-arrow)"/>
<line class="dd-edge" x1="254" y1="130" x2="276" y2="130" marker-end="url(#ft-hw-arrow)"/>
<line class="dd-edge" x1="254" y1="178" x2="276" y2="178" marker-end="url(#ft-hw-arrow)"/>
<line class="dd-edge" x1="254" y1="226" x2="276" y2="226" marker-end="url(#ft-hw-arrow)"/>
<rect class="dd-card" x="32" y="64" width="52" height="36" rx="6"/><text class="t-mono" x="58" y="87" text-anchor="middle">x0</text>
<rect class="dd-card" x="32" y="112" width="52" height="36" rx="6"/><text class="t-mono" x="58" y="135" text-anchor="middle">x1</text>
<rect class="dd-card" x="32" y="160" width="52" height="36" rx="6"/><text class="t-mono" x="58" y="183" text-anchor="middle">x2</text>
<rect class="dd-card" x="32" y="208" width="52" height="36" rx="6"/><text class="t-mono" x="58" y="231" text-anchor="middle">x3</text>
<rect class="dd-card" x="104" y="64" width="150" height="36" rx="6"/><text class="t-mono" x="179" y="87" text-anchor="middle">选 ±x0/0 · w0</text>
<rect class="dd-card" x="104" y="112" width="150" height="36" rx="6"/><text class="t-mono" x="179" y="135" text-anchor="middle">选 ±x1/0 · w1</text>
<rect class="dd-card" x="104" y="160" width="150" height="36" rx="6"/><text class="t-mono" x="179" y="183" text-anchor="middle">选 ±x2/0 · w2</text>
<rect class="dd-card" x="104" y="208" width="150" height="36" rx="6"/><text class="t-mono" x="179" y="231" text-anchor="middle">选 ±x3/0 · w3</text>
<rect class="dd-card" x="280" y="64" width="96" height="180" rx="6"/>
<text class="t-name" x="328" y="150" text-anchor="middle">加法树</text>
<text class="t-note" x="328" y="170" text-anchor="middle">3 个加法器</text>
<text class="t-say" x="32" y="270">64 元时：2618 个 LUT</text>
<line class="dd-edge-acc" x1="476" y1="82" x2="636" y2="82" marker-end="url(#ft-hw-arrow-acc)"/>
<line class="dd-edge-acc" x1="476" y1="178" x2="508" y2="178" marker-end="url(#ft-hw-arrow-acc)"/>
<line class="dd-edge-acc" x1="618" y1="178" x2="636" y2="178" marker-end="url(#ft-hw-arrow-acc)"/>
<line class="dd-edge-acc" x1="476" y1="226" x2="636" y2="226" marker-end="url(#ft-hw-arrow-acc)"/>
<rect class="dd-card" x="424" y="64" width="52" height="36" rx="6"/><text class="t-acc t-mono" x="450" y="87" text-anchor="middle">x0</text>
<rect class="dd-derived" x="424" y="112" width="52" height="36" rx="6"/><text class="t-note" x="450" y="135" text-anchor="middle">x1</text>
<text class="t-say" x="488" y="135">w = 0：这根线被删掉</text>
<rect class="dd-card" x="424" y="160" width="52" height="36" rx="6"/><text class="t-acc t-mono" x="450" y="183" text-anchor="middle">x2</text>
<rect class="dd-card" x="512" y="160" width="106" height="36" rx="6"/><text class="t-acc t-mono" x="565" y="183" text-anchor="middle">取负 −x2</text>
<rect class="dd-card" x="424" y="208" width="52" height="36" rx="6"/><text class="t-acc t-mono" x="450" y="231" text-anchor="middle">x3</text>
<rect class="dd-card" x="640" y="64" width="96" height="180" rx="6"/>
<text class="t-name" x="688" y="150" text-anchor="middle">加法树</text>
<text class="t-note" x="688" y="170" text-anchor="middle">2 个加法器</text>
<text class="t-say" x="424" y="270">64 元时：约 840 个 LUT</text>
</svg>
</div>
<figcaption>图 5 · 权重写死以后，选择器和存权重的寄存器都没了，权重为 0 的输入连线也一起消失</figcaption>
</figure>

可改的版本要为每个输入准备一个选择器，还要有地方存权重；写死以后，选择器消失了，权重为 0 的输入（我们的随机权重里约占三分之一）连线都不用接。这就是“专用”这两个字能走到的最远处：电路就是这一个模型，别的什么都不是。

### 不足：一个模型要上万块 FPGA

可是把这个数放大到真模型上，就看出问题了。三值写死的点积，平均每个权重大约要 13 个查找表（840 ÷ 64）。一个 80 亿参数的模型，就是大约 1000 亿个查找表；目前最大的 FPGA 之一 VP1902 有 846 万个，算下来要一万多块。哪怕 Vivado 能把面积再压掉一半，也是几千块。

而且这样的电路算得太“多”了：它在每个时钟周期把整个模型完整地算一遍，跑在 200 MHz 上，相当于每秒能处理两亿个 token。一个用户根本用不完。要是让同一块电路分时复用，轮流算不同的层，权重就又得存起来、读出来，又回到了第五节的容量问题。

FPGA 本身的“可编程”也有代价。同样一个电路做在 FPGA 上，面积大约是做成专用芯片（ASIC）的 35 倍（大量用硬核乘法器和存储块时能降到 5 倍左右），速度慢三四倍，动态功耗高 14 倍左右[^kuon]。FPGA 用这些代价换来了“能重新烧写”，可一块焊死了模型的电路，恰恰用不上重新烧写。

### 解决：交给 ASIC

所以“把模型焊进电路”这条路，最后走向了 ASIC。2026 年初，一家叫 Taalas 的公司发布了 HC1 芯片，把 Llama 3.1 8B 直接做进了芯片：815 平方毫米、530 亿个晶体管，权重用 3 bit 和 6 bit 的混合格式，一张卡约 200 W，厂商称每个用户每秒能生成约 17000 个 token[^taalas]。它的代价也写得很清楚：这块芯片只能跑这一个基础模型，最多加个 LoRA 微调；换模型，就得重新流片。

这里 FPGA 反而找到了自己的位置。模型结构每半年就变一次（分组注意力、混合专家、新的位置编码……），直接去流片风险很大，FPGA 正好可以先把架构跑通、拿去卖、收集反馈，再把成熟的设计做成 ASIC。前面提到的 Positron 就是这么走的：第一代产品 Atlas 用 FPGA，第二代 Asimov 改成自研 ASIC，并说以后都保持“FPGA 先行、ASIC 跟进”的节奏[^asimov]。

## 七、回到开头

开头那块 H100，给一个人写字时不到 0.3% 的乘法器在干活。现在我们知道，这不是乘法器设计得不好，而是每个 token 都要把 16 GB 权重从显存搬一遍，乘法器在等数据。专用电路能做的，就是想办法不让它等，而三条路各有各的代价：

- **让很多 token 共用一次读取**（批处理）：GPU 的做法，成本最低，但帮不了单个用户的延迟。
- **让每次读取更少、更顺**（低位宽、稀疏、流水线）：FPGA 推理加速器的做法，单用户时能比 16 位的 GPU 省几倍电，但卡在 FPGA 卡的显存带宽上。
- **干脆不读**（权重放在片上，或者焊进电路）：速度能高出一两个数量级，但芯片要装得下整个模型，FPGA 只装得下小模型，大模型要靠成百上千块芯片或者专门流片。

## 小结

1. **decode 时芯片在等什么？** 等权重。每生成一个 token 要把全部权重从显存读一遍，每个权重只用一次，单用户时乘法器 99% 以上的时间闲着。批处理能让多个 token 共用一次读取，这是 GPU 服务大模型的基本做法。
2. **专用电路能省在哪？** 运算本身只要几 pJ，取指令约 70 pJ，从 DRAM 取数 1–2 nJ。专用电路省的是指令和数据搬运：计算顺序写死、位宽按需、数据不出芯片。
3. **FPGA 流水线效果如何？** FlightLLM 在 U280 上跑 LLaMA2-7B，3.5 bit 权重加稀疏，带宽利用率 65.9%，能效是 GPU 的 3.8 到 6 倍，单用户场景下每 token 也更便宜。
4. **能降低成本吗？** 要看场景。GPU 也压到 4 bit 时，单用户的成本差不多；用户一多，GPU 靠批处理把成本压低一个数量级，FPGA 卡在 460–820 GB/s 的显存带宽和一万个上下的 DSP 上。边缘、低延迟、功耗受限时 FPGA 有优势，云端大批量服务时 GPU 更便宜。
5. **把权重搬进芯片呢？** 片上存储快得多，3.7 亿参数的三值模型在两块 U280 上能跑到每秒 16300 个 token，但一块 FPGA 的片上存储只装得下两亿权重左右。
6. **把权重焊进电路呢？** 我们的综合实验里，写死权重比可改省约 3 倍面积，三值写死比 8 位可改省 17 倍；但 8B 模型按这样算要一万多块 FPGA。这条路的终点是 ASIC（Taalas HC1），FPGA 适合做它的原型和小模型的载体。

想自己试的话，综合实验只需要 yosys 和 Python：`gen_dot.py` 生成六种点积写法，`run_synth.sh` 逐个综合并统计查找表，改一下点积长度或权重位宽就能看到面积怎么变[^notes]。

## 参考资料
