
1. 从 Transformer 到 AI 芯片为什么硬件必须为算法让路搞 AI 芯片设计这些年我最大的感受就是算法和硬件之间的关系从来不是谁服务谁而是互相成就、互相制约。你去看 TPUv1 的设计背景就明白了——2013 年左右Google 内部发现如果用户每天用语音搜索 3 分钟用 Google 的通用 CPU 集群来跑深度神经网络推理成本高到根本撑不住。这不是优化能解决的问题是架构层面的不匹配。于是他们决定自己造芯片TPUv1 就是这么来的。而今天Transformer 架构几乎统治了从 NLP 到 CV 的所有任务它的计算模式和当年 TPUv1 要伺候的 CNN 已经完全不一样了。这就带来一个很现实的问题你手里那颗为卷积优化的芯片跑 Transformer 到底能跑多快这个问题不搞清楚设计出来的东西就是空中楼阁。这篇文章我打算把 AI 芯片软硬件设计里最核心的几个问题掰开揉碎讲清楚脉动阵列到底怎么工作的、Transformer 的计算特征对硬件提出了什么新要求、TPUv1 的设计思路在今天还成不成立、以及从算法到 RTL 的完整链路该怎么走。适合做芯片架构的、做 AI 加速器的、以及想搞清楚“算法怎么变成硅片”的读者。不管你是刚入行的新手还是做了几年的老手应该都能从中找到一些能直接用的东西。2. 脉动阵列AI 芯片的“心脏”到底怎么跳2.1 脉动阵列基本原理数据流动的艺术脉动阵列Systolic Array这个概念其实很老了1980 年代就由 H.T. Kung 提出来了。但它在 AI 芯片领域真正大放异彩还是因为 TPUv1 把它用在了矩阵乘法上。我第一次看到脉动阵列的图时觉得这东西怎么这么绕——数据从不同方向流入在 PEProcessing Element阵列里“脉动”着前进每个 PE 只做最简单的乘加运算。但后来自己动手算了一遍才明白这种设计的精妙之处在于它把数据复用做到了极致同时把控制逻辑降到了最低。先讲清楚它解决什么问题。矩阵乘法 C A × B假设 A 是 M×KB 是 K×N那么需要 M×N×K 次乘加运算。如果用最朴素的方式每个输出元素 C[i][j] 都需要读取 A 的第 i 行和 B 的第 j 列内存访问量是 O(M×N×K)。但脉动阵列通过让数据在 PE 之间流动把内存访问量降到了 O(M×K K×N M×N)——数据复用率提升了几个数量级。具体怎么做到的以 TPUv1 的 256×256 脉动阵列为例。权重 B 从左侧流入激活值 A 从上方流入部分和partial sum在阵列中从下往上累积。每个 PE 只做一件事psum a * b然后把 a 往右传、把 psum 往上传。时钟每跳一拍数据就往前“脉动”一步。整个阵列不需要复杂的地址生成逻辑不需要多级缓存数据像流水一样自然流动。注意脉动阵列的高效有一个前提——数据流必须是有规律的、可预测的。如果计算模式经常变化比如稀疏矩阵、动态形状脉动阵列的效率会急剧下降。这也是为什么后来的 TPU 版本要加入更多灵活性。2.2 为什么是 256×256TPUv1 的参数选择逻辑TPUv1 选 256×256 的阵列规模不是拍脑袋决定的。这里面有几个约束条件在互相拉扯第一芯片面积和功耗。每个 PE 包含一个 8 位乘法器和一个 32 位累加器。256×256 65536 个 PE在 28nm 工艺下大约占芯片面积的 24%。如果再大面积和功耗就控制不住了。第二内存带宽匹配。TPUv1 的片上 SRAM 是 24MB权重带宽是 256 字节/周期。256×256 的阵列每个周期需要 256 个权重和 256 个激活值刚好匹配这个带宽。如果阵列再大内存就跟不上了PE 会饿死。第三矩阵维度的对齐。当时主流的神经网络层比如全连接层的输出维度经常是 256 的倍数。选 256 可以让阵列利用率最大化。如果选 128 或 512要么浪费面积要么利用率下降。我后来自己做加速器设计时也面临过类似的选择。阵列规模不是越大越好而是要跟你的目标工作负载、内存带宽、面积预算三者匹配。我见过有人设计 512×512 的阵列结果因为内存带宽不够实际利用率只有 30% 不到白白浪费了面积。2.3 脉动阵列的变体从权重固定到输出固定经典的脉动阵列是“权重固定”Weight Stationary的——权重预先加载到 PE 里激活值流过阵列。但这不是唯一的选择。根据数据复用策略的不同还有“输出固定”Output Stationary和“行固定”Row Stationary等变体。数据流类型权重复用激活复用适用场景权重固定高低大批量推理权重可预加载输出固定低高卷积层输出通道多行固定中中通用 CNN 加速无本地复用无无灵活但效率低TPUv1 用的是权重固定因为推理场景下权重是固定的可以提前加载好。但如果是训练场景权重每步都更新权重固定的优势就不明显了。这也是为什么后来的 TPUv2/v3 要支持多种数据流模式。2.4 实操心得脉动阵列设计中最容易踩的坑我自己在 RTL 层面实现脉动阵列时踩过几个坑这里分享出来坑一忘记处理边界条件。脉动阵列的边界 PE 需要处理数据流入和流出的时序。如果边界逻辑写错了整个阵列的输出就是错的。我当时的做法是先用 Python 写一个行为级模型验证数据流正确后再写 RTL。坑二时钟树综合没做好。65536 个 PE 分布在整个芯片上时钟偏斜clock skew会严重影响时序。TPUv1 用了 H-tree 时钟分布网络来保证偏斜在可控范围内。如果你做小规模阵列可能感觉不到但规模一大这个问题就致命了。坑三累加器位宽不够。8 位乘法结果是 16 位但累加 K 次后可能溢出。TPUv1 用了 32 位累加器支持 4 位对齐的累加。如果你只给 16 位累加器跑几层网络就溢出了。3. Transformer 来了硬件设计的新挑战3.1 Transformer 的计算特征和 CNN 有什么本质不同Transformer 和 CNN 的计算模式差异直接决定了硬件设计思路的不同。我总结下来核心差异有三个第一计算密集型操作从卷积变成了矩阵乘法。CNN 的核心是卷积有局部连接和权重共享的特性。Transformer 的核心是自注意力Self-Attention和前馈网络FFN本质上是大规模的矩阵乘法。这意味着硬件需要更通用的矩阵乘法引擎而不是专门的卷积引擎。第二内存访问模式完全不同。卷积的输入是局部感受野数据复用率高。而自注意力需要计算 Q、K、V 三个矩阵然后做 Q×K^T 得到注意力分数再和 V 相乘。这个过程中中间结果注意力矩阵的大小是序列长度的平方。序列长度 512 时注意力矩阵就是 512×512内存占用相当可观。第三动态形状和稀疏性。Transformer 的序列长度是可变的而且注意力矩阵往往有稀疏性很多分数接近零。脉动阵列这种固定数据流的架构处理动态形状和稀疏性时效率会下降。3.2 自注意力的硬件实现从公式到电路自注意力的公式是Attention(Q, K, V) softmax(QK^T / sqrt(d_k)) V。这个公式看起来简单但硬件实现时每一步都有讲究。第一步Q、K、V 的生成。输入 X 分别乘以三个权重矩阵 W_Q、W_K、W_V得到 Q、K、V。这三个矩阵乘法可以并行做也可以分时复用同一个矩阵乘法引擎。TPU 的做法是用同一个脉动阵列分时计算因为三个矩阵乘法的维度通常是一样的。第二步QK^T 计算。这是注意力机制的核心。Q 是 n×d_kK 是 n×d_kQK^T 是 n×n。当 n512、d_k64 时这个矩阵乘法需要 512×512×64 ≈ 1600 万次乘加。如果用 256×256 的脉动阵列需要约 256 个周期。第三步Softmax。这是硬件实现中最麻烦的部分。Softmax 需要先找最大值数值稳定性再做指数运算最后归一化。指数运算在硬件里通常用查找表LUT实现精度和面积的权衡很关键。我见过有人用 8 位 LUT 做指数结果精度损失导致模型准确率掉了 2 个百分点。第四步注意力分数乘以 V。这一步又是矩阵乘法维度是 n×n 乘以 n×d_v结果是 n×d_v。提示Softmax 的硬件实现建议用分段线性近似或查找表纯 CORDIC 实现面积太大。如果精度要求不高可以用 16 位定点数代替浮点数面积能省一半。3.3 位置编码和层归一化的硬件开销Transformer 里还有两个容易被忽视但硬件开销不小的操作位置编码和层归一化。位置编码在原始 Transformer 里是用正弦函数生成的硬件实现需要三角函数计算。后来的 BERT 和 GPT 改用了可学习的位置编码本质上就是一个查找表硬件实现简单很多。但如果你要支持可变序列长度查找表的地址生成逻辑还是需要仔细设计。层归一化Layer Normalization需要计算均值和方差然后做归一化。均值和方差的计算需要遍历整个特征维度这是一个归约操作reduction。在脉动阵列上做归约操作效率不高因为脉动阵列擅长的是矩阵乘法不是归约。通常的做法是用单独的归约单元或者用 SIMD 指令在向量单元上做。3.4 轻量 Transformer 对硬件的启示最近轻量 Transformer比如 MobileViT、EfficientFormer很火它们的核心思路是减少计算量和参数量。从硬件角度看这些轻量模型有几个特点值得注意深度可分离卷积和注意力的混合MobileViT 把卷积和注意力混合使用硬件需要同时支持两种计算模式。更小的隐藏维度隐藏维度从 768 降到 256 甚至 128矩阵乘法的维度变小脉动阵列的利用率会下降。更短的序列长度序列长度从 512 降到 128 甚至 64注意力矩阵变小但相对而言 Softmax 的开销占比上升。这些变化意味着为大型 Transformer 设计的加速器不一定适合轻量 Transformer。如果你要做端侧 AI 芯片需要重新考虑阵列规模、内存层次和指令集设计。4. 从算法到 RTLAI 芯片设计的完整链路4.1 算法建模先用 Python 跑通再谈硬件我见过太多人一上来就写 RTL结果仿真跑不通回头改算法模型来回折腾。正确的做法是先用 PythonNumPy 或 PyTorch把算法跑通确认数值正确后再逐步往硬件映射。具体步骤用 PyTorch 实现 Transformer 模型在 CPU 上跑通推理记录每一层的输入输出。用 NumPy 手写矩阵乘法和 Softmax验证和 PyTorch 结果一致。量化到定点数比如 8 位权重、8 位激活、32 位累加重新验证精度。写行为级硬件模型比如用 SystemC 或 Python 的 cycle-level 模型模拟脉动阵列的数据流。对比行为级模型和 NumPy 的结果确保数据流正确。这一步看起来费时间但实际上能省下大量调试 RTL 的时间。我自己的经验是算法建模花 1 周RTL 调试能省 3 周。4.2 硬件架构设计从数据流到微架构算法建模完成后接下来是硬件架构设计。核心问题是数据怎么流、计算怎么排、内存怎么管。以 Transformer 的矩阵乘法为例假设我们要设计一个 128×128 的脉动阵列支持 8 位整数运算。架构设计需要考虑权重缓存权重矩阵需要预加载到片上 SRAM。128×128 的权重矩阵8 位精度需要 16KB。如果有多层需要更大的缓存或者外部内存。激活缓存激活值需要从外部内存流入。为了隐藏内存延迟通常用双缓冲double buffering或者乒乓缓存。部分和存储脉动阵列的部分和需要存储在 PE 内部或者相邻的累加器里。128×128 的阵列每个 PE 一个 32 位累加器总共需要 64KB。控制逻辑需要生成地址、控制数据流、处理边界条件。这部分通常用有限状态机FSM实现。4.3 RTL 实现Verilog 代码示例下面是一个简化的脉动阵列 PE 的 Verilog 实现展示了核心的乘加逻辑和数据传递module pe #(parameter DATA_WIDTH 8, parameter ACC_WIDTH 32) ( input wire clk, input wire rst_n, input wire [DATA_WIDTH-1:0] a_in, // 从上方流入的激活值 input wire [DATA_WIDTH-1:0] b_in, // 从左侧流入的权重 input wire [ACC_WIDTH-1:0] psum_in, // 从下方流入的部分和 output reg [DATA_WIDTH-1:0] a_out, // 向右传递的激活值 output reg [DATA_WIDTH-1:0] b_out, // 向下传递的权重 output reg [ACC_WIDTH-1:0] psum_out // 向上传递的部分和 ); always (posedge clk or negedge rst_n) begin if (!rst_n) begin a_out 0; b_out 0; psum_out 0; end else begin a_out a_in; b_out b_in; psum_out psum_in (a_in * b_in); end end endmodule这个 PE 的逻辑很简单每个时钟周期把输入的 a 和 b 相乘累加到 psum 上然后把 a 往右传、b 往下传、psum 往上传。整个阵列就是这些 PE 的二维网格。注意实际设计中乘法器需要流水线化pipeline才能跑到高频率。上面的代码是组合逻辑乘法时序路径太长频率上不去。通常会在乘法器后面加一级寄存器。4.4 验证与测试怎么确保芯片跑得对RTL 写完后验证是重头戏。AI 芯片的验证有几个特殊之处数据量大矩阵乘法的输入输出都是大规模数据用传统的定向测试用例覆盖不全。通常用随机测试加上参考模型对比。精度要求高定点数的舍入模式、溢出处理都会影响最终精度。需要专门测试边界条件。性能验证不仅要验证功能正确还要验证吞吐量、延迟、内存带宽是否达标。通常用性能计数器performance counter来统计。我自己的做法是先用小规模矩阵比如 4×4验证功能再用大规模矩阵256×256验证性能和时序。小规模测试用波形调试大规模测试用脚本自动化对比。5. 常见问题与排查技巧实录5.1 脉动阵列利用率低原因和对策问题现象理论算力很高但实际跑模型时利用率只有 20%-30%。排查思路检查矩阵维度是否对齐。如果矩阵维度不是阵列规模的整数倍边界 PE 会空闲。比如 256×256 的阵列跑 100×100 的矩阵利用率只有 15%。检查内存带宽是否瓶颈。如果数据供给跟不上PE 会饿死。用性能计数器统计 PE 的空闲周期。检查数据流是否匹配。权重固定的阵列跑权重频繁更新的场景效率会下降。对策矩阵维度不对齐时可以用 padding 补齐或者用更小的阵列。内存带宽不够时增加片上缓存或者优化数据复用。数据流不匹配时考虑支持多种数据流模式。5.2 Softmax 精度不够从算法到硬件的联合优化问题现象硬件实现的 Softmax 导致模型准确率下降。排查思路检查指数运算的精度。查找表的位宽不够会导致精度损失。检查最大值查找是否正确。如果最大值找错了指数运算会溢出。检查归一化的除法精度。定点数除法容易损失精度。对策增加查找表位宽或者用分段线性近似。用 16 位定点数代替 8 位。在算法层面做补偿比如在训练时加入量化噪声。5.3 时序不收敛高频设计的常见坑问题现象综合后时序报告显示建立时间setup time违例。排查思路检查关键路径。通常是乘法器或者累加器的组合逻辑太长。检查时钟树。大规模阵列的时钟偏斜会导致时序问题。检查布线拥塞。如果布线资源不够时序会恶化。对策在乘法器和累加器之间插入流水线寄存器。用 H-tree 时钟分布网络。优化布局减少长距离布线。问题类型典型现象排查工具解决方向利用率低PE 空闲周期多性能计数器维度对齐、增加缓存精度不够模型准确率下降对比参考模型增加位宽、算法补偿时序违例建立时间不满足静态时序分析流水线、时钟树优化功耗超标芯片发热严重功耗分析工具时钟门控、电压频率调节5.4 实操心得几个让我少走弯路的经验经验一先做小规模原型。不要一上来就做 256×256 的阵列先用 16×16 的阵列验证数据流和时序确认没问题后再放大。小规模原型的调试时间可能只有大规模的五分之一。经验二用 FPGA 做原型验证。在流片之前用 FPGA 跑一遍完整的模型推理能发现很多 RTL 仿真发现不了的问题比如内存带宽瓶颈、时序问题。经验三保留足够的可配置性。芯片流片后不能改所以设计时要留足够的配置寄存器支持不同的数据流模式、不同的精度、不同的阵列规模。TPUv1 之所以成功很大程度上是因为它的设计足够通用。经验四关注软件栈。AI 芯片的竞争力不仅在于硬件还在于软件栈。编译器、驱动程序、算子库的成熟度直接决定了芯片好不好用。我见过硬件指标很好但软件栈太烂导致没人用的芯片。6. 写在最后一些个人的体会做 AI 芯片设计这些年我最大的体会是硬件设计没有银弹只有权衡。脉动阵列很高效但灵活性差通用处理器很灵活但效率低TPUv1 的设计很成功但那是针对特定场景优化的结果。Transformer 的出现又给硬件设计提出了新的挑战——更大的矩阵、更复杂的注意力机制、更动态的形状。我自己的做法是先理解算法的计算特征再设计硬件架构最后用软件栈把两者串起来。这个顺序不能反。如果先设计硬件再找算法大概率会做出一个没人用的东西。另外不要迷信“最新”的技术。脉动阵列是 1980 年代的概念但它在 AI 芯片里依然是最核心的架构。Transformer 是 2017 年的论文但它的核心计算模式——矩阵乘法加 Softmax——在硬件上依然是最难优化的部分。理解这些基础概念比追逐热点更重要。最后分享一个小技巧如果你在做 AI 芯片的架构设计建议花时间读一读 TPUv1 的论文ISCA 2017以及 Transformer 的原始论文NeurIPS 2017。这两篇论文加起来不到 20 页但包含了 AI 芯片设计最核心的思想。我每次遇到设计难题时都会回头翻一翻这两篇论文往往能找到灵感。