资讯详情

脉动阵列:AI芯片如何用数据流动破解算力与带宽困局

📅 2026/10/28 21:13:49 | 华诺云谱 👁 阅读
脉动阵列:AI芯片如何用数据流动破解算力与带宽困局
1. 算力与带宽的矛盾脉动阵列诞生的现实逻辑第一次听到脉动阵列这个名字是在一次AI芯片的架构评审会上。说实话刚接触这个概念的人很容易被名字唬住——脉动阵列听起来像是某种流体力学的模型。但搞了几年芯片设计之后你会发现这个名字起得相当直白它就是让数据像血液在血管里一样有节奏地流过一个计算单元阵列每一个计算单元PEProcessing Element只跟邻居打交道数据流到哪儿计算就在哪儿完成。要理解脉动阵列先得理解AI芯片面临的核心矛盾算力越来越贵但把数据搬进搬出的带宽更贵。传统CPU的思路是数据放到寄存器指令控制ALU去算。这个模型在通用计算里没有问题因为你要处理的分支、跳转、访存模式完全不可预测必须靠通用的取指-译码-执行流水线来兜底。但在神经网络推理里计算模式是极度规律的卷积、矩阵乘、全连接本质上就是乘累加的堆叠。你让CPU去做这件事绝大部分晶体管和功耗都消耗在指令调度、缓存替换、分支预测上真正干活的乘法器反而闲得很。更致命的是每做一次乘法数据要从内存里搬到寄存器算完又要搬回去——这个过程中数据通路的功耗比乘法器本身的功耗高一个数量级。脉动阵列换了一个思路既然数据搬运的代价远高于计算那我们就让数据少搬家让乘法器密集地排列在一起每个乘法器只跟上下左右四个邻居交换数据数据顺着阵列像流水一样一步步流过去。没有总线没有多级缓存没有复杂的调度器每个PE就是一个乘法器加一个累加器再加上一小块寄存器。你用不着从内存取两次数、做一次运算、再写回内存你只要把矩阵的数据一次性喂到阵列的边界上它们自己就会在阵列里流动边流动边把乘累加做完。这个思路最早来自20世纪80年代卡内基梅隆大学的H. T. Kung团队他们当时提出来是为了解决信号处理里的矩阵运算问题。真正让脉动阵列大放异彩是Google的TPU——第一代TPU里就是一个256×256的脉动阵列专门跑推理场景的矩阵乘法。后来的寒武纪、地平线、各种NPU/IPU几乎都采用了脉动阵列或它的变体作为矩阵运算的核心单元。所以脉动阵列解决的根本问题是数据搬运成本。它不是靠把时钟频率拉高来提升算力也不是靠堆更多的缓存来缓解带宽压力而是靠数据在整个计算阵列中有节奏地流动让每一个数据在生命周期内尽可能多地被复用。提示读到这里你可以先记住一个关键判断——脉动阵列不是某种算法而是一种硬件数据流组织方式。它牺牲了通用性换来了极高的计算密度和能效比。这个通用性换能效的取舍是所有AI芯片设计者绕不开的起点。2. 三种数据流模式与PE内部的微架构2.1 权重固定Weight StationaryTPU的选择脉动阵列最关心的一个问题是矩阵计算中谁应该留在阵列里不动谁应该在里面流动以经典的矩阵乘法 C A × B 为例A矩阵输入特征图和B矩阵权重都需要参与运算。如果让权重B预先加载到每个PE里保持不动输入数据A像流水一样从阵列中流过每个PE将自己持有的权重和流过的输入做乘累加最后得到部分的输出C——这就是Weight Stationary权重固定模式。这个模式最大的好处是权重被复用得非常充分。一个权重一旦加载进PE就可以连续参与多次乘法运算完全不用重复搬运。在卷积网络中权重本来就是参数总量最大、最需要复用的数据所以Google TPU v1选择了这种模式——它把权重从DDR搬到片上SRAM再从SRAM搬到每一个PE的寄存器里然后权重就钉在那里不动了接下来输入数据的每一行都会从这256个PE上流过去做一次乘累加。沿着一列PE往下看数据的流动路径同一列的所有PE共享同一个输入数据流每一时刻输入到达PE (i, j)该PE里的权重w(i, j)和输入a做乘法结果累加到本地寄存器。下一时刻相同路径上的输入a又流到PE (i1, j)与那里的权重相乘。与此同时整个阵列按照节拍同步推进部分和也可以选择纵向流动或留在本地累加。你会问这个过程中输出C在哪里算出来的如果每个PE只做一次乘法就结束那输出C(i, j)还需要对多个输入维度求和。所以权重固定模式下部分和是在PE内部不断累加的同一个PE会被多次不同的输入值访问每访问一次就做一次乘加最终在某个时刻PE内部寄存器里的值就是完整的输出C(i, j)。这也是为什么每个PE里必须累加器比乘法器还要关键——它才是真正计算发生的地方。2.2 输入固定Input Stationary与输出固定Output Stationary不是所有场景都适合让权重固定。比如在推理中如果输入特征图非常大但每次推理的权重都一样也许你想把输入钉在PE里、让权重流动这样可以减少输入数据的反复装载这就是Input Stationary输入固定。输入固定模式特别适合某种批量归一化卷积联合运算的场景多个卷积核共享同一块输入特征图输入数据被加载到PE上后不用动让不同的卷积核权重依次流过来做乘累加然后输出。这种方式对带宽压力最小的恰恰是输入侧。还有一种模式叫Output Stationary输出固定它把输出数据当作固定不动的一方。完整矩阵乘法的输出C需要通过多轮乘累加才能完成Output Stationary的思路是把一个输出C(i, j)的所有累加项全部算完之后再写回——这样片上的写回次数最少输出数据不会在片上反复搬进搬出。如果你的算法对片上存储总量比较敏感输出固定模式通常能减少中间结果的存储需求代价是输入数据流和权重数据流的组织会变得更复杂。三种数据流模式各有取舍没有哪一个在所有情况下都占优。我在实际引入脉动阵列时通常会先做一遍数据复用度的建模推理场景权重已经固定且复用度高 → Weight Stationary多数是不断变化的动态输入 → Input Stationary 或灵活切换输出侧的数据量极其庞大、片内存储吃紧 → Output Stationary2.3 PE内部到底有什么乘法器、累加器、寄存器还是SRAM很多人以为脉动阵列里的PE是一个完整的处理器其实不是。从第一代TPU的公开资料来看一个PE内部只包含三个关键部分一个低精度乘法器比如8bit×8bit整数乘法一个累加器专门负责把乘法的结果累加起来一小块寄存器用来临时寄存流经本PE的数据权重、输入或部分和就这么点东西构成了整个阵列的细胞。PE里几乎不会放SRAM更不会放指令。因为整个阵列的调度逻辑是集中式的外部控制器负责的PE自身只需同步接收时钟节拍按固定的延迟把数据从左边传到右边或从上边传下来。没有指令来打扰每个周期该怎么算这让PE变得异常精简——你能在同样面积上塞下更多的乘法器计算密度就上去了。我见过一个刚开始做脉动阵列的团队第一版设计里每个PE都塞了一个小的多周期乘法器结果整个阵列的频率根本提不上去而且面积翻了整整两倍。后来换成组合逻辑的8bit乘法器加定点累加器面积和功耗都骤降。记住脉动阵列是吃简单PE的红利PE越简单排列越密集数据流动的效率就越高。如果你非要每个PE都做成可以独立编程的微处理器那就不是脉动阵列了是一个二维的CPU簇。3. 从公式到硬件脉动阵列的装载与空泡问题3.1 矩阵怎么塞进阵列分块、装载、对角线调度这里有个非常重要但又经常被忽略的问题矩阵乘法公式里的整块矩阵怎么映射到一个物理尺寸固定的阵列上假设你的脉动阵列是4×4的PE阵列但你要算的矩阵是1000×1000的。不可能把1000列都摆在阵列上所以必须分块tiling。如图所示的经典做法是把大矩阵切成K个小块每次在阵上完成一块的乘累加部分和留在PE里循环把K个小块都算完最后一次性把输出写回。分块大小不是拍脑袋定的。一个经验法则你的分块的大小最好能适配片上的权重SRAM容量和输入数据的复用关系。块切得太小装载频率就高带宽会被消耗在装载上块切得太大片上SRAM装不下你得频繁做DMA换出换入出现数据倒灌。装载时序是另一个坑。脉动阵列的装载不是一下子全部填进去。准确地说权重数据需要按照列的节奏逐拍打入第0拍第一列不对往往是一条对角线。拿2×2阵列举例假设我们要算 C A × B其中A是2×2输入矩阵B是2×2权重矩阵。你并不需要在第0周期让A[0][0]、A[0][1]同时到达PE(0,0)和PE(0,1)。由于乘法器只能处理单数据流通常在硬件上你让输入矩阵A的第0行在时钟0到达第一行PE而权重矩阵B按对角线方式先装载B[0][0]在第0周期到达PE(0,0)B[1][1]在第1周期到达PE(1,1)……这样调度的结果是数据在阵列中形成斜向的波前wavefront每一拍数据向下/向右流动一个PE像波浪一样推进。这种对角线装载或者叫波前调度是脉动阵列里最核心的时序设计。用软件术语来说每一条对角线对应一个时钟周期所有同时参与计算的数据恰好落在一条对角线上。这就是为什么脉动阵列的控制器往往复杂——它不是算乘法而是管数据该在哪一拍、从哪条边、以什么顺序进入阵列。3.2 空泡Bubble是怎么产生的以及如何用软件抹掉真实世界的矩阵不可能长得那么整齐。比如卷积网络的输入张量是 [N, C, H, W]你在做3×3卷积时每个输出位置要滑动窗口取9个数据。如果数据流组织不当某些PE在一个时钟周期里等不到有效数据这个周期就被浪费了——这就是空泡bubble。空泡的产生主要有两个原因第一矩阵尺寸和物理阵列尺寸不匹配。300宽的矩阵放在256宽的阵列上你只有256列在干活剩下44列被闲置——严格说这不算空泡算是利用率损失。更致命的是一些不规则操作比如稀疏矩阵。现在很多AI芯片在搞稀疏化权重矩阵里大量元素是0如果你照样装载乘法器做的全是零乘有数的无效运算有效利用率会大幅下滑。第二卷积的滑动窗口天然会导致数据重复装载。同一块输入数据会被多个不同的卷积核复用如果你没有安排好装载顺序某个PE算完了手头的数据下一个数据还没到空泡就出现了。解决空泡问题软件和硬件要协同做。硬件层可以在PE数据通路上加有效数据标志位空周期直接旁路不做乘法降低功耗软件层则通过数据重排和数据流调度来尽量减少空泡。我在做卷积映射时通常会把滑动窗口的重复利用率算进调度里优先保证每个PE永远有活干。实测下来对常见的3×3卷积好的调度能把阵列利用率从不到50%拉到80%以上。3.3 卷积的im2col与直接数据流映射工程的真实取舍既然脉动阵列本身只擅长乘累加那卷积到底怎么送进去业界有两个经典方案各有取舍。方法一叫im2colImage to Column。它把3×3卷积的每个窗口展开成一行向量然后把所有窗口的行向量拼成一个大的矩阵最后把卷积核也展开成矩阵这样整个卷积就变成了一个标准的矩阵乘法。好处是你可以直接复用保证矩阵乘法的内核调度软件生态成熟坏处是特征图会被展开成原来几倍大的中间矩阵内存占用暴增而且展开操作本身也需要消耗额外的访存带宽。方法二是直接在阵列上做数据流卷积direct convolution。它不让数据中间落地成大矩阵而是利用脉动阵列的波前特性让卷积窗口的数据在流动过程中自然重叠。这样一来省了中间矩阵但对控制器的设计要求高得多——每次卷积核尺寸变化、步长变化、空洞率变化都要重新生成一套调度方案。灵活性是它的代价。从我的经验看纯推理芯片如果追求极致能效往往是混着来主流3×3卷积走直接数据流全连接和1×1卷积走im2col式的矩阵乘两者的调度器共用一套存储和装载通路。真正把卷积全部做成im2col的大多数是早期为了快速验证算法正确性的FPGA原型不是最终量产方案。4. 看懂一个真实的脉动阵列设计以Google TPU v1为例4.1 256×256还是64×64阵列规模的能耗与面积权衡脉动阵列的规模怎么定是架构师最头疼的问题之一。Google TPU v1公开的论文里提到它使用了一个256×256的脉动阵列实际有效是256×256的MAC计算能在一个时钟周期内完成65536次乘累加运算工作频率大约是700MHz峰值算力一度达到了92 TOPS8bit整数。你可能觉得阵列越大越好65536个PE的并行度多高啊。但大规模脉动阵列的物理实现代价非常具体面积每个PE就算再精简256×256个PE连在一起布线资源就会变得非常紧张。PE之间的数据通路是逐格连接的阵列规模翻一倍面积至少翻三倍PE本身连线重复的驱动buffer。做后端物理设计的时候你要在设定的功耗墙和时间收敛约束下保证每一列数据的延迟一致这是非常头疼的。功耗PE虽然单个功耗极低但六万多个PE同时在翻转动态功耗非常可观。Google TPU v1的功耗非常大很大一部分就烧在阵列的搬入搬出上。如果你没有足够的散热设计做支撑阵列规模再大也只能降频使用。装载效率阵列越大单次装载的浪费也越大。比如256×256的阵列去处理一个128×128的矩阵乘法利用率只有25%剩下的PE全在空转。所以现代很多芯片把阵列拆成多个中等规模的子阵列比如4个64×64的小阵列灵活性反而更好可以根据实际矩阵大小动态调度不同的子阵列。我在评估阵列规模时一般先算一个装载率曲线把目标模型里所有矩阵乘法的维度分布统计出来找出90%以上的矩阵长什么样然后拿这个分布去试不同规模的阵列选择一个在平均利用率上最优但面积不至于爆炸的尺寸。这个流程虽然听起来朴素但能避免很多买大求全的架构决策失误。4.2 为什么只做矩阵乘法其他算子怎么办第一代TPU最让人惊讶的一点它没打算什么都算。激活函数ReLU、池化、甚至某些非矩阵算子都会从阵列里拉出来交给单独的硬件模块处理。原因很现实脉动阵列这个物理结构是为密集乘累加量身定做的你让它在上面做ReLU直接判断是否是0、取最大值等于让一个重型卡车去送快递既慢又浪费。激活函数和池化的计算量虽然不高但其控制逻辑杂、访存模式随机强行往阵列里塞反而会打断数据的连续流动制造大量空泡。所以TPU的做法是数据流分段卷积层和矩阵乘下沉到脉动阵列处理每个卷积层结束后输出数据流经片上的激活/池化单元做非线性和降采样再回到片上的卸载缓冲区送去下一层。这种异构分工让每个硬件模块都做自己最擅长的事。换成我家用的语言来说把方钉放进方眼圆钉放进圆眼不要试图用一个万能插件解决所有问题。这对普通开发者有什么启发如果你要在FPGA或ASIC里做脉动阵列加速器千万别一上来就规划我要让阵列支持所有算子。先梳理你算法里的算子占比矩阵和卷积占了80%以上就老老实实做矩阵路径其他算子用独立的简单硬件单元配合流水线实现效率会高得多。4.3 脉动阵列如何配合流水线、量化、片外存储用脉动阵列做推理加速永远不是阵列自己一家独大它要跟周围的存储、控制、数据通路配合成一个完整系统。TPU v1片内SRAM只有28MB其实最开始是8MB的权重SRAM加一些中间状态和现在动辄上百MB的NPU相比少得可怜但它通过优化数据流来把DDR带宽压到极限。最关键的配合是权重SRAM的分块预装载。假设你要连续推理很多张图片那么权重应该尽量常驻在片内。TPU的权重存储是按层预分配的控制器在上一层计算还没结束时就会把下一层需要的权重从DRAM提前拉到SRAM的ping-pong缓冲区内以此隐藏装载延迟。这种双缓冲double buffering技巧是脉动阵列系统设计的老祖宗——没有它装载时间会和计算时间串行叠加再快的阵列也会被带宽拖垮。量化在这里面扮演的角色同样重要。TPU v1主打8bit整数推理而不是32bit浮点。8bit的乘法器面积大约是FP32乘法器的1/10功耗更是小一个量级。脉冲阵列里65536个PE如果都放FP32乘法器物理上根本做不出来功耗墙内的芯片。所以脉动阵列的锋芒很大程度上是依托低精度量化才真正发挥出来的。如果要做量化感知训练你甚至可以把量化误差的分布反过来反馈给阵列的装载调度哪些层容易出现精度下降就在该层用更保守的量化策略或者单独开一条高精度通路给敏感层。这个精度-性能联合设计思路我在好几个项目里都验证过很有用。5. 如果你要在FPGA/RTL里手搓一个脉动阵列实操与踩坑5.1 从调度表到Verilog先画时序不要先写代码我在带团队做FPGA原型时反复强调一句话不要一上来就写Verilog/VHDL先把调度图画出来。脉动阵列的调度表是整个设计的地基。它的本质是在每一个时钟周期哪些数据从哪一侧进入哪个PE。你需要画一个时间轴把PE阵列的坐标标上然后标出每个周期、每个PE的输入来源左邻居、上邻居、外部装载和输出去向。这一步往往比写代码花两倍时间但能帮你在仿真阶段省下二十倍的调试时间。画调度表的实操步骤确定矩阵维度、阵列维度、PE间单向数据流方向例如输入从左边流入权重从上边流入列出乘累加时每个PE需要什么数据、在哪一拍需要检查数据冲突同一拍、同一输入端口不能有两个数据源竞争调整装载顺序让二极管波前尽量覆盖所有有效PE补空泡周期在矩阵边缘数据还没有到达前插入无操作保证数据流整齐调度表画清楚了写代码时每个always块干什么就一目了然。我见过一个项目跳过调度表直接写RTL结果仿真波形一塌糊涂最后花费了整整三周改模块间握手信号比画调度表的时间长得多。5.2 数据位宽、部分和进位与固定点溢出处理脉动阵列最大的数值问题不是乘法器而是累加器的溢出。假设每个PE里权重和输入都是8bit整数一次乘法结果最大是255×25565025需要17bit表示。如果一次矩阵乘法你需要累加10000个乘加结果比如大矩阵的K维度是10000那累加器的位宽至少需要171431bit。很多人在这里犯了拍脑袋的错误累加器用32bit确实安全但如果你把位宽定小了会出现NAN一样的乱值而且这个问题在波形图上特别难捉。经验做法是先统计模型中最大K维累加次数然后计算所需的累加器位宽再加上4~8bit的安全余量。宁可面积多花一点也不要让溢出成为偶尔出现、无法复现的幽灵 bug。另外部分和在多PE之间的流动还可能产生另一个问题累积的时序路径太深导致时钟频率上不去。PE(0,0)的部分和流到PE(1,0)再流到PE(2,0)这条路径上每一级都要做加法数据依赖链很长。如果综合工具自动布局布线后频率不够你可能需要在数据流方向上加流水寄存器让部分和晚几个节拍到达最终输出。5.3 实测中常见的问题时序跑不上去、测试bench对不上、空泡泄漏挑几个我实际排查过的问题说一是时序跑不上去。脉动阵列的多bit数据线在PE之间级联时驱动能力和布线延迟会累积整个阵列的关键路径往往出现在PE的最底层。解决思路是给数据通路插入流水寄存器把PE间传播路径拆短或者用低摆幅信号优化功耗。做FPGA原型时还可以把大阵列拆成几个小阵列分频驱动虽然逻辑上慢一点但布线更容易收敛。二是测试bench和你自己的调度表对不上。仿真时输入矩阵的某一行应该在第几拍到达哪个PE这不是拿眼睛看就能确认的。我习惯在testbench里设置数据标签每个数据包携带一个元数据ID比如行3列5然后断言它到达PE(2,4)的时刻和预期一致。凡是出现ID错位肯定是调度表的数据路由画错了。这种断言式验证虽然无聊但查错效率非常高。三是空泡泄漏。就是本来该是空泡的周期PE里还残留着上一轮计算的数据被下一轮误当成有效输入。解决手段是每个数据包带1位valid标志一旦某个PE的输入valid为低后续就不执行乘累加而只是透传数据。这个设计几乎每版都要用到千万别省。5.4 用脉动阵列的替代方案什么时候不要用脉动阵列脉动阵列不是AI加速的唯一解法甚至不是所有场景的最优解。如果你的模型是高度稀疏的比如剪枝到90%以上的稀疏度密集脉动阵列会浪费大量乘法器在零值上。此时更适合的可能是稀疏感知的跳过零值架构或者依赖稀疏矩阵的专用数据流引擎。如果你的模型里动态形状占主导比如自然语言处理里sequence长度变化极其剧烈脉动阵列这种定长数据流的硬件往往利用率很低因为每次填充到新的长度都要重新装载。GPU/TPU的静态形状图物理架构还能忍但不规则的动态推理场景CPU或可编程的向量处理器可能更合适。另外如果你的目标是极致灵活地落地到边缘端一个只有4×4或者8×8的小型脉动阵列搭配向量ALU的组合可能会比一整块大阵列更实用——小阵列填充方便、功耗可控、调度变化也快。所谓小步快跑反而往往是最稳的工程选择。我在实际项目里选型时不会一上来就画256×256的大饼而是先做一轮工作负载分析把矩阵尺寸分布、稀疏度、动态范围都统计出来再决定用多大阵列、用哪种数据流。这样既有理论依据又经得起后端实现的检验。脉动阵列的潜力目前远没有挖尽。我之前用5nm工艺做仿真时8bit脉动阵列在能效上依然能比同等面积的SIMT架构强3~5倍但前提是你要把调度、存储和量化这三件事都做对。算力焦虑背后真正决定产品体验的从来不是峰值TOPS而是数据能不能在阵列里顺畅地脉动起来。踩过这些坑之后我现在的第一建议还是那句话想清楚了再动手调度表画明白了脉动阵列就已经成功了一半。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑