compute 和 communication 怎么 overlap?
什么时候 overlap 不起作用?
先弄清楚:每个概念是什么
- Overlap 与 exposed communication
- 通信和计算同时跑,step time 只多出没被盖住的那部分通信(exposed,trace 上怎么认见 训练慢排查)。像洗衣机转着的时候去做饭。
- CUDA stream
- GPU 上的一条任务队列,同一条里按顺序执行,不同条之间可以并发。NCCL collective 放在单独的 comm stream,靠 event 表达依赖。
- Gradient bucketing(DDP)
- 把 gradient 按
model.parameters()的逆序(近似 backward 的顺序)装进桶(默认 25 MiB),一桶装满就异步 all-reduce。桶太小 → 一堆低效的小 collective;只用一个大桶 → 得等 backward 全算完。 - Prefetch(FSDP / ZeRO-3)
- 参数平时分片存放,用之前要 all-gather。提前发下一层的 all-gather,算当前层时下一层参数已经在路上。
- Critical path 上的通信
- 下一步计算直接要用通信结果(TP 的 all-gather 之后紧接 GEMM),旁边没有独立计算可做,只能把 GEMM 切成块和通信流水起来。
看图:两条 stream 上的 overlap
30 秒回答
Run collectives on a separate CUDA stream while independent compute runs on the default stream, so only the unhidden part adds to step time. DDP buckets gradients in roughly reverse layer order; when a bucket fills during backward, its all-reduce launches asynchronously while earlier layers keep computing. FSDP and ZeRO-3 prefetch the next layer's parameter all-gather. Tensor-parallel communication is on the critical path, so the GEMM is split into chunks and pipelined with the all-gather or reduce-scatter. Overlap fails when communication outlasts the compute available to hide it.
关键公式
\(T_{\text{hide}}\):能和通信并行的独立计算;\(T_{\text{last}}\):最后一桶(或第一次 all-gather)总是盖不住。
| 并行 | 通信 | 和什么 overlap | 开关 |
|---|---|---|---|
| DP(DDP) | gradient all-reduce | 剩下的 backward | bucket;Megatron --overlap-grad-reduce |
| ZeRO / FSDP | 参数 all-gather、gradient reduce-scatter | 相邻层的 forward / backward | FSDP prefetch;Megatron --overlap-param-gather |
| TP + SP | activation all-gather / reduce-scatter | 同一 GEMM 的其他 chunk | --tp-comm-overlap(Transformer Engine) |
| PP | p2p send / recv | 1F1B 稳态里另一个 micro-batch | overlap_p2p_comm(需 virtual pipeline) |
手算例子
1B 参数、bf16 gradient 2 GB,DP = 8 跨机、每卡 50 GB/s:ring all-reduce 70 ms(见 all-reduce 页)。设 forward 100 ms、backward 200 ms:
- 不 overlap:\(100 + 200 + 70 = 370\) ms
- DDP 默认 25 MiB 桶:只剩最后一桶 \(2\cdot\tfrac78\cdot 25\) MiB / 50 GB/s ≈ 0.9 ms → ≈ 301 ms,快 1.23×
- backward 只有 50 ms(micro-batch 太小):至少露出 20 ms → 用 gradient accumulation(
no_sync())少同步几次
什么时候 overlap 不起作用
- 通信比可盖住的计算长:小 batch、慢网络、TP 跨机
- 抢 SM:NCCL 也是 CUDA kernel,channel 越多占的 CUDA block 越多,计算会变慢
- 顺序不对:bucket 顺序和实际 backward 顺序不一致,一个晚到的 gradient 卡住整桶;CPU launch 跟不上或
.item()同步,comm kernel 发晚了 - 显存:prefetch 要同时留两层完整参数;FSDP 的 rate limiter 最多两个 in-flight all-gather
必答点
- comm 放独立 stream,和无依赖的计算并发;只算 exposed 部分
- DDP:autograd hook + bucket,桶满即 async all-reduce,最后一桶暴露
- FSDP / ZeRO-3:提前 all-gather 下一层参数(backward prefetch 默认开,forward prefetch 默认关)
- TP:GEMM 切块 + p2p ring 流水;PP:1F1B 稳态里 p2p 和别的 micro-batch 计算重叠
- 失败:comm > 可盖住的计算、抢 SM、顺序 / 同步问题、显存
追问
CUDA_DEVICE_MAX_CONNECTIONS 是干什么的?Megatron 为什么要设成 1?
它控制 host 到 GPU 的硬件 work queue 数(默认 8)。设成 1 后所有 stream 的 kernel 按 launch 顺序进同一个队列,Megatron 在 backward 里先 launch dX 的 async all-reduce、再 launch dW 的 GEMM,通信就先被调度。TP 或 CP > 1 且 Blackwell 之前的 GPU 上 Megatron 要求设 1,FSDP 要求不设 1。
FSDP 的 backward prefetch 具体做什么?
backward 里本来是"当前层 reduce-scatter → 下一层 all-gather",同一条 NCCL stream 会让两次通信连着暴露;prefetch 把下一层的 all-gather 提到当前 reduce-scatter 之前发出。
和 launch bound 有什么关系?
kernel 很小、CPU launch 跟不上时,GPU 本来就有空隙,comm kernel 也会发晚,解法见 CUDA Graph。
来源Li et al. 2020(PyTorch DDP) §3.2.2–3.2.3(pp.4–5)· Zhao et al. 2023(PyTorch FSDP) §3.3(pp.6–7)、§3.4.2(p.7)· PyTorch DDP API(bucket_cap_mb)· NeMo Communication Overlap(DP、TP、PP)· Megatron-LM README(overlap flags)、megatron/training/arguments.py、megatron/core/tensor_parallel/layers.py(CUDA_DEVICE_MAX_CONNECTIONS)· CUDA Programming Guide §5.2.3.2 · NCCL env(NCCL_MIN_NCHANNELS)· PyTorch Performance Tuning Guide(no_sync、DDP 顺序)