3. 算子分类与计算特征

好,咱们今天聊聊算子的分类和计算特征。说实话,这部分内容看起来基础,但却是整个加速器设计的基石。我见过不少团队,一上来就扎进微架构设计,结果发现连算子的访存模式都没搞清楚——嗯,后面改起来那叫一个痛苦。

3.1 常见AI算子速览

先过一遍最常见的几个算子。你想想看,无论什么模型,翻来覆去就是这些玩意儿。

Conv(卷积)

卷积是CNN的绝对主力。它的计算模式是:滑动窗口 + 乘累加。说白了,就是一个窗口在输入特征图上滑来滑去,每个位置做一堆乘法和加法。

核心特征:

  • 计算密度高:一次卷积,数据复用率极高
  • 访存模式规律:基本上是顺序 + 跨步访问
  • 并行度大:输出通道、输入通道、空间位置都能并行

我在项目中遇到过一个问题:同样的卷积,在GPU上跑得飞快,换到FPGA上就卡成狗。后来一查,原来是数据排布方式不对。GPU喜欢NCHW,但FPGA对NHWC更友好。这个坑,我踩过。

GEMM(通用矩阵乘法)

GEMM是Transformer和全连接层的核心。它的本质就是 C = A × B。但你别小看这玩意儿,优化好了能上天,优化不好能让你怀疑人生。

我个人习惯把GEMM分成三种情况:

  • 大矩阵 × 大矩阵:计算密集型,适合用分块策略
  • 小矩阵 × 小矩阵:访存开销占比大,得考虑打包
  • 大矩阵 × 小矩阵:比如Batch GEMM,得注意数据复用

避坑指南:我曾经在优化GEMM时,花了大量时间优化计算部分,结果发现瓶颈在数据搬运上。后来才意识到,对于某些尺寸的矩阵,访存才是真正的老大。

Softmax

Softmax看起来简单,就一个指数 + 求和 + 归一化。但它在加速器上其实挺烦人的。

为什么?因为它的计算流程是:

  1. 先算指数(exp)—— 计算量大
  2. 再求和(reduce)—— 需要全局通信
  3. 最后做除法 —— 访存密集

你看,一个算子里面,既有计算密集的部分(exp),又有访存密集的部分(reduce)。这就很尴尬了——你很难用一个统一的加速策略去优化它。

LayerNorm

LayerNorm在Transformer里随处可见。它的计算模式是:

  • 先算均值和方差(两次全局reduce)
  • 再做归一化(逐元素操作)

嗯,这里要注意:LayerNorm的瓶颈往往不在计算,而在那个两次全局reduce。我见过有人把LayerNorm的加速器做得特别复杂,结果性能还不如用CPU跑——因为reduce操作在硬件上很难并行。

3.2 计算密集型 vs 访存密集型

这个分类,说白了就是看一个算子的瓶颈在哪。

类型 特征 典型算子 优化方向
计算密集型 计算时间 >> 访存时间 大卷积、大GEMM 提高计算单元利用率、流水线
访存密集型 访存时间 >> 计算时间 Softmax、LayerNorm、小卷积 减少访存量、提高带宽利用率
混合型 两者相当 中等尺寸的算子 需要平衡设计

重要提醒:同一个算子在不同尺寸下可能属于不同类型。比如3×3卷积在输入通道少的时候是访存密集型,通道多了就变成计算密集型。别死记硬背,要具体分析。

我个人的经验是:先判断算子的计算密度(FLOPs/Byte)。如果这个值远大于硬件平台的平衡点,那就是计算密集型;反之就是访存密集型。这个判断方法,我在好几个项目里都用过,挺准的。

3.3 算子融合的基本概念

算子融合,说白了就是把多个小算子合并成一个大算子。为什么要这么做?

你想想看,每个算子执行完,结果都要写回DDR。如果能把几个算子串起来,中间结果直接留在片上,那能省多少访存啊!

常见的融合模式有:

  • Conv + ReLU:ReLU是逐元素操作,完全可以嵌在卷积后面
  • Conv + BN + ReLU:BN的乘加操作可以和卷积的权重融合
  • GEMM + Softmax + GEMM:Attention里的经典融合模式
  • LayerNorm + 逐元素操作:LayerNorm的归一化可以和后面的缩放融合

融合的核心收益:

  • 减少中间结果的DDR读写
  • 减少kernel launch开销
  • 提高数据局部性

我曾经在一个项目里,把三个小算子融合成一个,性能直接翻了一倍。但要注意,融合不是万能的。如果融合后的算子太大,导致寄存器压力暴增,反而会适得其反。

3.4 知识体系总览

下面这张图,是我自己梳理的算子分类与计算特征的知识体系。你看一眼,心里就有数了。

算子分类与计算特征知识体系 常见AI算子 Conv(卷积) GEMM(矩阵乘) Softmax LayerNorm 分类维度 计算密集型 计算时间 >> 访存时间 访存密集型 访存时间 >> 计算时间 优化策略:算子融合(减少访存、提高局部性) 算子层 分类层 优化层

这张图把整个知识体系串起来了。从算子出发,先看它属于哪一类,再决定优化策略。说白了,就是先诊断,再开药

我的建议:刚开始做算子加速时,别急着写代码。先拿张纸,把算子的计算密度算一算,访存模式画一画。这一步花不了多少时间,但能帮你少走很多弯路。

好了,这一章的内容就到这。算子分类和计算特征这块,理解透了,后面讲微架构设计、数据流优化的时候,你就能跟上节奏了。


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