---
title: "第 12 课:配置搜索与性能诊断"
jupyter: python3
---
::: {.callout-note appearance="simple"}
**参考答案版**:包含完整代码实现与问答题深度参考解析。建议先独立完成练习题后再展开核对。
:::
::: {.callout-important title="统一完成标准与及格线"}
代码 4 分、Q1~Q3 各 2 分,通过线 8/10。必须解释正确性边界、显存/通信公式中的单位与分片维度;未实际运行的内容只能标记为静态审查。
:::
- 对应官方章节:Finding the Best Training Configuration、附录 A1(Profiling)、附录 A3(Compute/Communication Overlap Math)
- 前置:第 11 课
- 状态:未开始
## 本课目标
完成后你需要能够:
- 复述教材的三步配置流程,并说明每步的检查项。
- 计算 MFU 与 HFU,解释两者的区别与适用场景。
- 使用附录 A3 的重叠条件(t_comm/t_compute ≤ 1)判断 DP/ZeRO-3/TP/PP 的通信能否被隐藏。
- 用 PyTorch profiler / torch.cuda.Event / ncu 定位"装得下但慢"的瓶颈。
## 核心概念
### 1. 工业级训练配置三步搜索法(3-Step Systematic Search)
在大规模集群上配置分布式并行超参数绝不能靠盲目尝试,业内标准工程方法论遵循严格的**三步收敛法则**:
1. **Step 1:显存破局(让单个训练步能装进显存)**:
- **算力充足场景(按参数规模降阶选择)**:
- 若模型 $< 10\text{B}$:单技术即可满足,优先选择纯 DP(结合全激活重计算)或单机 ZeRO-3 / FSDP;
- 若模型处于 $10\text{B} \sim 100\text{B}$:首选机内 $\text{TP}=8$ + 跨机 $\text{PP}$,或者机内 $\text{TP}=8$ + $\text{ZeRO-3}$;
- 若集群规模超过 512 卡:纯 DP/ZeRO-3 因环延迟累积效率滑坡,必须引入机内 $\text{TP}=8$;若规模超过 1024 卡,推荐采用工业界黄金三角架构:$\text{TP}=8 + \text{ZeRO-2} + \text{PP}$。
- **算力受限/显存吃紧场景**:立即开启选择性激活重计算(Selective Recomputation),并将 Micro-Batch 降至 1,利用梯度累积凑齐批次。
- **特殊模态扩展**:遇到超长上下文($s \ge 32\text{k}$)追加上下文并行(CP);遇到 MoE 稀疏结构追加专家并行(EP)。
2. **Step 2:吞吐配平(锚定算法黄金批次 GBS)**:
- 算法团队根据缩放定律(Scaling Laws)预先测定了最佳全局批次 $\text{GBS}_{\text{tokens}}$。
- 必须严格满足量化平衡公式:
$$\text{GBS}_{\text{tokens}} = \text{MBS} \times \text{grad\_accum} \times N_{\text{dp}} \times s$$
- 若批次不足,逐步提升 $N_{\text{dp}}$ 或增加串行累积步数 $\text{grad\_accum}$;若批次过大超出优化器收敛边界,则削减 $N_{\text{dp}}$ 并将多余的算力转向模型并行(增加 PP 级数或加大模型容量)。
3. **Step 3:硬件利用率微调(逼近 Roofline 性能边界)**:
- 如**图 12-1** 所示,利用 Roofline 模型判断当前内核处于**访存受限区(Memory Bound)**还是**算力受限区(Compute Bound)**:
```{mermaid}
xychart-beta
title "Roofline 性能模型与瓶颈分界示意"
x-axis "算术强度 Operational Intensity (FLOPs/Byte)" [0.1, 1, 10, 100, 1000]
y-axis "可达计算性能 Attainable Performance (TFLOPS)" 0 --> 300
line [10, 50, 150, 300, 300]
```
<p class="caption" align="center"><em>图 12-1:Roofline 模型算术强度与硬件瓶颈分界图解</em></p>
- 调节 $\text{MBS}$ 尺寸,使矩阵乘 GEMM 的形状($M, N, K$ 维度)对齐 Tensor Core 的硬件倍数(16、32 或 64 的整数倍),确保算子脱离访存瓶颈,进入高算术强度计算区。
- 验证网络通信与计算的重叠率,最终确定全系统最优超参数。
### 2. MFU 与 HFU:模型利用率与硬件利用率的本质差异
衡量大模型训练系统性能有两个核心工业指标:**MFU(Model FLOPs Utilization)** 与 **HFU(Hardware FLOPs Utilization)**。
#### A. 基础理论:大模型 $6 \times P \times T$ 算力法则
Transformer 模型中每个 Token、每个参数在一次完整的前向与反向传播中所需的理论浮点运算次数(FLOPs)严格为 **$6 \text{ FLOPs}$**:
- **前向传播 GEMM**:每个权重参数对应 1 次乘法与 1 次加法(FMA 融合乘加),共计 $2\text{ FLOPs/param}$。
- **反向传播求导 GEMM**:反向包含两个对称的矩阵乘法:
1. 对输入激活求导:$\frac{\partial \mathcal{L}}{\partial X} = \frac{\partial \mathcal{L}}{\partial Y} \cdot W^T \implies 2\text{ FLOPs/param}$;
2. 对模型权重求导:$\frac{\partial \mathcal{L}}{\partial W} = X^T \cdot \frac{\partial \mathcal{L}}{\partial Y} \implies 2\text{ FLOPs/param}$。
反向合计消耗 $4\text{ FLOPs/param}$。
- **单步理论必需计算量**:
$$\text{FLOPs}_{\text{required}} = 6 \times P \times T_{\text{tokens}}$$
*(其中 $P$ 为模型参数量,$T_{\text{tokens}} = \text{GBS} \times s$ 为整步训练的 Token 总数;非 GEMM 算子如 Norm、Softmax 占比 $<1\%$,通常忽略)*。
#### B. MFU 与 HFU 的量化公式
- **MFU(模型算力利用率)**:
仅以“算法数学上必需完成的浮点量”为分子,衡量实际软硬件实现相比理论上限的有效产出:
$$\text{MFU} = \frac{6 \times P \times T_{\text{tokens}}}{T_{\text{step}} \times N_{\text{gpus}} \times \text{Peak\_TFLOPs}}$$
- **HFU(硬件算力利用率)**:
将显存优化所带来的额外前向重计算(Activation Recomputation)等实际发生的物理计算量全部计入分子:
$$\text{HFU} = \frac{6 \times P \times T_{\text{tokens}} \times (1 + \alpha_{\text{recompute}})}{T_{\text{step}} \times N_{\text{gpus}} \times \text{Peak\_TFLOPs}}$$
> **工程陷阱与警示**:
> 某些团队宣称训练取得了 65% 的超高硬件利用率(HFU),但在仔细审查后发现系统开启了 100% 全量重计算($\alpha_{\text{recompute}} \approx 0.33$),其有效 MFU 实际不足 48%!
> **决定训练集群总耗时与算力账单的唯一金标准永远是 MFU**。高 HFU 伴随低 MFU 意味着硬件在做大量的无用功。
### 3. 计算-通信重叠的解析数学模型(附录 A3 严格推导)
在现代异步通信流水线中,通信能否被完全隐藏(Overlap),取决于**通信耗时与计算耗时的比值**:
$$\text{Overlap Condition}: \quad \frac{T_{\text{comm}}}{T_{\text{compute}}} \le 1.0$$
设硬件单卡标称峰值算力为 $F$(FLOPS),通信链路有效总线带宽为 $B$(Bytes/s),不同并行维度的比值解析式推导如下:
| 并行模式 | 单步通信量 ($V_{\text{comm}}$) | 比值模型 $\frac{T_{\text{comm}}}{T_{\text{compute}}}$ | 关键特征与扩展性规律 |
|---|---|---|---|
| **DP (ZeRO-0)** | 梯度同步 $\approx 2 P$ 字节 | $\frac{P}{2 \times T_{\text{local}}} \cdot \frac{N_{\text{dp}}-1}{N_{\text{dp}}} \cdot \frac{F}{B}$ | 随单卡 Token 数 $T_{\text{local}}$ 增大而递减;大 Batch 下易于完全掩盖 |
| **ZeRO-3 (FSDP)** | 逐层参数重构 $\approx 3 P / N_{\text{dp}}$ | $\frac{1}{2 \times s \times \text{MBS}} \cdot \frac{N_{\text{dp}}-1}{N_{\text{dp}}} \cdot \frac{F}{B}$ | 随 $s \times \text{MBS}$ 增大而递减;长序列下 Prefetch 极易隐藏 |
| **TP (张量并行)** | 每层输出 $8 \cdot s \cdot \text{MBS} \cdot h / \text{TP}$ | $\frac{\text{TP}-1}{2h} \cdot \frac{F}{B}$ | **与 Batch Size / 序列长度完全无关!** 纯由隐藏维 $h$ 与硬件比率 $F/B$ 决定 |
| **PP (流水线)** | 每微批次边界 $4 \cdot s \cdot \text{MBS} \cdot h$ | $\frac{F}{32 \cdot h \cdot L_{\text{next}} \cdot B}$ | 与单 Stage 包含的层数 $L_{\text{next}}$ 成反比;层数越多越容易掩盖 |
**深刻启示**:
TP 的比值 $\frac{\text{TP}-1}{2h} \cdot \frac{F}{B}$ 与数据规模毫无关联。在机内 NVLink 互联下($B = 900\text{ GB/s}$),$F/B \approx 1000$ 左右,比值远小于 1;但一旦跨机走网卡($B = 50\text{ GB/s}$),$F/B$ 暴增近 20 倍,比值瞬间击穿 1.0,导致计算流频繁处于等待停滞状态。
### 4. 性能剖析工具与 OOM 故障排查诊断树(如**图 12-2** 所示)
```{mermaid}
flowchart TD
OOM["训练突发 OOM 崩溃"] --> CheckTime{"发生在第几步?"}
CheckTime -->|"Step 1 / 前向阶段"| CheckAct["动态激活爆炸 -> 开启选择性重计算 / 调小 MBS"]
CheckTime -->|"Step 1 / 反向阶段"| CheckGrad["静态梯度/工作缓冲超标 -> 调小 DDP 分桶 / 开启 ZeRO"]
CheckTime -->|数百步后突发| CheckData["数据脏样本 (长序列) 或 显存碎片率累积 (>30%)"]
```
<p class="caption" align="center"><em>图 12-2:大模型训练常见 OOM 故障根因排查与定位诊断流</em></p>
当大模型训练遭遇性能瓶颈或显存崩溃时,排查流程应如精密仪器般展开:
1. **核心诊断兵器库**:
- **PyTorch Profiler / Chrome Trace**:捕获 CPU 调度事件、CUDA Kernel 执行与通信流活动,直观可视化计算与通信的重叠气泡;
- **`torch.cuda.Event`**:高精度测量特定算子(如 Attention GEMM、AllReduce)在 GPU 上的真实物理耗时;
- **Nsight Systems (nsys) & Nsight Compute (ncu)**:深入 GPU 架构层面,剖析 SM 利用率、Tensor Core 占用率、SRAM 缓存命中率与 HBM 访存瓶颈;
- **显存探针(`torch.cuda.memory_allocated` vs `max_memory_reserved`)**:精准区分“真实激活/权重显存”与“PyTorch 缓存分配器碎片(Fragmentation)”。
2. **OOM 故障排查逻辑链(图 12-2)**:
- **第 1 步前向传播即 OOM**:说明单步显存已被击穿。根因必为单卡 $\text{MBS}$ 或序列长度 $s$ 过大,瞬间激活峰值超出硬件容量。立即降低 $\text{MBS}$ 或开启激活检查点(Activation Checkpointing)。
- **第 1 步反向传播中途 OOM**:说明前向保留的激活值未被及时释放,或者模型未正确清空上一轮累积梯度。
- **第 2 步初始阶段 OOM(最经典的工业踩坑点!)**:第 1 步的前向与反向均顺利通过,但在进入第 2 步时突然触发 OOM。
- **根因剖析**:PyTorch 的 Adam/AdamW 优化器采用了**延迟分配机制(Lazy Initialization)**!在第 1 步结束前,动量(Momentum)和方差(Variance)张量尚未在显存中物化;直到执行首次 `optimizer.step()` 时,底层才瞬时申请高达 $8P \sim 12P$ 字节的庞大显存,叠加前一步尚未完全释放的临时缓存碎片,瞬间引爆 OOM。
## 具体演示与数值推演
以单台配备 8 张 NVIDIA H100(每卡标称峰值算力 $F \approx 989\text{ TFLOPS}$ BF16,NVLink 双向带宽 $B \approx 900\text{ GB/s}$)的服务器,训练 7B 模型(参数量 $P = 7 \times 10^9$),全局批次 $\text{GBS} = 4\text{M tokens}$(即 $4,194,304$ tokens),采用 $\text{DP}=128$(每卡每步分摊 $T_{\text{local}} = 32,768$ tokens)为例:
- **反向计算耗时推演**:
单卡反向所需计算量为:
$$\text{FLOPs}_{\text{bwd}} = 4 \times P \times T_{\text{local}} = 4 \times (7 \times 10^9) \times 32,768 \approx 9.175 \times 10^{14} \text{ FLOPs}$$
在假设硬件发挥 50% 峰值算力(约 $495\text{ TFLOPS}$)的前提下,反向计算时间为:
$$T_{\text{compute}} \approx \frac{9.175 \times 10^{14}}{495 \times 10^{12}} \approx 1.853 \text{ 秒}$$
- **Ring AllReduce 梯度通信耗时推演**:
全模型 BF16 梯度总量为 $7 \times 10^9 \times 2\text{ 字节} = 14\text{ GB}$。通信耗时为:
$$T_{\text{comm}} = 2 \times \frac{N_{\text{dp}} - 1}{N_{\text{dp}}} \times \frac{\text{Grad Size}}{B_{\text{bus}}} = 2 \times \frac{127}{128} \times \frac{14\text{ GB}}{900\text{ GB/s}} \approx 0.0308 \text{ 秒} \ (30.8\text{ ms})$$
- **重叠比值计算**:
$$\frac{T_{\text{comm}}}{T_{\text{compute}}} = \frac{0.0308\text{ s}}{1.853\text{ s}} \approx 0.0166 \ll 1.0$$
通信耗时仅为计算耗时的 1.6%,$30.8\text{ ms}$ 的网络通信可以被近 $1.85\text{ 秒}$ 的反向计算完全吞没,通信重叠率达到 100%!
- **退化场景反思**:
若每卡分配的本地样本缩减至极小(例如 $T_{\text{local}} = 512$ tokens),则计算时间暴跌至仅约 $28.9\text{ ms}$。此时 $\frac{T_{\text{comm}}}{T_{\text{compute}}} > 1.0$,通信时间反超计算时间,Tensor Core 将产生强制通信空闲气泡,整体 MFU 开始大幅度下滑。
## 代码填空题
实现 MFU/HFU 与重叠条件判断。
```{python}
#| course_role: exercise
def step_flops(num_params: int, num_tokens: int) -> float:
"""
前向+反向必需 FLOPs ≈ 6·num_tokens·num_params
(前向 2、反向 4;忽略 attention 的 seq² 二次项,教材 A2 注释说明)。
"""
return ______
def mfu_hfu(
num_params: int, num_tokens: int, step_time_s: float,
peak_flops: float, recompute_extra: float = 0.0,
) -> tuple[float, float]:
"""
recompute_extra:重计算带来的额外前向比例(full 重计算约 0.3–0.4)。
MFU 不含重计算;HFU 含。
"""
required = step_flops(num_params, num_tokens)
mfu = required / (step_time_s * peak_flops)
hfu = ______ # 填空:把重计算计入真实 FLOPs
return mfu, hfu
def dp_overlap_ratio(
num_params: int, num_tokens: int, dp: int,
peak_flops: float, peak_bw: float,
bytes_per_param: int = 2,
) -> float:
"""
DP 梯度 all-reduce 能否被本卡 backward 隐藏:直接用时间比 t_comm / t_compute。
t_comm = 2·num_params·bytes_per_param·(DP-1)/DP / peak_bw
(ring all-reduce 的每卡发送量 ≈ 2×梯度字节;DP 很大时 (DP-1)/DP ≈ 1)
t_compute = 4·num_tokens·num_params / peak_flops
(本卡负责的那部分 backward;注意 num_tokens 是【每卡每步】token 数
= mbs·seq,不是全局 gbs —— 这是最容易算错的地方)
比 < 1 说明通信可以被完全重叠。教材 A3 的紧凑形式
num_params/(2·num_tokens)·(DP-1)/DP·F/B 是它的等价变形,
但换算时务必保持单位一致(bytes vs 元素数、全局 vs 每卡)。
"""
t_comm = ______ # 填空:ring all-reduce 每卡发送量/带宽
t_comp = ______ # 填空:本卡 backward FLOPs/峰值算力
return t_comm / t_comp
def zero3_overlap_ratio(
seq_len: int, mbs: int, dp: int,
peak_flops: float, peak_bw: float,
layer_params: int = 16 * 4096 * 4096,
) -> float:
"""
ZeRO-3 逐层 prefetch 能否藏住一层的参数 all-gather:
t_comm = 2·layer_params·bytes_per_param·(DP-1)/DP / peak_bw
t_compute = 2·seq·mbs·layer_params / peak_flops (该层前向计算)
比 < 1 说明第 n+1 层的参数 all-gather 可以藏在第 n 层的计算后面。
"""
t_comm = ______ # 填空
t_comp = ______ # 填空
return t_comm / t_comp
if __name__ == "__main__":
H100_F, H100_B = 989e12, 900e9 # BF16 TFLOPS、NVLink GB/s
# 7B、全局 gbs=4M tokens、dp=128 → 每卡每步 tokens = 4e6/128 = 31250
per_gpu = 4_000_000 // 128
mfu, hfu = mfu_hfu(7e9, per_gpu, 2.0, H100_F, recompute_extra=0.35)
print(f"MFU = {mfu:.3f}, HFU(含35%重计算) = {hfu:.3f}")
for dp in (32, 128, 512):
r = dp_overlap_ratio(7e9, 4_000_000 // dp, dp, H100_F, H100_B)
print(f"DP={dp:4d} 每卡tokens={4_000_000//dp:6d} t_comm/t_compute = {r:.2f} "
f"{'可隐藏' if r <= 1 else '不可隐藏'}")
# 小 mbs·seq 时通信藏不住(每卡计算太少)
r = dp_overlap_ratio(7e9, 512, 128, H100_F, H100_B)
print(f"DP=128 但每卡 tokens=512: {r:.2f} {'可隐藏' if r <= 1 else '不可隐藏'}")
# ZeRO-3:seq=4096、mbs=2,一层参数 16h²(h=4096)
r = zero3_overlap_ratio(4096, 2, 128, H100_F, H100_B)
print(f"ZeRO-3(seq=4096,mbs=2,dp=128): {r:.3f} {'可隐藏' if r <= 1 else '不可隐藏'}")
```
## 三个问答题
### Q1
MFU 与 HFU 的分子差在哪里?教材为什么说"重计算会抬高 HFU 但不一定代表训练更快"?如果要比较两台硬件谁的训练实现更高效,应该看哪个指标?为什么?
### Q2
某配置"装得下但训练慢"。请给出你的诊断流程:至少包含 1) 用什么工具看什么;2) 两个最可能的瓶颈假设及各自的证据特征(例如 trace 中通信与计算是否重叠、kernel 是否过小)。测量显存时 `max_memory_allocated` 与 `max_memory_reserved` 的差说明什么?
### Q3
用附录 A3 的公式回答:为什么"目标 gbs 固定时把 DP 加得很大"会导致 DP 通信藏不住(提示:每卡通信量 ≈ 2×模型梯度、与 dp 无关,而每卡 backward 时间正比于 mbs·seq)?为什么 TP 的重叠条件与 batch、seq 无关?这两个结论分别指导了什么工程决策(第 4/6/11 课的规则)?
## 检查与通过标准
总分 10 分:代码正确 4 分(FLOPs 公式、HFU、两个重叠比例)、三题各 2 分、通过线 8 分。
一票否决项:
- MFU/HFU 分子混淆(重计算计入谁)。
- 把 t_comm/t_compute 的方向搞反(>1 说成可隐藏)。
- 认为 python time 能准确测量 CUDA kernel 耗时。
- 只会报"慢"说不出定位方法。
::: {.callout-tip collapse="true" title="💡 点击展开参考答案与详细代码实现" icon="false"}
## 参考答案(仅 answer 分支)
先完成题目再核对;核对后必须解释关键公式,并改一个规模重新计算。
```{python}
#| course_role: solution
def step_flops(num_params: int, num_tokens: int) -> float:
"""
前向+反向必需 FLOPs ≈ 6·num_tokens·num_params
(前向 2、反向 4;忽略 attention 的 seq² 二次项,教材 A2 注释说明)。
"""
return 6 * num_tokens * num_params
def mfu_hfu(
num_params: int, num_tokens: int, step_time_s: float,
peak_flops: float, recompute_extra: float = 0.0,
) -> tuple[float, float]:
"""
recompute_extra:重计算带来的额外前向比例(full 重计算约 0.3–0.4)。
MFU 不含重计算;HFU 含。
"""
required = step_flops(num_params, num_tokens)
mfu = required / (step_time_s * peak_flops)
hfu = required * (1 + recompute_extra) / (step_time_s * peak_flops) # 填空:把重计算计入真实 FLOPs
return mfu, hfu
def dp_overlap_ratio(
num_params: int, num_tokens: int, dp: int,
peak_flops: float, peak_bw: float,
bytes_per_param: int = 2,
) -> float:
"""
DP 梯度 all-reduce 能否被本卡 backward 隐藏:直接用时间比 t_comm / t_compute。
t_comm = 2·num_params·bytes_per_param·(DP-1)/DP / peak_bw
(ring all-reduce 的每卡发送量 ≈ 2×梯度字节;DP 很大时 (DP-1)/DP ≈ 1)
t_compute = 4·num_tokens·num_params / peak_flops
(本卡负责的那部分 backward;注意 num_tokens 是【每卡每步】token 数
= mbs·seq,不是全局 gbs —— 这是最容易算错的地方)
比 < 1 说明通信可以被完全重叠。教材 A3 的紧凑形式
num_params/(2·num_tokens)·(DP-1)/DP·F/B 是它的等价变形,
但换算时务必保持单位一致(bytes vs 元素数、全局 vs 每卡)。
"""
t_comm = 2 * num_params * bytes_per_param * (dp - 1) / dp / peak_bw # 填空:ring all-reduce 每卡发送量/带宽
t_comp = 4 * num_tokens * num_params / peak_flops # 填空:本卡 backward FLOPs/峰值算力
return t_comm / t_comp
def zero3_overlap_ratio(
seq_len: int, mbs: int, dp: int,
peak_flops: float, peak_bw: float,
layer_params: int = 16 * 4096 * 4096,
) -> float:
"""
ZeRO-3 逐层 prefetch 能否藏住一层的参数 all-gather:
t_comm = 2·layer_params·bytes_per_param·(DP-1)/DP / peak_bw
t_compute = 2·seq·mbs·layer_params / peak_flops (该层前向计算)
比 < 1 说明第 n+1 层的参数 all-gather 可以藏在第 n 层的计算后面。
"""
t_comm = 2 * layer_params * 2 * (dp - 1) / dp / peak_bw # 填空
t_comp = 2 * seq_len * mbs * layer_params / peak_flops # 填空
return t_comm / t_comp
if __name__ == "__main__":
H100_F, H100_B = 989e12, 900e9 # BF16 TFLOPS、NVLink GB/s
# 7B、全局 gbs=4M tokens、dp=128 → 每卡每步 tokens = 4e6/128 = 31250
per_gpu = 4_000_000 // 128
mfu, hfu = mfu_hfu(7e9, per_gpu, 2.0, H100_F, recompute_extra=0.35)
print(f"MFU = {mfu:.3f}, HFU(含35%重计算) = {hfu:.3f}")
for dp in (32, 128, 512):
r = dp_overlap_ratio(7e9, 4_000_000 // dp, dp, H100_F, H100_B)
print(f"DP={dp:4d} 每卡tokens={4_000_000//dp:6d} t_comm/t_compute = {r:.2f} "
f"{'可隐藏' if r <= 1 else '不可隐藏'}")
# 小 mbs·seq 时通信藏不住(每卡计算太少)
r = dp_overlap_ratio(7e9, 512, 128, H100_F, H100_B)
print(f"DP=128 但每卡 tokens=512: {r:.2f} {'可隐藏' if r <= 1 else '不可隐藏'}")
# ZeRO-3:seq=4096、mbs=2,一层参数 16h²(h=4096)
r = zero3_overlap_ratio(4096, 2, 128, H100_F, H100_B)
print(f"ZeRO-3(seq=4096,mbs=2,dp=128): {r:.3f} {'可隐藏' if r <= 1 else '不可隐藏'}")
```
## 补全后的代码(关键填空处)
```python
def step_flops(num_params, num_tokens):
return 6 * num_tokens * num_params # 前向 2 + 反向 4
def mfu_hfu(num_params, num_tokens, step_time_s, peak_flops, recompute_extra=0.0):
required = step_flops(num_params, num_tokens)
mfu = required / (step_time_s * peak_flops)
hfu = required * (1 + recompute_extra) / (step_time_s * peak_flops)
return mfu, hfu
def dp_overlap_ratio(num_params, num_tokens, dp, peak_flops, peak_bw, bytes_per_param=2):
# 每卡每步:comm ≈ 2×梯度字节(ring all-reduce),compute = 本卡 backward(mbs·seq tokens)
t_comm = 2 * num_params * bytes_per_param * (dp - 1) / dp / peak_bw
t_comp = 4 * num_tokens * num_params / peak_flops
return t_comm / t_comp
def zero3_overlap_ratio(seq_len, mbs, dp, peak_flops, peak_bw, layer_params=16*4096*4096):
t_comm = 2 * layer_params * 2 * (dp - 1) / dp / peak_bw # 一层参数的 all-gather
t_comp = 2 * seq_len * mbs * layer_params / peak_flops # 该层前向
return t_comm / t_comp
```
运行结果(H100:989 TFLOPS / 900 GB/s,7B,gbs=4M,grad_acc=1):
```python
DP=32 每卡tokens=125000 t_comm/t_compute = 0.009 可隐藏
DP=128 每卡tokens= 31250 t_comm/t_compute = 0.035 可隐藏
DP=512 每卡tokens= 7812 t_comm/t_compute = 0.139 可隐藏
DP=128 但每卡tokens=512: 2.13 不可隐藏
ZeRO-3(seq=4096,mbs=2,dp=128): 0.267 可隐藏
```
MFU/HFU 示例(每卡 31250 tokens、2.0 s/step):MFU ≈ 0.66,HFU(+35% 重计算)≈ 0.90。
## 三个问答题要点
### Q1
- MFU 分子 = 模型必需 FLOPs(6·tokens·params,不含重计算);HFU 分子 = 硬件真实执行 FLOPs(含重计算)→ 同一硬件上 HFU ≥ MFU;
- 重计算抬高 HFU 但训练可能更慢:HFU 衡量"硬件被用满的程度"而非"端到端快慢";比较实现/硬件效率用 MFU(教材:"更少重计算但更快的机器应被奖励");
- 补充:MFU 是模型视角(与实现无关),HFU 是硬件视角(包含实现引入的额外运算)。
### Q2
诊断流程(要点):
1. 显存:`max_memory_allocated` vs `max_memory_reserved`——差是 caching allocator 预留/碎片(大则调 `PYTORCH_CUDA_ALLOC_CONF`、减 batch、或查长驻 buffer);
2. 时间线:torch.profiler(CPU+CUDA)看通信是否与计算重叠、GPU 空闲段(等待通信/数据加载)、kernel 规模与启动开销(过小过碎 → torch.compile/融合);
3. 单 kernel:ncu 看吞吐/占用率/访存模式;
4. 先算后测:用 t_comm/t_compute 预判该配置是否处在可隐藏区间,避免盲目调参。
### Q3
- 机制:每卡通信量 ≈ 2×模型梯度字节,与 dp 无关;每卡 backward 时间 ∝ 4·(mbs·seq)·Ψ/F。gbs 固定时加 dp → grad_acc 或 mbs 被迫减小 → 每卡 backward 变短而通信不变 → 比值随 dp 增大、超过 1(教材 benchmark 固定 gbs=1M 正是这个机制);
- TP:重叠条件 (TP−1)/(2h)·F/B 不含 seq/mbs——因为 TP 通信发生在每层内部,与单层计算竞争:单层计算 ∝ seq·mbs·h²、通信 ∝ seq·mbs·h,h²/h 抵消了 batch 项;
- 工程决策:a) 大批次/长序列优先 DP/ZeRO-3(通信可隐藏),小 mbs 场景慎用高 DP;b) TP 只用于节点内切模型/激活,其效率由硬件算力带宽比决定,跨节点必炸(第 6/11 课规则)。
## 通过要点
- 重叠条件:t_comm/t_compute ≤ 1 才可完全隐藏。
- 单位陷阱:tokens 用每卡每步的 mbs·seq;字节与元素数要一致;教材 A3 紧凑公式是等价变形,面试手推时直接用秒为单位最稳。
- 测量:CUDA 异步 → torch.cuda.Event/synchronize 或 profiler,不能用 python time。
- MFU 不含重计算,HFU 含。
## 官方主参考
- [Ultra-Scale Playbook](https://huggingface.co/spaces/nanotron/ultrascale-playbook)
- [PyTorch distributed documentation](https://pytorch.org/docs/stable/distributed.html)
:::
::: {.course-pagination}
[**← 上一课**<br>第 11 课:5D 并行](lesson11.qmd){.pagination-link .pagination-prev}
[**下一课 →**<br>第 13 课:GPU 内核与 FlashAtt...](lesson13.qmd){.pagination-link .pagination-next}
:::