Skip to content

feat(engine): add CPU-staged Megatron AdamW - #1613

Merged
sitabulaixizawaluduo merged 60 commits into
mainfrom
feature/optimizer_offload
Oct 16, 2026
Merged

sitabulaixizawaluduo merged 60 commits into
mainfrom
feature/optimizer_offload

Conversation

@yulangz

@yulangz yulangz commented Aug 18, 2026 •

Copy link
Copy Markdown
Collaborator

Description

Keep authoritative FP32 AdamW master parameters and moments in pinned CPU memory and stream updates through bounded reusable CUDA buffers. The staged optimizer integrates with Megatron checkpointing and AWEX colocation while preserving ordinary optimizer residency behavior.

  • Select precision-aware distributed AdamW automatically when CPU staging is enabled.
  • Reuse bounded CUDA slots for H2D, optimizer computation, and D2H state updates.
  • Keep managed optimizer slabs CPU-resident across AWEX phase transitions and checkpoint them synchronously.
  • Rebuild staged CPU masters and moments after model-only HF weight reloads.
  • Preserve optimizer-chain handling for dense/expert parameter groups and mixed managed/ordinary optimizers.

Muon support is split into stacked #1743, based on this branch. Activation-storage tracking is split into independent #1779; ordinary Megatron training no longer installs its patches or mutates activation storage in this PR.

Examples

examples/cpu_staged_offload/ contains DAPO-Math configurations for Qwen3-30B-A3B-Base and Qwen3.5-35B-A3B-Base, sharing one training entry point. The Qwen3.5 configuration defaults to three training steps on eight GPUs with attention TP2/DP4, MoE EP8, and four TP2 rollout servers.

export AREAL_PROXY_ADMIN_API_KEY="$(python -c 'import secrets; print(secrets.token_urlsafe(32))')"
# Set QWEN3_5_35B_A3B_BASE_MODEL_PATH and DAPO_MATH_17K_PATH to local directories.
python examples/cpu_staged_offload/dapo-math_rl_cpu_staged.py \
  --config examples/cpu_staged_offload/dapo-math_grpo_qwen3_5_cpu_staged.yaml \
  trial_name=staged

Repeat with trial_name=baseline actor.megatron.cpu_staged_offload.enabled=false for the baseline. Precision-aware mode stays enabled in both runs.

Validation

Current review fixes

  • Remove the temporary CI dependency-sync step; use AWEX 0.9.0 supplied by the CI image.
  • Remove the local CUDA IPC packer workaround. Use the native AWEX packer; upstream AWEX PR #129 addresses redundant clones and byte-limit overshoot (issue #128).
  • Revert v2 model-weight offload/reload to main; keep staged optimizer residency/checkpoint handling.
  • Preserve MCore 0.19 stub optimizers on fully frozen pipeline ranks. Non-stub zero-replacement and binding mismatches still fail.
  • Allow chunked LM Head for text-only Qwen3.5 MODEL_THD; continue rejecting actual vision inputs and unsupported layouts.
  • Pass AWEX dense transfer bases to separate rollout workers. Four isolated TP2 servers register ranks 0–7 rather than repeating 0/1; GPU placement stays unchanged.
  • Keep the earlier ACK-based IPC lifetime fix and CPU master/moment reset on HF model-only reload.

Local validation:

  • Staged/HF/config/AWEX cleanup selection: 109 passed.
  • Qwen3.5 chunked LM Head/AWEX regressions: 201 passed.
  • Controller initialization regressions: 14 passed; AWEX plugin/inference launch selection: 74 passed. Multi-node rank layouts were tested with mocked launches.
  • Staged optimizer/AWEX/config regressions including frozen-rank cases: 46 passed.
  • Full pre-commit run --all-files: passed.

The v1 Qwen3.5-35B-A3B-Base default example completed three PPO updates and three AWEX updates (versions 1–3), exit code 0, on eight L20X GPUs using the unmodified installed AWEX 0.9.0 packer. No local workaround or upstream patch was loaded: the old two clone() calls and post-insertion size check remain in the tested dependency. Each update completed on all eight writers/readers.

Configuration: batch size 8, two samples/prompt, max generation 128, max tokens 2047, microbatch token budget 4096, chunked logits enabled; attention TP2/DP4, MoE EP8, four TP2 rollout servers. Trial: qwen35-native-awex09-no-packing-fix-r3-20261013. Whole-GPU peak observed by 1-second NVML sampling: 81.32 GiB. No OOM occurred. This default workload does not require the upstream packing fix to run; tighter memory budgets and larger workloads were not validated. This is a short single-node execution check, not a convergence or v2 model-residency validation.

Latest completed remote validation: both SGLang and vLLM GPU CI, pre-commit, and package installation passed at 99e95070 (run 38677858683). The isolated runner labels/VM identities prevent cross-run cleanup from deleting another PR's test worker.

After merging native R3 from main 6cad8710, this PR uses main's Flash Next recovery fixture unchanged; the earlier duplicate fixture fix is no longer part of the PR diff. Existing Qwen3.5 chunked-forward tests initialize the new R3 state in their fake engines. Local R3/staged-optimizer/chunked-forward/recovery regressions: 259 passed; full pre-commit passed. No fresh multi-node integration run was performed for this merge.

Historical integration measurements

The following smoke measurements precede the activation-tracker split and describe the earlier integration snapshot.

Qwen3-30B-A3B and Qwen3.5-35B-A3B-Base smoke runs completed three PPO updates and three AWEX weight updates on one eight-L20X node.

Peak memory per GPU (GiB) Staged on Staged off
Actor allocated 35.38 74.30
Actor reserved 38.31 77.03
Whole GPU, all phases 99.51 99.01

Actor allocated memory decreased by 52.4%. Whole-GPU peaks include rollout and weight exchange and were essentially unchanged in these short execution checks.

After splitting Muon:

  • pytest -q tests/test_gpu_staged_optimizer.py tests/test_gpu_staged_optimizer_awex.py tests/test_megatron_optimizer_config.py tests/test_megatron_async_save.py tests/test_cpu_staged_offload_example.py: 53 passed, 2 skipped (CUDA-only AWEX residency)
  • pre-commit run --all-files: passed

The full multi-node suite was not run because validation used a single node.

yulangz and others added 12 commits August 11, 2026 12:16
Enable policy distillation from multiple teacher objectives while sharing colocated training and rollout resources.

Key changes:
- Add MOPD objectives, routing, losses, and teacher lifecycle management
- Support forked colocated workers across local, Ray, and Slurm schedulers
- Extend AWEX weight synchronization and memory residency handling
- Add bilingual documentation, a local GSM8K example, and focused tests
Forked colocated roles inherited only the owner environment, so rollout allocator settings could not differ from actor and teacher settings. Pass per-task overrides through every scheduler fork path and expose the active allocator configuration in residency logs.
Free CUDA graph allocations alongside weights and KV cache while colocated training owns the GPU. Enable SGLang's graph memory saver before capture and restore graphs after AWEX weight publication.

Key changes:
- preserve the preload hook required for pauseable CUDA graphs
- pass the cuda_graph tag through rollout offload and restore
- cover child environment and restore ordering with unit tests
Gate rollout recovery on the v1 AWEX colocate path and align regression tests with the current SGLang pause and Guard lifecycle contracts.
Regenerate the CLI references from the merged SchedulingSpec so the MOPD GPU GRES setting and the new Slurm reservation options are documented together.
Model the v1 actor configuration explicitly and isolate Guard port allocation tests from node-wide abstract socket leases.
Resolve SGLang base GPU IDs in the worker's logical device namespace so Local and Slurm colocation select non-overlapping devices without scheduler-specific overrides.

Preserve registered AWEX converter fusion policies to keep dense and Qwen3-MoE train/inference metadata aligned.
Keep AdamW and Muon optimizer state in pinned CPU slabs and stream bounded GPU updates.

Add transactional synchronous checkpointing with disk-backed rollback, managed AdamW asynchronous save and finalization, and Muon DP resharding.

Integrate AWEX residency handling and comprehensive unit and torchrun coverage.
Provide a reproducible eight-GPU AWEX setup for comparing standard and CPU-staged Megatron optimizers without changing precision-aware optimizer semantics.

Key changes:
- Add a Qwen3-30B-A3B Attention-DP8 and Expert-EP8 GRPO example
- Add an example-local CPU-staged toggle and worker configuration
- Capture step-2 CUDA allocator snapshots with tests and usage docs

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot wasn't able to review this pull request because it exceeds the maximum number of lines (20,000). Try reducing the number of changed lines and requesting a review from Copilot again.

Keep representative numerical, residency, checkpoint, AWEX, Muon, and async-save paths while removing exhaustive fault matrices and unused torchrun workers.

Reuse the existing multi-turn GSM8K workflow in the CPU-staged example so the PR stays below the automated review size limit.
@yulangz yulangz added the safe-to-test Ready to run unit-tests in a PR. label Aug 18, 2026
root added 3 commits August 20, 2026 04:57
Make the CPU-staged Qwen3 MoE recipe reusable with a local DAPO-Math loader and proxy-based rollout flow. Ensure AWEX releases precision-aware optimizer master state before colocated inference resumes.

Key changes:
- Add and register a DAPO-Math-17k RL dataset loader
- Rename and sanitize the CPU-staged example configuration
- Preserve optimizer-state tensor identity during AWEX migration
Route DAPO-Math prompt filtering through the shared helper so base tokenizers without a native chat template use the configured compatibility path.
Prevent Megatron activations, optimizer staging buffers, statistics, and CUDA IPC exports from overlapping the resumed rollout engine. Account for sequence alignment before packing to keep micro-batches under their token cap.

Key changes:
- release retained activation and optimizer staging storage
- defer CUDA IPC exporter cleanup until all readers finish
- flush actor statistics before the colocation handoff
- account for sequence alignment during micro-batch allocation
Comment thread areal/engine/awex/colocate_reader.py Outdated
root added 2 commits October 8, 2026 07:37
Preserve main training metrics, Qwen4 optimizer group overrides and CUDA IPC allocator handling alongside staged optimizer residency.

Update the DP1 and DP2 fixtures for Megatron Core 0.19 buffer metadata and bucket-group parameter synchronization. Preserve the parameterized per-token loss configuration instead of overwriting it.

Refs: #1613
Merge main to adopt AWEX 0.9.0 in both dependency manifests and lockfiles. Install the checkout-declared AWEX version in GPU CI so older runtime images can collect and run the updated tests.

Validate 5325 SGLang CI test collections, 174 AWEX/offload tests and DP2 staged AdamW parity locally. Optional DTE cases remain skipped.

Refs: #1613

@Le8r0nJames Le8r0nJames left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In addition to the points above, could you report optimizer-step and full training-step timings with CPU staging enabled versus disabled under the same workload? The memory savings are clear, but the CPU–GPU transfer overhead and throughput trade-off are not yet quantified.

Comment thread areal/engine/sglang_remote.py
Comment thread areal/engine/megatron_utils/activation_storage.py Outdated
Comment thread areal/engine/awex/colocate_writer.py Outdated
Comment thread areal/engine/awex/colocate_writer.py Outdated
Comment thread areal/engine/awex/sglang_plugin.py Outdated
Comment thread areal/engine/megatron_engine.py Outdated
root added 3 commits October 12, 2026 07:35
Reuse the checkpoint load lifecycle to refresh CPU masters and clear moments after a model-only HF load. Prevent updates after a partial load failure and preserve scheduler progress.

Validate the next AdamW update against a fresh optimizer with staging slots both resident and released.
Subtract the SGLang server base from scheduler GPU ordinals before adding the dense transfer-rank base. Cover isolated and fully visible GPU layouts across two nodes.
Remove activation storage patching and release from the optimizer-offload PR. Preserve the feature in its own branch for separate ownership and memory-lifecycle review.
@yulangz

yulangz commented Oct 12, 2026

Copy link
Copy Markdown
Collaborator Author

In addition to the points above, could you report optimizer-step and full training-step timings with CPU staging enabled versus disabled under the same workload? The memory savings are clear, but the CPU–GPU transfer overhead and throughput trade-off are not yet quantified.

image The red is baseline and the green is with optimizer-offload.

@yulangz

yulangz commented Oct 12, 2026

Copy link
Copy Markdown
Collaborator Author

Updated the code and addressed the correctness feedback:

  • IPC export lifetime (1d0f81a3): release packed export storage immediately after a successful AWEX reader ACK, before write_finished, instead of retaining it until final engine completion. Failed/timed-out acknowledgements retain the export; the final engine synchronization remains.
  • HF reload (4a2a6773): rebuild CPU master weights from the newly loaded model and reset staged optimizer moments/step, preserving the scheduler and parameter identities. Regression coverage includes resident/released staging slots and partial-load failure.
  • Transfer ranks (37431a68): subtract the SGLang server's base_gpu_id before applying the dense transfer-rank base, avoiding the double offset when all node GPUs are visible. Regressions cover four TP2 servers with full visibility and isolated device masks, plus multiple-node rank layouts.

Independent activation-storage tracking/reclamation has been removed from this PR and moved to PR #1779, based directly on main, for separate ownership/safety review. Muon support remains in the stacked PR #1743.

The redundant AWEX packing clone and post-insertion size-limit check are reported upstream in inclusionAI/Awex issue #128, with reproducible CPU/CUDA cases and proposed fixes. A 20-byte limit currently yields a 24-byte group; packing 256 MiB has a 512 MiB extra allocation peak with cat().clone(), versus 256 MiB with cat() alone. No tensor-value corruption was observed; these are memory/copy overhead and size-bound issues that can cause OOM. The local bounded packer remains a workaround pending an upstream fix.

Latest local validation: 109 related tests passed, including ACK success/failure/timeout lifetime checks; 10 consecutive exchanges on two GPUs passed with actual MetaServer/CUDA IPC/native AWEX reader/bounded NCCL and exact received weights, while checking exports were reclaimed before final engine completion; full pre-commit passed. The two-GPU exchange validation does not replace full SGLang training or multi-node convergence testing.

@Le8r0nJames

Copy link
Copy Markdown
Collaborator

Thanks for addressing the correctness issues and splitting out activation tracking. Could we also remove the local packer workaround from this PR and help land the fix upstream in inclusionAI/Awex#128?
If removing it causes OOM in the optimizer examples, please clarify that dependency so we can decide the merge order.

Comment thread .github/workflows/test-areal.yml Outdated
Comment thread areal/v2/weight_update/awex/megatron_adapter.py
Comment thread areal/engine/megatron_utils/gpu_staged_optimizer.py Outdated
@yulangz

yulangz commented Oct 13, 2026

Copy link
Copy Markdown
Collaborator Author

Addressed the latest review feedback and pushed through f4a707c:

  • Removed the temporary CI dependency-sync step and the local packing workaround; restored v2 model-weight residency methods to main (25ba63b).
  • Fixed Qwen3.5 text-only MODEL_THD chunked loss (e40259f), AWEX ranks for separate rollout workers (309a5ce), and MCore stub optimizers on frozen pipeline ranks (f4a707c). These fixes are separate commits.
  • Unmodified AWEX 0.9.0: the default Qwen3.5 example completed three PPO + three AWEX updates, exit 0, with no OOM. All eight writers/readers completed versions 1–3. Whole-GPU peak observed at 1-second sampling: 81.32 GiB. Trial: qwen35-native-awex09-no-packing-fix-r3-20261013. The upstream packing fix is not required for this validated workload.
  • Local regressions: cleanup selection 109 passed; chunked LM Head/AWEX 201 passed; controller initialization 14 passed; AWEX/inference launch selection 74 passed; staged optimizer/AWEX/config including stub cases 46 passed. Full pre-commit passed. These selections overlap; this is single-node smoke validation, not convergence testing.

The AWEX fix is now PR #129 for issue #128. Upstream local CPU CI: 279 passed, 1 skipped; tensor/CUDA IPC tests: 31 passed. GitHub requires maintainer approval before the upstream PR's CI runs.

Activation-storage tracking remains in separate formal PR #1779; Muon remains in stacked PR #1743. Updated this PR description to reflect that split and the native-AWEX result. CI for the latest head was retriggered through the PR's safe-to-test label: run. GPU CI is still provisioning; it has not passed yet.

@yulangz

yulangz commented Oct 13, 2026

Copy link
Copy Markdown
Collaborator Author

The failure in run 38379205319 is a runner ownership race, rather than a reported pytest failure. The SGLang suite completed successfully: 5309 passed, 187 skipped, 397 deselected.

The vLLM job was assigned to gcp-runner-38372471766-vllm, a VM created by another PR's run. At 10:41:03 UTC, that older run deleted its own VM while this PR's vLLM job was still using it. GitHub subsequently marked the job failed at 10:50:14 with “The self-hosted runner lost communication with the server.” The test step has no pytest completion/result.

Pushed isolated CI fix fdb3955. Fix: scope the provisioned runner's machine label and the test job's selector to github.run_id + github.run_attempt, and use the same run/attempt identity in VM creation and cleanup. The generic machine label is replaced, so older unmodified workflows cannot consume the new runners either. Backend labels still distinguish SGLang and vLLM. This is an isolated four-line workflow change.

Validation: actionlint 1.7.12 passed with ShellCheck disabled; YAML/routing checks cover correct matching, cross-run/prior-attempt/backend exclusion, exclusion of old generic selectors, and identical create/cleanup names. Full pre-commit passed. Also merged latest main 448de523 in 1cbf988, preserving both helpers in the SGLang conflict. After merging, 360 related regressions + 14 controller initialization tests passed; full pre-commit passed again.

CI was retriggered through the PR's safe-to-test label. The new run is visible for head 1cbf9881: run 38392961547 (queued/in progress; no result yet).

Evidence: current vLLM job, older run's VM teardown, successful SGLang job.

Le8r0nJames
Le8r0nJames previously approved these changes Oct 13, 2026

@Le8r0nJames Le8r0nJames left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

dingzhiqiang
dingzhiqiang previously approved these changes Oct 13, 2026

@dingzhiqiang dingzhiqiang left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@yulangz

yulangz commented Oct 15, 2026

Copy link
Copy Markdown
Collaborator Author

Resolved the main conflict and fixed the actual failures from run 38392961547 in two separate commits:

  • af68c53 merges main through ba237a1. The only conflict was adjacent imports in megatron_engine.py; both staged-optimizer and named-loss helpers are retained.
  • 99e9507 adds the required experiment_name to the fake config in test_qwen_flash_next_recovery.py. Recovery assertions and production code are unchanged.

Both previous GPU jobs ran on the correct isolated runners and completed pytest. They had the same five parameterized recovery failures (AttributeError: SimpleNamespace has no attribute experiment_name): SGLang otherwise had 5591 passed, vLLM 5531 passed. I reproduced all five locally before fixing the fixture. The real SWEPPOConfig already requires this field.

Local validation after the merge/fix:

  • Named-loss/Megatron/staged optimizer/AWEX regressions: 428 passed.
  • Qwen/SWE recovery/training/vision/diagnostics regressions: 104 passed, 9 skipped for optional Qwen4 runtime dependencies; this includes all five previously failing recovery cases.
  • Full pre-commit run --all-files passed for both commits. Multi-node integration was not run locally.

The PR is now conflict-free and head 99e9507 is pushed. CI was triggered through the PR's safe-to-test label; the new GPU run is visible and in progress: 38677858683. It has no final result yet.

Use the recovery fixture from main now that R3 includes the fix.
Initialize R3 state in the existing chunked-forward test fixture.
@yulangz

yulangz commented Oct 15, 2026

Copy link
Copy Markdown
Collaborator Author

Merged R3 main 6cad8710 and resolved the conflict in 59f5705.

The conflict was the duplicate experiment_name fixture fix in tests/test_qwen_flash_next_recovery.py. R3 already includes the fix, so this PR now takes that entire file from main. There is no remaining Flash Next recovery-test diff, and the obsolete PR-specific fix has been removed from the description.

Existing Qwen3.5 chunked-forward tests now initialize the three new R3 state fields, matching the existing upstream fixture pattern. No new production workaround was added.

Validation: 259 R3/staged-optimizer/chunked-forward/recovery tests passed; full pre-commit passed. No fresh multi-node run was performed. Both GPU variants of the preceding head's CI had passed (38677858683).

Head 59f5705 is pushed and GitHub reports no conflicts. Triggered CI via the PR's safe-to-test label: 38712487174 (queued; no result yet).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

safe-to-test Ready to run unit-tests in a PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants