Chapter 11

性能调优:bucket size / overlap / fused optimizer

📌 源码引用基于 deepspeed/runtime/comm/ 、 deepspeed/runtime/zero/ · commit 510ebe58e4e4 。

11.1 调优前先量化"瓶颈在哪"

打开 ds_config.json:

{
  "wall_clock_breakdown": true,
  "steps_per_print": 50,
  "flops_profiler": {
    "enabled": true,
    "profile_step": 5,
    "module_depth": -1,
    "top_modules": 3,
    "detailed": false,
    "output_file": null
  }
}

跑 100 步后能看到一份 breakdown:

step= 50  fwd= 240ms  bwd= 380ms  step= 95ms
       allgather=  60ms (24% of fwd)
       reduce_scatter= 110ms (28% of bwd)
       optim_step= 95ms

诊断思路:

慢在哪优先看哪些字段
fwd 占比大 attention / FlashAttention / TP=? / activation checkpointing
bwd_inner(实际反向)同上
bwd_allreduce / reduce_scatterbucket size、overlap_comm、网络带宽
optim_step 大 fused optimizer、cpu offload?
allgather 大(stage 3)prefetch_bucket_size、stage3_max_live_parameters

11.2 通信 bucket:reduce / allgather 的"批"

DS 通信不是一个张量一个张量地发,而是凑齐一个 bucket(≈ 几百 MB)后一次发出。bucket 太小 → call 多,启动开销大;bucket 太大 → 显存被占;最佳值通常 $2 \times 10^8 \sim 5 \times 10^8$

字段含义建议
reduce_bucket_size 反向 reduce-scatter 桶 5e8
allgather_bucket_size stage 1/2 all-gather 桶 5e8
stage3_prefetch_bucket_size stage 3 prefetch 桶 "auto"
stage3_max_live_parameters 同时驻留显存的参数 bytes 1e9
stage3_max_reuse_distance 多大距离内的参数复用 cache 1e9

11.3 overlap_comm:必须开

不开 overlap,整个 step 串行执行:

$$T_\text{step} = T_\text{compute} + T_\text{comm}$$

开 overlap:

$$T_\text{step} \approx \max(T_\text{compute}, T_\text{comm}) + \text{少量开销}$$

差距通常 30%+。任何场景都建议开

"zero_optimization": {
  "stage": 3,
  "overlap_comm": true,
  "contiguous_gradients": true
}

11.4 fused optimizer

DS 内置的 fused 实现比 PyTorch 自带快 1.5–3 倍:

原 PyTorchDS fused 对应
torch.optim.Adam deepspeed.ops.adam.FusedAdam
torch.optim.AdamW 同上(自动加 weight decay)
CPU Adam(offload) deepspeed.ops.adam.DeepSpeedCPUAdam
torch.optim.Lamb deepspeed.ops.lamb.FusedLamb

用法:直接在 ds_config 写 "optimizer": {"type": "AdamW"},DS 自动选 FusedAdam(如果 op 已编);offload 时自动选 CPUAdam。不需要你手写 import

注意 如果 ds_report 显示 FusedAdam: [NO],说明 op 没编。第 2 章 2.3 节里 DS_BUILD_FUSED_ADAM=1 重装一次。

11.5 activation checkpointing

大模型激活内存约:

$$M_\text{act} \approx B \cdot L \cdot H \cdot (\text{constant})$$

$B$ batch、$L$ sequence length、$H$ hidden size。开 checkpointing 后激活内存降到 $\approx \sqrt{L}$ 倍数级。代价:反向时要重做一次 forward,训练时间 +25%–35%

三种打开方式:

地方怎么写
HF Trainer TrainingArguments(gradient_checkpointing=True)
原生 DS ds_config 加 activation_checkpointing
模型内部 model.gradient_checkpointing_enable()(HF 模型)

同时开多个不会冲突,DS 内部去重。

11.6 torch.compile 加持

PyTorch 2.x 的 compile() 能给训练带 10–30% 提速。DS 0.14+ 支持:

{
  "torch_compile": {
    "enabled": true,
    "backend": "inductor"
  }
}

注意:

11.7 autotuning:让 DS 帮你调

DS 自带 autotuner:

{
  "autotuning": {
    "enabled": true,
    "fast": true,
    "results_dir": "autotune_results"
  }
}

启动命令加 --autotuning run

deepspeed --autotuning run --num_gpus 8 train.py --deepspeed --deepspeed_config ds_config.json

它会扫一组合理的 micro batch / accum / zero stage 组合,跑短训练测吞吐,给一份最佳推荐。第一次接触新硬件时极有用

11.8 一份"性能体检"清单

检查怎么修
1ds_report 里 FusedAdam / CPUAdam 都 [OKAY]?否则重装编译 op
2开了 overlap_comm 必开
3开了 contiguous_gradients 必开
4bucket size 在 5e8 量级? 过小会拖;过大占显存
5开了 activation_checkpointing 大模型必开,HF Trainer 用 gradient_checkpointing
6用 bf16 而非 fp16? A100/H100 用 bf16
7没有 offload 也能装下? 能不 offload 就不 offload
8num_micro_batches ≥ 4 × PP? 降低 pipeline bubble
9flash_attn 装好? Attention 大头
10样本长度齐整 / 开 packing? 否则显存波动大
11NCCL_DEBUG=INFO 查 P2P / NVLink 是否启用? 跨机 InfiniBand 必查
12autotuner 跑过一次? 大改架构 / 换卡时建议跑

11.9 实战吞吐参考(A100 80G)

模型 / 配置tokens / sec / GPU
7B + bf16 + ZeRO-2 ~4500
7B + bf16 + ZeRO-2 + flash_attn ~5500
13B + bf16 + ZeRO-2 ~2800
13B + bf16 + ZeRO-3 ~2200
13B + bf16 + ZeRO-3 + offload optim ~1600
13B + bf16 + ZeRO-3 + offload optim + ZenFlow~2000(blog 数据,未本机复测)
13B + bf16 + ZeRO-3 + DeepCompile ~2600(blog 数据,未本机复测)
70B + bf16 + ZeRO-3 + offload optim+param~250
70B + bf16 + ZeRO-3 + offload + SuperOffload~380(blog 数据,未本机复测)

具体数字会因模型架构、batch size、context length 浮动 ±30%;用作"我的训练是不是远低于预期"的参考标尺。

11.10 Coordinator 八个 timer 的实战用法

ch05.11.3 列了 Stage 3 PartitionedParameterCoordinator 的八个 timer。wall_clock_breakdown: true 时它们会逐步打印——下表给出"这个 timer 大代表哪类问题":

timer大时意味着第一反应调什么
FORWARD_FETCH_SUBMIT 每次 forward 进入 module 都要新提一次 all-gather(没命中 prefetch)查 trace 是否 INVALID(ch05.11.1);模型里去掉 rank 分支
FORWARD_FETCH_WAIT 提交了但还在等带宽升网络(IB / NVLink);减小 reduce_bucket_size 提早释放
FORWARD_PREFETCH_SUBMIT 预取量大(不一定坏,但占带宽)调小 stage3_prefetch_bucket_size
FORWARD_ALL_GATHER 实际 all-gather 字节大stage3_param_persistence_threshold(小参数不切,省 all-gather)
BACKWARD_FETCH_WAIT 反向时也要再 gather 一遍参数contiguous_gradients;保证 overlap_comm
BACKWARD_PREFETCH_SUBMIT 反向预取频繁 同上;考虑提高 stage3_max_live_parameters
BACKWARD_ALL_GATHER 反向 all-gather 大头 checkpointing 用 partition_activations 配合
"optim_step" (BF16/FP16 wrapper) step 本身慢 用 FusedAdam;offload 时检查 cpu_adam 是否真的编了

一个经验顺序:先看 *_WAIT 大不大;其次看 ALL_GATHER 大不大;最后看 SUBMIT。WAIT 大几乎都是带宽 / 拓扑问题,调字段救不回来。

11.11 ZeRO++(HPZ 分层 + 量化通信)

ZeRO++ 的核心 idea:节点内通信便宜(NVLink ~ 600 GB/s)、节点间通信贵(IB ~ 50 GB/s)。所以——

对应字段(ds_config):

字段含义典型值
zero_optimization.zero_hpz_partition_size"节点内"分区数;一般 = 节点的 GPU 数(如 8)8
zero_optimization.zero_quantized_weights 跨节点传 weight 时 int8 量化true(多机时开)
zero_optimization.zero_quantized_gradients跨节点传 grad 时 int4 量化true(多机时开)

陷阱:

11.12 DeepCompile:把 ZeRO 和 torch.compile 真正打通

torch.compile 单独用不难,但跟 ZeRO-3 的"参数动态 all-gather / release" 配合不是显然的——参数地址会变,graph 重编频率高。DeepCompile(deepspeed/compile/)的工作是把 ZeRO 的 hook 行为转成编译器能理解的 IR pass

字段含义
compile.deepcompile: true 启用
compile.free_activation 编译期分析后激进释放激活(比 ckpt 更细粒度)
compile.offload_activation 激活也搬 CPU
compile.offload_opt_states 和 ZeRO offload 协同(避免重复搬)
compile.offload_parameters 同上
compile.double_buffer 双缓冲通信,进一步藏延迟
compile.passes 启用哪些 pass,如 ["z1", "z3", "autosp"]

关键源码:compile/backend.py:44GraphOrder 跟踪 frame ID 与图执行顺序;line 79 register_compile_pass(name, fn) 注册 custom pass;line 95 launch_compile_passes(global_steps) 按 step 触发 pass。pass 调度逻辑在 compile/list_schedule.py

启用前置:PyTorch 2.x 带 torch._dynamo、装好 functorch、AOT autograd 可用。第一步 step 会卡几分钟编译。

11.13 Autotuner 搜索空间

deepspeed/autotuning/autotuner.py:42Autotuner 在跑一组短训练实验找最佳配置。搜索维度(你不写也会自动展开):

维度枚举范围
ZeRO stage 0 / 1 / 2 / 3
offload_optimizer.device none / cpu (/ nvme)
offload_param.device none / cpu
micro_batch_size_per_gpu 1, 2, 4, 8, ...(直到 OOM)
gradient_accumulation_steps 满足总 batch 的几个值

三种 tuner(autotuning/tuner/):

"max_evals": 30 控制最多跑多少组合。结果写到 results_dir,里面 exps_dir/exps_*.json 是每次实验的 config,results.csv 是吞吐对比。第一次接触新硬件时极有用——比手调省两天。