[PyTorch] Share CUDA graph memory across dynamic CP variants - #3353
Draft
xiaoyao0115 wants to merge 16 commits into
Draft
[PyTorch] Share CUDA graph memory across dynamic CP variants#3353xiaoyao0115 wants to merge 16 commits into
xiaoyao0115 wants to merge 16 commits into
Conversation
Signed-off-by: xiaoyao0115 <1804647152@qq.com>
for more information, see https://pre-commit.ci
Signed-off-by: xiaoyao0115 <1804647152@qq.com>
xiaoyao0115
force-pushed
the
test/dcp-cg-gap-fix
branch
from
August 12, 2026 09:24
e1cfcd3 to
c095214
Compare
added 5 commits
August 17, 2026 12:37
Signed-off-by: Tailai Ma <tailaim@ferries-work.nvidia.com>
Signed-off-by: Tailai Ma <tailaim@ferries-work.nvidia.com>
Signed-off-by: Tailai Ma <tailaim@ferries-work.nvidia.com>
Signed-off-by: Tailai Ma <tailaim@ferries-work.nvidia.com>
Signed-off-by: Tailai Ma <tailaim@ferries-work.nvidia.com>
Signed-off-by: Tailai Ma <tailaim@ferries-work.nvidia.com>
Signed-off-by: Tailai Ma <tailaim@ferries-work.nvidia.com>
Signed-off-by: Tailai Ma <tailaim@ferries-work.nvidia.com>
for more information, see https://pre-commit.ci
Signed-off-by: Tailai Ma <tailaim@ferries-work.nvidia.com>
xiaoyao0115
force-pushed
the
test/dcp-cg-gap-fix
branch
from
August 21, 2026 20:20
6bd367b to
3710b66
Compare
Signed-off-by: Tailai Ma <tailaim@ferries-work.nvidia.com>
xiaoyao0115
force-pushed
the
test/dcp-cg-gap-fix
branch
from
August 21, 2026 20:25
3710b66 to
de59f96
Compare
added 2 commits
August 22, 2026 07:54
Signed-off-by: Tailai Ma <tailaim@ferries-work.nvidia.com>
Signed-off-by: Tailai Ma <tailaim@ferries-work.nvidia.com>
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.
Summary
make_graphed_callablestorch.cuda.MemPoolMotivation
Dynamic context parallelism captures several CP-size alternatives. Capturing every alternative with independent graph allocations multiplies CUDA graph pool memory. Same-slot CP alternatives are mutually exclusive at replay time, so they can share the canonical branch's physical allocations.
This change keeps one private allocator pool and captures CP alternatives as checkpoint branches. Same liveness slots reuse the same addresses while retaining compatibility, saved-tensor interval, allocator-liveness, and StorageImpl ownership checks.
Compatibility and private contract
The slot-memory interface is private. It accepts exactly:
(saved_arena_id, physical_slot, io_branch_id, model_chunk, layer, warmup_alias_group, user_grad_arena_id)The paired MCore PR constructs the scheduler-reachable plan and lockstep branch order. Both PRs must roll out together. The ordinary CUDA graph path remains unchanged when no slot plan is supplied.
Only PyTorch's native CUDA caching allocator is supported. Other backends, including
cudaMallocAsync, are rejected before capture.Companion change
3de21ef4a70297bfeea1f7bec1f61771f094b81bValidation
Final TE head:
b07d7df77085964df35f5e3a2dd5678f7bc75d68.6431883): all 35 tests added by this PR passed; the parent surface reported332 passed, 105 skipped, 35 deselected6427059):29 passed, 443 deselected6413872):22 passed, 443 deselectedand338 passed, 105 skipped, 22 deselected6420861):26 passed, 443 deselected; exact and nonzero-offset aliases, parameter/buffer aliases, gradients, capture-state cleanup, and tensor-subclass fail-fast all passed6427059): trailing, middle, and consecutive-middle chunks with no graphable layers passed as part of the complete slot suite (29 passed, 443 deselected); the pre-fix control failed atgraph.py:1991with an out-of-range slot lookup (Slurm6425845)6413150):3 passedpylint==3.3.1command with no diagnostics and10.00/10; repository-exact local hooks passCorepasses.AllandPyTorchended only with the hosted-runner-lost-communication annotation and no downloadable job log; the available fork token cannot rerun those upstream jobs.JAXfails while building unchangedcommon/activation/gelu.cubecausefatbinarycannot opengelu.compute_75.ptx.git diff --checkpassed; every authored commit has a DCO trailer6413332): THD graph core56 passed, 11 skipped, dynamic static-input selection3 passed, router parent-group1 passed, and packing10 passed6413335): complete THD file65 passed, 2 skippedon both ranks; four-rank reroute/DCP and mixed-subgroup parent reduction passed on every rank6414410): CP4+CP2+CP1+CP1 replayed 50 times against one parent communicator6414336,6414384): 200 schedules / 2220 microbatches plus 800 scheduler-bound cases, with checkpoint-failure rollback passing in both runs6414248): CP1+CP2 on every training call, 16 graph/eager steps, zero skipped/NaN, exact sample and schedule match, maximum loss delta0.002740(0.02457%)6419837): capture, replay, training, validation, and test completed0:0with zero skipped/NaN; predecessor-head job6421024below is the authoritative 50-step Qwen3-30B control6421024): fixed-CP2 graph/eager and multi-CP1/2 graph/eager each completed 50 steps with zero skipped/NaN; graph and eager LM loss matched exactly at every logged step within each CP policy; multi versus fixed had the same maximum0.000040(0.0003238%) difference in graph and eager controls. The final commit only skips schedule events whose chunks contain zero graphable layers; every Qwen chunk in this control was nonempty.6424219): fixed captured 96 callables and multi captured 192; resetting every graph changed no allocator or whole-device counter immediately, callable release returned the graph-pool reservation, explicit-root release matched the root ledger, and the multi-minus-fixed allocator-external residual remained exactly+114 MiB/rank6424678): fixed and multi had the same initial+630 MiB/rankcommunicator warmup and zero external growth in the capture-time repeat; slot-bank preparation added zero external bytes; graph-bank capture added150/148 MiBfor fixed and264/262 MiBfor multi, an exact+114 MiB/rankmulti-minus-fixed delta6429674): eager, native non-slot graph, and slot graph were compared. Both graph paths preserved output-output overlap, relative byte offset, write propagation, values, and gradients; neither graph path retained storage identity with the runtime input. Native graph retained an incidental alias to the capture sample, while slot graph intentionally moved that output to its arena.6430636): two liveness colors starting from one caller-owned sample storage received distinct staging targets and produced correct values and gradientsThe complete test file also exposed a separate same-process runtime limitation. After hundreds of
parent TE graph captures, exact-head job
6430234segfaulted while a newly constructed usertorch.cuda.MemPoolwas unwound. Unmodified TE base job6431216reproduces the same triggerbefore any slot code exists. A pure PyTorch control retaining up to 512 graphs over 256 pool
handles passes, so the additional TE/runtime lifecycle condition is not yet identified. This is
why the exact PR regression result above uses clean processes; it does not claim arbitrary
same-process repeated TE capture lifecycles are proven safe.
Existing production-scale evidence predates the final audit commit: Qwen3-235B, PP4/VPP12, DCP CP4/8 completed 50/50 steps with no skipped or NaN iterations; DCP+CUDA graph versus eager DCP had maximum logged loss difference
0.00398(0.0351%). This validates the core design but is not a final-head rerun.In the predecessor-head Qwen3-30B control, slotized multi-CP1/2 versus slotized fixed-CP2 used one allocator pool in both cases and had a byte-identical explicit-root ledger on every rank. After step 50 and
empty_cache(), the whole-device delta was+738 .. +832 MiB/rank(+0.4624 .. +0.5151%). Only+7.737 .. +7.807 MiB/rankremained allocated; reserved memory was+624 .. +718 MiB/rank, and the derived allocator-external delta was exactly+114 MiB/rank. Allocator snapshots place the reserved delta in inactive allocator topology, primarily the graph-private pool. Lifecycle and phase probes establish that the external+114 MiB/rankis created during capture of the additional CP graph bank, survives graph reset plus callable/root release, and is neither explicit tensor-root memory nor additional communicator initialization at the measured boundary. The available counters do not distinguish CUDA graph executable state from graph-capture-time driver or library retained/cache state, so no narrower object label is claimed.Known limitations and review notes
expandable_segmentsrestoration infers its original state from environment because PyTorch exposes no stable getter; prior programmatic allocator-setting changes can diverge__call__, RNG, and allocator state and is not safe for concurrent callshelper lifetimes that share logical CP groups are unsupported because one teardown can remove the
other's mapping
MemPoolafter a long sequence of existing TE graph tests can hit a baseline same-process segfault in the tested container; it reproduces without this PR and is not exercised by normal initial DCP capture