第 12 课:配置搜索与性能诊断

参考答案版:包含完整代码实现与问答题深度参考解析。建议先独立完成练习题后再展开核对。

Important统一完成标准与及格线

代码 4 分、Q1~Q3 各 2 分,通过线 8/10。必须解释正确性边界、显存/通信公式中的单位与分片维度;未实际运行的内容只能标记为静态审查。

本课目标

完成后你需要能够:

  • 复述教材的三步配置流程,并说明每步的检查项。
  • 计算 MFU 与 HFU,解释两者的区别与适用场景。
  • 使用附录 A3 的重叠条件(t_comm/t_compute ≤ 1)判断 DP/ZeRO-3/TP/PP 的通信能否被隐藏。
  • 用 PyTorch profiler / torch.cuda.Event / ncu 定位”装得下但慢”的瓶颈。

核心概念

2. MFU 与 HFU:模型利用率与硬件利用率的本质差异

衡量大模型训练系统性能有两个核心工业指标:MFU(Model FLOPs Utilization) 与 HFU(Hardware FLOPs Utilization)。

A. 基础理论:大模型 6×P×T6 \times P \times T 算力法则

Transformer 模型中每个 Token、每个参数在一次完整的前向与反向传播中所需的理论浮点运算次数(FLOPs)严格为 6 FLOPs6 \text{ FLOPs}:

  • 前向传播 GEMM:每个权重参数对应 1 次乘法与 1 次加法(FMA 融合乘加),共计 2 FLOPs/param2\text{ FLOPs/param}。
  • 反向传播求导 GEMM:反向包含两个对称的矩阵乘法:
    1. 对输入激活求导:∂ℒ∂X=∂ℒ∂Y⋅WT⟹2 FLOPs/param\frac{\partial \mathcal{L}}{\partial X} = \frac{\partial \mathcal{L}}{\partial Y} \cdot W^T \implies 2\text{ FLOPs/param};
    2. 对模型权重求导:∂ℒ∂W=XT⋅∂ℒ∂Y⟹2 FLOPs/param\frac{\partial \mathcal{L}}{\partial W} = X^T \cdot \frac{\partial \mathcal{L}}{\partial Y} \implies 2\text{ FLOPs/param}。 反向合计消耗 4 FLOPs/param4\text{ FLOPs/param}。
  • 单步理论必需计算量: FLOPsrequired=6×P×Ttokens\text{FLOPs}_{\text{required}} = 6 \times P \times T_{\text{tokens}} (其中 PP 为模型参数量,Ttokens=GBS×sT_{\text{tokens}} = \text{GBS} \times s 为整步训练的 Token 总数;非 GEMM 算子如 Norm、Softmax 占比 <1%<1\%,通常忽略)。

B. MFU 与 HFU 的量化公式

  • MFU(模型算力利用率): 仅以“算法数学上必需完成的浮点量”为分子,衡量实际软硬件实现相比理论上限的有效产出: MFU=6×P×TtokensTstep×Ngpus×Peak_TFLOPs\text{MFU} = \frac{6 \times P \times T_{\text{tokens}}}{T_{\text{step}} \times N_{\text{gpus}} \times \text{Peak\_TFLOPs}}

  • HFU(硬件算力利用率): 将显存优化所带来的额外前向重计算(Activation Recomputation)等实际发生的物理计算量全部计入分子: HFU=6×P×Ttokens×(1+αrecompute)Tstep×Ngpus×Peak_TFLOPs\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% 全量重计算(αrecompute≈0.33\alpha_{\text{recompute}} \approx 0.33),其有效 MFU 实际不足 48%! 决定训练集群总耗时与算力账单的唯一金标准永远是 MFU。高 HFU 伴随低 MFU 意味着硬件在做大量的无用功。

3. 计算-通信重叠的解析数学模型(附录 A3 严格推导)

在现代异步通信流水线中,通信能否被完全隐藏(Overlap),取决于通信耗时与计算耗时的比值: Overlap Condition:TcommTcompute≤1.0\text{Overlap Condition}: \quad \frac{T_{\text{comm}}}{T_{\text{compute}}} \le 1.0

设硬件单卡标称峰值算力为 FF(FLOPS),通信链路有效总线带宽为 BB(Bytes/s),不同并行维度的比值解析式推导如下:

并行模式 单步通信量 (VcommV_{\text{comm}}) 比值模型 TcommTcompute\frac{T_{\text{comm}}}{T_{\text{compute}}} 关键特征与扩展性规律
DP (ZeRO-0) 梯度同步 ≈2P\approx 2 P 字节 P2×Tlocal⋅Ndp−1Ndp⋅FB\frac{P}{2 \times T_{\text{local}}} \cdot \frac{N_{\text{dp}}-1}{N_{\text{dp}}} \cdot \frac{F}{B} 随单卡 Token 数 TlocalT_{\text{local}} 增大而递减;大 Batch 下易于完全掩盖
ZeRO-3 (FSDP) 逐层参数重构 ≈3P/Ndp\approx 3 P / N_{\text{dp}} 12×s×MBS⋅Ndp−1Ndp⋅FB\frac{1}{2 \times s \times \text{MBS}} \cdot \frac{N_{\text{dp}}-1}{N_{\text{dp}}} \cdot \frac{F}{B} 随 s×MBSs \times \text{MBS} 增大而递减;长序列下 Prefetch 极易隐藏
TP (张量并行) 每层输出 8⋅s⋅MBS⋅h/TP8 \cdot s \cdot \text{MBS} \cdot h / \text{TP} TP−12h⋅FB\frac{\text{TP}-1}{2h} \cdot \frac{F}{B} 与 Batch Size / 序列长度完全无关! 纯由隐藏维 hh 与硬件比率 F/BF/B 决定
PP (流水线) 每微批次边界 4⋅s⋅MBS⋅h4 \cdot s \cdot \text{MBS} \cdot h F32⋅h⋅Lnext⋅B\frac{F}{32 \cdot h \cdot L_{\text{next}} \cdot B} 与单 Stage 包含的层数 LnextL_{\text{next}} 成反比;层数越多越容易掩盖

深刻启示: TP 的比值 TP−12h⋅FB\frac{\text{TP}-1}{2h} \cdot \frac{F}{B} 与数据规模毫无关联。在机内 NVLink 互联下(B=900 GB/sB = 900\text{ GB/s}),F/B≈1000F/B \approx 1000 左右,比值远小于 1;但一旦跨机走网卡(B=50 GB/sB = 50\text{ GB/s}),F/BF/B 暴增近 20 倍,比值瞬间击穿 1.0,导致计算流频繁处于等待停滞状态。

4. 性能剖析工具与 OOM 故障排查诊断树(如图 12-2 所示)

flowchart TD
    OOM["训练突发 OOM 崩溃"] --> CheckTime{"发生在第几步?"}
    CheckTime -->|"Step 1 / 前向阶段"| CheckAct["动态激活爆炸 -> 开启选择性重计算 / 调小 MBS"]
    CheckTime -->|"Step 1 / 反向阶段"| CheckGrad["静态梯度/工作缓冲超标 -> 调小 DDP 分桶 / 开启 ZeRO"]
    CheckTime -->|数百步后突发| CheckData["数据脏样本 (长序列) 或 显存碎片率累积 (>30%)"]

图 12-2:大模型训练常见 OOM 故障根因排查与定位诊断流

当大模型训练遭遇性能瓶颈或显存崩溃时,排查流程应如精密仪器般展开:

  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:说明单步显存已被击穿。根因必为单卡 MBS\text{MBS} 或序列长度 ss 过大,瞬间激活峰值超出硬件容量。立即降低 MBS\text{MBS} 或开启激活检查点(Activation Checkpointing)。
    • 第 1 步反向传播中途 OOM:说明前向保留的激活值未被及时释放,或者模型未正确清空上一轮累积梯度。
    • 第 2 步初始阶段 OOM(最经典的工业踩坑点!):第 1 步的前向与反向均顺利通过,但在进入第 2 步时突然触发 OOM。
      • 根因剖析:PyTorch 的 Adam/AdamW 优化器采用了延迟分配机制(Lazy Initialization)!在第 1 步结束前,动量(Momentum)和方差(Variance)张量尚未在显存中物化;直到执行首次 optimizer.step() 时,底层才瞬时申请高达 8P∼12P8P \sim 12P 字节的庞大显存,叠加前一步尚未完全释放的临时缓存碎片,瞬间引爆 OOM。

具体演示与数值推演

以单台配备 8 张 NVIDIA H100(每卡标称峰值算力 F≈989 TFLOPSF \approx 989\text{ TFLOPS} BF16,NVLink 双向带宽 B≈900 GB/sB \approx 900\text{ GB/s})的服务器,训练 7B 模型(参数量 P=7×109P = 7 \times 10^9),全局批次 GBS=4M tokens\text{GBS} = 4\text{M tokens}(即 4,194,3044,194,304 tokens),采用 DP=128\text{DP}=128(每卡每步分摊 Tlocal=32,768T_{\text{local}} = 32,768 tokens)为例:

  • 反向计算耗时推演: 单卡反向所需计算量为: FLOPsbwd=4×P×Tlocal=4×(7×109)×32,768≈9.175×1014 FLOPs\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 TFLOPS495\text{ TFLOPS})的前提下,反向计算时间为: Tcompute≈9.175×1014495×1012≈1.853 秒T_{\text{compute}} \approx \frac{9.175 \times 10^{14}}{495 \times 10^{12}} \approx 1.853 \text{ 秒}

  • Ring AllReduce 梯度通信耗时推演: 全模型 BF16 梯度总量为 7×109×2 字节=14 GB7 \times 10^9 \times 2\text{ 字节} = 14\text{ GB}。通信耗时为: Tcomm=2×Ndp−1Ndp×Grad SizeBbus=2×127128×14 GB900 GB/s≈0.0308 秒(30.8 ms)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})

  • 重叠比值计算: TcommTcompute=0.0308 s1.853 s≈0.0166≪1.0\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 ms30.8\text{ ms} 的网络通信可以被近 1.85 秒1.85\text{ 秒} 的反向计算完全吞没,通信重叠率达到 100%!

  • 退化场景反思: 若每卡分配的本地样本缩减至极小(例如 Tlocal=512T_{\text{local}} = 512 tokens),则计算时间暴跌至仅约 28.9 ms28.9\text{ ms}。此时 TcommTcompute>1.0\frac{T_{\text{comm}}}{T_{\text{compute}}} > 1.0,通信时间反超计算时间,Tensor Core 将产生强制通信空闲气泡,整体 MFU 开始大幅度下滑。

代码填空题

实现 MFU/HFU 与重叠条件判断。

Code
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 耗时。
  • 只会报”慢”说不出定位方法。

参考答案(仅 answer 分支)

先完成题目再核对;核对后必须解释关键公式,并改一个规模重新计算。

Code
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 '不可隐藏'}")

补全后的代码(关键填空处)

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):

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 含。

官方主参考