第 2 章 GPU 性能模型:算力、带宽与通信¶
开场问题:Profiler 显示 GPU 利用率 100%,为什么实际只达到理论算力的 20%?
2.1 "利用率"这个指标在骗你¶
nvidia-smi 里的 GPU utilization 只表示"这段时间里有 kernel 在跑",不表示跑得好。一个只用了 2% 算力的逐元素算子跑满一秒,利用率也是 100%。开场问题就是这么来的。
真正该看的是三个量:实际达到的 FLOPS(对比硬件峰值)、实际达到的 HBM 吞吐(对比峰值带宽)、以及各类 kernel 的时间占比。前两个里哪个接近峰值,就说明瓶颈在哪一侧;两个都不接近,说明问题在别处(通信或 CPU)。
2.2 算术强度与 Roofline¶
算术强度 = 这个操作的 FLOPs ÷ 它需要搬运的字节数。把它和硬件的"峰值算力÷峰值带宽"比一下:低于这个阈值,操作受带宽限制;高于它,才可能受算力限制。
这个简单的除法能省掉大量无效优化。举例:Decode 阶段每生成一个 token 要读全部权重(16 GB),做的计算量却只有 2×参数量×batch。batch=1 时算术强度约等于 2,而硬件阈值在数百——它离算力受限差了两个数量级。所以优化 Decode 的方向只有两个:减少要读的字节(量化)或者让更多请求分摊这次读取(增大 batch)。这条结论在第三部分会反复用到。
2.3 小算子与启动开销¶
一个 Transformer Block 里,LayerNorm、激活、残差加、转置、切片各自都是一个 kernel。它们的计算量微不足道,但每个都要完整地从 HBM 读一遍、写一遍,再加上 kernel 启动的固定开销。
累加起来,这些"不干活"的算子可以占掉相当比例的时间。算子融合(第 17 章)就是把它们合并成一个 kernel,中间结果留在寄存器里,把多次 HBM 往返压成一次。
2.4 通信与计算重叠¶
反向传播是从最后一层往前算的,所以最后几层的梯度早早就绪,完全可以一边通信一边继续算前面的层。
好的系统不只是减少通信量,更是把通信藏到计算后面。判断藏没藏住的方法很直接:看时间线上通信 kernel 有没有和计算 kernel 在时间上重叠,以及关键路径上暴露出来的通信时长占 step 时间多少。这个技巧从第 3 章的梯度桶一直用到第 6 章的 AllToAll 隐藏。
2.5 用时间线定位,而不是猜¶
优化之前先回答一个问题:GPU 此刻在算、在搬、在等通信,还是在等 CPU?
时间线上四种典型形态对应四种修法:连续的计算块但 FLOPS 偏低 → kernel 效率或形状问题;大量 NCCL 块暴露在关键路径 → 通信没藏住;kernel 之间有规律的空洞 → CPU 提交跟不上,考虑 CUDA Graph;step 开头有固定空洞 → 数据管线跟不上。
猜错方向,后面做的所有工作都是白工。这是本册最重要的工作习惯。
核心问答¶
Q:什么时候优化 FLOPs 是没用的? 当操作受带宽或通信限制时。此时少算一点,时间几乎不变,因为 GPU 本来就在等数据。
Q:为什么算子融合常比改算法更有效? 因为很多算子的成本几乎全在读写 HBM,而不在计算。融合把往返次数降下来,直接命中真正的瓶颈。
Q:通信量没变,重叠之后为什么能加速? 总工作量没少,但通信和计算同时发生,关键路径变短了。墙上时钟看的是关键路径。
Q:性能排查第一步看什么? 端到端 step 时间,然后是时间线。单独看 GPU 利用率是最容易误判的做法。