Repository navigation
Reuse continuous rollout graphs and release captured resources on shutdown - #9009
Merged
delock merged 4 commits intoNov 13, 2026
Merged
Conversation
jinyouzhi
marked this pull request as ready for review
November 9, 2026 04:26
Codex Review Summary: ✅ CompletedThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
delock
self-requested a review
November 10, 2026 09:01
banxingmjj
pushed a commit
to openanolis/DeepSpeed
that referenced
this pull request
Nov 12, 2026
…edai#9058) ## what `TestZero3SynchronizedEarlyStop` (added in deepspeedai#8390) builds its tensors on `torch.device(f"cuda:{rank}")`. on the cpu multi-rank nightly (`cpu-torch-multi-latest`, `LOCAL_SIZE=4`) the test is admitted by the world-size gate, and the first `torch.tensor(..., device=device)` raises `AssertionError: Torch not compiled with CUDA enabled`. that is the 2026-11-11 recurrence in deepspeedai#9033 (run 42957495892). this takes the device from `get_accelerator().current_device_name()`, which is `cpu` on the cpu accelerator and `cuda:<local rank>` on cuda, so the gpu path is unchanged. ## why this is not a duplicate no open pr touches this test; deepspeedai#9009 and deepspeedai#9037 edit the same file but not this class. ## tests on cpu (8 vcpu x86_64, torch 2.15.0+cpu, python 3.12), with `LOCAL_SIZE=4`: - before: `pytest unit/runtime/rollout/test_hybrid_engine_rollout.py::TestZero3SynchronizedEarlyStop` fails with `Torch not compiled with CUDA enabled`, same traceback as the nightly - after: `1 passed` - `pytest unit/runtime/rollout/`: 94 passed, 2 skipped; `TestZero3SubgroupGeneration` failed locally only because `accelerate` is not installed in my env - flake8 and yapf clean on the changed file not run on gpu; the cuda device string is the same as before apart from using the local rank instead of the global rank. fixes deepspeedai#9033 (the hybrid engine rollout part; the earlier `test_tp_compile.py` failure looked flaky per the thread) Signed-off-by: Dundy Pasupuleti <dundysm@gmail.com> Co-authored-by: Ma, Guokai <guokai.ma@gmail.com>
This was referenced Nov 13, 2026
Collaborator
|
Hi @jinyouzhi , my agent found these issues, can you take a look? Thanks!
|
jinyouzhi
force-pushed
the
perf/rollout-graph-lifecycle
branch
from
November 13, 2026 11:05
dedd6c8 to
2313079
Compare
Contributor
Author
|
@delock Thanks for the review. Addressed in 2313079 (branch rebased onto current master):
Tested on 1× L20: rollout unit + v1 tests give 102 passed, 6 skipped. B70 runs the same files, but the graph-capture cases skip there. Details are in the updated description. #9077 builds on this and will be rebased. |
delock
approved these changes
Nov 13, 2026
Signed-off-by: Jin, Youzhi <youzhi.jin@intel.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Jin, Youzhi <youzhi.jin@intel.com>
Captured kernels read each parameter through the address and strides fixed at capture. A same-address layout change such as transpose_() kept the old signature and replayed with a stale layout. Key the graph state on each parameter's address, device, dtype, shape and strides instead. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Jin, Youzhi <youzhi.jin@intel.com>
Captured decode kernels read buffers such as rotary inv_freq or quant scales at fixed addresses, so a re-registered buffer must trigger a recapture like a replaced parameter. Also normalize an unindexed device and drop a capacity check already done by option validation. Signed-off-by: Jin, Youzhi <youzhi.jin@intel.com>
anyadontfly
added a commit
to anyadontfly/DeepSpeed
that referenced
this pull request
Nov 14, 2026
Resolve conflicts with deepspeedai#9009 (reuse continuous rollout graphs). Take its graph signature, which keys on each parameter's and buffer's address and layout instead of its version, so an in-place optimizer step no longer recaptures, and its shutdown(). Keep this branch's graph loop, which already dropped the prompt width from the signature and keeps its own single-turn capacity check. Port the new GPU test to rollout records, and describe reuse and shutdown() in the graph-capture part of the docs. Signed-off-by: Puyuan Yao <williamyao034@gmail.com>
SuperGoodGame
added a commit
to SuperGoodGame/DeepSpeed
that referenced
this pull request
Nov 14, 2026
Resolves the conflict with deepspeedai#9009 (rollout graph lifecycle) and applies the latest Codex finding. - Keep both new imports (`itertools` from deepspeedai#9009, `math` from this branch). - Documentation: keep deepspeedai#9009's graph-lifecycle text, and state that manual aligned decode fronts are rejected only with Auto disabled while cache trimming is rejected in both graph modes. - Auto now also rejects a custom generation preparer that drops the `attention_mask` key. Previously the missing key fell back to the request mask, so generate() would infer an all-ones mask while the CB routes used the request mask. The mask test covers replaced, in-place edited and dropped masks. Validation (1 x H200, PyTorch 2.9.1+cu128, Transformers 4.57.1): 230 passed, 9 skipped, 1 deselected across the runtime rollout, continuous-batching, interface, static-cache and v1 accelerator suites. Pre-commit passed. Co-Authored-By: Claude Code <noreply@anthropic.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
Keep the continuous-decode graph alive across RL rollout rounds, and add an explicit way to release it.
prompt_lenand every parameter's(data_ptr, _version).inv_freqor quant scale), or changing its layout at the same address (for exampletranspose_()), triggers a recapture. An unindexed device is normalized to the current device index.continuous_cache_capacitypins the cache length.HybridEngineRollout.shutdown()shutdown()implements the existingRolloutEngine.shutdown()lifecycle hook. It matters for TP>1: in the NCCL reproducer below, a persistent graph holding captured collectives makesdestroy_process_group()hang inProcessGroupNCCL.shutdown(). Callshutdown()on each rank before destroying the process group. The docs describe reuse and release.Why dropping
_versionis safe.data, which does not bumpparam._version. On master, single-width bf16 rollouts already reused the graph afterengine.step(), and the replayed outputs changed with the weights. The version check gave no protection there._versionchanges on every step, so master recaptured on every rollout round..data =swap) changedata_ptr, which stays in the signature.param.data = param.data.t(), which keeps bothdata_ptrand_versionand was therefore missed by the old signature.Related work / scope
_get_continuous_graph_state; whichever merges second needs a trivial rebase. TP2 results below use Size HybridEngine rollout KV caches from local prefill shapes under AutoTP #9008 + this PR.test_continuous_graph_first_call_benchmark_on_cudaassertscapture.call_count == 2because an in-place SGD step currently forces a recapture. With this PR that step reuses the graph, so whichever of the two merges second needs that expectation changed to 1 (and the benchmark's post-update number no longer includes a recapture). perf(rollout): select generation and prefill buckets with a tunable cost model #8786 buckets prefill and does not change graph reuse or release.Validation
After merging master
Merge
dedd6c840includes mastera4c989b76. On 1× NVIDIA L20 (driver 580.173.02, CUDA 13.0, torch 2.14.1+cu130, Transformers 5.17.0), the tested files matched the merged worktree by SHA-256:On CPU (AMD EPYC 9575F, 256 logical CPUs visible; Python 3.12.3, torch 2.14.0.dev20260804+xpu):
DS_ACCELERATOR=cpu OMP_NUM_THREADS=1 MKL_NUM_THREADS=1 python -m pytest -q -p no:cacheprovider -o addopts= \ tests/unit/runtime/rollout/test_hybrid_engine_rollout.py tests/unit/utils/test_static_cache.py \ tests/unit/v1/test_hybrid_engine_rollout_v1.py # 78 passed, 28 skipped (accelerator-only cases and the optional pretrained test)Pre-commit passed on the four PR files after resolving the conflict.
Earlier validation
CUDA: 8× NVIDIA L20 available (1/2/4 devices used below), driver 580.173.02, CUDA 13.0, torch 2.14.1+cu130, Transformers 5.17.0. Base:
d8241a9ba. The three tested Python files on L20 have identical SHA-256 hashes to the then-current PR head55384d86d.With
DEEPSPEED_PRETRAINED_ROLLOUT_MODELunset:The earlier run (before the layout fix) with the optional pretrained model enabled and the native inference kernel available:
The pre-existing failure on baseline
d8241a9bawas the opt-intest_continuous_graph_matches_pretrained_model_when_enabledwith Transformers 5.17 ('DeepSpeedStaticCache' object has no attribute 'get_query_offset', see #8852). The fix is now in master and the opt-in test passes after the merge, as shown above; the test remains skipped in CI without the model.The unit test covers in-place updates, a same-address
transpose_(), parameter replacement, capacity changes andshutdown()with graph capture mocked. On a real device the new test runs a tiny Llama with a fixed capacity and widths 8 → 8 → 12 → 8. It applies an in-place weight change before round 2 and a same-addresslm_head.weight.transpose_()before round 4. Each round matches plain eager token for token, the output changes after the weight update, and there are exactly 2 captures (initial + after the layout change).shutdown()frees at least the cache size.Review follow-up (
2313079fa, rebased on master84481c333). Buffers are now in the signature. The unit test also re-registers a buffer, and the device test adds a fifth round that re-registersmodel.model.rotary_emb.inv_freqat half its values: the output still matches eager, the tokens change, and there are 3 captures. On 1× NVIDIA L20 (torch 2.14.1+cu130):tests/unit/v1/test_hybrid_engine_rollout_v1.py+tests/unit/runtime/rollout/test_hybrid_engine_rollout.pygive 102 passed, 6 skipped. With buffers removed from the signature, the device test fails on the token comparison after the buffer swap. On 1× Arc Pro B70 the same files give 71 passed, 37 skipped (graph-capture cases skip on this XPU stack). Multi-GPU TP and the RL loop below were not rerun for this change, which only adds buffers to the signature.RL loop on L20. Qwen3-0.6B, TP1, 8 prompts, 4 slots, 32 new tokens, greedy, 6 rounds. Each round does a graph rollout, then a real
engine.backward()+engine.step()with AdamW (lr 1e-4). Times are wall-clock per rollout, including prefill.After the layout fix, the patched fp32 TP1, bf16 TP1 and fp32 TP2×DP2 runs were repeated: still 1 capture, the same per-round token-match rates and the same
shutdown()release. Optimizer steps do not change parameter shape or strides.These are exploratory short runs, not a controlled end-to-end speedup claim. In particular the two fp32 rows use different prompt schedules and cache capacities. No long-run convergence or training-throughput claim is made.
Correctness against plain eager generation.
Tiny Llama fp32 runs:
Release with TP2 (tiny Llama, #9008 + this PR):
destroy_process_group()shutdown()shutdown()firstshutdown()released 116.5 MiB (Qwen3-0.6B bf16, TP1, capacity 160) and 43.7 MiB on rank 0 (TP2×DP2).Intel XPU (1× Arc Pro B70, Intel compute runtime package 26.27.39122.11, Level Zero loader 1.28.2, torch 2.14.1+xpu, Transformers 5.22.1, on top of #9007, which adds XPU graph capture):
The earlier torch 2.15.0+xpu run also reported 52 passed / 3 skipped, but aborted during interpreter teardown (exit 134). A minimal
torch.ones(16, device="xpu"); torch.xpu.synchronize()program reproduces that abort without importing DeepSpeed. It exits normally on torch 2.14.1+xpu; the clean-exit test run above uses that version. The earlier #9007-only comparison reached the expected assertion failure (3 captures rather than 1), but its process also used the affected 2.15 runtime.CPU: Python 3.12.3, torch 2.14.0.dev20260804+xpu selected with
DS_ACCELERATOR=cpu.Compatibility: fp32 ZeRO-0 at TP1/TP2/TP2×DP2 and fp32 ZeRO-2 at TP1 are token-exact on CUDA. Single-device fp16 graph inference matches eager for the tested Qwen3 and tiny Llama cases after the merge. Single-device fp32 graph tests pass on XPU with #9007. bf16 runs establish capture/release behavior only; their eager-output discrepancies are reported above. Not validated: ZeRO-1, ZeRO-3, offload, fp16 training, autocast, quantization, HybridEngine kernel injection, multi-device XPU graph, pipeline/expert/sequence parallelism, or training convergence.
AI assistance: GitHub Copilot helped with implementation, documentation, tests, review and validation. The original signed code commit is preserved; this PR also includes signed documentation and layout-signature follow-ups.