📑 本页目录(点开跳转)
04 · Roofline 与 MFU
⏱ 28 分钟 | ⭐ 唯一能回答"我还有多少空间"的工具
🎯 一句话
不会算 MFU,你的所有优化都是盲调。 这一章给你两个工具:Roofline 告诉你瓶颈在哪,MFU 告诉你离天花板还有多远。
📈 一、Roofline 模型
横轴是算术强度(FLOP/字节),纵轴是性能(TFLOPS)。曲线由两段拼成:左边一段斜坡(斜率 = 显存带宽,这是带宽上限),右边一段水平屋顶(算力上限,A100 是 312 TFLOPS)。两段的交点就是拐点,A100 落在 156 FLOP/字节。你的每个 kernel 都会落在这张图的某个位置上:
| 你的 kernel 落在哪 | 说明 | 该优化什么 |
|---|---|---|
| ⭐ 斜坡上 | 带宽瓶颈(算术强度不到 156) | 优化【搬运】 |
| ⭐ 屋顶下方 | 算力瓶颈 | 优化【计算】或用 Tensor Core |
| ⭐ 屋顶上 | 超过了硬件峰值 | 你算错了 😄 |
$$\text{可达性能} = \min(\text{峰值算力},\; \text{算术强度} \times \text{带宽})$$
💡 人话:要么被算力卡住,要么被带宽卡住,取小的那个。
🔨 三步用起来
流程图
🔑 Roofline 最大的价值不是"告诉你能快多少", 而是"告诉你【不能】快多少" —— 一个算术强度只有 2 的 kernel,无论你怎么优化计算,都不可能超过 4 TFLOPS。 知道这个,你就不会在错误的地方浪费一周。
📊 二、MFU:最重要的一个指标
$$\text{MFU} = \frac{\text{实际有效算力}}{\text{硬件峰值算力}}$$
怎么算(这是你真正会用到的):
算一算
① 算出一次前向+反向的 FLOP:
⭐ Transformer 的经验公式:
每个 token 的训练 FLOP ≈ 6 × 参数量
(前向 2N,反向 4N —— 反向要算两组梯度:
对输入的和对权重的,所以是前向的 2 倍)
② 实测吞吐(tokens/秒)
③ MFU = (6 × N × tokens/秒) / (GPU数 × 峰值FLOPS)
def mfu(params_B, tokens_per_sec, n_gpus, peak_tflops=312):
"""Transformer 训练的 MFU。peak_tflops: A100 BF16 = 312"""
flops_per_token = 6 * params_B * 1e9
achieved = flops_per_token * tokens_per_sec
peak = n_gpus * peak_tflops * 1e12
return achieved / peak
# 例:7B 模型,8 卡 A100,实测 12000 tokens/秒
print(f"MFU = {mfu(7, 12000, 8):.1%}") # MFU = 20.2% ⚠️ 偏低,有优化空间
📐 为什么是 6N(想看再点)
对一个权重矩阵 $W \in \mathbb{R}^{m\times n}$,处理一个 token:
- 前向:$y = Wx$ → $2mn$ FLOP(一次乘一次加)
- 反向对输入的梯度:$\partial x = W^\top \partial y$ → $2mn$
- 反向对权重的梯度:$\partial W = \partial y\, x^\top$ → $2mn$
合计 $6mn$,而 $mn$ 就是这个矩阵的参数量 → 6 × 参数量 ⭐
⚠️ 这个公式忽略了 attention 的 $O(n^2)$ 部分。 序列很长时要加上:$\text{每 token} \approx 6N + 12 \cdot L \cdot h \cdot s$ (L=层数,h=hidden,s=序列长度)。 序列 ≥ 8K 时这一项不能忽略。
📏 MFU 该是多少(对照基准)
| MFU | 评价 | 该干什么 |
|---|---|---|
| < 20% | 💀 有明显问题 | 先查数据管线,多半是没喂饱 |
| 20–35% | ⚠️ 偏低 | 查激活检查点配置、通信、算子融合 |
| 35–45% | 🟡 一般 | 常规优化还有空间 |
| 45–55% | ✅ 良好 | 已经不错了 |
| 55–60% | ⭐ 优秀 | 接近天花板 |
| > 60% | 🤔 检查你的公式 | 可能算错了(或用了 FP8) |
🔑 为什么天花板是 60% 而不是 100%: 存在无法消除的开销 —— 通信、非矩阵乘操作(LayerNorm/Softmax/激活函数)、 数据加载、kernel 启动、优化器更新。这些都不算进"有效 FLOP"但要占时间。
🔍 三、MFU 低了怎么查(按顺序)
流程图
⭐ 第 ② 步是最值得先做的诊断:
# 把真实数据换成固定的随机张量,其他不变 batch = torch.randint(0, vocab, (B, S), device='cuda') # 如果 MFU 显著上升 → 瓶颈在数据管线,不在模型(第 22 章)这一个实验能在 5 分钟内排除掉一整类问题。
🔬 用 PyTorch Profiler 找热点
# 🧩 骨架:`train_step` 来自你自己的代码,这一段只看写法
from torch.profiler import profile, ProfilerActivity, schedule
with profile(
activities=[ProfilerActivity.CPU, ProfilerActivity.CUDA],
schedule=schedule(wait=1, warmup=2, active=3), # ⭐ 跳过预热
record_shapes=True, profile_memory=True, with_stack=True,
) as prof:
for _ in range(6):
train_step()
prof.step()
print(prof.key_averages().table(
sort_by="self_cuda_time_total", row_limit=20)) # ⭐ 看谁最耗时
prof.export_chrome_trace("trace.json") # 拖进 chrome://tracing
读 trace 的三个信号:
| 你看到 | 说明 |
|---|---|
| GPU 时间轴上有空隙 | GPU 在等 CPU 或数据 → 数据管线 / CPU 开销 |
| 一堆小 kernel 密集排列 | 没有融合,kernel 启动开销占比高 → 第 7 章 |
| 通信 kernel(nccl)占比大 | 通信瓶颈 → 检查拓扑或换并行策略 |
💰 四、另外两个你该会算的数
① 训练一个模型要多久
$$\text{训练时间} \approx \frac{6 \times N \times D}{\text{GPU数} \times \text{峰值FLOPS} \times \text{MFU}}$$
(N = 参数量,D = 训练 token 数)
算一算
例:7B 模型,1T tokens,64 张 A100,MFU 45%
6 × 7e9 × 1e12 / (64 × 312e12 × 0.45)
= 4.2e22 / 8.98e15
≈ 4.7e6 秒 ≈ 54 天
⭐ 如果 MFU 只有 25%:97 天 —— 差了 43 天的机时
💡 这个公式的实用价值:在开始训练之前就能估算成本。 MFU 从 25% 提到 45%,在这个例子里省下的机时价值几十万元。
② 推理成本
算一算
每个输出 token 的 FLOP ≈ 2 × 参数量 (只有前向)
⚠️ 但推理是【带宽瓶颈】,用 FLOP 算会严重低估
⭐ 正确的估法:每生成 1 个 token 至少要把【全部权重读一遍】
→ 理论最快解码速度 = 显存带宽 / 模型大小
例:7B 模型 BF16(14GB),A100(2TB/s)
2000 / 14 ≈ 143 tokens/秒 ← 单个请求的理论上限 ⭐
🔑 这个"带宽除以模型大小"是推理领域最有用的心算公式: 它解释了为什么量化能直接提速(模型小一半 → 速度快一倍), 也解释了为什么批处理是免费的(读一遍权重服务多个请求)。 🔗 第 15、16、18 章全建立在这上面。
🔗 和站内其他章的关系
| 相关的地方 | 这里的位置 |
|---|---|
| 第 3 章 算术强度 | Roofline 的横轴 ⭐ |
| 第 3 章 平衡点 156 | Roofline 的拐点 |
| 第 1 章 先测再优化 | 本章就是"怎么测" |
| 第 2 章 GPU-Util 有误导性 | MFU 才是真指标 ⭐ |
| 《机器学习与深度学习基础》11 训练调试手册 「GPU 跑不满」 | 那里只能定性说"利用率忽高忽低",MFU 把它变成一个能横向比较的数字 ⭐ |
| 《大模型全景导论》06 推理优化 量化 / 批处理 / 投机解码 | 那三件武器的共同依据,就是本章末尾的"带宽 ÷ 模型大小" ⭐ |
| 《模型上线之后》18 成本与容量 | 本章算的是机时;折成"一次请求多少钱、峰值要几台机器"在那边 |
✅ 检查点
- Roofline 模型的两条线分别代表什么?拐点在哪?
- Roofline 最大的价值是什么?(不是"能快多少")
- MFU 怎么算?Transformer 每 token 的训练 FLOP 是多少?
- 为什么是 6N?这个公式什么时候会失准?
- MFU 多少算良好?为什么天花板是 60% 而不是 100%?
- MFU 低了,最值得先做的一个诊断实验是什么?
- 读 profiler trace 时,"GPU 时间轴有空隙"说明什么?
- 推理领域最有用的心算公式是什么?它解释了哪两件事?
👀 答案
- 斜坡是带宽上限(斜率=显存带宽),屋顶是算力上限。拐点在算术强度 = 峰值算力/带宽,A100 上是 156 FLOP/字节。
- 告诉你"不能"快多少。一个算术强度只有 2 的 kernel,无论怎么优化计算都超不过 4 TFLOPS——知道这个就不会在错误的地方浪费一周。
- MFU = (6 × 参数量 × tokens/秒) / (GPU数 × 峰值FLOPS)。每 token 训练 FLOP ≈ 6 × 参数量。
- 前向 2mn + 反向对输入 2mn + 反向对权重 2mn = 6mn,而 mn 就是参数量。忽略了 attention 的 O(n²) 部分,序列 ≥ 8K 时会失准,要加 12·L·h·s。
- 45–55% 良好,55–60% 优秀。天花板不是 100% 是因为存在无法消除的开销:通信、非矩阵乘操作(LayerNorm/Softmax)、数据加载、kernel 启动、优化器更新——占时间但不算有效 FLOP。
- 把 dataloader 换成固定的随机张量,其他不变。如果 MFU 显著上升 → 瓶颈在数据管线不在模型。5 分钟排除一整类问题。
- GPU 在等 CPU 或等数据 → 数据管线或 CPU 开销有问题。
- 理论最快解码速度 = 显存带宽 ÷ 模型大小(7B BF16 在 A100 上 ≈ 143 tokens/秒)。解释了:量化能直接提速(模型小一半速度快一倍)、批处理是免费的(读一遍权重服务多个请求)。
🛑 可以停在这里
⚡ 走神救援
先记住这几件事
- Roofline 比较算力上限和带宽上限,帮助判断理论上还能快多少。
- MFU 的计算口径要对应模型、精度和硬件,不能拿 GPU-Util 代替。
- 先保留基线,再从时间线检查数据等待、通信与算子开销,一次只改一个因素。
下一节 👉 05-互联与集群拓扑.md