深入讲解脉动阵列Systolic Array架构原理,涵盖数据流动模式、PE阵列设计、矩阵乘法映射、TPU中的脉动阵列实现与性能特性。学习如何理解脉动阵列计算范式、分析数据复用效率、对比GPU与TPU架构差异。通过系统化方法与工程实践,可建立标准化流程,提升开发效率与系统可靠性。适用于AI芯片架构理解、TPU编程优化、硬件加速器设计等场景。
需要学习脉动阵列Systolic Array,机器人需要端侧AI推理,硬件选型和算力优化是关键。本篇涵盖GPU/TPU/NPU、CUDA、集群调度、算力优化,帮助你理解硬件加速原理。
可视化演示
展示脉动阵列PE结构、数据流模式、矩阵乘法映射及GPU与TPU架构对比。
本节配有交互动效(进入视口后自动加载,约 3KB)。
PE阵列与数据脉动 每个PE执行乘加运算并将结果传递给相邻PE 数据有节奏地流动类似心脏搏动 PE: C += A × B → pass to neighbor 局部互连减少连线长度与互连功耗
脉动阵列计算范式原理
脉动阵列(Systolic Array)是一种专用于矩阵乘法等规则计算的高速并行计算架构。其核心思想是数据在处理单元(Processing Element,PE)阵列中有节奏地流动,每个PE执行简单运算并将中间结果传递给相邻PE,类似心脏搏动泵送血液的节律,因此得名「脉动」。
每个PE的结构非常简单,通常只包含一个乘加器(Multiply-Accumulate Unit,MAC)和少量寄存器。PE执行的基本操作是乘加运算:计算a*b+c,即两个输入数据相乘后与累加器中的部分和相加。这种简单结构使PE面积小、功耗低,可以在有限芯片面积上集成大量PE——例如TPU v1集成了256x256=65536个PE。
脉动阵列的计算模式由全局时钟信号同步驱动。每个时钟周期,数据按固定方向(如从左到右、从上到下)流动一个PE位置,同时每个PE执行一次乘加运算。这种有节奏的数据流动和计算形成了流水线并行——数据像流水线上的产品一样依次经过多个PE,每个PE完成一步计算。
脉动阵列的核心优势在于高计算密度(PE面积小可大量集成)、低功耗(数据局部流动减少长距离传输)和高数据复用(数据在阵列中多次使用)。这些优势使其特别适合深度学习中的矩阵乘法和卷积运算,Google TPU正是采用脉动阵列作为核心计算单元。
数据流模式与驻留策略
数据流模式定义了数据在脉动阵列中的流动方式和驻留策略,直接影响数据复用效率和访存开销。主流的数据流策略包括权重驻留(Weight Stationary)、输出驻留(Output Stationary)和行驻留(Row Stationary)三种。
权重驻留策略将权重数据固定在PE的寄存器中,输入数据从左侧流入PE进行乘加运算,部分和从上方流入并累加后从下方流出。权重只需从DRAM加载一次即可在整个矩阵乘法过程中多次使用,最大化了权重复用。Google TPU采用此策略,适合权重矩阵固定且输入数据批量处理的推理场景。
输出驻留策略将输出结果固定在PE中累加,权重和输入数据流经PE进行计算。每个PE负责累加输出矩阵的一个元素,避免部分和在PE间传递。输出驻留策略减少了部分和的互连开销,但需要权重和输入数据都流动,访存开销高于权重驻留。
行驻留策略由MIT Eyeriss架构提出,最大化卷积运算中行向量的复用。它同时复用三类数据:权重复用(同一滤波器行在多个输入行上复用)、输入复用(同一输入行被多个滤波器行复用)和部分和复用(沿输出行方向累加)。三类数据复用最小化总访存量,在卷积计算中能效最高。
不同策略的算术强度(FLOPs/Byte,每字节数据执行的运算次数)不同。权重驻留策略的算术强度为2S(S为阵列边长),输出驻留约为2S/3,行驻留约为3S。算术强度越高访存开销越低,因此行驻留在卷积计算中能效最优,权重驻留在矩阵乘法中效率最高。
PE阵列互连与流水设计
脉动阵列的PE互连采用局部互连拓扑——每个PE只与相邻PE(如左、右、上、下四个方向)相连,不与远距离PE直接连接。局部互连减少了连线长度和互连功耗,适合VLSI实现,是脉动阵列高效的关键设计。
最常见的互连拓扑是2D网格(2D Mesh),每个PE连接上下左右四个邻居。2D网格适合标准的矩阵乘法映射:输入数据从左向右流动,部分和从上向下流动。2D网格的边界PE负责从外部(如SRAM/DRAM)接收输入数据或输出计算结果。
2D环面(2D Torus)是2D网格的变体,将网格边界环绕连接——最左列PE与最右列PE相连,最上行PE与最下行PE相连。环面拓扑支持数据循环流动,在某些计算模式(如循环卷积)中可减少边界数据加载。但环面的长距离连线(跨芯片环绕)增加延迟和功耗。
PE的流水设计是脉动阵列效率的核心。每个时钟周期内,PE执行三步操作:接收上游数据(从左方PE接收输入、从上方PE接收部分和)、执行乘加运算(输入乘以驻留权重加到部分和)、向下游发送数据(向右方PE发送输入、向下方PE发送部分和)。这三步在流水线中重叠执行,实现每周期一次乘加的吞吐。
数据对齐是脉动阵列正确工作的前提。输入数据需按特定时序错位输入阵列——例如矩阵A的第i行延迟i个周期输入——确保数据在正确的时钟周期到达正确的PE进行乘加运算。数据对齐通过输入端的延迟寄存器链实现,无需额外控制逻辑。
矩阵乘法到阵列的映射
矩阵乘法C=A*B在脉动阵列中的映射方式充分利用了矩阵乘法的数据复用性。在权重驻留策略下,权重矩阵B预先加载到PE阵列中(B的转置,使B[j][i]驻留在PE[i][j]),输入矩阵A的行从左侧流入,部分和从上方流入并累加,结果C从下方流出。
映射过程的数据流动如下:A的第i行从第i行PE的左侧流入,每个周期向右移动一个PE;B的第j列预先驻留在第j列PE中;每个PE[i][j]计算A[i]*B[j]的部分积并累加到部分和中;部分和从上向下流动,经过K个PE累加后得到C[i][j]的完整值从下方流出。
当矩阵尺寸大于阵列尺寸时,需要将矩阵分块为阵列大小的子矩阵,逐块计算后拼接结果。分块策略需考虑数据重用:将同一权重分块与多个输入分块配对计算,减少权重重新加载的开销。分块大小等于阵列尺寸(如256x256)时可最大化阵列利用率。
卷积运算可通过两种方式映射到脉动阵列。第一种是im2col变换,将卷积转为矩阵乘法后用标准映射计算,简单但增加内存开销(im2col展开后的矩阵远大于原始输入)。第二种是行驻留策略直接映射卷积计算(如Eyeriss),避免im2col的内存开销,在卷积计算中能效更高。
矩阵乘法的计算量与矩阵尺寸的关系决定了脉动阵列的计算效率。对于MxK乘以KxN的矩阵乘法,计算量为2*M*N*K次FLOP,需要M*N个输出元素。在SxS脉动阵列上,峰值利用率为阵列填满时的100%,实际利用率受矩阵尺寸与阵列尺寸的匹配度和数据对齐开销影响。
GPU与TPU架构范式对比
GPU和TPU代表了两种不同的AI加速器设计哲学。GPU追求通用性和灵活性,通过大量可编程流处理器处理各种并行计算任务;TPU追求特定计算(矩阵乘法)的极致效率,通过专用脉动阵列牺牲灵活性换取高算力和低功耗。
GPU的架构核心是流多处理器(Streaming Multiprocessor,SM),每个SM包含多个CUDA核心和Tensor Core。GPU通过SIMT(单指令多线程)执行模型并行处理大量线程,适合各种计算密集型任务。Tensor Core借鉴了脉动阵列的数据复用思想,但设计更灵活,支持多种矩阵尺寸和精度。
TPU的架构核心是脉动阵列,专为矩阵乘法优化。TPU v1使用256x256的INT8脉动阵列,峰值算力65 TOPS;TPU v4支持BF16精度和更大的阵列规模。TPU通过权重驻留策略最大化数据复用,在矩阵乘法上能效比GPU高数倍,但灵活性低——不适合分支密集或非规则计算。
在性能对比上,小batch size下GPU更灵活高效(可处理小矩阵、支持多种算子),大batch size下TPU的高算力优势显现(矩阵尺寸增大、利用率提高)。存在性能交叉点需根据负载特征选择。TPU的专用架构在推理场景(计算密集、精度要求低)优势明显,GPU在训练场景(需高精度、灵活性)更适用。
脉动阵列不能完全替代GPU进行AI计算。虽然脉动阵列在矩阵乘法上效率极高,但深度学习中的逐元素运算(如激活函数、LayerNorm)、分支逻辑(如条件计算)和非规则计算(如稀疏矩阵)无法高效映射到脉动阵列。实际AI加速器需要脉动阵列(处理矩阵乘法)和通用计算单元(处理其他算子)的混合架构。
AI 应用场景
AI加速器架构设计与选型
阶段:推理优化
解决的问题:在设计和选型AI加速器时需要理解脉动阵列的计算特性和适用场景,针对不同AI负载(推理/训练、稠密/稀疏、大/小矩阵)选择最优的加速器架构。
具体案例:某公司需要为端侧AI推理选择加速器。分析负载特征:模型为量化INT8的ResNet50,主要计算为3x3卷积(大矩阵乘法),batch size为1(小矩阵)。对比GPU和TPU方案:TPU脉动阵列在大矩阵乘法上能效高但batch size为1时阵列利用率低;GPU灵活性好但能效低。最终选择基于Eyeriss行驻留脉动阵列的NPU,针对小batch卷积优化,在10mW功耗下实现15FPS的推理速度。
Python 演示
import numpy as npfrom typing import List, Dict, Optionalfrom dataclasses import dataclass, field @dataclassclass ProcessingElement: """脉动阵列处理单元""" weight: float = 0.0 partial_sum: float = 0.0 input_buffer: float = 0.0 class SystolicArray: """脉动阵列模拟器(权重驻留策略)""" def __init__(self, size: int = 4): self.size = size self.array = [[ProcessingElement() for _ in range(size)] for _ in range(size)] def load_weights(self, W: np.ndarray): """加载权重矩阵到PE阵列(权重驻留)""" for i in range(self.size): for j in range(self.size): self.array[i][j].weight = W[i][j] def matmul(self, A: np.ndarray, B: np.ndarray) -> np.ndarray: """模拟脉动阵列矩阵乘法 C = A x B A: MxK, B: KxN, 权重B驻留在PE中, A的行从左侧流入 """ M, K = A.shape K2, N = B.shape assert K == K2 assert M <= self.size and N <= self.size self.load_weights(B[:N, :self.size].T) C = np.zeros((M, N)) total_cycles = M + self.size + K for cycle in range(total_cycles): for row in range(min(self.size, M) - 1, -1, -1): for col in range(min(self.size, N) - 1, -1, -1): if col > 0: self.array[row][col].input_buffer = ( self.array[row][col - 1].input_buffer ) if row > 0: self.array[row][col].partial_sum = ( self.array[row - 1][col].partial_sum ) for row in range(min(self.size, M)): a_col = cycle - row if 0 <= a_col < K: self.array[row][0].input_buffer = A[row][a_col] else: self.array[row][0].input_buffer = 0.0 for row in range(min(self.size, M)): for col in range(min(self.size, N)): pe = self.array[row][col] pe.partial_sum += pe.input_buffer * pe.weight for col in range(min(self.size, N)): out_row = cycle - self.size - col + 1 if 0 <= out_row < M: C[out_row][col] = self.array[self.size - 1][col].partial_sum return C class DataflowAnalyzer: """数据流策略分析器""" def __init__(self, array_size: int, freq_ghz: float = 1.0): self.size = array_size self.freq = freq_ghz def peak_flops(self, precision_bytes: int = 1) -> float: """计算峰值算力(TOPS)""" macs_per_cycle = self.size * self.size flops_per_cycle = macs_per_cycle * 2 peak = flops_per_cycle * self.freq * 1e9 return peak / 1e12 def utilization(self, M: int, N: int, K: int) -> float: """估算矩阵乘法的阵列利用率""" if M >= self.size and N >= self.size: align_overhead = self.size / (K + self.size) return 1.0 - align_overhead * 0.5 fill_ratio = (M * N) / (self.size * self.size) return fill_ratio def effective_flops(self, M: int, N: int, K: int) -> float: """有效算力 = 峰值算力 x 利用率""" return self.peak_flops() * self.utilization(M, N, K) def arithmetic_intensity(self, strategy: str = "weight_stationary") -> float: """计算算术强度(FLOPs/Byte)""" if strategy == "weight_stationary": return 2 * self.size elif strategy == "output_stationary": return 2 * self.size / 3 elif strategy == "row_stationary": return 2 * self.size * 1.5 return 1.0 if __name__ == "__main__": A = np.array([[1, 2, 3, 0], [0, 1, 2, 3], [3, 0, 1, 2], [2, 3, 0, 1]]) B = np.array([[1, 0, 0, 1], [0, 1, 0, 1], [0, 0, 1, 1], [1, 1, 1, 1]]) expected = A @ B sa = SystolicArray(size=4) result = sa.matmul(A, B) print("Expected:\n", expected) print("Systolic:\n", result) print("Match:", np.allclose(expected, result)) analyzer = DataflowAnalyzer(array_size=256, freq_ghz=1.5) print(f"Peak TOPS: {analyzer.peak_flops():.1f}") print(f"Utilization(512x512x512): {analyzer.utilization(512, 512, 512):.2%}") print(f"Effective TOPS: {analyzer.effective_flops(512, 512, 512):.1f}") print(f"Arithmetic Intensity (WS): {analyzer.arithmetic_intensity('weight_stationary'):.1f}")
class SystolicArray:脉动阵列模拟器类,模拟权重驻留策略下的矩阵乘法计算过程。def load_weights(self, W: np.ndarray):加载权重矩阵到PE阵列——权重驻留策略的核心步骤,每个PE存储一个权重值,后续计算中重复使用。def matmul(self, A: np.ndarray, B: np.ndarray) -> np.ndarray:模拟脉动阵列矩阵乘法C=AxB。权重B驻留在PE中,A的行数据从左侧流入,部分和从上方流入并累加。total_cycles = M + self.size + K总计算周期数=矩阵行数M+阵列尺寸size+矩阵内积维度K,包含数据流入填充和对齐开销。self.array[row][col].input_buffer = (输入数据从左向右流动——每个周期将左侧PE的输入缓冲传递到当前PE,实现数据的流水传递。self.array[row][col].partial_sum = (部分和从上向下流动——将上方PE的累加结果传递到当前PE继续累加,实现矩阵乘法的累加过程。pe.partial_sum += pe.input_buffer * pe.weightPE核心计算:执行乘加运算(MAC),将流入的输入数据与驻留权重相乘并累加到部分和。class DataflowAnalyzer:数据流策略分析器,计算峰值算力、利用率和算术强度等性能指标。def peak_flops(self, precision_bytes: int = 1) -> float:计算峰值算力(TOPS):每周期MAC数=size^2,每次MAC=2次运算(乘+加),乘以频率得到峰值FLOPS。def utilization(self, M: int, N: int, K: int) -> float:估算阵列利用率:大矩阵利用率高(接近1减去对齐开销),小矩阵利用率低(填充比例低)。def effective_flops(self, M: int, N: int, K: int) -> float:有效算力=峰值算力x利用率,反映阵列在实际矩阵尺寸下的真实计算吞吐。def arithmetic_intensity(self, strategy: str = "weight_stationary") -> float:计算算术强度(FLOPs/Byte):权重驻留策略最大化权重复用,算术强度为2*size,高于输出驻留策略。
常见问答
脉动阵列为什么叫「脉动」?
权重驻留策略的核心优势是什么?
脉动阵列和GPU的Tensor Core有什么区别?
脉动阵列为什么不能完全替代GPU?
行驻留策略为什么在卷积计算中效率最高?
常见误区
误区脉动阵列就是简单的并行计算阵列
正解脉动阵列与简单的并行计算阵列有本质区别。简单并行阵列中所有PE独立计算、共享内存访问;脉动阵列中数据在PE间有节奏地流动,每个PE执行简单运算后传递数据给相邻PE,通过数据流复用最大化数据利用率。脉动阵列的核心是数据流动驱动计算,而非简单的并行执行。
为何常见:两者都使用多个PE并行计算,表面上看似相同。但脉动阵列的核心特征是数据流动和局部通信,简单并行阵列是独立计算和共享内存访问,这种深层差异容易被忽略。
误区权重驻留是唯一的数据驻留策略
正解权重驻留只是三种主流策略之一。输出驻留策略将输出结果固定在PE中累加,避免部分和传递;行驻留策略(Eyeriss)同时复用权重、输入和部分和三类数据,在卷积计算中能效最高。不同策略在不同计算模式(矩阵乘法vs卷积)和不同优化目标(算力vs能效)下各有优势,需根据场景选择。
为何常见:TPU采用权重驻留策略最为知名,且权重驻留在矩阵乘法中效果显著,容易让人误以为是唯一策略。实际上学术界和工业界有多种驻留策略,各有适用场景。
误区脉动阵列可以完全替代GPU进行AI计算
正解脉动阵列无法完全替代GPU。虽然脉动阵列在矩阵乘法上效率极高,但深度学习中的逐元素运算(激活函数、LayerNorm、Softmax)、分支逻辑和非规则计算(稀疏矩阵)无法高效映射到脉动阵列的固定数据流模式。实际AI加速器需要脉动阵列(处理矩阵乘法)和通用计算单元(处理其他算子)的混合架构,两者互补而非替代。
为何常见:TPU在矩阵乘法基准测试中性能远超GPU,容易让人误以为脉动阵列可以全面替代GPU。忽略了AI计算中除矩阵乘法外还有大量其他算子,这些算子需要GPU的通用性。
参考来源
- paperIn-Datacenter Performance Analysis of a Tensor Processing UnitGoogle TPU论文,详细介绍了TPU v1的256x256脉动阵列架构、权重驻留数据流策略和性能评测结果。
- paperEyeriss: A Spatial Architecture for Energy-Efficient Dataflow for Convolutional Neural NetworksMIT Eyeriss论文,提出行驻留(Row Stationary)数据流策略,最大化卷积运算中三类数据的同时复用。
- docSystolic Arrays - Architecture and Applications脉动阵列架构与应用技术文档,涵盖PE互连拓扑、数据流模式和矩阵乘法映射方法。
- webpageTPU System Architecture - Google Cloud DocumentationGoogle Cloud TPU系统架构文档,介绍TPU v4的脉动阵列设计、BF16精度支持和多芯片扩展架构。