想象你是一家汽车装配厂的厂长。旧厂房里,每位工人装配零件时都要一路小跑去中心仓库领取物料——领一颗螺丝、再跑回去拧上,拧完又跑回去取下一个。工人们把大部分时间都耗在“跑路”上,真正干活的双手反而闲得发慌。你决定彻底改造厂房:把工人们排成一个整齐的方阵,每个人的工位只跟左右邻居用传送带相连;物料不再由工人去取,而是放在传送带上,伴着统一的“嗒—嗒—”节拍,自动流到每个人手边。工人只需在物料流经自己时,伸手完成一个简单的装配动作。 这就是 TPU(张量处理单元)的核心秘密——脉动阵列。它把 AI 计算中最常见的矩阵乘法,变成一条井井有条的“数据流水线”:无数个计算单元(工位)排成二维方阵,数据和部分结果像血液一样,随着同步的节拍在单元间有规律地流动。每一拍,每个单元只做一次乘法和一次加法,然后把结果传给邻居。流动本身代替了“搬运”,让访存带宽不再是瓶颈。这篇文章,我们就走进这座“数字工厂”,把脉动阵列的每一条传送带、每一个节拍都拆开看个明白。
理解脉动阵列,是看懂 AI 芯片为什么能又快又省电的关键。无论你做模型部署、算法优化还是芯片设计,它都会帮你判断:算力到底卡在了哪里。
可视化演示
展示系统处理流水线、实时性能指标曲线及多方案对比分析,包含数据流转、吞吐延迟监控与方案评估。
本节配有交互动效(进入视口后自动加载,约 3KB)。
系统可视化 处理流程:输入→处理→模型→输出 性能监控:实时指标 P=L×T
源于访存瓶颈
旧工厂里最刺眼的画面,是工人们来回奔跑的腿。拧一颗螺丝需要五秒,跑去仓库领料却要三十秒。我们把这种行为叫“冯诺依曼瓶颈”:在传统计算机里,计算单元(工人)和内存(仓库)是分离的,每算一步都要先跨过一条狭窄的通道把数据取回来。计算本身再快,也被这条来回取数的通道死死卡住——工人空有一双快手,却等不来物料。
AI 计算放大了这个矛盾。神经网络里的矩阵乘法动辄涉及百万千万个数据,可内存带宽的增长速度远远赶不上芯片算力的增长速度。结果就是:芯片的计算单元绝大部分时间在“空转等数据”,我们称之为“内存墙”。因此,聪明的芯片设计师不再急着把单个工人训练得更快,而是换了一个思路——为什么不让数据自己走起来,主动流到工人们手边呢?
这个思路的转变,正是脉动阵列的起点。它把“以计算为中心”变成“以数据流动为中心”,从根子上绕开了冯诺依曼瓶颈。从此,工人不必再奔跑,传送带替他们跑完所有的路。
阵列拓扑结构
厂房改造的第一件事,是重新规划工位布局。旧工厂的工位围着仓库转,新工厂把工人排成一个横平竖直的方阵:你站在任何工位上,只需伸手够得着左右和上下四个邻居——物料从左边工位传来,做完自己的活,又递给右边的工位。这就是脉动阵列的拓扑结构:二维 PE 网格。
每个工位有一个名字,叫 PE(Processing Element,处理单元)。它很“笨”,里里外外只装了一个乘法器和一个加法器,负责把两数相乘再加到已有结果上。但正因为它足够简单,才能在芯片上铺得密密麻麻。Google 第一代 TPU 里,这样的 PE 排成了一个 256×256 的大方阵,共 65536 个,每个都只与相邻单元通过极短的“局部总线”相连,全阵列没有任何一条贯穿全局的昂贵总线。
局部互连意味着什么?好比工位之间是几十厘米的小传送带,而不是横跨整座厂房的天桥。数据从一个 PE 传到隔壁 PE,几乎不花什么代价;而每一条长长的全局总线,都意味着高能耗、高延迟、占面积。阵列拓扑的智慧,就是把长距离通信消灭在摇篮里——所有搬运都发生在邻居之间,一步传递,成本极低。
脉动节拍机制
新厂房上线前,你给每个工位装了一只统一的节拍器。铃响一声,所有工人同时做一个动作;再响一声,再做一个动作。没有人抢跑,也没有人拖延——这就是“脉动”二字的来历:像心脏收缩一样,整个阵列在同一节拍下同步搏动,把数据一波一波地泵向远方。
一个节拍内,每个 PE 只做一件事:把刚传到手边的激活值 x,乘上自己工位上驻留的权重 w,再加到从上方传来的部分和 p 上,然后把新的部分和交给下方的邻居。用数学语言表达,就是:
节拍器一响,全厂上下同时呼吸。你可能担心这么多工位要同步,时钟信号怎么传得过来?办法是把时钟像水波一样从中心向外扩散,让所有 PE 在同一微秒内踏出同一步。精准的同步,保证了数据在单元间的接力不会乱——前一个工位刚松手,后一个工位刚好接住。
- 上方传来的部分和\(p_{\text{in}}\) 是上一行 PE 算到一半的结果,顺着列向下传递,等待被继续累加。
- 本地的乘加当前 PE 用自己驻留的权重 \(w\) 乘以刚到达的激活值 \(x\),得到一个局部乘积。
- 累加并传给下方把局部乘积加到传入的部分和上,形成新的部分和 \(p_{\text{out}}\),在下一拍交给下方的邻居,让它继续累加。
数据流模式
生产线上有三样东西可以动:零件盒、半成品、成品。聪明的厂长发现,不同的产品适合不同的物料安排。第一条线把零件盒固定在各工位,半成品沿产线流动;第二条线让半成品一直待在流水线上,工人来回调整;第三条线让成品在生产过程中逐级传递、边做边组装。脉动阵列的数据流,也分为这样三种经典模式,分别叫权重驻留、输入驻留、输出驻留。
TPU 采用的是第一种——权重驻留:把神经网络训练好的权重矩阵像零件盒一样,提前“烧录”进每个 PE 的寄存器里,在整个计算过程中纹丝不动。需要移动的只有两样东西:激活值从阵列左侧注入,沿着每一行向右边的邻居逐拍传递;部分和从阵列顶部(通常是零)注入,沿着每一列向下累加。
为什么 TPU 偏爱权重驻留?在推理场景里,权重是固定不变的一道“模具”,而输入数据是源源不断流过的“毛坯”。模具安在工位上不动,毛坯不停地流过,每条产线就能持续不断地吐出成品。这种模式让数据流动的路径最短、规律最强,也最适合硬件资源有限的芯片。三种模式没有绝对优劣,但权重驻留恰好踩中了 AI 推理的节拍。
边界处理策略
流水线有起点,也有终点。厂房左侧设了上料斜坡,毛坯从那里送上传送带;右侧是出货口,成品在那里打包。脉动阵列同样需要精心打理自己的边界——处理不好,就会像传送带两端的物件堆积、掉落,乱成一团。
阵列的边界有三道工序。第一道是注入:每个时钟周期,要算的新激活值向量从左侧逐列推进,就像把毛坯一个接一个放上传送带;阵列顶部则不断注入 0,作为部分和的初始起点。第二道是零填充:当矩阵尺寸比阵列小、或计算刚开始、快要结束时,某些输入位置是空的,就用 0 把传送带上的空位填满,保证节奏不断拍。第三道是收集:底部每拍吐出一行部分和,这些数据还要经过重排缓冲器,把流水线上“错位”的输出归位,才能得到规规矩矩的结果矩阵。
边界处理还很考验耐心——一条流水线刚开动时,前几拍传送带上稀稀拉拉,产不出完整成品;快要停工前,也必须多跑几拍“空拍”,把线上遗留的半成品清空。这就是流水线固有的“填充期”和“排空期”,硬件工程师必须在阵列旁边备好缓冲,接住这些先乱后齐的输出。
访存优化收益
改造成流水线后,厂长算了一笔账:工人再也不用跑仓库了,取料的次数从“每个零件跑一趟”变成了“整批材料搬一次”。脉动阵列的收益,同样可以精确地算出来。
在一个 N 列的脉动阵列里,每个从左侧进入的激活值,会沿着它所在的行一路向右,依次经过 N 个 PE。它每经过一个 PE,就被这个 PE 读取并乘上一次。也就是说,同一个数据在片上被复用了 N 次,而这 N 次复用只占用了 N 次片上寄存器传递,完全不需要多次访问主存:
在传统架构里,每一次乘法都要先到主存取两个数;而在脉动阵列中,主存只在最左侧“投喂”数据流、在底部“回收”结果,中间的亿万次乘加全部靠片上流动完成。访存带宽的悬崖,就这样被数据复用这座桥轻轻跨了过去。传送带的一小步,是省下带宽的一大步。
- 阵列宽度\(N\) 表示脉动阵列一行(或一列)包含多少个 PE。TPU 中 N=256,意味着每个数据可以被 256 个工位依次使用。
- 复用次数\(R\) 表示一个输入数据从进入阵列到送出阵列,被读取使用的总次数。它直接衡量“一次取数,值多少回”。
- 复用等于宽度因为数据沿着行流经每一个 PE,所以 \(R = N\)。N 越大,复用越充分,主存的负担就越轻。
矩阵乘法映射
最后一步,是把数学上的矩阵乘法,翻译成流水线的生产计划。假设要计算输出向量 \(\mathbf{y} = \mathbf{W}\mathbf{x}\),其中 \(\mathbf{W}\) 是一个 K×K 的权重矩阵,\(\mathbf{x}\) 是输入向量。厂长一声令下,整条生产线按三个步骤运转起来。
第一步,烧录。把权重矩阵 \(\mathbf{W}\) 的第 \(i\) 行第 \(j\) 列元素 \(w_{ij}\),预先写进阵列第 \(i\) 行、第 \(j\) 列的 PE 里。权重从此安家落户,不再移动。第二步,注入。把输入向量 \(\mathbf{x}\) 的分量们,按顺序从左侧送入每一行,让它们像一排士兵齐步走,沿行向右推进。第三步,收集。每个 PE 每拍把 \(w_{ij}\) 乘以流到眼前的 \(x\),累加到从上方传下的部分和中;当数据走完整个阵列,底部吐出的就是完整的输出:
就这样,一条 256 列的生产线,每拍能同时完成 65536 个乘加运算。矩阵乘法没有惊天动地的魔法,有的只是把千万次独立的乘加,拆解成整齐划一的流水节拍,交给每一滴“血液”去完成自己的使命。数据流动之处,算力自然涌现。
- 输入分量\(x_i\) 表示输入向量的第 \(i\) 个元素,它沿水平方向流经阵列各行,每经过一个 PE 就参与一次乘法。
- 权重元素\(w_{ij}\) 是权重矩阵中第 \(i\) 行、第 \(j\) 列的值,它驻留在对应位置的 PE 里,等待与流过的 \(x_i\) 相遇。
- 乘累加求和把 \(w_{ij}\) 与 \(x_i\) 的乘积,从 \(i=0\) 到 \(i=K-1\) 全部累加起来,得到输出第 \(j\) 个分量。阵列中每个 PE 各贡献一项,部分和沿列一路向下汇成最终答案。
AI 应用场景
云端大模型推理加速
阶段:模型推理加速(AI 芯片设计与部署)
解决的问题:训练好的大模型(如 Transformer)在推理时,每一步都要做超大矩阵乘法。传统 GPU 虽有海量核心,但反复访问显存搬运权重和激活值,功耗高、吞吐受限。脉动阵列把权重驻留在计算单元内,输入数据流式注入,将访存开销压缩到极致。
具体案例:Google 的 TPU v4/v5 在云上支撑搜索、翻译和大语言模型服务。当用户输入一句英文时,模型庞大的矩阵乘法被切分成固定大小的分块,以“数据流”的方式灌入 256×256 的脉动阵列,每一拍吞吐数万次乘加,同时让功耗远低于同等算力的 GPU 集群。云端推理引擎也因此能为海量用户提供毫秒级响应。
边缘 NPU 的低功耗实现
阶段:端侧推理(嵌入式 AI 部署)
解决的问题:手机、汽车等边缘设备没有数据中心那样的大电力和散热条件,芯片必须“每瓦特算得更狠”。类脉动阵列恰好以极低功耗完成高频矩阵运算,成为端侧 NPU 的理想选择。
具体案例:华为麒麟 SoC 中的达芬奇架构 NPU、地平线征程系列自动驾驶芯片,都借鉴了脉动式数据流设计。手机相册里的“AI 消除路人”、汽车的实时车道检测,背后是 NPU 中的 PE 阵列以节拍方式持续处理来自摄像头的高维张量,将相机的数据流化为可执行的计算流,在几瓦功耗内完成每秒万亿次运算。
Python 演示
class SystolicArray2D: """权重驻留的二维脉动阵列:每个 PE 一拍做一次乘加。""" def __init__(self, rows, cols): self.rows = rows self.cols = cols self.w = [[0] * cols for _ in range(rows)] self.psum = [[0] * cols for _ in range(rows)] self.x = [[0] * cols for _ in range(rows)] def load_weights(self, w): self.w = [row[:] for row in w] def clock(self, x_in): """一个脉动拍:先算部分和,再右移输入。""" new_psum = [[0] * self.cols for _ in range(self.rows)] for i in range(self.rows): for j in range(self.cols): up = 0 if i == 0 else self.psum[i - 1][j] new_psum[i][j] = up + self.w[i][j] * self.x[i][j] self.psum = new_psum for i in range(self.rows): for j in range(self.cols - 1, 0, -1): self.x[i][j] = self.x[i][j - 1] self.x[i][0] = x_in[i] def run(self, x_seqs): """逐拍注入输入向量,返回每拍底部输出快照。""" out = [] for t, xv in enumerate(x_seqs): self.clock(xv) out.append([self.psum[-1][j] for j in range(self.cols)]) return out def reference(w, x): """参考实现:第 j 个输出 = 权重矩阵第 j 列与输入向量的点积。""" return [sum(w[i][j] * x[i] for i in range(len(w))) for j in range(len(w[0]))] def main(): w = [ [1, 0, 1, 2], [2, 1, 0, 1], [1, 3, 1, 0], [0, 2, 2, 1], ] x_seqs = [ [1, 0, 2, 1], [0, 1, 1, 0], [2, 1, 0, 2], [1, 2, 1, 1], ] sa = SystolicArray2D(4, 4) sa.load_weights(w) history = sa.run(x_seqs) print("=== 逐拍底部输出(流水线效果) ===") for t, row in enumerate(history): print(f"第 {t + 1} 拍: {row}") print("\n=== 验证时序关系 ===") for s in range(len(history)): for j in range(4): expect = 0 for i in range(4): idx = s - 1 - i - j if 0 <= idx < len(x_seqs): expect += w[i][j] * x_seqs[idx][i] got = history[s][j] assert abs(got - expect) < 1e-9, (s, j, got, expect) print("所有拍与参考计算一致!") if __name__ == "__main__": main()
class SystolicArray2D:定义一个二维脉动阵列类,结构上等同厂房里的方阵工位。self.w = [[0] * cols for _ in range(rows)]给每个 PE 准备一个权重寄存器,初始化全 0。self.psum部分和数组,记录每个 PE 当前的累积结果,对应“半成品”。self.x记录流经每个 PE 的激活值,对应“传送带上的毛坯”。up = 0 if i == 0 else self.psum[i - 1][j]取上方传下的部分和;最顶行从 0 起步。new_psum[i][j] = up + self.w[i][j] * self.x[i][j]核心公式 p_out = p_in + w×x,一拍一乘加。for i in ... for j in range(self.cols - 1, 0, -1):从右往左倒序右移数据,避免覆盖未使用的旧值。self.x[i][0] = x_in[i]把新的输入向量注入阵列最左列,好比毛坯送上产线。out.append([self.psum[-1][j] ...])每拍采集底部一行结果,模拟出货口打包。for s in range(len(history)):用推导出的时序公式逐项核验,保证模拟器没有“偷算”。
常见误区
误区“脉动阵列不就是把几千个乘法器摆成方阵吗?算得快全靠并行度高,跟普通 GPU 没啥本质区别。”
正解并行乘法器只是表象,灵魂在数据流。GPU 的众多核心各自独立取数、各自计算,数据仍要频繁往返于显存和核心之间;而脉动阵列让数据沿着 PE 链式流动,一个数据被反复复用。好比旧工厂是雇了一千个工人各自跑仓库,新工厂是让一条传送带把物料送到一千个工位面前。省下的不是并行度,而是海量的访存。
为何常见:看到二维方阵第一反应就是“并行”,容易忽略排列方式本身带来的访存收益,这是只看了硬件照片、没读数据流设计导致的。
误区“脉动阵列把权重都驻留在 PE 里了,所以主存带宽变得无关紧要,随便配个慢内存就行。”
正解权重驻留只优化了“计算过程中的中间数据复用”,但权重本身还是要从主存加载到阵列里,输入数据要持续从左边注入,底部的结果要不断写回主存。TPU 恰恰搭配了非常高带宽的 HBM 高带宽内存。好比传送带把物料送到工位,但物料总得从仓库先运到厂房门口,这趟运输依然关键。
为何常见:把“单个数据复用次数高”误解为“总数据量小”,忽略了权重加载和结果回写的固定开销。
误区“卷积、注意力这些复杂算子脉动阵列肯定做不了,只有 GEMM 这种规整计算才能跑。”
正解卷积可以通过 im2col 变换重排成矩阵乘法;注意力机制里的 QK^T 和 softmax 后的加权和本质也是 GEMM。现代 TPU 上的神经网络几乎全部算子,最终都会被编译器重新组织成一组矩阵乘法,再映射到脉动阵列上。好比汽车厂不仅能造轿车,把夹具一换,也能造卡车——核心流水线是同一条。
为何常见:看到硬件结构固定,就以为软件只能按原样映射,忽视了编译器的“reshape 魔术”。
误区“TPU 算 GEMM 那么强,脉动阵列应该在所有任务上吊打 GPU。”
正解脉动阵列的强项是密集、规则、可复用的矩阵运算。遇到稀疏矩阵、动态分支控制、不规则数据流,脉动阵列的固定节奏反而成了短板——大量 PE 可能空转,编译器也无从优化。GPU 的灵活调度在通用计算上仍然有优势。好比流水线适合标准化量产,但接到“每件都不同”的定制订单,手艺灵活的小作坊反而更快。
为何常见:把某一领域的峰值性能误当成通用性能,忽略了“固定结构 vs 灵活调度”的权衡本质。
面试题
Q1. 脉动阵列名称中的“脉动”主要指什么?
- A.芯片运行时的散热节奏
- B.数据像血液般节律流动← 正确答案
- C.权重像心跳般频繁更新
- D.芯片时钟像脉搏跳动
答案:B。脉动阵列得名于数据在 PE 之间像心脏泵血一样有节奏地流动,每个时钟周期同步推进一拍。“脉动”说的是数据流动的节律感,不是散热、权重更新或时钟本身,因此 B 正确。
Q2. TPU v1 的脉动阵列包含多少个 MAC 单元?
- A.128 个 MAC 单元
- B.256 个 MAC 单元
- C.1024 个 MAC 单元
- D.65536 个 MAC 单元← 正确答案
答案:D。第一代 TPU 内部是 256×256 的二维脉动阵列,总计 65536 个乘加单元。这是“方阵规模”这个考点:把行数乘以列数 256×256=65536,D 正确;128、256、1024 分别是边长或其倍数,容易误选。
Q3. 每个 PE 在一个节拍内完成什么操作?
- A.从主存读取一组新的权重
- B.把结果写回全局共享内存
- C.一次乘加并将数据传给邻居← 正确答案
- D.比较两个输入并取最大值
答案:C。PE 的核心动作是“乘加然后传递”:用当前到达的激活值乘驻留权重,加到部分和上,再让数据流向下一个邻居。它不会访问主存,也不会做取最大值这类非线性操作,这些工作由阵列外的单元负责。
Q4. 权重驻留模式中,哪部分数据在阵列内部保持不动?
- A.权重矩阵的元素← 正确答案
- B.输入激活值
- C.部分和的累加值
- D.同步时钟信号
答案:A。权重驻留(weight stationary)模式把权重提前烧录进各 PE 的寄存器,计算过程中不再移动;激活值沿行流动,部分和沿列累加,两者都在“动”。所以保持不动的是权重矩阵的元素。
Q5. 脉动阵列主要通过什么方式缓解访存瓶颈?
- A.提高芯片的核心频率
- B.增大片外缓存的容量
- C.复用数据降低访存次数← 正确答案
- D.采用更高级的流水线分支预测
答案:C。脉动阵列的核心收益是数据复用:一个激活值沿行流经 N 个 PE,被反复使用,避免了传统架构“每算一次就去主存取一次”的重复访存。它没有靠提频或加缓存,而是改变了数据取用方式本身。
Q6. 在 TPU 的权重驻留脉动阵列中,部分和是如何流动的?
- A.从右向左水平流动
- B.从上到下按列累加← 正确答案
- C.从左向右水平流动
- D.在每行内部循环往复
答案:B。部分和从阵列顶部注入初始值 0,沿每一列自上而下地流经各 PE,每经过一个 PE 就累加一项 w×x,最终从底部输出。激活值才是从左向右水平流动,两者方向不同,注意区分。
Q7. 阵列开始注入新输入后,前 N-1 拍的底部输出通常如何处理?
- A.已经是完整结果可以立即使用
- B.包含部分结果需配合零填充
- C.属于流水线填充期不可直接使用← 正确答案
- D.是上批数据的残留需要丢弃
答案:C。脉动阵列像一条流水线,开线后的前几拍,半成品还在线上未走完全程,底部吐出的部分和不完整,必须等待 N-1 拍“填满”后才能读到第一份可用结果。这些输出是真实的中间状态,不是数值错误,也不是上批残留,只是还没走完“全程”。
Q8. 下列哪些计算任务适合映射到脉动阵列上执行?
- A.全连接层的矩阵乘法← 正确答案
- B.卷积层的 im2col 矩阵乘法← 正确答案
- C.图片的随机裁剪与翻转
- D.注意力机制中的打分矩阵乘← 正确答案
答案:ABD。全连接层本质是矩阵乘;卷积可经 im2col 重排成 GEMM;注意力机制中的 QK^T 和加权求和也都能规约成矩阵乘法。而随机裁剪是数据增强分支操作,含动态随机逻辑,无法规约为规整的矩阵乘,不适合脉动阵列。
Q9. 关于脉动阵列降低访存带宽的说法,正确的有哪些?
- A.每个输入激活值被多次复用← 正确答案
- B.大幅度减少主存的重复访问← 正确答案
- C.权重和结果完全不需要主存
- D.同等算力下能效比显著提升← 正确答案
答案:ABD。激活值沿行流经多个 PE 被复用,主存访问次数因此大幅下降,同时省下的带宽也直接转化为能耗优势。“完全不需要主存”不对——权重要加载进阵列,最终结果也要写回主存,只是访问频率大幅降低。
Q10. TPU 脉动阵列的边界处理通常包括哪些手段?
- A.对输入序列补零来维持节奏← 正确答案
- B.增加冲刷拍来排空流水线← 正确答案
- C.用重排缓冲把错位输出归位← 正确答案
- D.关闭边缘 PE 来节省动态功耗
答案:ABC。当矩阵尺寸不足时补零以维持节拍;计算结束时多跑几个空拍清空线上残留;流水线输出天然错位,需要重排缓冲恢复顺序。关闭边缘 PE 属于动态功耗管理,与边界数据流处理无关。