ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

脉动阵列深度解析:AI芯片的算力引擎与数据流架构

脉动阵列深度解析:AI芯片的算力引擎与数据流架构

AI芯片这片领域里,"脉动阵列"这四个字出镜频率高得吓人。你去看任何一家做AI加速芯片的发布会,不管是云端的训练大卡还是边缘的小盒子,几乎都要在架构图上画一个规整的网格,然后标注"Systolic Array"。在很多项目介绍里,它甚至成了"AI芯片"的代名词。

但多数人其实说不清楚脉动阵列到底是什么:它怎么工作、凭什么能效高、又有什么坑。我也是在啃完Google TPU的论文、又跟着开源RTL跑过模拟之后,才算把这条计算流水彻底理清楚。这篇文章我就用自己的话把它从头到尾拆一遍,从基本原理讲到工程权衡,中间放一段能直接运行的Python代码演示数据流的细节。适合想搞懂底层架构的算法工程师、刚入行的芯片从业者,也包括所有被"脉动阵列"四个字困扰过、想真正看明白它的人。

1. 算力焦虑的根源:数据搬运耗电,而不是乘法耗电

1.1 AI计算的主旋律:朴素而庞大的矩阵乘法

先从一个最朴素的问题切入:AI芯片到底在算什么?

把卷积、全连接、注意力机制全部拆到最底层,你会发现绝大多数计算量最后都收敛到了矩阵乘法,也就是GEMM(General Matrix Multiply)。一个卷积往往会被改写成Implicit GEMM来执行,Transformer里的QKV投影和Attention矩阵同样是GEMM。所以AI芯片设计的第一性原理其实是:用最高效的方式做大规模矩阵乘法。

矩阵乘法的操作本身不复杂——乘再加,这就是MAC(Multiply-Accumulate)。一个8bit乘加在先进工艺下消耗的能量大约在0.05到0.2皮焦耳这个量级。听起来微不足道,但AI推理动不动就是几百万次、几亿次这样的操作,量变引起质变。更麻烦的是:每次计算你都得把数据送到计算单元面前来,而"搬数据"这个动作的能耗,比"做乘法"的能耗高了几百倍。

举个直观数字:从DRAM里读一个8bit数,能耗大约是几百皮焦耳量级。你当然能靠多级缓存来缓解,但缓存的每一级也有成本。芯片设计圈里有个经典说法——计算是便宜的,存储和搬运是昂贵的。理解了这句话,你就能理解AI芯片架构演进的大方向。

1.2 冯·诺依曼瓶颈:一堵翻不过去的墙

传统CPU和GPU归根结底还是冯·诺依曼架构的思路:指令和数据都要经过总线,从存储器取出来,送到运算单元,算完再写回去。这套模式对通用计算非常灵活,但在AI这种高度规则的负载面前,效率很低。

原因在于AI计算有极强的规律性:大量相同形状的矩阵、大量的重复乘加。可是在冯·诺依曼架构里,每次计算都要重新取指令、重新访存。CPU为了弥补这个短板,把大量芯片面积和功耗花在了缓存、分支预测、乱序执行、多级存储上——真实用来做乘法的ALU面积占比并不高;GPU则用庞大的线程并行和海量带宽来掩盖延迟,本质上还是在"把数据反复搬进计算单元"。

还有个更难处理的点:功耗墙和内存墙。提高主频或者增加访存带宽都会带来功耗的快速上升,而AI芯片真正需要的其实是"在尽量低的功耗下完成海量规则计算"。这时候业界把目光重新投向了40年前提出的一个架构思路——脉动阵列。

1.3 换个思路:把"取数据"变成"数据来找计算单元"

脉动阵列想解决的核心问题,就是冯·诺依曼瓶颈里"计算单元等待数据"的死结。它的做法很反直觉:不让计算单元去内存里取数据,而是让数据从内存出来之后,在计算单元之间自动流动,每个计算单元只从邻居手里接过数据、算一次乘加、再把结果传给下一个单元。

数据一旦离开了内存,就不再需要频繁回到内存里去绕一圈。它像一股水流,从阵列的一侧流入,沿途每一个计算节点都顺手取走自己需要的那份数据,最终把计算结果在阵列出口聚合出来。整个过程中,计算单元不需要访问内存,不需要取指令,不需要等数据——因为数据是自己"送上门"的。

这个思路绕开了传统处理器最耗时的环节:指令获取、数据总线的反复仲裁、各级缓存的中转。它把"指令驱动"变成了"数据流驱动",把复杂调度变成了物理连接上的自然传递。这就是脉动阵列能够实现高能效的根本原因。

2. 脉动阵列的底层逻辑:让数据在计算单元间顺流而下

2.1 "脉动"这个名字是怎么来的

"Systolic"这个词来自医学里的"systole",也就是心脏收缩的意思。提出脉动阵列的是卡内基梅隆大学的孔祥重(H.T. Kung)教授,1982年前后提出的。他取这个名字的用意很形象:心脏收缩时把血液泵出去,血液沿血管一路流动、逐级输送;脉动阵列里的数据也是被时钟"一拍一拍"泵进计算单元网络,像脉搏一样向前传播。

这个比喻精准地抓住了架构的核心:数据不是随机被取用的,而是沿着固定的物理路径、按固定节奏流动。每个计算单元就像血管壁上的一个节点,血液流过时它完成一次物质交换,然后血继续流向下一个节点。在阵列里,交换动作就是"乘加"。

2.2 PE:只会做一次乘加的小单元

脉动阵列的基本构件叫PE(Processing Element,处理单元)。一个PE里面的核心很简单:一个乘法器、一个加法器、一个累加寄存器,外加少量用来暂存数据的缓冲寄存器。它每个时钟周期能干的事情就一件:读入一个操作数和自己的权重,做一次乘法,再和寄存器里的部分和相加,存回去,然后把数据传给邻居。

它不取指、不译码、不做分支判断、不访问内存。PE的控制逻辑几乎可以用"一拍一动"四个字概括。

把大量这样简单的PE排列成正方形网格,就是二维脉动阵列。比如Google TPU里是256×256,一个阵列里头有65536个PE。每个PE都死磕一个乘加操作,数量够了,算力自然就出来了。

2.3 三种数据流风格:WS、OS与BS

PE阵列物理上可以是任意排列,但不同计算负载对数据流的需求不一样,于是学术界把脉动阵列的数据流风格分成几类,最常听到的是三个:

  • WS(Weight Stationary,权重静止):权重提前预加载进PE并一直留在里面,输入激活数据从阵列一侧流入。适合推理场景,因为模型权重固定、反复使用,复用率高。
  • OS(Output Stationary,输出静止):部分和结果固定在PE的累加器里不动,输入和权重都从外部流入。好处是输出数据不搬来搬去,适合输出规模相对小的层。
  • BS(Row Stationary,行静止):MIT的Eyeriss项目提出,围绕卷积的行数据重用设计,一行卷积结果尽量固定在一个PE内处理,减少跨行数据的搬运。

三种流派本质是在对算法中的三种数据——权重、输入激活、输出部分和——做取舍:哪个数据最贵、复用率最高,就让它在整个计算过程中尽量"静止"或者"少动"。

想直观理解WS数据流,可以把它想象成一条工厂传送带:权重是站在工位旁边的工人,输入数据是放在传送带上的工件。工件依次流过每个工位,被每个工人加工一次,再流向下一个工位。工人不会离开工位,工件也不用被反复抬回仓库,最后在传送带末端收集成品。

3. 手推一遍向量乘矩阵:看懂数据是怎么"流"起来的

3.1 用19行Python模拟1D脉动阵列

纯讲概念听着还是有点玄,我写了一个一维(1D)脉动阵列的Python模拟,用来演示向量乘矩阵的核心数据流。别急着看代码,先看它的物理设定:阵列是一排PE,数量等于权重矩阵的列数N;每个PE j里面预先驻留权重矩阵第j列的所有K个元素;输入向量v从阵列左侧一个接一个流入。

def systolic_1d(v, W): """一维脉动阵列(WS数据流)。 执行: out[j] = sum_k v[k] * W[k][j] v: 长度为K的输入向量 W: K行N列的权重矩阵 阵列由N个PE组成,PE j 持有W第j列的全部权重。 """ K = len(v) N = len(W[0]) # 权重预装载:pe_weight[j][k] = W[k][j] pe_weight = [[W[k][j] for k in range(K)] for j in range(N)] psum = [0] * N # 每个PE的累加器 # 核心:第t拍时,v[k]到达PE j 的条件是 k = t - j for t in range(K + N): for j in range(N): k = t - j if 0 <= k < K: psum[j] += v[k] * pe_weight[j][k] return psum

这段代码只做了两重循环,但你看那个索引关系k = t - j——这就是脉动数据流的签名。

3.2 逐拍追踪:为什么输出会有流水线延迟

假设K=4,N=3,v = [1, 2, 3, 4]。我们盯着时钟拍t来看每个PE在做什么:

  • 第0拍:PE0收到v[0],PE1和PE2没有有效输入;
  • 第1拍:PE0收到v[1],同时v[0]从PE0流到了PE1,所以PE1正在处理v[0];
  • 第2拍:PE0处理v[2],PE1处理v[1],PE2处理v[0];
  • 第3拍:PE0处理v[3],PE1处理v[2],PE2处理v[1];
  • 第4拍:PE0已经没有新输入了(v已经全部进入阵列),v[3]流到PE1,v[2]流到PE2;
  • 第5拍:v[3]流到PE2,完成最后一次计算。

看出来了吗?数据不是同时广播给所有PE的,而是"错开一拍"逐级流过去。同一时刻,PE0在处理v[t],PE1处理v[t-1],PE2处理v[t-2]——整条PE链上同时处理的是一段连续的、错位的输入序列,看起来就像一波数据涟漪碾过整条流水线。

这不是SIMD。SIMD的典型做法是把同一个数据同时扔给所有处理单元,那样每个PE收的是同一份数据;脉动的关键在于空间上的"时间偏斜",每个PE对同一波数据流的不同快照进行处理。

那么完成全部N个输出需要多少拍?答案是K+N-1拍。K拍让最后一个输入走完整个链还需要N-1拍。这多出来的N-1拍就是流水线填充延迟。只有当K远远大于N时,这点填充损失才微不足道。这也是为什么脉动阵列特别适合大矩阵——启动成本被摊薄了。

3.3 从一维到二维:矩阵乘法的全景图

一维脉动阵列处理的是向量乘矩阵,把它"复制"很多行并整齐排列,就得到了二维脉动阵列。这时输入不再是一个向量,而是整个A矩阵的行激活数据;权重矩阵B的各列预装载在每一列的PE里;A矩阵的行从左往右流过一行PE,列方向的数据也可以从上往下传播,每个PE在自己的累加器里累积部分和,或者把部分和传给下面的PE,最后在阵列的右侧或底端流出完整的C矩阵。

用这个视角看一个三维GEMM,C[M][N] = A[M][K] × B[K][N],脉动阵列的物理本质是:把A矩阵的M行看作M个并行的数据流同时冲进阵列,每一行数据穿过N个PE时,依次完成对B各列的乘累加。所有中间结果都在PE内部和PE之间流动,不返回内存。

所以"数据复用"不是软件层面的tiling技巧,而是物理层面的必然:A的每个元素进入阵列后,沿途每个PE都用它做一次乘法,它被复用了N次之后才离开。B的权重则干脆驻留在PE里,一个权重会被源源不断流入的激活反复使用M次。

4. 对比CPU/GPU:脉动阵列凭什么把能效拉高两个数量级

4.1 ALU密度:把芯片面积都花在乘法器上

能效差距首先来自面积的用途分配。桌面CPU的核心面积里,真正做计算的ALU占比很低——大部分面积给了多级缓存、分支预测器、乱序执行窗口、寄存器重命名等"为了快而快"的部件。GPU把更多面积让给计算单元,但依然有大量线程调度逻辑、共享内存和寄存器堆。

脉动阵列则极端得多:PE阵列的面积基本上就等于乘法器和加法器的面积。你可以把它理解为一块芯片上密密麻麻排满了计算器,几乎没有多余的"管理人员",没有指令缓存、没有复杂调度器,控制逻辑被压缩到极致。每一块晶体管都在出算力,这就是算力密度高的直接来源。

4.2 数据搬运量:一个让GPU脸红的估算

看一个具体的例子:计算C[128][128] = A[128][128] × B[128][128]。按朴素实现,计算每个输出元素C[i][j]需要读A的第i行和B的第j列,也就是128+128=256次数据读取,128×128个输出就要读大约419万次数据。

脉动阵列怎么做?A矩阵的128×128=16384个激活值,从左侧流入阵列一次;B矩阵的16384个权重,预装载一次并驻留。总共只需要从内存读出约32768个数据,数据搬运量降了128倍左右。

这不是严格的时序仿真,量级概念已经够清楚了。而芯片能耗的大头恰恰是数据搬运——你省掉了128倍的内存访问,能效自然会有质的提升。这也是为什么TPU这类芯片能在功耗远低于同代GPU的情况下跑出可观的推理算力。

4.3 控制逻辑:从复杂调度到一拍一动

CPU里每个时钟周期要处理取指、译码、乱序调度、数据前递、预测分支等一大堆工作;GPU要让成千上万个线程保持在轨、处理访存冲突和bank conflict。而脉动阵列的PE几乎不需要任何控制:它做的事情是固定的——接收、乘、加、传。

输入数据的到达节奏由时钟和物理连接决定。你不需要软件去"调度"某个乘法在第几个周期执行,数据走到哪、算到哪,天然就是流水线。这样的设计让控制功耗几乎可以忽略,也让时序验证变得非常规整——阵列很大,但每个PE的行为一致,局部化极强。

4.4 一张表看清三种架构的取舍

特性CPUGPU脉动阵列
计算单元面积占比低,大量面积给缓存和调度器中等,仍有线程调度和寄存器堆开销极高,几乎全部面积用于乘加
数据复用方式多级缓存加乱序执行Tensor分块加共享内存数据在PE间物理流动
控制复杂度极高高极低,一拍一动
访存特征通用、不规则高吞吐并行规则、顺序、低搬移
擅长场景通用逻辑大规模并行计算稠密规则矩阵运算

当然,GPU靠大规模并行和成熟软件生态,在很多AI训练场景依然是首选。但"能效"这件事上,脉动阵列确实把资源用到了刀刃上。

5. 走进真实芯片:从TPU的256×256阵列到边缘NPU的精简版

5.1 Google TPU:教科书级的脉动阵列

说到脉动阵列的商业化落地,绕不开Google TPU。TPU v1在设计思路上几乎就是为脉动阵列量身定做的:它把256×256个8bit整数MAC单元排成一个方阵,运行在约700MHz的较低主频上。注意,它没有靠主频跑分,纯粹靠阵列规模堆算力。

256×256是多少?65536个PE。每拍可以完成65536次8bit乘累加:

  • 每拍操作数:65536 MAC
  • 每秒钟MAC数:65536 × 700MHz ≈ 4.59 × 10^13
  • 按一次MAC等于2次FLOP计算:约9.18 × 10^13 FLOPs,也就是92 TOPS

TPU论文里公开的峰值算力正好是92 TOPS,就是这么推出来的。

它还配了24MB左右的统一缓冲区(Unified Buffer),充当"血库"的角色:数据先从外部存储器搬运到这块片上SRAM,再按固定节奏注入脉动阵列。权重预装载后驻留在PE里,激活数据从缓冲区流向阵列,计算结果再写回缓冲区。整个组织方式一目了然:数组是心脏,缓存是血液储备,数据流是血管。

5.2 从算力目标反推阵列规模

如果换你来设计一颗AI推理芯片,阵列该做多大?有一个简单公式可以反推:峰值算力(按FLOPs计约等于阵列宽度W的平方乘以频率f再乘以2。

TOPS = W × W × f(Hz) × 2

举例,边缘场景目标16 TOPS,频率1GHz:

  • W² = 16 × 10^12 / (1 × 10^9 × 2) = 8000
  • W ≈ 89.4

取整后你会倾向于做96×96或者直接128×128,给未来留余量。如果目标降到2 TOPS、频率500MHz,算出来W约45,取64×64就很合适。这解释了为什么边缘端NPU的阵列尺寸普遍在几十这个量级,而云端芯片动不动就上百甚至两百以上。阵列越大,算力越大,但面积、功耗、喂数带宽压力也同步上升,这不是越大越好的游戏。

5.3 处理器+阵列:真实芯片的完整组织

你可能会问:脉动阵列那么"笨",只会算乘加,别的活谁干?实际AI芯片都是一个异构组合:脉动阵列负责GEMM,旁边还得有向量处理单元(负责激活、归一化、各种element-wise运算)、标量核(负责控制流、地址计算)、DMA(负责外部存储和片上SRAM之间的搬运),以及足够大的SRAM。

所以当你看到某款AI芯片的架构图里画了一个矩阵网格,不要以为整颗芯片都长这样。那个网格通常只是其中的GEMM引擎,真正让芯片跑起来的是一整套外围系统。编译器还要负责把神经网络计算图变成一系列"填充阵列—喂数据—取结果"的命令。阵列只负责埋头算,喂什么、什么时候喂,取决于软件栈的调度能力。

6. 工程师视角:设计脉动阵列必须算清的几笔账

6.1 喂数带宽:阵列再强,喂不饱就是空转

脉动阵列真正吃掉时间的不是计算本身,而是"如何及时喂饱它"。一个128×128阵列跑在1GHz,意味着每拍需要向阵列边界送入128个激活数据(按行从左侧流入),也就是每秒约128G个8bit数据,折合128GB/s的持续注入带宽。

外部DRAM根本扛不住这种带宽,所以芯片内部必须有大容量SRAM做中转。SRAM容量不够,你就得频繁从外部搬数据,一旦阵列"断粮",算力就像发动机空转一样被白白浪费。这就是为什么GPU强调显存带宽,而脉动阵列芯片强调片上缓冲区容量。很多NPU的性能测试看起来远低于峰值,根子往往不在阵列本身,而在于数据喂不进来。

6.2 权重重载与流水线延迟

WS模式下权重是预装载的,这个动作需要时间。假设矩阵K很大,权重加载一次能撑很久;但如果矩阵很小、形状频繁切换,你就得隔三差五停下来重载权重,阵列利用率会断崖式下跌。

同理还有流水线填充延迟。小矩阵意味着K值小,K+N-1拍里N-1拍的启动开销占比变大。比如说K=8、N=64,流水线里光填充就占了56拍,接近90%的周期是在"走空路"。这类损耗编译器可以用更大的batch和矩阵分块来缓解,但根源在于:脉动阵列的设计目标就是大块稠密矩阵,用碎算子去喂它,效果一定大打折扣。

6.3 稀疏输入的尴尬

现实中很多AI模型权重经过剪枝后有一半以上是零,激活值经过ReLU后也有大量零。GPU上的稀疏加速可以跳过零值,直接把有效数据打包执行。但脉动阵列的PE是"盲"的:它不会检查输入是不是零,不管零还是非零,每拍都一样地做一次乘加。零也占一个PE周期。

这意味着你千辛万苦换来的稀疏模型,在传统脉动阵列上可能颗粒无收。业界现在做稀疏加速的芯片,往往会在脉动阵列旁边单独设计一条稀疏数据感知的路径,或者采用结构化稀疏让零值分布有规律,好让编译器提前把人力和运送路径安排好。这是脉动阵列目前最明显的短板之一,也是很多架构创新的切入空间。

6.4 利用率:当矩阵形状不匹配阵列形状

阵列宽度是固定的,但训练好的神经网络矩阵大小千变万化。输入矩阵的维度不是阵列宽度的整数倍时,边缘PE就会空转。例如K=100的矩阵乘,阵列宽度128,那么最后28列PE在前面某些拍里根本没有有效数据进来。这样统计下来,理论TOPS再高,实际有效算力也得打个折。

解决办法无非两条:一是编译器做padding,把100补齐到128,浪费一些带宽;二是做tiling,把大矩阵切成和阵列匹配的块,尽量提高占满率。这事看似软件层的细节,实际上直接决定了你在芯片手册上看到的"峰值TOPS"和真实跑分的差距。评估一颗AI芯片,一定要看它在实际网络结构上的利用率,而不是被峰值数字迷惑。

7. 别迷信脉动阵列:边界条件、常见误区和我的实践感受

7.1 几个被反复误解的说法

围绕脉动阵列的误解比它的实际原理流传得更广。我把最常见的几条掰开说:

第一,"脉动阵列=TPU的全部"。TPU的成功绝不只是靠一个阵列,更靠配套的软件编译器、缓冲区组织和系统级协同设计。阵列只是负责GEMM的心脏,但心脏再强也不能脱离整个身体的运转。

第二,"脉动阵列一定比GPU快"。这是个典型的以偏概全。在稠密大矩阵、低精度推理场景,脉动阵列确实有极高的能效优势;但在训练场景、稀疏场景、小shape频繁切换的场景,优势没有想象中那么大,甚至可能吃亏。架构没有绝对的优劣,只有匹配不匹配。

第三,"脉动阵列只能做矩阵乘法"。它确实最擅长GEMM,但卷积通过Implicit GEMM变换后,也能吃到阵列的算力红利。真正需要关注的不是"能不能算卷积",而是"编译器能不能把不同形状的算子都高效映射到固定尺度的阵列上"。

7.2 阵列之外不可或缺的"配角"

再回到工程一线。脉动阵列擅长规则运算,但Transformer里的Softmax、LayerNorm、各类激活函数、Gelu、残差相加等操作,是真的不擅长。你不可能把这些也算进脉动阵列——它们需要的是灵活的一对一映射,也就是向量引擎。

所以你看成熟AI芯片的内部结构,几乎都是"脉动阵列 + 向量单元 + 标量控制核 + SRAM + 互连总线"的组合方案。各种算子各司其职:GEMM进阵列,逐元素运算进向量单元,控制逻辑走标量核。芯片设计者做的最核心工作,其实就是把这些单元的分工和带宽配比调到恰到好处,让不同算子不会互相等待、不会某一单元闲置成瓶颈。

7.3 对下一步的一些实际想法

从我的实践体会来说,脉动阵列的局限恰恰是下一代芯片设计的机会。

  • 稀疏化会逼迫阵列改变数据流协议:要么在PE里加零值跳过逻辑,要么改成更灵活的数据打包方式,但这会牺牲面积和频率。
  • Transformer这类大模型带来更多样的算子,阵列调度器和编译器的作用越来越重要,甚至比硬件本身更能决定实际算力。
  • 多精度支持正在成为标配:INT4、INT8、FP16在同一个阵列上切换,带来的是PE里乘法器位数和排线的重新设计,工程复杂度远比想象中高。

做AI芯片这行,没有哪个架构是"银弹"。脉动阵列用一个极其简洁的机制解决了数据搬运的痼疾,代价是你必须在算子形状、稀疏度、精度方面让步。它是一把非常锋利的刀,但只适合切它擅长的那一类食材。

我这两年评估过几款边缘侧AI芯片,印象最深的一件事是:同样标称几TOPS算力的两颗芯片,跑同一个量化网络时,真实延迟可能差出一倍以上。拆开看,差距几乎都出现在喂数带宽、权重重载策略和编译器tiling的质量上,而不是阵列本身。所以我现在看芯片,先看峰值TOPS,然后马上去查三个东西:阵列尺寸多大、片上SRAM有多少、编译器对分块的控制力怎么样。理解了脉动阵列,你不光能看懂芯片PPT里那个规整的方块矩阵,更能在实际部署时多一份判断力——矩阵怎么分块、batch设多大、精度压到哪里,都会直接反映在真实延迟和功耗里。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进