📑 本页目录(点开跳转)
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{带宽})$$
💡 人话:要么被算力卡住,要么被带宽卡住,取小的那个。
🔨 三步用起来
① 算这个 kernel 的 FLOP 和字节数 → 得到算术强度
② 在图上找到它的位置 → 知道理论上限是多少
③ 和实测对比:
· 接近理论上限 → 这个 kernel 已经到头了,去优化别的
· 远低于理论上限 → 有 bug 或实现差,值得深挖 ⭐
🔑 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 低了怎么查(按顺序)
① GPU 有没有在跑? → nvidia-smi 看利用率,长期低于 90% = 没喂饱
② 数据管线跟得上吗? → 把 dataloader 换成随机张量,MFU 变了吗?⭐
③ 通信占多少? → 单卡 vs 多卡的 MFU 差多少
④ 非矩阵乘操作占多少? → profiler 看 kernel 时间分布
⑤ 有没有用上 Tensor Core?→ 精度对不对、维度对齐了吗
⑥ 激活检查点是不是开太狠?→ 全量重算会掉 30% 以上
⭐ 第 ② 步是最值得先做的诊断:
# 把真实数据换成固定的随机张量,其他不变 batch = torch.randint(0, vocab, (B, S), device='cuda') # 如果 MFU 显著上升 → 瓶颈在数据管线,不在模型(第 22 章)这一个实验能在 5 分钟内排除掉一整类问题。
🔬 用 PyTorch Profiler 找热点
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/秒)。解释了:量化能直接提速(模型小一半速度快一倍)、批处理是免费的(读一遍权重服务多个请求)。
🛑 可以停在这里
⚡ 走神救援
⭐⭐不会算 MFU,所有优化都是盲调。Roofline:可达性能 = min(峰值算力, 算术强度×带宽);斜坡=带宽上限、屋顶=算力上限、拐点在 156(A100)。⭐它最大的价值是告诉你"不能"快多少——算术强度 2 的 kernel 怎么优化都超不过 4 TFLOPS,知道这个就不会浪费一周。⭐⭐MFU = (6×参数量×tokens/秒) / (GPU数×峰值FLOPS);每 token 训练 FLOP ≈ 6N(前向2N + 反向对输入2N + 反向对权重2N),⚠️忽略了 attention 的 O(n²),序列≥8K 时要加 12·L·h·s。基准:<20% 有明显问题、20-35% 偏低、45-55% 良好、55-60% 优秀、>60% 检查公式;⭐天花板是 60% 不是 100%,因为通信/LayerNorm/Softmax/数据加载/kernel启动/优化器更新占时间但不算有效 FLOP。MFU 低了按序查:GPU在跑吗 → ⭐把 dataloader 换成随机张量(5 分钟排除一整类问题) → 通信占比 → 非矩阵乘占比 → Tensor Core 用上没(精度和维度对齐)→ 检查点开太狠。读 trace 三信号:时间轴有空隙=在等CPU/数据、一堆小 kernel=没融合、nccl 占比大=通信瓶颈。训练时间 ≈ 6ND / (GPU数×峰值×MFU)(7B/1T tokens/64卡/MFU45% ≈ 54 天,MFU 25% 则 97 天)。⭐⭐推理最有用的心算:理论最快解码速度 = 显存带宽 ÷ 模型大小(7B BF16 在 A100 上 ≈ 143 tokens/秒)——它解释了量化为什么直接提速和批处理为什么是免费的。
下一节 👉 05-互联与集群拓扑.md