深入讲解脉动阵列Systolic Array架构原理,涵盖数据流动模式、PE阵列设计、矩阵乘法映射、TPU中的脉动阵列实现与性能特性。学习如何理解脉动阵列计算范式、分析数据复用效率、对比GPU与TPU架构差异。通过系统化方法与工程实践,可建立标准化流程,提升开发效率与系统可靠性。适用于AI芯片架构理解、TPU编程优化、硬件加速器设计等场景。

岗位关联

需要学习脉动阵列Systolic Array,机器人需要端侧AI推理,硬件选型和算力优化是关键。本篇涵盖GPU/TPU/NPU、CUDA、集群调度、算力优化,帮助你理解硬件加速原理。

目录
  1. 可视化演示
  2. 脉动阵列计算范式原理
  3. 数据流模式与驻留策略
  4. PE阵列互连与流水设计
  5. 矩阵乘法到阵列的映射
  6. GPU与TPU架构范式对比
  7. AI 应用场景
  8. Python 演示
  9. 常见问答
  10. 常见误区
  11. 参考来源

可视化演示

展示脉动阵列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%,实际利用率受矩阵尺寸与阵列尺寸的匹配度和数据对齐开销影响。

矩阵乘法计算量
FLOPs=2×M×N×KFLOPs = 2 \times M \times N \times K
矩阵A(MxK)乘以B(KxN)的计算量为2*M*N*K次浮点运算(每个输出元素需要K次乘加,每次乘加=2次运算)。M、N为输出矩阵行列数,K为内积维度。
阵列利用率
U=min⁡(M,S)×min⁡(N,S)S2U = \frac{\min(M, S) \times \min(N, S)}{S^2}
阵列利用率为实际参与计算的PE数除以总PE数。S为阵列边长,M和N为矩阵尺寸。当M和N均大于等于S时利用率为100%,否则部分PE空闲。
权重驻留算术强度
AIWS=2×KKS+1≈2SAI_{WS} = \frac{2 \times K}{\frac{K}{S} + 1} \approx 2S
权重驻留策略的算术强度约等于2S(S为阵列边长),K远大于S时近似成立。每加载一个权重(K/S次复用)和输入数据可获得约2S次运算,算术强度高意味着访存开销低。

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 演示

python126 行
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}")
目的:模拟脉动阵列的矩阵乘法计算过程(权重驻留策略),包括PE数据流动、乘加计算和结果输出;并实现数据流分析器计算峰值算力、利用率和算术强度等性能指标。
  1. class SystolicArray:脉动阵列模拟器类,模拟权重驻留策略下的矩阵乘法计算过程。
  2. def load_weights(self, W: np.ndarray):加载权重矩阵到PE阵列——权重驻留策略的核心步骤,每个PE存储一个权重值,后续计算中重复使用。
  3. def matmul(self, A: np.ndarray, B: np.ndarray) -> np.ndarray:模拟脉动阵列矩阵乘法C=AxB。权重B驻留在PE中,A的行数据从左侧流入,部分和从上方流入并累加。
  4. total_cycles = M + self.size + K总计算周期数=矩阵行数M+阵列尺寸size+矩阵内积维度K,包含数据流入填充和对齐开销。
  5. self.array[row][col].input_buffer = (输入数据从左向右流动——每个周期将左侧PE的输入缓冲传递到当前PE,实现数据的流水传递。
  6. self.array[row][col].partial_sum = (部分和从上向下流动——将上方PE的累加结果传递到当前PE继续累加,实现矩阵乘法的累加过程。
  7. pe.partial_sum += pe.input_buffer * pe.weightPE核心计算:执行乘加运算(MAC),将流入的输入数据与驻留权重相乘并累加到部分和。
  8. class DataflowAnalyzer:数据流策略分析器,计算峰值算力、利用率和算术强度等性能指标。
  9. def peak_flops(self, precision_bytes: int = 1) -> float:计算峰值算力(TOPS):每周期MAC数=size^2,每次MAC=2次运算(乘+加),乘以频率得到峰值FLOPS。
  10. def utilization(self, M: int, N: int, K: int) -> float:估算阵列利用率:大矩阵利用率高(接近1减去对齐开销),小矩阵利用率低(填充比例低)。
  11. def effective_flops(self, M: int, N: int, K: int) -> float:有效算力=峰值算力x利用率,反映阵列在实际矩阵尺寸下的真实计算吞吐。
  12. def arithmetic_intensity(self, strategy: str = "weight_stationary") -> float:计算算术强度(FLOPs/Byte):权重驻留策略最大化权重复用,算术强度为2*size,高于输出驻留策略。

常见问答

脉动阵列为什么叫「脉动」?
脉动阵列的数据在PE阵列中有节奏地流动,每个时钟周期数据按固定方向移动一个PE位置,同时PE执行乘加运算。这种有节奏的数据流动和计算类似心脏搏动泵送血液的节律,因此得名「脉动」(Systolic)。
权重驻留策略的核心优势是什么?
权重驻留策略将权重数据固定在PE寄存器中,只需从DRAM加载一次即可在整个矩阵乘法过程中多次使用。这最大化了权重复用,减少了权重数据的访存开销(访存是主要功耗来源),使TPU在矩阵乘法上实现高能效。
脉动阵列和GPU的Tensor Core有什么区别?
脉动阵列规模大(如256x256)但灵活性低,专为固定尺寸的矩阵乘法设计;Tensor Core规模小(如16x16x16)但更灵活,支持多种矩阵尺寸和精度。脉动阵列通过专用化换取极致效率,Tensor Core在通用性和效率间取得平衡。
脉动阵列为什么不能完全替代GPU?
脉动阵列的固定数据流模式只适合规则计算(如矩阵乘法),但深度学习中还有大量逐元素运算(激活函数、LayerNorm)、分支逻辑和非规则计算(稀疏矩阵)无法高效映射到脉动阵列。实际AI加速器需要脉动阵列和通用计算单元的混合架构。
行驻留策略为什么在卷积计算中效率最高?
行驻留策略由Eyeriss提出,同时复用三类数据:权重复用(同一滤波器行在多个输入行上复用)、输入复用(同一输入行被多个滤波器行复用)和部分和复用(沿输出行方向累加)。三类数据复用最小化总访存量,使行驻留在卷积计算中算术强度最高、能效最优。

常见误区

误区脉动阵列就是简单的并行计算阵列

正解脉动阵列与简单的并行计算阵列有本质区别。简单并行阵列中所有PE独立计算、共享内存访问;脉动阵列中数据在PE间有节奏地流动,每个PE执行简单运算后传递数据给相邻PE,通过数据流复用最大化数据利用率。脉动阵列的核心是数据流动驱动计算,而非简单的并行执行。

为何常见:两者都使用多个PE并行计算,表面上看似相同。但脉动阵列的核心特征是数据流动和局部通信,简单并行阵列是独立计算和共享内存访问,这种深层差异容易被忽略。

误区权重驻留是唯一的数据驻留策略

正解权重驻留只是三种主流策略之一。输出驻留策略将输出结果固定在PE中累加,避免部分和传递;行驻留策略(Eyeriss)同时复用权重、输入和部分和三类数据,在卷积计算中能效最高。不同策略在不同计算模式(矩阵乘法vs卷积)和不同优化目标(算力vs能效)下各有优势,需根据场景选择。

为何常见:TPU采用权重驻留策略最为知名,且权重驻留在矩阵乘法中效果显著,容易让人误以为是唯一策略。实际上学术界和工业界有多种驻留策略,各有适用场景。

误区脉动阵列可以完全替代GPU进行AI计算

正解脉动阵列无法完全替代GPU。虽然脉动阵列在矩阵乘法上效率极高,但深度学习中的逐元素运算(激活函数、LayerNorm、Softmax)、分支逻辑和非规则计算(稀疏矩阵)无法高效映射到脉动阵列的固定数据流模式。实际AI加速器需要脉动阵列(处理矩阵乘法)和通用计算单元(处理其他算子)的混合架构,两者互补而非替代。

为何常见:TPU在矩阵乘法基准测试中性能远超GPU,容易让人误以为脉动阵列可以全面替代GPU。忽略了AI计算中除矩阵乘法外还有大量其他算子,这些算子需要GPU的通用性。

参考来源