脉动阵列深度解析:AI芯片核心计算引擎的设计与量产实战 脉动阵列这词做AI芯片的人多少都听过。但说实话我第一次看架构图的时候也懵了很久——一堆小方块连在一起数据像潮水一样涌进去结果就出来了。它既没有复杂的总线仲裁也没有大规模缓存一致性协议却能成为谷歌TPU、各家AI加速卡里的核心计算引擎。这篇文章我想从自己实际接触过的项目经验出发把脉动阵列的原理、设计取舍、以及量产芯片里那些没人明说的坑一次性讲透。这篇文章适合这几类人看正在做AI芯片前端RTL设计的工程师、搞体系结构研究的学生、写AI算子库想优化底层性能的软件开发者以及单纯好奇AI芯片内部到底怎么干活的技术爱好者。不管你是哪一类我保证看完之后再看任何一款AI加速器的白皮书你都能一眼认出脉动阵列在哪儿并且知道它为什么长这样。1. 脉动阵列到底解决了什么问题1.1 核心思想让数据动起来而不是让数据停下来等计算传统CPU或者GPU里的计算单元通常是“取指-译码-执行”这种模式。数据从寄存器或者缓存里被取出来送进乘法器算完再写回存储。每个操作都像一次独立的快递你下单快递员去仓库取货送到加工厂加工完再送回仓库。脉动阵列的逻辑完全不同。它像一条流水线或者说像一个传送带生产线。计算单元PEProcessing Element排成一个阵列数据从阵列边缘注入在相邻PE之间按节拍传递。每个PE只和它左右、上下的邻居通信算完一个部分和后把中间结果直接传给下一个PE继续累加。这个设计的精髓在于数据复用。权重存进PE里不动输入激活值像波浪一样扫过整个阵列每个输入会和阵列里每一列的权重都相乘累加一次。这样数据从外面搬进阵列的次数大幅减少而“搬数据”恰恰是芯片里最耗能、最耗时的事情。我用一个比喻来帮助理解假设你要给100个产品贴标签传统方法是一个人跑100趟拿标签脉动阵列的方法是100个人排成一排站好第一个人拿一叠标签每贴一张就传给下一个人一张。标签数据在整个过程中只被拿进来一次但每个工人PE都摸到了它。1.2 两种主流结构权重固定型和输入固定型别再搞混了很多文章一讲脉动阵列就画一个二维网格然后说“数据从左往右流权重从上往下流”。但实际设计里数据怎么流、谁固定在PE里不动决定了整个阵列的面积效率和控制复杂度。权重固定型Weight Stationary这是TPU采用的方式。权重在计算开始前就加载进每个PE的本地寄存器之后权重一动不动。输入激活值沿着行方向水平传播部分和沿着列方向垂直累加。这种方式的优点是权重复用率高适合权重参数较大的全连接层和卷积层。缺点是需要一个额外的“权重预加载”阶段如果权重更新频繁预加载开销会吃掉性能。输入固定型Input Stationary输入激活值进驻PE权重在阵列里流动。这种方式在深度可分离卷积或者激活值复用更重要的场景下有优势因为激活值往往比权重大得多尤其在Transformer里固定住激活值能减少它们的搬运量。还有一种输出固定型Output Stationary部分和留在PE里累加权重和输入都在外面流。这种方式适合小卷积核、多通道累加的场景但控制逻辑比前两种复杂产品里较少见到。设计时选哪一种是第一道选择题选错了后续所有优化都别扭。我的经验是先做一轮profiling看你的核心负载里到底哪个数据张量最大、最频繁被复用。大多数图像类负载选权重固定型Transformer类负载如果权重太大放不进片上SRAM可能输入固定型更划算。2. 脉动阵列如何一步步算出矩阵乘法2.1 矩阵乘法在脉动阵列里的执行过程以一个2x2的脉动阵列计算两个2x2矩阵相乘为例C[0][0] A[0][0] * B[0][0] A[0][1] * B[1][0]C[0][1] A[0][0] * B[0][1] A[0][1] * B[1][1]C[1][0] A[1][0] * B[0][0] A[1][1] * B[1][0]C[1][1] A[1][0] * B[0][1] A[1][1] * B[1][1]如果按教科书方法每个输出元素要单独从内存里取两对数据做乘加。但在脉动阵列里我们是这么干的第一步把B矩阵对应权重加载进PE。PE(0,0)存B[0][0]PE(0,1)存B[0][1]PE(1,0)存B[1][0]PE(1,1)存B[1][1]。第二步把A矩阵的行按时间错开注入。第一个时钟周期只把A[0][0]注入PE(0,0)A[0][0]和B[0][0]相乘部分和暂存在PE(0,0)。第二个时钟周期A[0][1]注入PE(0,0)同时前一个周期的A[0][0]向右传到PE(0,1)在PE(0,1)里和B[0][1]相乘。同时A[1][0]从下方注入PE(1,0)。第三个周期A矩阵的第三行数据继续注入。你可能注意到了注入时间必须错开一拍这就是脉动systolic这个词的来历——数据像心跳泵血一样一波一波脉冲式地进入阵列每个PE在同一时刻只处理一个乘法。最终在PE(0,0)里累加出了C[0][0]PE(0,1)里累加出了C[0][1]下方两个PE分别累加出C[1][0]和C[1][1]。计算结束后部分和从底部逐行移出。2.2 一个卷积计算的具体映射过程矩阵乘法好懂卷积怎么映射假设输入特征图是7x7卷积核是3x3步长1。做法是把卷积核的9个权重加载进例如3x3的脉动阵列或者更大阵列里的一块然后输入特征图按规律展开成“数据流”注入。我见过很多第一次做加速器的人在这里栽跟头他们试图把输入图像原样一行一行送进去。实际上脉动阵列处理卷积需要做的是把输入特征图变成类似Toeplitz矩阵的形式。具体做法是把输入按卷积窗口的滑动位置重新编排让每个PE在某一个时刻持有一个窗口内的有效像素。不同行的像素要提前缓存并错拍发送。具体来说用9个PE排成3行3列第一行的三个PE分别负责卷积核第一行权重和输入图某一窗口第一行的三个像素乘累加滑动窗口右移一格后新的像素进入第一行PE。第二行PE处理下一行输入。这种映射方式数据复用率很高因为相邻卷积窗口之间有大量重叠像素这些像素在每行PE里都会被复用一次不需要重复从全局缓存里取。2.3 性能瓶颈为什么在真实芯片里脉动阵列经常“吃不饱”理论峰值算力看着很吓人128x128阵列1GHz主频每个PE一个乘加器每秒就是16.4 TOPS128乘128乘1G乘2。但实际项目中我见过太多团队最终只能跑出理论峰值的30%-50%。问题出在哪数据供不上。脉动阵列是深度流水线它要求每一拍都有新数据从边界注入。一旦数据源通常是片上SRAM或者DRAM的带宽不够流水线就出现气泡。假设你的阵列有128行每拍需要从SRAM里读出128个激活值每个值16位那么光是输入带宽就是128乘以2字节乘以1GHz等于256GB/s。还要同时考虑输出带宽部分和也要从阵列底部写回。实际项目中我们一般用多bank交叉的SRAM、双缓冲double buffering、以及数据重排来缓解带宽。但这些都意味着面积和功耗的额外开销设计时你得算一笔总账增加20%的SRAM带宽换来阵列利用率从40%提到65%值不值。3. 一个脉动加速子系统是怎么搭起来的3.1 整体架构不只是阵列本身脉动阵列不是一个独立的单元它是一个完整的子系统。我在做芯片的时候把它拆成四个部分第一部分是计算阵列本身就是那些PE构成的网格以及PE内部的乘法器、加法器、累加寄存器、权重寄存器。第二部分是数据供给模块包括输入激活缓存通常是一块大SRAM、权重缓存、以及把DRAM数据搬运到SRAM的DMA引擎。第三部分是输出收集模块部分和从阵列底部出来之后经过ReLU、量化、池化等后处理单元再写回输出SRAM。第四部分是控制器负责向阵列发射指令告诉它“这一层从哪个地址取数据、把结果写到哪个地址”。这四个部分单独看都不复杂放在一起就涉及数据流编排的问题。硬件上权重缓存和数据缓存最好做成多个独立SRAM bank这样可以在同一拍同时读输入、写输出、预取下一批权重。3.2 计算阵列里的PE单元设计细节单个PE内部其实很简单主要包含一个16位或8位的乘法器、一个32位的累加器防止多次累加溢出、一组权重寄存器、以及输入输出寄存器。但细节里藏着魔鬼。最重要的细节是PE之间的数据传输不能打断计算。所以每个PE的输入寄存器一般做两级甚至三级流水。当数据从左边PE传过来时先在第一个寄存器里稳一拍下一个周期才送进乘法器。这样保证了传播延迟不会形成时序违例。还有一个容易被忽略的点部分和的位宽和最终输出位宽不一样。举例来说如果你做8位乘法累加器是32位那么最终从阵列底部出来的时候你得决定是直接输出32位定点数还是马上做截断和饱和处理。我见过一个团队因为忘了在阵列底部做饱和导致后续ReLU层输入了一个超出预期范围的值网络精度掉了好几个点。3.3 从设计到验证别忘了时钟门控和功耗评估脉动阵列功耗优化的核心手段是时钟门控。因为不是每个PE每个周期都在做有效计算尤其是边缘的PE数据还没传到的时候其实在空转。我们做低功耗版本时给每个PE加了一个valid信号只有valid有效时才打开该PE的时钟。如果你用Verilog做RTL仿真还要注意一个问题脉动阵列的仿真速度非常慢因为数据依赖关系强很难做并行仿真加速。建议尽早搭一个cycle-accurate的C模型和RTL做交叉验证。先保证C模型结果正确再拿它生成测试向量喂给RTL效率会高很多。4. 从IP到量产脉动学派的产品路线4.1 谷歌TPU与AI芯片加速器的主流选择看了这么多原理我们落到产品层面。谷歌每一代TPU都采用脉动阵列作为核心计算单元TPU v1的65x64阵列TPU v3是128x128。选择脉动阵列的原因很清楚整数和定点计算下矩阵乘法占绝对主导脉动阵列在给定算力和面积下能效比最高。国内一些AI芯片例如寒武纪的部分加速核、含光800等也在特定场景里采用了类似的空间阵列结构。它们和谷歌的差别主要在数据格式、可编程性和编译器配合上。我接触过一个做NPU的团队他们一开始在脉动阵列和simd向量单元之间犹豫。后来选脉动阵列主要是考虑到能效比同样的TOPS/W指标脉动阵列在65nm工艺下就能做到别的方案在28nm下才能达到的水平成本优势明显。4.2 脉动阵列更吃编译器还是更吃硬件很多人的直觉是硬件到位了就完事了。实际上脉动阵列芯片对编译器优化极其敏感。因为计算阵列本身不聪明它只会傻傻地按照指令流执行预定的数据搬运而怎么把循环和矩阵乘法“排”成阵列能高效执行的指令是编译器的活儿。如果你用的是现成的AI框架PyTorch、TensorFlow编译器会自动生成算子kernel。你需要关心的反而是“布局”和“分块”。比如说一个1024x1024的大矩阵放不进128x128的脉动阵列那就得切成多个tile依次送入计算。分块尺寸的选择直接影响片上SRAM的利用率和DRAM访问次数。块太大SRAM装不下发生spill块太小边界开销占了太多比例阵列利用不充分。4.3 nVidia GPU的常规路线对比GPU走的是SIMT加向量流水线的路线靠大量线程和warp调度来隐藏延迟这和脉动阵列的设计哲学完全不同。GPU的通用性好什么形状的模型都能跑但能效比不如专用阵列。所以现在的AI推理芯片市场基本形成了分水岭追求极致能效比和固定场景比如云端推理大规模Transformer负载脉动阵列是主力选项追求灵活性和频繁更新算法模型的场景GPU或可编程阵列更合适。5. 实测项目里的典型误区和排查手记5.1 误区一阵列利用率低第一反应就是加大阵列规模我见过一个团队脉动阵列利用率只有35%他们的第一反应是“计算能力不够把阵列从64x64扩到128x128”。结果面积功耗翻倍利用率更低了。问题根源是数据搬运带宽不足输入数据供不上阵列在空等扩展阵列规模等于修错了墙。正确的排查顺序是先看存储带宽算一算IO吞吐能不能支撑阵列的峰值计算再看调度器有没有出现流水线气泡最后才看阵列本身的计算吞吐。排查工具上做一个性能计数器performance counter统计每个周期的有效计算比例比任何理论推演都好使。5.2 误区二SRAM bank冲突导致阵列“喝粥”脉动阵列对存储带宽要求齐整如果你用的SRAM只有一个bank会发生严重的数据冲突。我们做过一次实测权重和激活值放在同一个SRAM里结果因为读写端口冲突阵列利用率从70%降到42%。解决方案是存储切分激活缓存和权重缓存物理隔开各用各的bank。更激进的做法是按阵列的行做bank切分每行对应一个独立bank这样可以做到单周期读满整行数据。使用Xilinx或Altera的FPGA做原型验证时要注意片上BRAM的端口数是有限的通常最多两个读端口一个写端口这时候你得把阵列设计成一次从BRAM读固定数量的数据再在内部做一个crossbar分发而不是指望BRAM本身能同时读任意地址。5.3 误区三脉动阵列的RTL仿真正确但芯片回来不能用这是最惨的。仿真波形完美FPGA验证通过芯片回片后跑出来结果错乱。问题大多出在时钟和复位上。脉动阵列是深度流水线data path上有几十甚至上百级寄存器。如果复位信号设计成异步复位、同步释放但释放时刻不均匀会导致部分PE复位不完全数据错拍。我们在一个项目里吃过这个亏后来所有PE的复位改为完全同步复位并且复位释放后加一个“清空指令”周期让阵列里所有残留数据先流干净再做正式计算。5.4 常见问题速查表问题现象可能原因排查方法解决建议阵列利用率低存储带宽不足 / 调度气泡性能计数器统计双缓冲SRAM分bank存储算出的结果偶发错误累加器位宽溢出打印部分和最大值加宽累加器位宽功耗异常偏高无时钟门控PE空转功耗仿真工具分析加valid信号做时钟门控FPGA验证频率低PE间组合逻辑过长时序报告分析输入寄存器多打一拍回片后烧芯片部分和短路输出驱动过强IO测试输出缓冲加限流电阻chiplet间数据传输瓶颈上板协议时钟频率不够物理层测试提高SerDes速率或多通道并行5.5 调试脉动阵列最实用的小工具如果你在写RTL验证我给你推荐一个思路在仿真里每个PE的输入输出都打上tag带一个唯一的序列号。这样当你看到结果不对时可以顺着序列号回溯是哪个PE在哪个周期算错了。这比盯着波形一个个信号找快一整个数量级。这也是我自己写了好久波形才悟出来的土办法。6. 脉动阵列面积估算与设计选型参考6.1 一个8位脉动阵列的RTL资源消耗估算很多朋友问我做一个128x128的8位脉动阵列大概要多大面积。这个问题没法精确回答但可以给一个估算框架单个PE如果包含8x8乘法器32位累加器两组8位数据寄存器在28nm工艺下大约等效于2500到4000个与非门。128x128总共是16384个PE等效门数大约在4000万到6500万之间。加上权重缓存128x128x8bit128Kb、激活缓存、输出缓存、控制逻辑整个加速子系统的面积大约是纯阵列面积的1.5到1.8倍。6.2 选型建议多少算力用多大阵列这里给一个经验公式如果你目标算力是X TOPS工作频率是F GHz精度是N位那么脉动阵列的行列数大约是sqrt(X / (F * N * 2))的1.2到1.5倍。算力翻倍时优先提高频率和存储带宽再来扩展阵列规模这样面积和功耗代价更小。考虑到人工智能芯片的工艺节点趋势目前7nm和5nm下128x128的8位定点阵列是性能和面积平衡点。再往上扩要么走chiplet方案多个小阵列互联要么就得接受良率和功耗的代价。7. 实际做出来的效果与经验总结7.1 一组实测数据以我们在某个28nm项目里的一个32x32的8位脉动阵列子模块为例核心电压0.9V频率800MHz纯阵列峰值算力是1.6TOPS32乘32乘2乘0.8G。实际跑了ResNet-18的第一层卷积输入224x224x3卷积核7x7stride 2实测算力1.18TOPS阵列利用率约73.75%。数据从DDR经DMA搬运到SRAM再喂给阵列整个数据通路没成为瓶颈。同样的设计如果把SRAM换成同样容量的寄存器堆面积会增加大约35%但利用率可以提升到85%以上。这就是典型的“用面积换性能”的权衡产品定位决定你选哪个路径。7.2 最后的经验分享软件协同设计比什么都重要脉动阵列不是一个孤立硬件能发挥价值的方案。它需要一套完整的软件栈来支撑编译器负责算子映射运行时负责内存管理和数据布局驱动负责配置寄存器。如果软件栈中间任何一环跟不上再厉害的阵列也是白搭。我个人的强烈建议是做脉动阵列芯片之前先把软件仿真环境搭好。用一套指令集模拟器先把目标模型跑通统计每层的计算、数据搬运需求再倒推需要多大阵列、多大缓存。这比先拍脑袋定硬件规格再求软件适配成本低得多成功率也高得多。最后再分享一个小技巧如果你的芯片是面向多场景的推理芯片不妨在脉动阵列旁边留一个小而灵活的向量处理单元。脉动阵列处理大矩阵乘法时效率极高但处理类似LayerNorm、Softmax这种元素级操作时效率非常差。预留一个小向量单元做互补整体芯片的实际能效比会比只堆脉动阵列高出不少。这也是我在多个项目里反复验证过的做法。