Repository navigation
[PyTorch] Share CUDA graph memory across dynamic CP variants - #3353
Draft
xiaoyao0115 wants to merge 1 commit into
Draft
xiaoyao0115 wants to merge 1 commit into
xiaoyao0115 wants to merge 1 commit into
Conversation
3 of 6 tasks
xiaoyao0115
force-pushed
the
test/dcp-cg-gap-fix
branch
from
August 12, 2026 09:24
e1cfcd3 to
c095214
Compare
xiaoyao0115
force-pushed
the
test/dcp-cg-gap-fix
branch
2 times, most recently
from
August 21, 2026 20:25
3710b66 to
de59f96
Compare
xiaoyao0115
force-pushed
the
test/dcp-cg-gap-fix
branch
5 times, most recently
from
August 30, 2026 15:28
560c2d3 to
da564cf
Compare
xiaoyao0115
force-pushed
the
test/dcp-cg-gap-fix
branch
from
September 8, 2026 12:08
da564cf to
a0a7dad
Compare
Add a private `_graph_memory_slots` argument to `make_graphed_callables`. Each callable gets a seven-field entry (saved_arena, io_slot, branch, chunk, layer, warmup_group, grad_arena), which lets mutually exclusive graph variants, such as Megatron-LM's per-CP-size graph banks, capture into one `torch.cuda.MemPool`: - Forward replays snapshot the tensors saved for backward into liveness-colored arenas, so a replay of another variant cannot overwrite them before the matching backward. Native saved tensors are grouped by storage identity, and aliases and versions are preserved. - Branches of one slot share output and returned-gradient storage, so each arena is sized by the largest branch rather than the sum. - During slot capture, quantized tensors are decomposed into their physical storages and rebuilt for backward. Fused-wgrad parameters stay autograd inputs on replay, with a zeroed placeholder gradient when they lack `grad_added_to_main_grad`. - Delayed weight-gradient `_order` entries are rejected with slots, because only tensors saved for backward are protected. - `NVTE_CUDA_GRAPH_SLOT_DEBUG=1` adds a host-side check that no other live graph owns the arena or output family a replay reuses. Without slots, capture behaves as before, except that a failed capture now restores GC state, temporary hooks, allocator settings and `.grad` state, and `make_weak_ref` keeps the strides of non-contiguous views. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tailai Ma <tailaim@nvidia.com>
xiaoyao0115
added a commit
to xiaoyao0115/Megatron-LM
that referenced
this pull request
Oct 16, 2026
Dynamic context parallelism (DCP) picks a CP size for every microbatch, and CP communicators are capture-time constants. TECudaGraphHelper therefore captures one graph bank per power-of-two CP size, from --min-dynamic-context-parallel-size to DP x CP, each with the real process group of that size. All banks share one TE memory pool through make_graphed_callables(_graph_memory_slots=...) (NVIDIA/TransformerEngine#3353), so graph memory does not grow with the number of banks. - Slot plan: the physical slot ring is sized from the liveness union of every PP/VPP order the DCP scheduler can produce. Saved-tensor and returned-gradient arenas are colored so that frames that are live at the same time never share storage. - Replay: a layer selects its bank (graphs, process group, static inputs) from PackedSeqParams.local_cp_size, including the mHC attention split and the hybrid wrapper. Router-only captures get a DCP PackedSeqParams so they use the bank's group. - Capture state: THD capture seeds a bounded RoPE table and cu_seqlens per bank, and eager RoPE uses the same bound under DCP. During training the packed microbatch count is checked against the captured range, and the DCP scheduler honors thd_max_packed_sequences, so a pack never exceeds the captured cu_seqlens capacity. Capture can be retried, and capture-only state is released on failure and on delete. - Config rules: DCP with CUDA graphs requires TE graphs and power-of-two DP x CP, and rejects delayed wgrad, EP overlap, moe_paged_stash and mamba scopes. Runs without DCP keep dev behavior. Co-authored-by: HaochenYuan <haocheny@nvidia.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tailai Ma <tailaim@nvidia.com>
xiaoyao0115
force-pushed
the
test/dcp-cg-gap-fix
branch
from
October 16, 2026 05:50
27c0fff to
8980cd0
Compare
xiaoyao0115
added a commit
to xiaoyao0115/Megatron-LM
that referenced
this pull request
Oct 16, 2026
Dynamic context parallelism (DCP) picks a CP size for every microbatch, and CP communicators are capture-time constants. TECudaGraphHelper therefore captures one graph bank per power-of-two CP size, from --min-dynamic-context-parallel-size to DP x CP, each with the real process group of that size. All banks share one TE memory pool through make_graphed_callables(_graph_memory_slots=...) (NVIDIA/TransformerEngine#3353), so graph memory does not grow with the number of banks. - Slot plan: the physical slot ring is sized from the liveness union of every PP/VPP order the DCP scheduler can produce. Saved-tensor and returned-gradient arenas are colored so that frames that are live at the same time never share storage. - Replay: a layer selects its bank (graphs, process group, static inputs) from PackedSeqParams.local_cp_size, including the mHC attention split and the hybrid wrapper. Router-only captures get a DCP PackedSeqParams so they use the bank's group. - Capture state: THD capture seeds a bounded RoPE table and cu_seqlens per bank, and eager RoPE uses the same bound under DCP. During training the packed microbatch count is checked against the captured range, and the DCP scheduler honors thd_max_packed_sequences, so a pack never exceeds the captured cu_seqlens capacity. Capture can be retried, and capture-only state is released on failure and on delete. - Config rules: DCP with CUDA graphs requires TE graphs and power-of-two DP x CP, and rejects delayed wgrad, EP overlap, moe_paged_stash and mamba scopes. Runs without DCP keep dev behavior. Co-authored-by: HaochenYuan <haocheny@nvidia.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tailai Ma <tailaim@nvidia.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Megatron's dynamic context parallelism (NVIDIA/Megatron-LM#6479) captures one CUDA graph bank per context-parallel (CP) size, because CP communicators are capture-time constants. Giving each bank its own memory pool multiplies graph memory by the number of banks.
This PR adds a private
_graph_memory_slotsargument tomake_graphed_callables, which lets mutually exclusive graph variants share onetorch.cuda.MemPool. Without the argument, graph capture behaves as before, apart from the error-path cleanup (change 3) and themake_weak_reffix (change 5).The PR now targets
main(it previously targetedrelease_v2.14) and is a single commit.Type of change
make_weak_refstrides (change 5)Changes
1. Slot memory (
graph.py)Each graph input carries
(saved_arena, io_slot, branch, chunk, layer, warmup_group, grad_arena):saved_arenaholds the tensors the forward saves for backward, plus its public outputs.grad_arenaholds the gradients returned for user inputs. The caller (Megatron) assigns arenas so that inputs share one only if their forward-to-backward lifetimes never overlap in any schedule it replays.(io_slot, chunk, layer)are branches of one slot. They must expose identical output and gradient layouts, and they share output and gradient storage, so each arena is sized by the largest branch rather than the sum.warmup_group.Forward replays snapshot saved tensors into the arenas, so a replay of another variant cannot overwrite them before the matching backward. Native saved tensors are grouped by storage identity, and aliases and versions are preserved.
Contract: only tensors saved for backward (
ctx.save_for_backwardor saved-tensor hooks) are protected. For that reason, delayed weight-gradient_orderentries are rejected when slots are used. Fused-wgrad parameters remain autograd inputs on replay; when a parameter lacksgrad_added_to_main_grad, its placeholder gradient is zeroed, because autograd accumulates it into.grad.2. Quantized tensors (
quantized_tensor.py)During slot capture,
prepare_for_savingdecomposes quantized wrappers into their physical storages, so the arena path can place them; backward then rebuilds the wrappers.3. Capture error paths (
graph.py)If capture fails, GC state, temporary hooks, allocator settings and
.gradstate are restored. This also applies without slots.4. Debug guard
With
NVTE_CUDA_GRAPH_SLOT_DEBUG=1, each replay checks on the host that no other live graph owns the arena or output family it is about to reuse. The variable is documented indocs/envvars.rst.5.
make_weak_ref(utils.py)make_weak_refnow keeps the strides of non-contiguous views. Previously, transposed or expanded static outputs were reinterpreted as row-major after the reuse block dropped their strong references.Possible split
If reviewers prefer, changes 3 (error-path cleanup), 2 (quantized decomposition) and 4 (debug guard) can each become a separate PR ahead of change 1.
Testing
tests/pytorch/test_cuda_graphs.pyadds slot tests. They cover:This commit (on
main53e241c) was built from source in the torch 2.11 NGC container and tested withtests/pytorch/test_cuda_graphs.py. The DPA and FP8-weight-caching graph tests run in separate processes (see the note below the table).-k slot_memorytest_make_graphed_callables_with_dot_product_attentiontest_make_graphed_callables_with_fp8_weight_cachingWith upstream
main'sgraph.pyandquantized_tensor.py, the "all other tests" selection on H100 gives 255 passed and 5 failed. The 5 failures are this PR's new capture-error-path tests.In this container, a process that has run these graph tests segfaults at interpreter exit, after pytest has written its report. This happens with upstream
main's Python files too: the DPA and FP8 graph tests leave the Python heap corrupted, and a later allocation crashes.Megatron validation (unit tests on this TE build, and the Qwen3.5 benchmark) is in NVIDIA/Megatron-LM#6479.
Checklist:
🤖 Generated with Claude Code