音乐
暂未播放
HG-PIPE 完全拆解:混合粒度流水线,让 FPGA 把 ViT 跑到 7118 FPS(ICCAD 2024)

HG-PIPE(Hybrid-Grained Pipeline)是北京大学李萌、王源团队于 2024 年提出并发表在 ICCAD 2024 上的 FPGA 加速器设计,论文题目为《HG-PIPE: Vision Transformer Acceleration with Hybrid-Grained Pipeline》(arXiv:2407.17879),代码已在 GitHub 开源。它解决的问题很具体:怎么在 FPGA 上把视觉 Transformer(ViT)的推理速度推到极致。它的答案是两个互补的设计——混合粒度流水线(hybrid-grained pipeline)解决数据流问题,基于查找表(LUT)的算子实现解决 DSP 资源问题。最终效果是:在 AMD Versal VCK190 单片 FPGA 上达到 7118 张/秒(17.8 TOP/s)的 ViT 推理吞吐,是 NVIDIA V100 GPU 的 2.81 倍。
这篇文章会把这套设计从头拆到尾:先讲 ViT 加速为什么难、既有方案卡在哪里;再讲混合粒度流水线到底”混合”了什么,为什么能同时消灭流水线气泡和巨额缓冲;然后讲并行度设计怎么跟数据流耦合、怎么用公式算出每个模块的并行度;最后讲 LUT 算子实现里那些精妙的近似技巧(Power-of-Two 索引、算子融合、分段表、逆指数表),以及实验数据里的每一个数字是怎么来的。
背景:ViT 推理与 FPGA 加速的两条老路#
Vision Transformer 自 2020 年由 Google 提出(An Image is Worth 16x16 Words)之后迅速成为视觉任务的主流结构。相比 CNN,ViT 的参数量和计算量都大得多:以本文测试的 DeiT-tiny 为例,一次推理约 2.5 GFLOPs、550 万参数,而同样规模的 CNN 要便宜得多。在端侧(车载、边缘摄像头)芯片面积和功耗受限的场景下,怎么高效地跑 ViT 推理是一个真实问题。
FPGA 的吸引力在于”可重构”:既有接近 ASIC 的能效,又能像软件一样改配置。我在 FPGA 加速器完全拆解(一) 里讲过 FPGA 的四种基本资源——LUT、DSP、BRAM、FF——这里直接沿用:LUT 擅长做任意布尔逻辑(也能拼出乘法),DSP 是硬化的乘加单元,BRAM 是片上存储。三者数量有限、各有上限,加速器设计本质上就是在这三个资源盘子之间做分配。
在 HG-PIPE 之前,FPGA 上的 ViT 加速器分成两大流派:
时序架构(temporal architecture)。用一套统一的计算引擎(通常是针对矩阵乘 GeMM 优化的脉动阵列或 PE 阵列),在不同时间片里处理不同算子:先算 QKV 投影,再算注意力,再算 MLP,都是同一批硬件。代表作如 AutoViTAcc(FPL 2022)、HeatViT(HPCA 2023)。优点是设计简单,缺点是每层算完中间激活都要写回片外 DDR,下个算子再读回来,内存访问开销极大,而且算子之间无法并行(同一批 PE 一次只能干一件事)。
流水线架构(pipelined architecture)。为不同算子实例化不同的专用硬件模块,模块之间直接流式传数据,中间激活不出片。因为多个层可以同时计算,理论上吞吐高、延迟低。流水线又分两档:
- 粗粒度流水线:以整个张量(如一张图的所有 token 的激活)为单位在模块间流动,模块之间用 Ping-Pong(PIPO)双缓冲。为了兼容 ViT 中任意顺序的数据访问(后面会解释为什么需要),缓冲要存下整个张量,代价是 BRAM 消耗巨大。
- 细粒度流水线:把张量切成 tile 在模块间流动,模块间用 FIFO 缓冲,成本低、利用率高。这在 CNN 加速器里是主流(CNN 数据访问是局部、规则的),但很难直接用在 ViT 上——注意力里的转置和全局依赖会打断数据流的连续性。
HG-PIPE 团队用 Roofline 模型把这三条路的上限算了一遍,得到论文 Figure 1 的核心结论(该图是 Roofline 曲线图,这里把结论整理成表):
| 架构 | 瓶颈 | 可达吞吐(估) |
|---|---|---|
| 时序架构(GeMM 引擎) | 片外带宽 | 1.1 TOP/s |
| 粗粒度流水线 | DSP 数量 | 3.2 TOP/s |
| 粗粒度流水线 + LUT 也参与计算 | 带宽 | 7.8 TOP/s |
| HG-PIPE(混合粒度 + LUT 算子) | 无明显瓶颈 | 17.8 TOP/s |
注意第三行:如果把 LUT 也用来算矩阵乘,DSP 不再是瓶颈,但数据流如果还是粗粒度的,带宽又成了墙。这说明单独解决一个瓶颈是不够的,必须数据流和资源分配两手抓——这正是 HG-PIPE 的基本姿态。
挑战一:数据流设计的两难(transpose 与残差缓冲)#
为什么 ViT 不能直接套用 CNN 的细粒度流水线?论文用一张图(Figure 3)讲清了两件事,这张图也是本文的第一张配图:

(图片来源:论文 Figure 3,CC BY-NC-SA 4.0)
转置:注意力机制对数据流的”背叛”#
细粒度流水线要求数据按顺序流过各模块。CNN 里这没问题:卷积核滑过特征图,数据访问天然是顺序的。但多头注意力(MHA)内部有一步关键的 Q×Kᵀ:
生成注意力分数矩阵时,K 是以”行”为单位被访问的,而 K 在内存里是按 token 存储的。换句话说,Q 按 token 流进、K 却要按行流进——一个 token 的 Q 需要跟整张 K 的所有行做点积。这种”行式访问”与”token 式访问”的错位,让 K(以及后续 R×V 中的 V)无法沿细粒度流水线顺序供给,必须先把整个 K、V 张量缓存下来再乱序读取。
这还不是最致命的。真正的杀手是残差连接。
残差连接:缓冲成本的爆炸源#
ViT 每个 block 都有残差:输出 = 模块输出 + 输入。残差要求”输入”在模块算完之前一直活着,不能丢。在粗粒度流水线里,残差路径上的每个阶段都要用一个 PIPO 双缓冲——为什么是双份?因为流水线里上一张图的数据还没读完,下一张图的残差就开始写了,读和写必须错开,所以每个缓冲都要存两份张量。
论文给了一笔具体的账(DeiT-tiny,序列长度 196、隐藏维度 192):
- 缓冲一个残差张量:14 个 BRAM;
- 一个注意力 block 的残差路径需要 6 个 PIPO 阶段:6 × 14 × 2(双缓冲)= 168 个 BRAM;
- 12 个 block 的残差路径就要 2000+ 个 BRAM。
而 VCK190 整片 FPGA 的片上 BRAM 总数不过 1960 个左右(论文实测整机用量 1006.5 个)。也就是说,光给残差路径做缓冲,粗粒度流水线就把 BRAM 用光了,别的什么都放不下。这就是论文说的”dataflow design dilemma”:转置迫使你选粗粒度(只有它能乱序访问),粗粒度又带来无法承受的缓冲成本。两条路都被堵死。
下图把三种范式的差异摆在一起对比(来自论文 Figure 2a):

(图片来源:论文 Figure 2a,CC BY-NC-SA 4.0)
论文用一张表总结了三种范式的定性差异:
| 特性 | 时序架构 | 粗粒度流水线 | 细粒度流水线 | HG-PIPE |
|---|---|---|---|---|
| 缓冲类型 | 全局缓冲 | PIPO | FIFO | Buffer + FIFO |
| 缓冲成本 | 小 | 大 | 小 | 中 |
| 数据访问顺序 | 任意 | 任意 | 顺序 | 任意 |
| 数据访问次数 | 多次 | 多次 | 仅一次 | 多次 |
| ViT 兼容性 | ✔ | ✔ | ✘ | ✔ |
| 吞吐 | 低 | 高 | 高 | 高 |
| 延迟 | 高 | 中 | 低 | 低 |
HG-PIPE 的目标就是同时拿到”细粒度的低缓冲”和”粗粒度的任意访问”,还要把延迟压到最低——这就是”混合粒度”的含义,后面会看到它具体怎么做到的。
挑战二:非线性函数的 DSP 黑洞#
第二个挑战来自计算资源。ViT 里的非线性函数全是 DSP 大户。论文用 HLS 综合实验给出了每个函数用浮点实现时的 DSP 消耗:
| 函数 | 用在哪 | 浮点实现 DSP 消耗 |
|---|---|---|
| Exp | Softmax | 7 |
| Rsqrt | LayerNorm(开方+除法融合) | 8 |
| Recip(倒数) | Softmax 归一化 | 9 |
| GeLU | MLP 激活 | 26 |
| ReQuant(量化反量化) | 每个量化层的输入 | 1 |
在 DeiT-tiny 里把这些函数全部例化一遍,总共需要 3024 个 DSP。而 VCK190 片上总共只有约 1970 个 DSP(论文实测整机只用了 312 个)。也就是说,不做任何处理的话,光非线性函数就把 DSP 预算超了 50%,矩阵乘还没地方放。
为什么非线性函数这么吃 DSP?因为浮点的指数、倒数、开方没有简单的组合逻辑实现,标准做法是查表 + 多项式迭代(如 CORDIC),每一步迭代都是一堆乘法。GeLU 尤其贵——它内部要算 erf(误差函数),等于嵌套了积分和指数,26 个 DSP 就是这么来的。
这也解释了 HG-PIPE 的第二个核心思路:既然 LUT 资源充裕(VCK190 上是百万量级,论文实测整机用掉 669k)、DSP 稀缺,那就用 LUT 把非线性函数全部”表”化——把函数采样成一张表,查表代替计算。后面「LUT-based 算子」一节会详细拆这套表化的每个技巧。
核心思想:混合粒度流水线#
一句话概括 HG-PIPE 的架构思想:把 ViT 的每一层都变成一块专用电路,层与层之间用细粒度的 FIFO 流式衔接,而注意力内部那些必须乱序访问的地方,用”深缓冲 + 深 FIFO”做粗粒度的局部缓存——细粒度管”层间”,粗粒度管”层内特殊点”,这就是”混合粒度”。
展开说,这套设计有三个支柱:
-
全层流水线化:从 Patch Embedding 到最后分类头,26 个网络块(1 个 PatchEmbed + 12 个 MHA block + 12 个 MLP block + 1 个 Head)各有一份专用硬件,层与层之间直接流式传数据,中间激活和权重完全不碰片外内存。数据从 DMA 进来,算完直接从最后一个模块写回片外。这是把”消除片外访存”做到了极致——我之前拆解的 Blink 讲的是把 CPU 请出关键路径,这里是把 DDR 请出计算路径。
-
计算数据流与并行度耦合设计:流水线里每个模块的并行度(同时算几个 MAC)不是随意定的,而是以”消除气泡”和”BRAM 不浪费”两个目标联立求解的,保证整条流水线以理论极限的速度推进。
-
LUT-based 算子:线性算子(低比特矩阵乘)和非线性算子(GeLU/Softmax/LayerNorm/ReQuant)都用 LUT 实现,DSP 只留给少数非做不可的地方,最终只用 312 个 DSP。
下面从系统级到模块级逐层拆解。
系统总览:一张 “全流水线” 的 SoC#
HG-PIPE 的整体架构见论文 Figure 4(本文配图):

(图片来源:论文 Figure 4,CC BY-NC-SA 4.0)
注意看这张图的几个关键特征:
(1)SoC 框架。加速器挂在 Versal 平台的 PS(处理系统,Arm 核)+ PL(可编程逻辑)体系里:外部内存(DDR)通过 DMA 模块给流水线喂数据,主机(Host)通过 AXI 总线控制,片内网络(NoC)负责各部件互联。PS 只负责控制和搬运,计算全部在 PL。
(2)数据只进一次。DMA 把输入图片的 token 序列流进 Patch Embedding,之后所有中间激活都在片上模块之间流动,没有任何一层的结果写回 DDR。全部算完后,分类头的输出才经 DMA 写回。这意味着片外带宽的消耗被压到”输入图片 + 输出 logits”,而计算强度(每字节访存对应的算力)达到最大化——这正是 Roofline 模型里最理想的状态。
(3)20,000+ MAC 单元,但没有集中控制器。这是工程上非常关键的一个决策。20000 个 MAC 意味着大规模控制逻辑,如果像传统做法那样用一个中央调度器统一指挥所有模块,控制信号要走遍全片,布线距离过长会直接把时钟频率拖垮。论文举了 FixyFPGA 的例子:它用了超过 133 万 LUT 做类似的事,结果主频只有 132 MHz。HG-PIPE 反其道而行:每个流水线阶段用自己的 FSM 独立控制,模块之间用 AXI-Stream 协议握手(ready/valid),中间用 FIFO 缓冲解耦——下游没准备好,上游就自动停(反压);数据产生速率有波动,FIFO 吸收波动。整个系统没有”全局时钟调度”,只有局部握手,这是异步去中心化控制,避免了长布线也避免了死锁。代价是每条 FIFO 要足够深(见下节),论文通过仿真确定典型深度为 512。
(4)数据以 tile(分块张量)形式流动,不是整张量也不是单元素——tile 的尺寸由并行度决定,这是「并行度设计」一节的主题。
这张系统图里有 26 个网络块。论文 Figure 13 展示了 Vivado 自动布局后的器件视图,每个模块的物理位置随机分布(自动布局的结果),PS、PLL、DMA、AXI Crossbar 等外围模块也标注其中。值得强调的是:整个设计没有用 Versal 的 AI Engine(VCK190 上的 AIE 阵列一个没开),全部计算都在 PL 侧的 LUT/DSP/BRAM 上完成——这也是为什么论文能把自己和纯 PL 的 ZCU102 方案直接对比。
混合粒度流水线的原理:注意力内部怎么流数据#
现在进入最核心的部分:混合粒度到底怎么实现。关键是注意力模块(MHA)内部的数据流设计——这正是论文 Figure 5 的内容。原文配图在论文里,这里用文字把这张图讲透:先看输入进来分成哪四路、每一路的访问模式,再看 K/V 深缓冲、转置模块和深 FIFO 分别解决哪一路的问题。
四个分支,两种”粒度”#
一个注意力 block 的输入 x 进来后分四个分支:
- 残差分支:x 原样保留,最后跟注意力输出相加;
- Q 分支:x 经 LayerNorm → QKV 投影生成 Q → 与 K 做矩阵乘;
- K 分支:生成 K → 与 Q 做矩阵乘;
- V 分支:生成 V → 与注意力分数 R 做矩阵乘。
四个分支的数据访问模式完全不同:
- Q 分支局部性极好:生成一个注意力分数 token 只需要”一个 Q token + 全部的 K”,而且 Q 是按 token 顺序生成的,完全适合细粒度流水线;
- K、V 分支全局性强:Q 需要跟所有 K 做点积(K 按行访问),R 需要跟所有 V 相乘(V 按行访问)——如挑战一节所说,这俩是”行式访问”,跟 Q 的 token 式访问错位。
解法一:K/V 深缓冲 + Transpose 模块#
HG-PIPE 在 K 和 V 分支各放了一个深缓冲(deep buffer),深度足够存下整个 K(或 V)张量,并在 V 分支加了一个转置模块(Transpose Module):V 先按 token 写入、再按行读出,把”行式访问”转成”token 式访问”,从而跟 Q 的细粒度流水线对齐。
这里有个细节值得注意:K 和 V 缓冲的内容会被读 COT 次(COT = 输出通道方向的循环次数,见下文「并行度设计」一节)——因为 Q×Kᵀ 的输出 R 有 CO 个输出通道,每算一个输出通道的切片,就要把整个 K 重读一遍。所以 K/V 深缓冲不只是”存一下”,而是”多次重读的驻留缓存”。
解法二:深 FIFO 维持流水线不断流#
但深缓冲只解决了 K/V 的供给问题,还不够:Q 分支和残差分支在 K/V 还没填满时如果继续向前跑,会追上前面的数据依赖(Q 要等 K/V 就绪才能算),从而阻塞整个流水线。为此 HG-PIPE 在所有四个分支都加了深 FIFO,让每个分支都能独立缓冲一段数据,即使某个分支暂时停摆,其他分支还能继续灌数据。
FIFO 要多深?论文的做法是用仿真实验找最浅深度:逐步减小 FIFO 深度直到出现死锁,取恰好不死锁的深度。典型值是 512。这是一个很”工程”的回答——不追求理论最优,而是用仿真逼近、留出安全余量。
时间图:三张图的数据流#
论文 Figure 6 用”每图 3 个 token”的简化例子展示了这套机制的时间行为(原文是时序图,这里把它整理成时刻表,这是理解混合粒度如何工作的关键):
| 时刻 | 发生什么 |
|---|---|
| T=0 | 流水线初始化:图 1(蓝色)的第一个 token 进入前一阶段,残差 token 和 Q token 存入各自的深 FIFO,K、V token 直接进深缓冲(V 先过转置) |
| T=3 | 图 1 的全部 K、V 已缓冲完毕,数据依赖(Q 需要全部 K)满足,第一个 Q token 被消费,产生第一个注意力分数 token A。与此同时图 2(红色)的 token 已经开始流入——注意,图 2 的 K/V 还在路上,图 1 的注意力还没算完,两张图在流水线上重叠处理 |
| T=6 | 图 1 的 Q 全部处理完,K/V 缓冲清空,开始装载图 2 的数据 |
| T=7 | 进入图 2 的处理循环,之后每张图都按这个节奏重叠推进 |
这个例子说明了两件事:第一,流水线在图片之间是重叠的,填满之后每张图的吞吐只由最慢的模块决定(II = 启动间隔);第二,K/V 深缓冲 + 四路深 FIFO 让”等待依赖”这件事只发生在流水线启动阶段(首张图的 T=0~3),稳态下没有气泡。
论文 Figure 7 则从硬件时序视角把”混合”二字落到了具体模块上(同样以文字复述):QKV 生成模块(QKV Gen)在 LayerNorm 还没算完时就开始接收数据——这是细粒度层的部分重叠;而 Q×Kᵀ 必须等 K 缓冲装满才能启动——这是粗粒度层的依赖等待;残差缓冲的成本比传统 PIPO 实现降低了 83.3%——论文原文的表述是”residual buffer cost is significantly reduced by 83.3% compared to traditional PIPO implementation”。注意这里的 83.3% 是相对 PIPO 双缓冲而言:混合粒度下残差只走一条深 FIFO,而不是存双份整张量缓冲。
并行度设计:让每个模块的节奏严丝合缝#
光有数据流还不够——流水线里 20,000 多个 MAC 怎么排布、每个模块算多快,决定了流水线能不能满速跑。这一节是 HG-PIPE 最”设计方法”的部分。
Tiled GEMM 与三种并行维度#
HG-PIPE 的矩阵乘采用 Output Stationary(OS)数据流:每个 MAC 的输出(部分和)固定驻留在计算单元里,输入激活和权重流动过来累加,避免部分和的搬运。矩阵乘有三个循环维度:Token 方向(T)、输入通道方向(CI)、输出通道方向(CO),每个维度都做 tiling(分块),并在每个维度上展开并行。记法如下:
| 符号 | 含义 |
|---|---|
| T | 序列长度(token 数) |
| CI | 输入通道数(嵌入维度) |
| CO | 输出通道数(隐藏维度) |
| TP | Token 方向的并行度 |
| CIP | 输入通道方向的并行度 |
| COP | 输出通道方向的并行度 |
| TT=T/TP | Token 方向的循环次数 |
| CIT=CI/CIP | 输入通道方向的循环次数 |
| COT=CO/COP | 输出通道方向的循环次数 |
一次矩阵乘(输出形状 T×CO)的总工作量是 MOPs=T×CI×CO(百万次乘加),并行度为 P=TP×CIP×COP 个 MAC 单元。而整个模块完成一次推理需要的周期数(II,Initiation Interval)是三个方向的循环次数相乘:
II=TT×CIT×COT直觉上可以这样理解:一次矩阵乘的输出张量是 T×CO,被并行度切成 TT×COT 个输出 tile;每个输出元素还需要沿着输入通道方向累加 CIT 次(每次读入 CIP 个输入通道的切片)。所以一个模块从头到尾算完一次推理所需周期数,正好是三个方向 tile 数的乘积 TT×CIT×COT。
设计目标一:流水线平衡#
整条流水线的 II 是所有阶段 II 的最大值:
IIaccelerator=max(IIstage1,IIstage2,⋯,IIstageN)如果各阶段不均衡——某个模块算得慢——快的模块就会空转等待,产生气泡(bubble),流水线有效吞吐直接掉到最慢模块的速度。论文 Figure 9a 画了这个问题:上图是失衡状态,Matmul1 的 II 明显长于其他模块,下游都停下来等它;下图把更多 MAC 资源分配给 Matmul1,各阶段 II 对齐,气泡消除。下图就是本文的配图之一:

(图片来源:论文 Figure 9,CC BY-NC-SA 4.0)
注意 Figure 9a 里被平衡的正是 Q×Kᵀ 和 R×V 这两个动态矩阵乘——它们工作量一样(2.46 MOPs),并行度必须配平,否则注意力内部就出气泡。
设计目标二:BRAM 利用效率#
第二个目标更隐蔽但同样致命:权重要在片上冻结(freeze),尽量不访存,而权重的内存布局由并行度直接决定。论文 Figure 9b 画的就是这个例子(以文字复述):假设权重矩阵按 (CIP, COP) 的二维排布存进 BRAM,BRAM 是一个固定尺寸的格子(宽 Bwidth 位、深 Bdepth 项)。Layout 1 里某个 (CIP, COP) 组合恰好把一个 BRAM 撑满还多出一行,于是要用两个 BRAM,第二个浪费大半;把 CIP 减半后(Layout 2),同样的数据用一整个 BRAM 正好装下,效率 100%。
一个矩阵乘模块需要的 BRAM 数可以精确算出来:
#BRAM=⌈BwidthDWw⋅CIP⋅COP⌉⋅⌈BdepthCIT⋅COT⌉其中 DWw 是权重的位宽。第一个向上取整是”每个 BRAM 行能放几个权重、需要几行”(宽度维度),第二个是”每个 BRAM 能存几行、需要几个 BRAM”(深度维度)。对应地,BRAM 利用效率定义为实际数据量与总容量的比:
η=#BRAM⋅Bwidth⋅BdepthDWw⋅CI⋅CO分子是权重矩阵的真实总位数,分母是所分配 BRAM 的总容量。η=100% 意味着每一个 BRAM 位都被用上了。
并行度设计结果:DeiT-tiny 的完整账本#
论文没有用自动化搜索——因为 Transformer 每层结构相同、形状相同,设计空间很小,手调就够——直接给出了 DeiT-tiny 上所有模块的并行度配置(论文 Table 1):
| 模块 | TT | CIT | COT | MOPs | 并行度 P | II(周期) | η |
|---|---|---|---|---|---|---|---|
| MHA·LayerNorm | 98 | 192 | — | 0.11 | 2 | 56448 | — |
| MHA·QKV Gen | 98 | 32 | 16 | 2.41 | 48 | 50176 | 100% |
| MHA·QK MatMul | 98 | 16 | 28 | 2.46 | 56 | 43904 | 68.1% |
| MHA·Softmax | 98 | 196 | — | 0.11 | 2 | 57624 | — |
| MHA·RV MatMul | 98 | 28 | 16 | 2.46 | 56 | 43904 | 68.1% |
| MHA·Output Proj | 98 | 16 | 32 | 7.23 | 144 | 50176 | 100% |
| MHA·Residual Add | 98 | 192 | — | 0.038 | 2 | 18816 | — |
| MLP·LayerNorm | 98 | 192 | — | 0.11 | 2 | 56448 | — |
| MLP·MatMul1 | 98 | 16 | 32 | 28.9 | 576 | 50176 | 100% |
| MLP·GeLU | 98 | 98 | — | 0.15 | 4 | 37632 | — |
| MLP·MatMul2 | 98 | 32 | 16 | 28.9 | 576 | 50176 | 100% |
读这张表有几个要点:
第一,II 高度接近但刻意不相等。所有矩阵乘模块的 II 都在 43904~50176 之间,而故意让 Softmax 成为 II 瓶颈(57624)——为什么?因为论文”为了节省 DSP 资源,选择让非线性算子成为 II 瓶颈”。Softmax 的并行度只要 2,其他算子拉满对齐到它,这样非线性函数这块最省资源。整个加速器的 II = 57624 周期。
第二,静态权重的模块全部做到 100% BRAM 效率(QKV Gen、Output Proj、MatMul1/MatMul2)——它们冻结权重,BRAM 是主要消耗,必须榨干。而动态权重的 QK/RV MatMul 只有 68.1%——它们缓存的是激活(K/V 张量)而非权重,且要兼顾深缓冲和转置的布局,效率打折扣是权衡后的结果。
第三,Residual Add 只给了 TP=2 的最小并行度(II 只有 18816,远低于瓶颈),意味着残差加法模块永远在空转等待——论文解释了为什么不心疼:它只有 0.038 MOPs,是整条流水线里工作量最小的模块,就算把它并行度拉满也省不了几个周期,反而要浪费逻辑资源。让最闲的模块继续闲,是成本最优解。
第四,LayerNorm 和 Softmax 各需要三遍扫描(II 是 3 倍的单遍成本):一遍算均值/最大值,一遍算方差/指数,一遍做归一化/累加。所以表格脚注说”for LayerNorm and Softmax modules, three passes are needed, therefore they require 3 times cycles”。
这套并行度设计的产出是:总并行 MAC 数 = 576(MLP 最大)+ 144 + 56×2 + 48 + 若干非线性单元的并行度,凑出 20,000+ MAC 规模(Vitis HLS 展开后的最终实现)。
LUT-based 算子:把”算”变成”查”#
现在讲第二个支柱:怎么让非线性函数不吃 DSP。核心思想一句话:函数值采样成表,查表代替计算;索引计算用 2 的幂缩放代替乘法。论文一共用了七招,每一招都值得拆开看。
第 1 招:LUT-based MAC——乘法也是”查表”#
先看最基本的:LUT 怎么算乘法。以两个 3-bit 操作数相乘为例,结果最多 6 bit。乘法本质是一个 6 输入(3+3)→ 6 输出的布尔函数,可以分解成 6 个独立的 6 输入单输出布尔函数——每个输出位一个函数。FPGA 的 LUT-6 恰好是 6 输入 1 输出,于是 3-bit × 3-bit 乘法 = 6 个 LUT-6,零 DSP。这个技巧 LUTNet 等前作已经用过,HG-PIPE 继承下来并把它推广到非线性函数上。
当然代价是:位数越高,LUT 消耗指数增长。3-bit 乘法 6 个 LUT,4-bit 乘法就要 16 个 LUT-6(8 个输出位 × 8 输入)。所以 LUT-MAC 必须配合低比特量化:HG-PIPE 的激活和权重都量化到 3~4 bit(A4W4 / A3W3 两档配置),量化方案来自 “Variation-aware ViT quantization”(论文 arXiv:2307.00331)。这也是低比特量化的成本:3-bit 满配的精度 71.05% 比全精度 DeiT 的 74.5% 低约 3.5 个点,换 4-bit 配置精度可以回到 74.37%(ZCU102 版实测)。
第 2 招:Power-of-Two 索引——把乘法换成移位#
表化非线性函数的第一步是算索引(查表地址):把连续输入值离散化到 2n 个格子。传统做法是线性映射:
index=⌈(data−α)×β−α2n−1⌋其中 (α,β) 是输入张量的数据范围,n 是表地址位宽。但这个映射需要一次高精度乘法(缩放因子乘上差值)——又是 DSP,与表化的初衷矛盾。
HG-PIPE 引入 Power-of-Two 量化(PoT,来自 Miyashita 等人的对数数据表示工作)的思路:把缩放因子近似成最近的 2 的幂,乘法就变成移位:
indexPoT=(data−α)≫sPoT,其中 sPoT=⌈log2(2n−1β−α)⌉注意 sPoT 用了向上取整而不是四舍五入,论文特别说明:这是为了避免索引溢出(上取整让缩放因子偏大,索引偏小,安全)。
代价是边界不再对齐:输入最大值 β 不再精确映射到最大索引 2n−1,最顶端的格子可能没被用到。论文 Figure 10a 画得很清楚:蓝条是输入分布,黑色虚线是精确映射(边界对齐),红色箭头是 PoT 映射(静态移位,不保证对齐)。这是一次”精度换资源”的明码交易——表索引的乘法(DSP)换成了移位(免费)。
第 3 招:GeLU-ReQuant 算子融合#
量化网络里每个矩阵乘之前都插着量化器(ReQuant),把激活重新量化回低位。GeLU 后面紧跟一个 ReQuant。把两个函数合成一个查表:与其先查 GeLU 表再算 ReQuant(两跳),不如直接对复合曲线采样,一张表搞定。论文 Figure 10b 展示了这个融合过程:左边是 GeLU 曲线,中间是 4-bit ReQuant 的阶梯曲线,右边是融合后的复合曲线——采样直接打在复合曲线上。
好处:省掉一半的表访问逻辑,也省掉 ReQuant 的 DSP(见下一条)。这个思路本质上是”算子融合”思想在表实现上的应用,跟 vLLM 里把 RMSNorm 融进 GEMM kernel 是同一个哲学。
第 4 招:ReQuant 直接表化#
融合不掉的 ReQuant(比如矩阵乘输出端的、Softmax 前后的)仍然各吃 1 个 DSP。HG-PIPE 把 ReQuant 也当成非线性函数表化:
ReQuant(x)=clamp(⌈(x−αint)⋅Sfixed⌋,Qmin,Qmax)其中 αint 是整数零点、Sfixed 是定点缩放因子。索引计算同样用 PoT 移位。实验表明 64 项的表就足够保住精度——低比特量化的一个红利:输出只有 24=16 个合法值,表天然就小。
第 5 招:联合表范围校准(Joint Table Range Calibration)#
在实现 ReQuant 表和融合表时,论文发现一个浪费:clamp 的两端产生大量重复表项。比如 4-bit 输出的合法范围只有 16 个值,而表有 64 项,输入分布两端被 clamp 截断的区域对应同一输出值,白占了表项,等于浪费了表的”表达能力”。Figure 10c 里两端橙色的部分就是重复项。
校准算法很直接:迭代地找到最低有效索引(LSI)和最高有效索引(MSI)——即输入分布实际覆盖的表项范围——据此重算数据范围 (α,β),更新表,再重新统计,直到范围稳定。校准后 LSI 映射到 0,MSI 落到右边界附近;由于 PoT 近似,右端仍会残留少量重复项,但这是可接受的。消融实验证明这一招值 0.3~0.5 个点精度。
第 6 招:分段表(Segmented Table)处理高动态范围倒数#
Softmax 里的倒数(Recip,1/x,x ∈ (0,1])有个麻烦:函数在接近 0 的地方陡峭、接近 1 的地方平坦,动态范围极高。均匀采样一张表要很密才压得住 0 附近的误差——论文说初始实现需要整整一个 BRAM bank(1024 深 × 36 宽)才够精度。
解法是分段:在 x 的前 1/8 处切开——陡峭段(0 ~ 1/8)单独用更密的采样(更多表项 + 独立缩放因子),平坦段(1/8 ~ 1)用稀疏采样。分界点是经验选的(“we empirically divide the input range at the first 1/8”)。Figure 10d 画了对比:橙色线是均匀表的绝对误差,蓝色是分段表的误差,分界点标注在曲线上。效果:MSE 从 0.032 降到 0.0034,差不多一个数量级,而表的总深度只有 128(64×2)。
第 7 招:逆指数表(Inversed Exponential Table)——零点的选择决定成败#
最后这招是全文最微妙的一个细节,也是消融实验里最关键的(去掉它精度掉 42 个百分点)。
先看问题:对 Exp 直接套用第 2 招的 PoT 索引会带来灾难性的精度损失。论文的分析是:Softmax 为了保证数值稳定,每个元素先减去所在组的最大值:
Softmax(x)=∑Exp(x−xmax)Exp(x−xmax)这导致输入范围的最大值被锚定到 0(β=0)。而第 2 招的 PoT 索引以最小值 α 为零点。锚点一错位,最敏感的锚值区域(接近 0 的输入,对应 Softmax 里概率最大的元素)恰好落在索引最粗糙的地方,精度崩盘——消融实验里 3-bit 量化下掉了 42.25% 的精度,直接让模型变成随机猜测。
解法一句话:把零点换成最大值。索引方向反转,从最大值往小数:
indexPoT=(β−data)≫sPoT,其中 sPoT=⌈log2(2n−1β−α)⌉这样指数函数的输入(β−data,非负)从 0 开始递增,而数据 data 恰好等于 β(即 Softmax 中最大的元素、概率最大的位置)时索引为 0——最敏感的区域拿到了最精确的索引,而索引值本身仍然只是移位,不花 DSP。论文把这称为 “Inversed Exponential Table”(逆指数表)。消融实验里这一招单独把精度从 28.80% 拉回接近正常水平,是整个表化方案里最关键的一步。
七招合力的资源账#
论文 Figure 11c 给出了每个非线性函数表化前后的资源对比:
| 函数 | 表深度 | 表位宽 | LUT-6 消耗(前 → 后) | DSP(前 → 后) |
|---|---|---|---|---|
| Exp | 64 | 8 | 945 → 50 | 7 → 0 |
| GeLU | 64 | 3 | 1650 → 43 | 26 → 0 |
| Recip | 64×2 | 8 | 196 → 72 | 8 → 0 |
| Rsqrt | 64 | 12 | 425 → 48 | 9 → 0 |
| ReQuant | 64 | 3 | 0 → 3 | 1 → 0 |
LUT 消耗平均降一个数量级(因为浮点实现的”逻辑”被换成了一次简单的查表),DSP 全部清零。整套优化的净效果:DSP 从 3024 降到 312(降 89.6%),精度损失可以忽略。
实验:两个平台上的硬数据#
实验设置#
- 模型:DeiT-tiny(2.5 GFLOPs/张、550 万参数)和 DeiT-small(9.2 GFLOPs、2200 万参数),精度 A4W4 或 A3W3。
- 平台:ZCU102(UltraScale+,用于跟历史方案直接对比)+ VCK190(Versal,用于单芯片全量部署)。
- 测量:吞吐用 PYNQ 框架测,功耗用 Xilinx BEAM 工具测。
时序验证:混合粒度确实没有气泡#
论文 Figure 12 是用 RTL 仿真画出的各模块时序图,几个数字值得对一遍:
- 图片逐张进入流水线,第一张图(Image1)加载完成后第二张立刻开始——重叠执行成立;
- MHA block 有粗粒度的缓冲行为,输出第一个 tile 略延迟(K/V 缓冲时间);
- 第三张图起,稳态 II 稳定在 57,624 周期——与 Table 1 的预测完全一致(Softmax 的 57624),说明流水线平衡设计在仿真里兑现了;
- Image1 全程耗时 824,843 周期 = 1.94 ms(首张图的流水线填充成本);
- 用稳态 II 折算:每张图 0.136 ms,理想帧率 7353 FPS,实测 7118 FPS 达到理想值的 96.8%——剩下 3.2% 是首图延迟的摊销和边界效应。
这个”实测/理想 = 96.8%“是论文最有说服力的一张成绩单:它证明气泡基本被消灭干净了。
与既有工作的对比#
论文 Table 2 是最全的对比,我按 DeiT-tiny 口径整理(另有 Deit-small 一行放在最后):
| 工作 | 范式 | 平台 | 频率 | 精度 | FPS | LUTs | DSPs | BRAMs | 功耗 |
|---|---|---|---|---|---|---|---|---|---|
| V100 GPU | GPU | V100 | 1455 MHz | fp32 | 2529 | — | — | — | — |
| Deit Baseline(TCAS-I 2023) | 时序 | ZCU102 | 300 MHz | A8W8 | 245 | 114k | 1268 | 648 | 29.6W |
| AutoViTAcc(FPL 2022) | 时序 | ZCU102 | 150 MHz | A4W4+A4W3 | 155.8 | 193k | 1549 | — | 10.34W |
| HeatViT(HPCA 2023) | 时序 | ZCU102 | 150 MHz | A8W8 | 183.4 | 137.6k | 1968 | 355.5 | 9.45W |
| SSR(FPGA 2024) | 粗粒度流水线 | VCK190 | PL 250MHz / AIE 1GHz | A8W8 | 4545 | 619k | 14405* | 1456 | 46W |
| HG-PIPE(4-bit) | 混合粒度 | ZCU102 | 375 MHz | A4W4 | 1579 | 514k | 78 | 1284 | 21.9W |
| HG-PIPE(4-bit) | 混合粒度 | VCK190 | 425 MHz | A4W4 | 3629 | 212.7k | 156 | 324.5 | 43.4W |
| HG-PIPE(3-bit,满配) | 混合粒度 | VCK190 | 425 MHz | A3W3 | 7118 | 669k | 312 | 1006.5 | 46.7W |
| HG-PIPE | 混合粒度 | VCK190 | 350 MHz | A3W3 | 1490(Deit-small) | 869k | 312 | 2748† | 48.1W |
(*SSR 的 DSP 数 14405 是把 394 个 AI Engine 按 1 AIE = 32 DSP 归一化后的等效值;†此处 2748 是 718.5 BRAM + 36 URAM 按 1 URAM = 8 BRAM 归一化的结果。)
注意 HG-PIPE 自己在 VCK190 上有两档配置:4-bit 量化用 212.7k LUT 就跑到 3629 FPS;把并行度和表精度拉满到 3-bit、669k LUT,才冲到 7118 FPS。Deit-small 因为是 9.2 GFLOPs 的大模型,同样 312 DSP 只能跑 1490 FPS。几个关键读法:
对 GPU:2.81 倍。 V100 跑 fp32 的 DeiT-tiny 是 2529 FPS,HG-PIPE 是 7118 FPS。注意这不是同精度的公平比较(fp32 vs 3-bit),但论文的目的正是展示”FPGA + 量化 + 定制数据流”这条组合路线的潜力——能效上的差距更夸张,V100 的 GOPs/W 没给,但 HG-PIPE 的 381 GOPs/W 已经比 SSR 的 246 高 55%。
对时序架构(同平台 ZCU102):2.78 倍。 论文摘要口径下,ZCU102 上 HG-PIPE 的吞吐是 AutoViTAcc 等既有工作的 2.78 倍(贡献节里写的是 2.72 倍,两处数值口径略有差异),资源效率(GOPs/kLUT)从 AutoViTAcc 的 7.35 提到 HG-PIPE 的 18.55,即 2.52 倍。
对粗粒度流水线(SSR):DSP 是决胜点。 SSR 用 619k LUT + 等效 14405 DSP(含 AIE)换 4545 FPS;HG-PIPE 用 669k LUT + 312 DSP 换 7118 FPS。HG-PIPE 的 DSP 只有 SSR 的 2.2%——这就是 LUT 算子实现的价值:同样的 LUT 盘子,DSP 省下来换吞吐。
一个反常数据点:HG-PIPE 的功耗(46.7W)比 ZCU102 上所有方案都高。原因很简单:VCK190 是 Versal 旗舰,425 MHz 高频 + 全片 LUT 翻转,功耗自然高。但折算到能效(GOPs/W = 381)仍然碾压所有对手,因为吞吐涨得更快。
消融实验:每个技巧值几个点#
论文 Figure 11b 对四个近似技巧做了消融(DeiT-tiny,3-bit 和 4-bit 两档):
| 去掉的技巧 | DeiT-tiny 3bit 精度 | DeiT-tiny 4bit 精度 |
|---|---|---|
| 完整方案 | 71.05% | 74.37% |
| w/o 逆指数表 | 28.80%(−42.25%) | 48.87%(−25.50%) |
| w/o ReQuant 校准 | 70.56%(−0.49%) | 74.08%(−0.29%) |
| w/o GeLU 校准 | 69.49%(−1.56%) | 72.44%(−1.93%) |
| w/o 分段倒数表 | 70.57%(−0.48%) | 74.04%(−0.33%) |
注意量级差异:逆指数表是”生死攸关”级(−42%),其余三个都是”锦上添花”级(−0.3% ~ −1.9%)。这很符合直觉——Softmax 的锚点问题是结构性的(前面第 7 招的分析),而表范围校准只是省表项。也提醒我们:做硬件近似时,数值结构的敏感性远比资源节省量更重要,先把结构性错误修掉,再谈优化。
LUT 优化的总账#
Figure 11a 展示了 DSP 消耗随优化逐步推进的变化轨迹:
- 浮点全精度实现:14304 DSP(直接不可行);
- 上低比特量化 + LUT-MAC:降到 3024 DSP(仍超 VCK190 容量);
- 非线性函数全表化(PoT):降到 312 DSP,但精度崩到 28.80%;
- 逆指数表 + 校准 + 分段表逐步救回精度,DSP 始终 312。
这条轨迹把”资源-精度”的博弈过程完整展示出来:先砍资源,再补精度,最后收敛在 312 DSP / 71.05%(3-bit)/ 74.37%(4-bit)。
小结与启发#
HG-PIPE 的核心贡献可以压缩成三句话:
- 混合粒度流水线:层间用细粒度 FIFO 流式衔接(缓冲小、无片外访存),注意力内部的 K/V 用深缓冲 + 转置模块 + 深 FIFO 处理乱序访问(兼容 ViT 的全局依赖),残差缓冲成本比粗粒度 PIPO 方案降 83.3%,配合并行度耦合设计把流水线推到 96.8% 的理想效率;
- LUT-based 算子:PoT 移位索引、GeLU-ReQuant 融合、联合表范围校准、分段倒数表、逆指数表五个技巧,把非线性函数的 DSP 消耗清零,整机 DSP 从 3024 降到 312(−89.6%);
- 可验证的成绩:VCK190 单芯片 7118 FPS / 17.8 TOP/s,V100 的 2.81 倍;ZCU102 上比 AutoViTAcc 吞吐高 2.78 倍、资源效率高 2.52 倍。
这篇论文最有价值的不是那 7118 FPS 的数字,而是几个可以迁移到任何加速器设计的方法论:
- 先画 Roofline,再动手。论文的第一张图就把四条路线各自的天花板算清楚了,所有设计决策都对着这个天花板做。做 GPU kernel 或其他加速器优化同理:先算清是带宽受限还是计算受限,再决定优化方向。
- “让最闲的继续闲”。Residual Add 只有 0.038 MOPs,就不给它并行度,让它空转。资源要用在产出最高的地方——这是工程直觉,不是数学结论。
- 数值结构优先于查表密度。逆指数表的教训说明:硬件近似的失败往往不是表不够大,而是锚点选错。做任何数值近似前,先分析数据分布的结构(最大值在哪、零点在哪、曲率在哪)。
- 异步去中心化控制。20000 个 MAC 用局部握手 + FIFO 而不是集中调度,避开长布线、保住频率。这个思想在芯片设计里叫 Globally Asynchronous Locally Synchronous(GALS),值得了解。
想继续深挖的读者:HG-PIPE 的完整代码在 GitHub hguq/HG-PIPE(HLS 源码 + SpinalHDL 仿真 + Jupyter 板级测试),支持 VCK190 复现;它跟 FPGA 加速器完全拆解(二) 里讲的 FlightLLM 恰好是两条路线:HG-PIPE 是”整网全流水线、数据不出片”,FlightLLM 是”编译映射 + 稀疏,让 LLM 也能上 FPGA”——一个面向 ViT、一个面向 LLM,一个重数据流、一个重编译,对照着看能更完整地理解 FPGA 加速器的设计空间。
参考资料#
- HG-PIPE: Vision Transformer Acceleration with Hybrid-Grained Pipeline(arXiv 摘要页,arXiv:2407.17879)
- 论文全文(arXiv HTML 版,含全部图表)
- 论文全文(ar5iv 渲染版)
- HG-PIPE 官方开源仓库(GitHub: hguq/HG-PIPE,HLS 设计 + SpinalHDL 仿真 + 板级测试脚本)
- ICCAD 2024 论文页面(ACM Digital Library, DOI: 10.1145/3676536.3676681)
- 论文 IEEE 版本页面(IEEE Xplore)
- 北大团队打造数据流架构,解决视觉 Transformer 加速难题(新浪科技/网易转载的团队解读)
- An Image is Worth 16x16 Words: Transformers for Image Recognition at Scale(ViT 原论文,arXiv:2010.11929)
- Training Data-Efficient Image Transformers & Distillation through Attention(DeiT,ICML 2021)
- Auto-ViT-Acc: An FPGA-Aware Automatic Acceleration Framework for Vision Transformer(FPL 2022,DOI: 10.1109/FPL57034.2022.00027)
- HeatViT: Hardware-Efficient Adaptive Token Pruning for Vision Transformers(HPCA 2023,DOI: 10.1109/HPCA56546.2023.10071047)
- FixyFPGA: Efficient FPGA Accelerator for DNNs with High Element-Wise Sparsity and without External Memory Access(FPL 2021,DOI: 10.1109/FPL53798.2021.00010)
- LUTNet: Learning FPGA Configurations for Highly Efficient Neural Network Inference(IEEE TC 2020,DOI: 10.1109/TC.2020.2978817)
- Convolutional Neural Networks Using Logarithmic Data Representation(PoT 量化思想来源,arXiv:1603.01025)
- Variation-aware Vision Transformer Quantization(HG-PIPE 使用的量化方案,arXiv:2307.00331)
文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!
部分内容可能已过时
评论区
分享你的想法,与大家交流讨论
音乐
暂未播放



