Skip to content

Commit 7499495

Browse files
committed
[doc] chore: update CUDA graphs guidance with GPT-OSS validation
Capture the measured GPT-OSS CUDA graph results and current rollout blockers so the stable docs and skill card reflect the latest validation state. Signed-off-by: yaoyu-33 <yaoyu.094@gmail.com> Made-with: Cursor
1 parent 856febf commit 7499495

2 files changed

Lines changed: 417 additions & 67 deletions

File tree

‎docs/training/cuda-graphs.md‎

Lines changed: 198 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,93 +1,233 @@
11
# CUDA Graphs
22

33
CUDA graphs capture a sequence of GPU operations once and replay them with
4-
minimal host overhead, eliminating repeated kernel-launch and driver costs on
5-
every training step. Megatron Bridge supports two capture implementations and
6-
fine-grained scope selection to balance performance gain against memory cost.
4+
minimal host overhead, reducing repeated kernel-launch and driver costs on
5+
every training step.
76

8-
This page is the stable overview for what CUDA graphs are, when to use them,
9-
and which constraints are durable. For operational setup, code anchors, and
10-
verification commands, see [skills/perf-techniques/cuda-graphs/SKILL.md](../skills/perf-techniques/cuda-graphs/SKILL.md).
7+
This page is the stable guide for what CUDA graphs are, when they help, and
8+
what tradeoffs to expect. For exact enablement knobs, code anchors, and
9+
verification commands, see `skills/perf-techniques/cuda-graphs/SKILL.md`.
1110

1211
## What It Is
1312

14-
CUDA graphs work by recording a sequence of GPU operations (kernels, memory
15-
copies, etc.) into a graph during a capture phase, then replaying that graph
16-
on subsequent steps. This eliminates per-step host-side overhead such as
17-
kernel launch latency and driver API calls.
13+
CUDA graphs record a fixed sequence of GPU work during a capture phase and then
14+
replay that sequence on later steps. The main benefit is lower host-side
15+
launch overhead.
1816

19-
In Bridge, there are two capture implementations:
17+
Megatron Bridge supports two capture implementations:
2018

2119
| `cuda_graph_impl` | Mechanism | Scope support |
2220
|---|---|---|
23-
| `"local"` | MCore `CudaGraphManager` / `FullCudaGraphWrapper` | `full_iteration` (whole fwd+bwd) |
21+
| `"local"` | MCore `CudaGraphManager` / `FullCudaGraphWrapper` | `full_iteration` |
2422
| `"transformer_engine"` | TE `make_graphed_callables()` per layer | `attn`, `mlp`, `moe`, `moe_router`, `moe_preprocess`, `mamba` |
25-
| `"none"` (default) | Disabled | — |
23+
| `"none"` | Disabled | — |
24+
25+
`"local"` captures the whole forward-backward iteration. `"transformer_engine"`
26+
captures selected submodules and is usually the more flexible default path.
27+
28+
## What Problem It Solves
29+
30+
CUDA graphs mainly solve launch-bound training steps where GPU compute is fast
31+
enough that repeated host-driver submission overhead becomes noticeable.
32+
33+
This is most useful when:
34+
35+
- tensor shapes are static across steps
36+
- the workload has high step frequency or relatively small kernels
37+
- the run has enough memory headroom to keep graph buffers resident
38+
39+
It is less about changing the math and more about reducing runtime overhead.
40+
41+
## Impacted Training Dimensions
42+
43+
| Dimension | Effect | Confidence | Why |
44+
|---|---|---|---|
45+
| `speed` | `+` to `++` | medium | Replays pre-captured GPU work and reduces launch overhead. |
46+
| `memory` | `-` | high | Graph buffers stay allocated for replay and reduce memory reuse. |
47+
| `scale` | `+/-` | low | Can help at scale if launch overhead matters, but memory overhead can limit larger configs. |
48+
| `convergence` | `0` | medium | Intended to preserve training math when capture constraints are satisfied. |
49+
| `stability` | `-` | medium | Adds stricter runtime constraints around shapes, supported scopes, and environment settings. |
2650

2751
## When to Use It
2852

29-
CUDA graphs are most effective when:
53+
Enable CUDA graphs when all of the following are mostly true:
54+
55+
- sequence length and micro-batch size are static
56+
- host overhead is a meaningful part of step time
57+
- the run has spare memory budget
58+
- you want throughput improvement without changing the training objective
59+
60+
As a rule of thumb:
61+
62+
- prefer `transformer_engine` scoped graphs for the safer first rollout
63+
- use `local` `full_iteration` graphs only when you specifically want the
64+
largest launch-overhead reduction and can accept the stricter constraints
65+
66+
## When Not to Use It
67+
68+
Avoid CUDA graphs when any of these are true:
69+
70+
- sequence length or batch shapes vary step to step
71+
- CPU offloading is enabled
72+
- memory is already tight, especially with `PP > 1`
73+
- you rely on runtime checks that conflict with `full_iteration` capture
74+
- you need unsupported scope combinations for MoE or recompute paths
75+
- SFT/LoRA with packed sequences (`packed_sequence=True`) — TE-scoped graphs
76+
cannot capture `packed_seq_params` (non-Tensor input)
77+
- full activation recompute (`recompute_granularity=full`) with TE-scoped
78+
graphs — only `local` full-iteration graphs support full recompute
3079

31-
- **Tensor shapes are static** across training steps (fixed sequence length,
32-
fixed micro-batch size). Variable-length sequences break graph replay
33-
assumptions.
34-
- **Host overhead is significant** relative to GPU compute — smaller models
35-
or high step rates benefit most.
36-
- **Memory budget allows it** — graph capture allocates static buffers,
37-
typically adding a few GB. Models with `PP > 1` can consume over 10 GB
38-
of additional memory.
80+
## Feature Interactions
3981

40-
### Local full-iteration graphs
82+
The most important interactions are:
4183

42-
Captures the entire forward-backward pass as one graph. Provides the highest
43-
host-overhead reduction but requires disabling NaN checks and has the largest
44-
memory footprint.
84+
- `use_te_rng_tracker` and `rng.te_rng_tracker`: required when CUDA graphs are enabled
85+
- `rerun_state_machine.check_for_nan_in_loss`: must be disabled for `local` + `full_iteration`
86+
- MoE routing scopes: `moe` and `moe_router` are mutually exclusive
87+
- `moe_preprocess`: requires `moe_router`
88+
- `delay_wgrad_compute`: adds extra constraints when captured scopes include attention or MoE router
89+
- `PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True`: requires `NCCL_GRAPH_REGISTER=0` in the relevant path
90+
- CPU offloading: incompatible
4591

46-
### Transformer Engine scoped graphs
92+
These interactions are stable enough to treat as design constraints, not just
93+
debugging tips.
4794

48-
Captures individual layer components (attention, MLP, MoE router, etc.)
49-
through TE. More flexible, works with MoE models where only dense modules
50-
can be graphed, and supports selective scope combinations.
95+
## Bridge Configuration
5196

52-
## Configuration
97+
Minimal high-level configuration:
5398

5499
```python
55-
cfg.model.cuda_graph_impl = "transformer_engine" # or "local"
56-
cfg.model.cuda_graph_scope = ["attn", "moe_router"] # scope list
57-
cfg.model.cuda_graph_warmup_steps = 3 # warmup before capture
58-
cfg.rng.te_rng_tracker = True # required
100+
cfg.model.cuda_graph_impl = "transformer_engine" # or "local"
101+
cfg.model.cuda_graph_scope = ["attn"] # or other valid scopes
102+
cfg.model.cuda_graph_warmup_steps = 3
103+
cfg.model.use_te_rng_tracker = True
104+
cfg.rng.te_rng_tracker = True
59105
```
60106

61-
### Key constraints
107+
If you use `local` + `full_iteration`, also disable:
62108

63-
- `cfg.rng.te_rng_tracker` must be `True` when `cuda_graph_impl != "none"`.
64-
- `full_iteration` scope requires `cuda_graph_impl = "local"` and
65-
`rerun_state_machine.check_for_nan_in_loss = False`.
66-
- MoE models with token-dropless routing have limited graph support
67-
(dense modules only).
68-
- `cuda_graph_impl = "none"` automatically clears `cuda_graph_scope`.
109+
```python
110+
cfg.rerun_state_machine.check_for_nan_in_loss = False
111+
cfg.ddp.check_for_nan_in_grad = False
112+
```
113+
114+
## Minimal Runnable Example
115+
116+
For a minimal Bridge-facing example, start from the functional smoke test:
117+
118+
- `tests/functional_tests/recipes/test_llama_recipes_pretrain_cuda_graphs.py`
119+
120+
For a lightweight CLI-driven path, use the performance harness with scoped
121+
capture and a small model recipe.
122+
123+
## Expected Metric Changes
69124

70-
## MoE Considerations
125+
| Metric | Expected Change | Conditions | Evidence |
126+
|---|---|---|---|
127+
| `step_time` | ~15-25% down | Static shapes, MoE, TE scoped (`attn+moe_router+moe_preprocess`) | measured: Qwen3-30B-A3B 623→484ms; GPT-OSS-20B 467-520→391-399ms |
128+
| `tokens_per_sec` | ~20-33% up | Same as above | measured: Qwen3-30B-A3B 214→274 TFLOP/s/GPU; GPT-OSS-20B 37.9-42.2→49.4-50.4 |
129+
| `peak_memory` | same pre-capture | TE scoped graphs on H100 80GB | measured: no increase in allocated memory on Qwen3-30B-A3B and GPT-OSS-20B |
130+
| `OOM risk` | up | Tight memory budget or large MoE configs | measured: GPT-OSS-120B blocked at ~70/79 GB before capture |
71131

72-
MoE models often cannot graph the full expert dispatch path due to dynamic
73-
token routing. Common practice:
132+
Do not assume a fixed throughput gain across models. The improvement depends on
133+
how launch-bound the workload is and how much scope is captured.
74134

75-
- Graph `moe_router` and `moe_preprocess` (static portions).
76-
- Add `attn` scope for the dense attention blocks.
77-
- Leave expert dispatch in eager mode.
135+
## Measured Results (Qwen3-30B-A3B MoE, H100, TP2 PP2 EP4, 2 nodes)
78136

79-
Do not combine `moe` scope with `moe_router` scope — they are mutually
80-
exclusive.
137+
### Pretrain
81138

82-
## Memory Impact
139+
TE-scoped CUDA graphs (`attn + moe_router + moe_preprocess`) on Qwen3-30B-A3B
140+
with mock data, GBS=8, MBS=1:
141+
142+
- **~22% faster** iteration time (484ms vs 623ms steady-state)
143+
- **~28% higher TFLOP/s** (274 vs 214 TFLOP/s/GPU)
144+
- **Loss matches** baseline within 0.001 across all 20 iterations
145+
- 24 graphable layers per pipeline rank, capture completes in ~5.6s
146+
- No memory increase pre-capture, no NCCL errors
147+
148+
### SFT (packed sequences)
149+
150+
SFT with packed sequences (`packed_sequence=True`, SQuAD dataset) hits a
151+
hard incompatibility:
152+
153+
```
154+
AssertionError: CUDA graph accepts only Tensor inputs.
155+
inference_context and packed_seq_params are excluded from input list.
156+
```
83157

84-
CUDA graphs allocate static buffers that persist for the duration of training.
85-
Expect a few GB of additional memory. With `PP > 1`, memory overhead can
86-
exceed 10 GB due to pipeline-stage buffering. Plan activation memory
87-
accordingly.
158+
TE-scoped CUDA graphs require all forward inputs to be Tensors. Packed
159+
sequence SFT passes `packed_seq_params` (a dataclass), which is not captured.
160+
The baseline SFT runs fine without graphs (~880ms/iter).
161+
162+
Workarounds: disable packing, or use `local` full-iteration graphs. Also make
163+
sure the TE/container build actually supports the packed-sequence attention
164+
backend your recipe needs.
165+
166+
## Additional Validation (GPT-OSS, H100, Mar 2026)
167+
168+
### GPT-OSS-20B pretrain
169+
170+
TE-scoped CUDA graphs on `gpt-oss-20b` with `TP2 PP4 EP4 CP1`, 2 nodes, and
171+
mock data:
172+
173+
- capture succeeds with 6 graphable layers per pipeline rank; capture completes
174+
in ~0.95s
175+
- steady-state iteration time improves by ~16-24% (467-520ms to 391-399ms)
176+
- throughput improves by ~19-33% (37.9-42.2 to 49.4-50.4 TFLOP/s/GPU)
177+
- the pre-capture memory report is unchanged and the 20-iteration run completes
178+
without NCCL or illegal-memory-access errors
179+
- loss comparison is inconclusive: the first ~10 post-capture iterations are
180+
close, but the run used mock data, `GBS=4`, and a production LR, so later
181+
divergence is too noisy to treat as a correctness signal
182+
183+
A cleaner loss-match pass should lower LR and/or raise GBS before drawing
184+
equivalence conclusions.
185+
186+
### GPT-OSS-20B SFT and LoRA
187+
188+
Both packed-sequence finetuning workloads were blocked in the
189+
`mbridge-260128.sqsh` container before any CUDA-graph-specific behavior could
190+
be isolated:
191+
192+
- baseline and graphed runs both fail with no TE attention backend available
193+
for the packed-sequence path
194+
- treat this as an environment/container blocker first, not as proof that CUDA
195+
graphs are or are not the root cause
196+
- after upgrading TE/container support, these workloads still need separate
197+
validation because packed-sequence plus TE-scoped graphs remains a sensitive
198+
combination
199+
200+
### GPT-OSS-120B pretrain
201+
202+
`gpt-oss-120b` pretrain at `TP2 PP4 EP8`, 4 nodes, hits OOM on iteration 2:
203+
204+
- iteration 1 already uses ~69-70 GB allocated and ~72-73 GB reserved on 79 GB
205+
H100s
206+
- the failure is a `torch.OutOfMemoryError` on an additional 1.54 GiB
207+
allocation
208+
- treat larger MoE rollouts as memory-gated even before capture benefits are
209+
realized; more PP or different memory settings may be needed
210+
211+
## Common Failure Modes
212+
213+
- Missing TE RNG tracker settings causes an assertion before training starts.
214+
- Dynamic sequence or batch shapes break capture or replay assumptions.
215+
- `local` `full_iteration` graphs fail when NaN-loss checking is still enabled.
216+
- Illegal scope combinations such as `moe` with `moe_router` fail validation.
217+
- Runs that fit in eager mode can OOM after enabling graphs because buffers stay pinned.
218+
- Full activation recompute (`recompute_granularity=full`) with TE-scoped graphs
219+
asserts: `full recompute is only supported with full iteration CUDA graph`.
220+
Disable recompute or switch to `local` implementation.
221+
- Packed-sequence SFT/LoRA asserts: `CUDA graph accepts only Tensor inputs.
222+
inference_context and packed_seq_params are excluded from input list.`
223+
TE-scoped graphs cannot capture non-Tensor forward arguments.
224+
- Older TE/container builds can fail packed-sequence attention before graph
225+
capture begins (`Available backends = {FlashAttention=False,
226+
FusedAttention=False, UnfusedDotProductAttention=False}`). In that case the
227+
baseline and graph runs are both blocked, so fix the environment first.
88228

89229
## Related Docs
90230

91-
- [docs/performance-guide.md](../performance-guide.md)
92-
- [docs/training/communication-overlap.md](communication-overlap.md)
93-
- [skills/perf-techniques/cuda-graphs/SKILL.md](../skills/perf-techniques/cuda-graphs/SKILL.md)
231+
- [Performance Guide](../performance-guide.md)
232+
- [Communication Overlap](communication-overlap.md)
233+
- `skills/perf-techniques/cuda-graphs/SKILL.md`

0 commit comments

Comments
 (0)