☰
AI芯片脉动阵列:从矩阵乘法到TPU架构设计与工程实践
2026/10/4 21:53:30 网站建设 项目流程

1. 从矩阵乘法说起:为什么AI芯片需要脉动阵列

搞AI芯片的人绕不开一个话题:矩阵乘法怎么加速。不管是大模型里的注意力机制,还是卷积神经网络里的特征提取,底层计算几乎全是矩阵乘加。一个典型的Transformer层,QKV投影、输出投影、FFN里的两层全连接,全都是大规模矩阵乘法。问题在于,通用CPU做矩阵乘法效率极低——取数、计算、存回,大部分时间花在数据搬运上,而不是计算本身。

我第一次接触脉动阵列这个概念时,脑子里冒出的第一个问题是:为什么不让数据像流水一样穿过计算单元,算完就走,而不是反复存取?这个直觉其实就抓住了脉动阵列的核心思想。它的本质是让数据在计算单元之间流动,每个计算单元只做简单的乘加,数据流过时顺便被计算掉。这种设计把数据复用做到了极致,特别适合矩阵乘法这种规整的计算模式。

脉动阵列(Systolic Array)这个名字来源于“systole”这个词,原本是描述心脏收缩泵血的节律。在硬件架构里,它指的是数据像心跳一样有节奏地流过阵列,每个周期都有新的数据进入、旧的数据流出,计算单元始终处于忙碌状态。这个概念最早由H.T. Kung在1982年提出,当时是为了解决VLSI(超大规模集成电路)中计算与通信的平衡问题。但真正让它大放异彩的,是Google在2016年发布的TPU(Tensor Processing Unit)。

为什么TPU选择脉动阵列而不是传统的SIMD或GPU架构?核心原因在于能效比。GPU虽然算力强,但它的架构是为图形渲染设计的,有大量的寄存器文件和复杂的调度逻辑,功耗很大一部分花在了指令调度和数据搬运上。而脉动阵列把控制逻辑简化到了极致——每个计算单元只需要知道“什么时候把数据往下传、什么时候把部分和往右传”,不需要复杂的指令译码。这种极简的控制逻辑意味着更少的晶体管用于控制、更多的晶体管用于计算,能效比自然就上去了。

我实测过一些边缘端的AI加速方案,发现一个规律:当矩阵规模足够大且形状规整时,脉动阵列的能效优势非常明显;但当矩阵形状不规则、稀疏度高时,它的利用率会急剧下降。这也是为什么后来的AI芯片架构往往采用脉动阵列加其他灵活计算单元的混合方案。理解这个边界,比单纯知道“脉动阵列好”要重要得多。

2. 拆开一个脉动阵列:数据怎么流、计算怎么叠

2.1 基本结构:PE、连线和节拍

一个典型的脉动阵列由二维网格状的处理单元(PE,Processing Element)组成。每个PE内部只有一个乘法器和一个累加器,外加几个寄存器用于暂存数据。PE之间通过水平方向和垂直方向的连线连接,数据沿着这些连线逐拍传递。

以最经典的N×N脉动阵列做矩阵乘法C=A×B为例。矩阵A的元素从左侧流入,矩阵B的元素从上方流入。每个PE在每一拍做一件事:把来自左边的A元素和来自上方的B元素相乘,累加到自己的部分和寄存器里,然后把A元素往右传、把B元素往下传。注意,A元素在阵列中向右流动,B元素向下流动,而部分和则沿着对角线方向累积。

这里有个关键细节:为了让正确的元素在正确的时刻相遇,A和B的输入需要做斜向排列(skewed input)。具体来说,A矩阵的第i行需要延迟i拍输入,B矩阵的第j列需要延迟j拍输入。这样当A[i][k]和B[k][j]在PE(i,j)相遇时,它们恰好是同一拍到达的。这个斜向排列是脉动阵列能正确工作的前提,也是硬件实现时最容易被忽略的地方。

我见过一些开源的脉动阵列实现,在仿真阶段结果总是对不上,排查半天发现就是输入没有做skew。skew的本质是补偿数据在阵列中传播的延迟——A[i][k]从左边进入PE(i,j)需要经过j个PE,所以它要比A[i][0]晚j拍出发;同理B[k][j]从上方进入需要经过i个PE,要晚i拍出发。这个逻辑用一句话概括就是:让所有需要相乘的元素在同一时刻到达同一个PE。

2.2 三种数据流模式:权重固定、输出固定、行固定

脉动阵列不是只有一种数据流方式。根据哪个数据在阵列中停留、哪个数据在流动,可以分为几种典型模式:

数据流模式固定数据流动数据适用场景特点
权重固定(WS)权重B激活A、部分和推理为主权重预加载后不动,适合批量推理
输出固定(OS)部分和C激活A、权重B训练/通用部分和留在PE内累加,减少搬运
行固定(RS)激活A的一行权重B、部分和特定卷积一行激活复用多次

权重固定模式在推理芯片里最常见。因为推理时权重是固定的,可以提前加载到PE的寄存器里,然后让输入激活流过阵列。这样权重不需要反复搬运,省了大量带宽。Google TPU用的就是权重固定加输出固定的混合方案——权重预加载,部分和在PE内累积,输入激活从左侧流入。

输出固定模式则更适合训练场景。训练时权重需要更新,部分和需要反复读写,把部分和固定在PE内可以减少对内存的访问。但这也带来一个问题:部分和寄存器需要足够的位宽来避免溢出。做INT8推理时,部分和通常需要32位;做FP16训练时,部分和可能需要FP32。这个位宽设计直接影响到PE的面积和功耗。

2.3 阵列尺寸怎么定:不是越大越好

脉动阵列的尺寸(比如16×16、32×32、128×128)是一个核心设计参数。直觉上,阵列越大,一次能处理的矩阵越大,算力越高。但实际设计中,阵列尺寸受限于几个因素:

第一是面积和功耗。一个128×128的脉动阵列有16384个PE,每个PE哪怕只有几百个晶体管,总数也是千万级别。而且阵列越大,时钟树越复杂,布线延迟越难控制。

第二是利用率。如果实际矩阵是64×64,但阵列是128×128,那有一半的PE是闲置的。矩阵越小,利用率越低。我见过一些芯片标称算力很高,但实际跑小模型时利用率不到30%,就是因为阵列尺寸和实际负载不匹配。

第三是数据供给带宽。阵列越大,每拍需要喂进去的数据越多。一个128×128的阵列,每拍需要从左侧输入128个A元素、从上方输入128个B元素。如果内存带宽跟不上,PE就会饿死。这就是所谓的内存墙问题——算力上去了,但数据供不上。

实际设计中,阵列尺寸的选择需要在算力、面积、功耗、利用率之间做权衡。常见的做法是用适中的阵列尺寸(如32×32或64×64)配合高带宽的片上缓存,而不是一味追求大阵列。另外,很多芯片会支持阵列分区——把大阵列切成几个小阵列,分别处理不同的矩阵块,提高小矩阵的利用率。

3. 脉动阵列在AI芯片里的真实落地形态

3.1 TPU的脉动阵列设计细节

Google TPU v1是最经典的脉动阵列落地案例。它采用了一个256×256的INT8脉动阵列,峰值算力92 TOPS。这个阵列用的是权重固定加输出固定的混合模式:权重预加载到PE中,激活从左侧流入,部分和在PE内累积。

TPU v1的PE设计很精简:每个PE有一个8位乘法器、一个32位累加器、几个寄存器。控制逻辑极其简单——每个PE只需要知道当前拍是加载权重还是计算,以及数据往哪个方向传。这种极简设计让TPU v1的能效比达到了当时GPU的几十倍。

但TPU v1也有明显的局限:它只支持INT8推理,不支持训练。而且256×256的阵列对于小批量推理来说利用率不高。后来的TPU v2/v3增加了浮点支持、支持训练、引入了更多的灵活性(比如支持稀疏计算),但核心的脉动阵列架构一直保留。

我在分析TPU的架构时注意到一个细节:TPU的权重加载是逐列进行的,而不是一次性全部加载。这是因为256×256的权重矩阵有65536个元素,如果一次性加载需要很大的带宽。逐列加载可以让权重加载和计算重叠,减少等待时间。这个设计思路在实际芯片实现中很值得借鉴——把大操作拆成小操作,让不同阶段重叠起来。

3.2 边缘端AI芯片的脉动阵列变体

边缘端芯片对功耗和面积的要求比数据中心严格得多,所以脉动阵列的设计也需要调整。常见的做法包括:

缩小阵列尺寸。边缘端常见的阵列尺寸是8×8到32×32,而不是数据中心的128×128以上。小阵列面积小、功耗低,虽然峰值算力不高,但对于边缘端的模型(如MobileNet、TinyBERT)来说够用。

支持可变阵列尺寸。有些边缘芯片支持把大阵列配置成多个小阵列,或者动态关闭部分PE来省电。比如一个16×16的阵列可以配置成4个8×8的阵列,分别处理不同的计算任务。

混合精度支持。边缘端模型往往需要INT8和FP16混合精度。脉动阵列的PE需要支持多种精度的乘加,这增加了PE的复杂度,但提高了灵活性。

与DSP或CPU核的协同。纯脉动阵列只能做矩阵乘法,但AI模型里还有激活函数、归一化、池化等操作。边缘芯片通常把脉动阵列和DSP或RISC-V核集成在一起,脉动阵列做矩阵乘法,其他核做后处理。

我实际调试过一款边缘AI芯片,它的脉动阵列是16×16的,支持INT8和INT16。实测下来,跑MobileNet V2时阵列利用率能到70%左右,但跑一些自定义的小模型时利用率掉到30%以下。关键问题是模型形状和阵列尺寸的匹配度——如果模型的通道数不是16的倍数,阵列就会有闲置。后来我们在模型部署时做了通道对齐(把通道数补齐到16的倍数),利用率明显提升。

3.3 脉动阵列与存内计算的结合趋势

最近几年,存内计算(Compute-in-Memory)和脉动阵列的结合成为一个热门方向。传统脉动阵列的权重存在PE的寄存器里,而存内计算把权重存在SRAM或RRAM阵列里,计算直接在存储阵列里完成。这样省去了权重加载的功耗和延迟,能效比可以再上一个台阶。

但这种结合也带来新的挑战。存内计算的模拟特性导致计算精度不如数字电路,而且编程模型和传统脉动阵列差异很大。目前这个方向还在探索阶段,离大规模商用还有距离。不过从架构演进的逻辑来看,脉动阵列的核心思想——数据流动、计算复用——在存内计算时代依然适用,只是实现方式会发生变化。

4. 设计脉动阵列时最容易踩的五个坑

4.1 输入skew做错导致结果全错

这是最经典的坑。前面提到过,A矩阵的第i行需要延迟i拍输入,B矩阵的第j列需要延迟j拍输入。如果skew做错了,A[i][k]和B[k][j]就不会在PE(i,j)相遇,算出来的结果完全是乱的。

我见过一个开源实现,作者把skew的方向搞反了——A矩阵延迟的是列而不是行。结果仿真出来的矩阵乘法结果看起来“有点像”但完全不对。排查这种问题的方法是:先用一个3×3的小矩阵做仿真,手动跟踪每一拍每个PE的输入和输出。把每一拍的中间结果打印出来,和理论值对比,很快就能定位到skew的问题。

提示:做脉动阵列仿真时,建议先用小尺寸(如4×4)验证功能正确性,再扩展到大规模。小尺寸下手动跟踪数据流是可行的,大规模下只能靠断言和覆盖率验证。

4.2 部分和位宽不够导致溢出

部分和的位宽是一个容易被低估的问题。做INT8乘法时,两个8位数相乘得到16位结果,累加N次后需要log2(N)+16位。如果阵列是256×256,N=256,部分和需要16+8=24位。但实际设计中,为了保险通常会留更多余量,用32位累加器。

如果部分和位宽不够,累加过程中会溢出,结果完全错误。更麻烦的是,这种溢出往往在特定输入下才出现,仿真时不一定能覆盖到。建议在PE的累加器上加上溢出检测逻辑,一旦溢出就置一个标志位,方便调试。

4.3 阵列利用率被小矩阵拖垮

前面提到过,阵列尺寸和矩阵形状不匹配会导致利用率下降。但实际中还有一个更隐蔽的问题:批量大小(batch size)的影响。推理时如果batch size=1,矩阵乘法的另一个维度可能很小,阵列的利用率会很低。

解决这个问题的方法有几种:一是批处理,把多个请求攒在一起做推理;二是阵列分区,把大阵列切成小阵列处理小矩阵;三是数据重排,把多个小矩阵拼成一个大矩阵。具体用哪种方法,取决于实际负载的特征。

4.4 权重加载带宽成为瓶颈

权重固定模式下,权重需要预加载到PE中。如果权重矩阵很大,加载时间可能很长,影响整体性能。比如一个256×256的权重矩阵,如果每个周期只能加载一行(256个元素),需要256个周期才能加载完。在这256个周期里,阵列无法做计算。

解决方法是权重加载和计算重叠。比如在计算当前矩阵块的同时,预加载下一个矩阵块的权重。这需要双缓冲的权重寄存器,增加了面积但提高了吞吐。另一种方法是权重压缩,利用权重的稀疏性减少加载量。

4.5 时钟频率上不去

脉动阵列的时钟频率受限于PE之间的连线延迟。阵列越大,最远两个PE之间的连线越长,延迟越大,时钟频率就越低。一个128×128的阵列,如果每个PE的延迟是100ps,最远路径的延迟可能超过10ns,时钟频率只能跑到100MHz左右。

提高时钟频率的方法包括:插入流水线寄存器(把长连线切成短段)、优化PE布局(让相邻PE的连线最短)、使用更先进的工艺。但这些方法都有代价——流水线寄存器增加面积和延迟,先进工艺增加成本。实际设计中需要在频率、面积、功耗之间做权衡。

5. 从架构到落地:脉动阵列的编程与验证

5.1 怎么把模型映射到脉动阵列上

把神经网络模型映射到脉动阵列上,核心工作是矩阵分块(tiling)。一个大矩阵乘法需要切成多个小块,逐块送入阵列计算。分块的大小取决于阵列尺寸和片上缓存大小。

以一个全连接层为例,权重矩阵是1024×1024,输入是1024×1,输出是1024×1。如果阵列是32×32,那么权重矩阵需要切成32×32的块,共32×32=1024个块。每个块计算完后,部分和需要累加。这里的关键是部分和的存储和累加顺序——如果部分和存在片上缓存里,需要足够的缓存来存放中间结果;如果存在片外内存里,带宽会成为瓶颈。

实际映射时,还需要考虑数据复用。输入向量在计算不同输出元素时会被复用,权重矩阵在计算不同batch时会被复用。好的映射策略会尽量把可复用的数据留在片上,减少片外访问。

我实际做模型映射时的经验是:先用一个简单的映射策略跑通,再逐步优化。一开始不要追求极致的利用率,先把功能做对。功能对了之后,再通过调整分块大小、调整数据流顺序、增加双缓冲等方式提升性能。

5.2 功能验证:从单元测试到系统测试

脉动阵列的验证是一个分层的过程:

PE级验证。先验证单个PE的功能——乘法、累加、数据传递是否正确。PE的测试用例应该覆盖各种输入组合,包括边界值(最大正数、最小负数、零)。

阵列级验证。把多个PE连成小阵列(如4×4),验证数据流是否正确、skew是否正确、部分和是否正确。这个阶段可以用随机矩阵做测试,对比软件计算结果。

系统级验证。把脉动阵列集成到完整芯片中,验证与内存、DMA、控制器的交互。这个阶段需要跑真实的模型,对比端到端的精度和性能。

验证中最容易漏掉的是边界情况:矩阵尺寸不是阵列尺寸整数倍时怎么处理?输入数据有异常值(如NaN、Inf)时怎么处理?这些情况在实际部署中一定会遇到,验证时必须覆盖。

5.3 性能调优:从理论峰值到实际利用率

脉动阵列的理论峰值算力很容易算:PE数量×时钟频率×2(乘加各算一次)。但实际利用率往往远低于100%。影响利用率的因素包括:

  • 数据供给带宽:如果数据供不上,PE会空闲
  • 矩阵形状:不规则的矩阵导致PE闲置
  • 部分和搬运:部分和读写占用时间
  • 权重加载:权重加载期间阵列无法计算

提升利用率的方法包括:增加片上缓存(减少片外访问)、优化数据流(让数据复用最大化)、支持稀疏计算(跳过零元素)、动态阵列分区(适应不同矩阵尺寸)。

我实测过一个64×64的脉动阵列,理论峰值是64×64×2×500MHz=4 TOPS。跑一个典型的卷积层时,实际利用率在60%到75%之间波动。瓶颈主要在数据供给——片上缓存不够大,部分数据需要从片外读取。后来把片上缓存加大了一倍,利用率提升到了85%左右。

6. 脉动阵列的边界与未来演进方向

脉动阵列不是万能的。它的优势在于规整的矩阵乘法,但对于非矩阵运算(如激活函数、归一化、排序)效率很低。而且它对矩阵形状敏感,小矩阵和稀疏矩阵的利用率不高。这些边界决定了脉动阵列在AI芯片中的定位——它是矩阵计算的核心加速器,但需要其他计算单元配合完成整个模型的计算。

从演进方向来看,几个趋势值得关注:

稀疏脉动阵列。利用权重的稀疏性,跳过零元素的乘加,可以大幅提升有效算力。但稀疏性会破坏数据流的规整性,需要额外的控制逻辑。

可重构脉动阵列。支持多种数据流模式,根据不同的计算任务动态配置。这增加了灵活性,但也增加了面积和功耗。

三维脉动阵列。把二维阵列扩展到三维,增加数据复用的维度。但三维集成的工艺复杂度和散热问题需要解决。

与近存计算的结合。把部分权重存在离PE更近的存储里,减少数据搬运。这需要新的存储器件和电路设计。

我在实际项目中体会最深的一点是:脉动阵列的设计没有“最优解”,只有“最适合当前负载的解”。数据中心的大阵列、边缘端的小阵列、支持稀疏的变体阵列,各有各的适用场景。理解负载特征,比追求架构的“先进”更重要。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询