第三章 脉动阵列(Systolic Array)详解
各位同学,今天我们来聊聊脉动阵列。说实话,这是深度学习加速器里最经典、也最容易被误解的结构之一。我当年刚接触这个名词时,以为是什么高深莫测的数学变换,后来才发现——说白了,就是一种让数据像心跳一样有节奏地在计算单元间流动的架构。
3.1 经典脉动阵列原理
脉动阵列的概念最早由卡内基梅隆大学的孔祥重(H.T. Kung)教授在1982年提出。你想想看,四十多年前的想法,到现在还是AI芯片的基石,这本身就说明了很多问题。
它的核心思想很简单:让数据在多个处理单元(PE)之间规律地流动,每个PE只做最简单的乘加运算。这样做的好处是什么?我举个例子你就明白了。
假设我们要做两个3×3矩阵的乘法:
C[i][j] = Σ A[i][k] × B[k][j]
传统做法是:取A的一行,取B的一列,做点积。但脉动阵列不一样,它把A矩阵的每一行从左到右推,把B矩阵的每一列从上到下推,数据在PE阵列中像脉搏一样跳动。
关键洞察:脉动阵列的本质是用空间换时间。通过增加并行度,把O(n³)的时间复杂度降到O(n),代价是O(n²)的硬件资源。
我在项目中遇到过这样一个坑:刚开始设计时,我天真地以为PE越多越好。结果发现,当阵列规模超过某个阈值后,布线延迟和时钟歪斜会严重拖累性能。嗯,这里要注意——不是所有问题都能靠堆PE解决。
3.2 权重固定与数据固定流
脉动阵列有两种主流的数据流模式,我分别说说。
3.2.1 权重固定(Weight Stationary)
这种模式下,权重数据预先加载到每个PE的寄存器中,然后保持不变。输入特征图的数据在阵列中流动,每个PE计算部分和,最后累加得到结果。
我个人习惯在卷积层使用权重固定模式。为什么?因为卷积核的权重是固定的,加载一次就可以反复使用,省去了频繁搬运权重的功耗。
// 权重固定模式的伪代码
for (int i = 0; i < ROWS; i++) {
for (int j = 0; j < COLS; j++) {
PE[i][j].weight = W[i][j]; // 预加载权重
}
}
// 数据流动
for (int t = 0; t < TIME_STEPS; t++) {
for (int i = 0; i < ROWS; i++) {
for (int j = 0; j < COLS; j++) {
PE[i][j].compute(input[i][t], PE[i][j].weight);
// 部分和向右传递
if (j < COLS-1) PE[i][j+1].psum += PE[i][j].psum;
}
}
}
3.2.2 数据固定(Output Stationary)
这种模式反过来——输入数据固定不动,权重在阵列中流动。每个PE负责计算输出矩阵中的一个元素,累加来自不同权重的部分结果。
我记得有一次做全连接层加速时,发现数据固定模式更合适。因为全连接层的输入特征图通常较大,固定输入可以减少数据搬运次数。
| 特性 | 权重固定 | 数据固定 |
|---|---|---|
| 适用场景 | 卷积层(权重复用度高) | 全连接层(输入复用度高) |
| 数据搬运量 | 输入数据搬运多 | 权重数据搬运多 |
| PE利用率 | 高(权重常驻) | 中等(权重流动) |
| 控制复杂度 | 低 | 中等 |
我的建议:实际项目中不要死板地只用一种模式。我做过一个混合方案——卷积层用权重固定,全连接层用数据固定,中间加一个数据重排模块。效果比单一模式好15%左右。
3.3 性能建模
做加速器设计,光会画架构图不行,你得会算性能。我见过太多人拍脑袋定参数,结果流片回来发现性能差一大截。
脉动阵列的性能模型主要考虑三个因素:
- 计算延迟:PE完成一次乘加需要几个时钟周期
- 数据加载延迟:从片外DRAM搬运数据到PE阵列的时间
- 流水线填充/排空时间:阵列启动和结束时的额外开销
假设我们有一个N×N的脉动阵列,每个PE的乘加延迟为T_pe,数据加载延迟为T_load,那么完成一次M×K×N的矩阵乘法所需的总时钟周期数大约为:
T_total = T_load + (M + N + K - 2) × T_pe + T_drain
其中T_drain是排空时间,大约等于N个时钟周期。
我曾经犯过一个错误:忽略了数据加载延迟。当时设计了一个32×32的阵列,算下来理论吞吐量很高,结果实际测试时发现,大部分时间都在等数据从DDR搬过来。说白了,计算单元再快,喂不饱也是白搭。
避坑指南:我曾经在项目里把阵列规模从16×16直接翻到32×32,以为性能能翻4倍。结果因为数据带宽没跟上,实际只提升了1.8倍。所以做性能建模时,一定要把存储带宽作为约束条件加进去。
下面我用一张图来展示脉动阵列的核心逻辑:
这张图展示了一个3×3的脉动阵列。红色箭头表示输入数据从左向右流动,绿色箭头表示权重或部分和从上向下流动。每个PE接收来自左边和上边的数据,做一次乘加后,把结果继续向右和向下传递。
你想想看,这种结构天然适合矩阵乘法——数据流动有规律,控制逻辑简单,而且每个PE的硬件设计可以高度统一。这也是为什么几乎所有AI芯片(Google TPU、NVIDIA Tensor Core等)都采用了脉动阵列的变体。
性能建模小技巧:我一般会用Roofline模型来评估脉动阵列的瓶颈。把计算峰值画成一条水平线,把存储带宽画成一条斜线,你的实际性能点落在哪里,瓶颈就在哪里。这个方法帮我快速定位过好几个项目的性能问题。
好了,关于脉动阵列的核心内容就讲到这里。记住三点:数据流动要有节奏、权重固定和数据固定各有适用场景、性能建模一定要考虑存储带宽。这些经验都是我用实际项目换来的,希望能帮你少走弯路。