3、计算瓶颈分析:FLOPs利用率、算子耗时分布、矩阵乘法与卷积的优化空间

好,咱们进入第三个核心话题——计算瓶颈分析。

说实话,很多同学跑模型的时候,一看GPU利用率100%,就觉得“嗯,跑满了”。但你真的跑满了吗?我见过太多项目,GPU利用率看着高,实际算力只用了不到30%。那剩下的70%去哪了?这就是我们今天要聊的——FLOPs利用率。

3.1 FLOPs利用率:别被数字骗了

FLOPs利用率,说白了就是你的硬件到底发挥了多大本事。

公式很简单:

FLOPs利用率 = 实际达到的FLOPs / 硬件理论峰值FLOPs

举个例子。一块A100,FP16的理论峰值是312 TFLOPS。你跑一个BERT-Large,实测只有80 TFLOPS。那利用率就是80/312 ≈ 25.6%。

嗯,这个数字其实挺常见的。我刚开始做推理优化时,看到25%的利用率还觉得正常。后来才发现,这中间有巨大的优化空间。

关键认知:FLOPs利用率低,不代表模型小,而是说明计算单元在“等数据”。

为什么会这样?原因主要有三个:

  • 内存带宽瓶颈——数据从HBM搬到SRAM太慢,计算单元闲着等数据
  • 算子启动开销——小算子太多,每次启动kernel都有固定开销
  • 计算密度不足——矩阵太小,无法填满GPU的SM(流式多处理器)

我个人习惯,拿到一个模型先看FLOPs利用率。低于30%的,基本都有优化空间。低于10%的,那简直就是“开着超跑在胡同里遛弯”。

3.2 算子耗时分布:找到那个“罪魁祸首”

光看整体利用率还不够。你得知道时间花在哪了。

我建议的做法是——做一次profiling。用NVIDIA的Nsight Systems或者PyTorch Profiler都行。

下面是我常用的分析流程:

  1. 跑一次完整推理,记录每个算子的耗时
  2. 按耗时从大到小排序
  3. 看Top 5算子占总耗时的比例
  4. 分析这些算子的计算密度(FLOPs / 访存量)

给你看个真实案例。我之前优化一个GPT-like模型,profiling结果是这样的:

算子 耗时占比 计算密度 优化建议
MatMul (QKV投影) 32% 低 使用cuBLASLt或Triton手写
Softmax 18% 极低 融合到Attention中
LayerNorm 12% 极低 融合到前一个MatMul
Reshape + Transpose 8% 极低 尽量消除,或使用layout优化
ElementWise Add 5% 极低 融合到前一个算子

看到没?MatMul占了32%,但剩下的68%里,大部分是“计算密度极低”的算子。这些算子才是真正的性能杀手。

我的经验:很多时候,优化一个MatMul只能提升5%,但把三个小算子融合成一个,能省下15%的时间。别只盯着大算子。

3.3 矩阵乘法的优化空间

矩阵乘法是深度学习的心脏。几乎所有模型的计算量都集中在MatMul上。

那MatMul的优化空间在哪?我总结了几点:

3.3.1 分块(Tiling)策略

GPU的共享内存只有几十KB。一次算完大矩阵是不可能的。必须分块。

关键参数是块大小(block size)。太小了,SM利用率低;太大了,共享内存不够用。

// 常见的分块大小
// A100: 128x128 或 128x64
// V100: 64x64 或 128x32
// T4:   64x64 或 32x32

我记得有一次,一个客户在A100上跑一个M=512, N=4096, K=1024的MatMul。默认的cuBLAS实现只用了40%的利用率。我手动调整了分块策略,把利用率提到了65%。

3.3.2 数据布局(Layout)

矩阵在内存中的排列方式,直接影响访存效率。

  • 行主序(Row-major):C/C++默认,连续行
  • 列主序(Col-major):Fortran默认,连续列
  • 平铺(Tiled):按块排列,适合GPU

我建议,对于推理场景,尽量使用平铺布局。虽然转换有开销,但一次转换,多次使用,整体收益很大。

3.3.3 使用Tensor Core

从V100开始,NVIDIA引入了Tensor Core。这东西专门做矩阵乘法,速度是普通CUDA Core的几倍。

但要注意:Tensor Core对数据精度和形状有要求。

// Tensor Core要求:
// 1. 数据类型:FP16, BF16, INT8, INT4
// 2. 维度:M, N, K 必须是16的倍数(最好64的倍数)
// 3. 布局:必须使用特定的数据布局(如row-major for A, col-major for B)

避坑指南:我曾经遇到一个模型,所有MatMul的K维度都是17。结果Tensor Core根本用不上,性能直接腰斩。后来我把K padding到32,利用率从28%提到了52%。

3.4 卷积的优化空间

虽然Transformer火了之后,卷积用得少了,但在视觉模型里,卷积依然是主力。

卷积的优化,核心思路和矩阵乘法类似,但更复杂一些。

3.4.1 Im2Col + GEMM

这是最经典的方法。把卷积转换成矩阵乘法,然后调用优化好的GEMM库。

优点:简单,复用MatMul优化。缺点:内存开销大,Im2Col会复制数据。

3.4.2 Winograd算法

对于小卷积核(3x3, 5x5),Winograd能减少乘法次数。

我记得在优化ResNet-50时,用Winograd把3x3卷积的FLOPs减少了约30%。但要注意,Winograd对精度有影响,FP16下偶尔会出问题。

3.4.3 直接卷积(Direct Convolution)

对于大卷积核(7x7以上),或者特殊步长(stride=2),直接卷积反而更快。

为什么?因为Im2Col的内存开销太大了,直接卷积可以避免这个开销。

下面是我总结的卷积优化选择策略:

卷积类型 推荐方法 适用场景
3x3, stride=1 Winograd ResNet, MobileNet
1x1 Im2Col + GEMM Inception, ShuffleNet
7x7, stride=2 Direct Convolution ResNet初始层
Depthwise 手写优化 MobileNet, EfficientNet

3.5 知识体系总览

说了这么多,咱们用一张图把整个计算瓶颈分析的知识体系串起来。

计算瓶颈分析知识体系 计算瓶颈分析 FLOPs利用率 实际FLOPs / 理论峰值 内存带宽瓶颈 算子启动开销 计算密度不足 算子耗时分布 Profiling工具分析 Top 5算子定位 计算密度分析 算子融合策略 优化空间 矩阵乘法优化 卷积优化 Tensor Core利用 算子融合 目标:提升FLOPs利用率 → 降低推理延迟

这张图把整个计算瓶颈分析的核心逻辑串起来了。从左到右,从发现问题(FLOPs利用率低),到定位问题(算子耗时分布),再到解决问题(矩阵乘法与卷积优化)。

我的建议:每次拿到一个新模型,先按这个流程走一遍。花半天时间做profiling,比盲目优化一周更有效。

好了,计算瓶颈分析就聊到这。记住一句话:别只看GPU利用率,要看FLOPs利用率;别只看大算子,要关注那些“小但多”的算子。

想要更多资料,联系可可