第二讲:计算核心拆解——从SIMT到脉动阵列,矩阵乘法加速器的硬件实现

各位同学,今天我们聊点硬核的。

上一讲我们梳理了AI芯片的宏观架构。这一讲,咱们把镜头拉近,看看芯片里最核心的“计算单元”到底长什么样。

说白了,大模型推理的绝大部分计算量,都集中在矩阵乘法上。你想想看,Transformer里的Self-Attention、FFN,哪个不是矩阵乘?所以,搞懂矩阵乘法加速器的硬件实现,就等于拿到了AI芯片设计的“入场券”。

一、从SIMT到SIMD:一个“不那么优雅”的转变

很多做GPU编程的同学,对SIMT(单指令多线程)很熟悉。GPU里成百上千个线程,看似并行执行,实际上在硬件层面,它们被分组打包成warp,以SIMD(单指令多数据)的方式运行。

我个人习惯把SIMT看作是“程序员视角的并行”,而SIMD是“硬件工程师视角的并行”。

为什么AI芯片不直接照搬GPU的SIMT架构?

原因很简单:效率

GPU的SIMT为了通用性,付出了巨大的代价。每个线程都需要独立的寄存器文件、独立的程序计数器,还要处理复杂的线程调度和分支发散。这些开销,在AI推理这种“计算密集、控制简单”的场景下,显得有点浪费。

我在项目中遇到过这样一个案例:某团队试图用通用GPU核去跑BERT推理,结果发现大部分晶体管都耗在了线程调度和缓存一致性上,真正做乘加运算的ALU利用率不到30%。

所以,AI芯片选择了更“纯粹”的SIMD路线,甚至更进一步,走向了脉动阵列。

二、脉动阵列:让数据“流动”起来

脉动阵列(Systolic Array)这个名字,听起来很玄乎。其实你把它想象成一条“数据流水线”就行了。

数据像血液一样,在计算单元之间“脉动”式地流动。每个计算单元(PE,Processing Element)只做一件事:接收输入,做一次乘加,然后把结果传给下一个PE。

这样做的好处是什么?

  • 数据复用最大化:权重数据可以“驻留”在PE内部,不需要反复从内存搬运。
  • 控制逻辑极简:每个PE只需要一个状态机,甚至不需要。
  • 高吞吐量:一旦流水线填满,每个时钟周期都能输出一个结果。

核心要点:脉动阵列的本质,是用“数据流”代替“控制流”,用“空间并行”代替“时间并行”。

嗯,这里要注意:脉动阵列并不是万能的。它对数据流的规整性要求很高。如果矩阵维度不匹配,或者有稀疏性,效率就会大打折扣。我曾经在一个稀疏矩阵加速项目里尝试用脉动阵列,结果发现大部分PE都在空转,最后不得不改用“稀疏感知”的架构。

三、矩阵乘法加速器的硬件实现:一个经典案例

我们来看一个具体的例子。假设我们要实现一个 C = A × B 的矩阵乘法,其中A是M×K,B是K×N,C是M×N。

在脉动阵列里,我们通常把权重矩阵B“固定”在PE阵列中,让输入矩阵A的数据“流过”阵列,同时累加结果。

下面是一个简化的Verilog代码片段,展示了一个PE的核心逻辑:

module pe #(
    parameter DATA_WIDTH = 8
)(
    input clk,
    input rst_n,
    input [DATA_WIDTH-1:0] data_in,   // 来自上游PE的输入数据
    input [DATA_WIDTH-1:0] weight_in, // 权重输入(通常从外部加载)
    input [DATA_WIDTH-1:0] partial_sum_in, // 部分和输入
    output reg [DATA_WIDTH-1:0] data_out,
    output reg [DATA_WIDTH-1:0] partial_sum_out
);

    reg [DATA_WIDTH-1:0] weight_reg;

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            weight_reg <= 0;
            data_out <= 0;
            partial_sum_out <= 0;
        end else begin
            // 权重只在加载阶段更新
            if (weight_load_en) begin
                weight_reg <= weight_in;
            end
            // 数据直接透传
            data_out <= data_in;
            // 乘加运算
            partial_sum_out <= partial_sum_in + data_in * weight_reg;
        end
    end

endmodule

你看,这个PE的逻辑非常简单。它只做三件事:存权重、传数据、做乘加。没有分支预测,没有乱序执行,甚至连个像样的控制器都没有。

这就是AI芯片的“暴力美学”——用最简单、最规整的硬件,做最高效的计算。

四、脉动阵列的变体与权衡

在实际工程中,脉动阵列有很多变体。我简单列几个常见的:

类型 数据流方向 特点 典型应用
权重固定(Weight Stationary) 权重不动,输入和部分和流动 权重复用率高,适合权重较大的场景 Google TPU v1
输入固定(Input Stationary) 输入不动,权重和部分和流动 输入复用率高,适合输入特征图较大的场景 某些CNN加速器
输出固定(Output Stationary) 部分和不动,输入和权重流动 减少部分和的搬运,适合累加次数多的场景 某些脉动阵列变体

你可能会问:哪种最好?

说实话,没有绝对的“最好”。我个人的经验是:取决于你的模型特点

比如,大模型推理时,权重通常是固定的(模型已经训练好了),而且权重尺寸很大(动辄几十GB)。这时候,权重固定(Weight Stationary)的脉动阵列就很有优势。因为权重可以一次性加载到PE阵列里,然后反复使用,大大减少了对内存带宽的需求。

反过来,如果你做的是实时视频处理,输入特征图不断变化,那输入固定(Input Stationary)可能更合适。

避坑指南:我曾经在一个项目里,盲目选择了“权重固定”的脉动阵列,结果发现模型是动态的,权重每几个推理步骤就要更新一次。每次更新权重,都要花几百个时钟周期去“冲刷”PE阵列,导致实际吞吐量只有理论峰值的一半。所以,一定要先分析你的数据流模式,再选架构

五、从脉动阵列到AI芯片:一个完整的计算核心

单个脉动阵列只能做矩阵乘法。但在AI芯片里,我们还需要很多“周边”电路。

一个完整的计算核心(Compute Core),通常包含:

  • 脉动阵列:核心计算单元,负责矩阵乘法。
  • 激活函数单元:ReLU、GELU等非线性函数。
  • 归一化单元:LayerNorm、BatchNorm等。
  • 池化单元:MaxPool、AveragePool等(CNN场景)。
  • 本地存储(SRAM):存放权重和中间结果,减少对HBM的访问。
  • 数据分发单元:负责把数据从SRAM搬运到PE阵列。

下面这张图,展示了一个典型的AI计算核心的结构:

AI计算核心结构图 计算核心 (Compute Core) 数据分发单元 (Data Dispatch) 脉动阵列 (Systolic Array) PE x PE 网格 激活函数 (ReLU/GELU) 归一化 (LayerNorm) 本地存储 (SRAM) 权重 / 中间结果 权重加载 输出

从这张图你可以看到,数据从SRAM出发,经过数据分发单元,进入脉动阵列做矩阵乘法,然后依次经过激活函数、归一化,最后结果写回SRAM。整个过程就像一个精心设计的“流水线工厂”。

每个环节的延迟和吞吐量都必须匹配。否则,就会出现“木桶效应”——最快的单元在等最慢的单元。

重要提醒:很多初学者只关注脉动阵列本身,忽略了数据搬运的开销。实际上,在AI芯片里,数据搬运的能耗和延迟,往往比计算本身还要大。所以,设计计算核心时,一定要把SRAM的带宽、数据分发单元的调度策略考虑进去。否则,你的脉动阵列再快,也只能“饿着肚子干活”。

六、总结:从SIMT到脉动阵列,我们学到了什么?

回顾这一讲,我们走了一条“从通用到专用”的路线:

  • SIMT:灵活,但开销大,适合通用计算。
  • SIMD:更高效,但需要规整的数据。
  • 脉动阵列:极致高效,但牺牲了灵活性。

说白了,AI芯片的设计哲学就是:用硬件的“确定性”去换取计算的“高效率”

你想想看,大模型推理的场景非常固定:矩阵乘法、激活函数、归一化……这些操作的数据流模式几乎不变。既然如此,我们为什么还要保留那些“通用但低效”的硬件呢?直接为这些操作定制专用电路,不是更香吗?

嗯,这就是AI芯片设计的核心思路。下一讲,我们会深入探讨“存储墙”问题——为什么内存带宽比计算能力更重要,以及如何用HBM、近存计算等技术来打破这个瓶颈。


公众号:蓝海资料掘金营,微信deep3321