41

CUDA Graph 与 MFU——怎么把一张卡用满

阅读约 5 分钟第 41/60 课

GPU 的理论算力很高,但实际训练中很难用满。这一章节来看为什么会浪费,以及怎么减少浪费。

MFU:模型浮点利用率

MFU(Model FLOPs Utilization)衡量的是:实际使用的算力占 GPU 理论峰值的百分比。

如果 GPU 的理论峰值是每秒 100 TFLOPS(100 万亿次浮点运算),而你的训练实际只做到了 40 TFLOPS,MFU 就是 40%。

40% 意味着 GPU 有 60% 的时间在「空转」——不是真的空转,而是在等数据从显存到达、在启动核函数、在做 CPU 和 GPU 之间的同步、在做 Python 层面的开销。

大模型训练的 MFU 通常在 30%-60% 之间。达到 50% 以上就算很好的工程实践。

算力浪费的来源

核函数启动开销。 每次调用一个 GPU 核函数,CPU 需要向 GPU 发送指令,GPU 需要调度核心。如果一层 Transformer 包含 20 个小操作(矩阵乘法、加法、归一化……),就需要启动 20 次核函数。每次启动的开销是微秒级——听起来很少,但 28 层 × 20 个操作 × 几百万步训练,累积起来可观。

CPU-GPU 同步。 PyTorch 默认异步执行——CPU 发指令后不等 GPU 做完就继续发下一条指令。但某些操作(比如读取 loss 的值到 CPU 端打印)会强制同步:CPU 等 GPU 做完了才能继续。同步期间 GPU 可能在空等。

显存带宽瓶颈。 归一化、激活函数、残差加法这些操作的计算量小但数据搬运量大——GPU 核心在等数据。

CUDA Graph

CUDA Graph 是 NVIDIA 提供的一种优化技术。它的思路是:把一系列核函数调用预先录制成一个「计划」,然后每次训练步只需要执行一次这个计划——省掉了反复的核函数启动和 CPU-GPU 通信。

普通执行:CPU 发指令1 → GPU 执行 → CPU 发指令2 → GPU 执行 → ...
CUDA Graph:CPU 发"执行整个计划" → GPU 连续执行所有操作,中间不等 CPU

效果是:核函数启动开销从每步几十次降到一次,GPU 利用率显著提升。

Sophia 的训练代码使用了 CUDA Graph 来加速训练循环。

其他优化

算子融合(Kernel Fusion)。 把多个小操作合并成一个大核函数。比如把「矩阵乘法 → 加偏置 → 激活函数」三个操作融合成一个核函数,只启动一次,中间数据留在寄存器里不回写显存——同时减少了启动开销和显存带宽消耗。

FlashAttention。 注意力计算涉及大量的显存读写(分数矩阵、softmax、注意力权重矩阵)。FlashAttention 通过重新安排计算顺序,让大部分数据留在高速的共享内存中,显著减少了显存带宽消耗。

这些优化不改变数学上的计算结果——模型的输出和梯度完全一样。它们只是让相同的计算在 GPU 上跑得更快。

这个单元学了什么

五个章节覆盖了精度和硬件的核心概念:

  1. 浮点数(第 37 章节):FP32 精度高但慢,BF16 范围大且快,FP16 容易溢出。
  2. 混合精度(第 38 章节):大部分算 BF16,关键操作用 FP32。
  3. GPU 基础(第 39 章节):大量简单核心并行计算,显存带宽是常见瓶颈。
  4. 显存估算(第 40 章节):参数 + 梯度 + 优化器 + 激活 ≈ 20 GB。
  5. 效率优化(本章节):CUDA Graph、算子融合、FlashAttention 提升 GPU 利用率。

到这里,「怎么训练」已经全部讲完了——从数学(梯度、优化器)到工程(精度、显存、GPU 优化)。但还有一个前提:训练用的数据从哪来?下一个单元来看训练数据的采集和处理。