Repository navigation
Conversation
…tion group A ZeRO-3 engine whose data-parallel group is a subset of the world (two trainer layouts living in the same processes, or a trainer next to inference-only ranks) hung or failed its trace checks: the checks broadcast from global rank 0 over the world and the barriers waited for every rank. They now run over the parameters' partition group; with the default group nothing changes. Signed-off-by: RunguoLi <li19107254665@gmail.com>
…age relay, pipelined execution, ZeRO-3 training-state move relayout.py: RelayoutPlan moves weights between two sharded layouts in GPU memory. Both sides are declared per rank as affine pieces; the source and destination ranks may be disjoint, equal or overlapping, and a weight may have several holders (the trainer's ZeRO-3 partitions, surviving shards of the previous layout, replicas). - Planning runs identically on every rank. Holders are preferred in the order: the destination rank itself, its node, the caller's layout order, the least loaded. - topology="relay": a block that crosses nodes is fetched once per node and copied to the node's other ranks in a second stage; spread=True has the replicas of a node fetch bands of each large block and exchange them. - Execution: one all_to_all_single per bucket of weights and stage; over buckets the stages are pipelined (stage one of a bucket travels with stage two of the bucket before). affine_plan.py: the same moves planned through the affine IR's plan_transfer (planner="affine"), a relay being two plans through a relay map whose shards are bands of the destinations themselves. Weights it does not express yet (holders in two layouts, partial targets) fall back to the rectangle planner per weight. zero3_state.py: Zero3StateMove moves a ZeRO-3 engine's fp32 master partitions, optimizer moments and step counters to another ZeRO-3 engine of the same model over other ranks, from the optimizer storage maps; release_training_state / restore_training_state give back and re-allocate the inactive engine's memory. Tests come with the next commit (they use the native rollout's layouts). Signed-off-by: RunguoLi <li19107254665@gmail.com>
…a; tests of the relayout and the state move native/: a small tensor-parallel decoder whose weights are views of one buffer per rank, allocated when the layout is created and written in place, so CUDA graphs captured over the first version of the weights stay valid for later ones. NativeRollout syncs it from a ZeRO-3 trainer and changes its ranks or tensor-parallel degree through RelayoutPlan; a replica whose ranks and degree are unchanged keeps its buffer, KV cache and graphs. build_rollout takes engine="native"; RolloutBatch gains an optional response_logprobs. Tests: test_relayout.py (both planners: ZeRO-3 to tensor parallel, degree changes, a lost rank refilled, two nodes flat / relay / spread, pipelined buckets, the planners' inter-node bytes compared, fallbacks, contradictory maps), test_native_decoder.py, test_native_rollout.py (sync, re-placement, the trainer switch with Zero3StateMove). Signed-off-by: RunguoLi <li19107254665@gmail.com>
|
Thanks @LiRunGuo, this is great to see running end to end. A DP4 → DP2 → DP4 state move in 0.6–0.8 s with every move digest-checked is the comparison the RFC needed against the checkpoint path: the same Qwen3-0.6B ZeRO-3 changes through a universal checkpoint took me 46 s (DP4 → DP2) and 51 s (DP2 → DP4) on 4× A100, most of it the relaunch. To your two questions, as the RFC's author: The split: +1.
The relay-map formulation: yes. Bands of the destinations as relay shards is the alternative already recorded under open question 1 in #9026, from your earlier comment: no partial targets and no relay buffer. On the The choice. I'll take what is a flag here: whether a weight goes direct, through a relay, or through a relay with spread, and the thresholds ( Before and after. I can run your state move on the same 4× A100 node as my checkpoint-path numbers, with the same model and changes, so there is one comparison on one machine. I'll update #9026's body with the relay-map formulation and the split once @0z5a has weighed in. |
|
@LiRunGuo, thanks for the split at the end of the description. A note on step 2 (the training-state move rewritten on The description says no open PR covers a live move of the optimizer state. deepspeedai/DeepSpeedExamples#1021 (open since 10-22) already does this on the IR, beyond ZeRO-3:
Suggestion. Since step 2 is a rewrite anyway, could it target this wider matrix rather than ZeRO-3 only? The IR already describes every one of these layouts. What differs per case is:
One gap the TP cases expose. A second engine with another TP size overwrites the process-global groups in This comment was drafted with AI assistance and checked by me against #9074's description and the #1021 comments. |
|
Thanks @Achyuthan-S. One machine, both paths. Thanks for the offer. The benchmark script is not in the PR and I'm not publishing it yet, so I can't hand you the run for now. I have the same run queued on a 4× A100 node and will post it here when it finishes; that is another machine than yours, so it puts the same GPU model next to your checkpoint numbers, not one machine. @jinyouzhi's run on #9026 already gives one pair on one machine, measured independently (4× L20, ZeRO-1 DP4 → ZeRO-3 DP4: 0.62 s in the running processes against 27.4 s through a universal checkpoint). The difference to this PR is the check: sums there, a bitwise digest here. Per-weight planning. Right:
The choice. Good, it is yours. Data for it, on 2 nodes × 4 H100 with Qwen3-8B (a ZeRO-3 trainer on node 0, replicas on node 1): I ran 30 configurations with both planners: three syncs from the trainer (into TP4, TP2 × 2 and TP1 × 4) and seven transitions (four layout changes, read from the old replica or from both sources), each direct, relayed, and relayed with spread.
Both planners run on this PR's executor, not on #8809. I can post the per-configuration table (bytes inside and across nodes, times) on #9026 as points for §3. The two-layout fallback. In those runs 217 of 399 weights planned from both sources fell back to the rectangle planner. I'll rerun that case on #9052, and on #9070 for the column case, and report how many each removes. #9076 now carries its regression test and is marked ready for review. |
NativeRollout is the destination the relayout and state-move tests write into, not a rollout engine proposed for users. build_rollout is as on master again; the tests construct NativeRollout directly. Signed-off-by: RunguoLi <li19107254665@gmail.com>
Status: draft for discussion, not for merge as it is. Part of RFC #9026, which has not been accepted yet. I'm opening it so that the two-stage relay, the pipelined execution and the training-state move can be discussed on code that runs, next to #9045. The split I'd propose for upstream is at the end; each piece would be its own PR after the RFC is settled.
This PR does not propose a new rollout engine. The
NativeRolloutin the third commit is only the destination its tests write into; it is not registered withbuild_rolloutand stays out of upstream. For the hybrid engine, the re-shard at the training/rollout boundary (ZeRO-3 partitions into the inference layout ofHybridEngineRollout) is #9045'sAffineWeightSync, which takes maps only and can be called fromHybridEngine.eval(). What is here are the mechanisms beyond a fixed trainer on one node: the trainer's own layout changing with its optimizer state, ZeRO-3 over a subset of the ranks (#9076), and moves across nodes.What it is. Moving model state between two sharded layouts in GPU memory, without a checkpoint and without gathering a whole parameter on any rank:
The four commits
[zero]ZeRO-3's rank-agreement checks and barriers run over the partition group. 31 lines added and 19 removed in five core files (stage3.py,partition_parameters.py,partitioned_param_coordinator.py,parameter_offload.py,zero/utils.py).assert_ints_same_as_other_ranks(ints, group=None)compares over the world, and an engine's defaultdp_process_groupholds every rank.[rollout]relayout.py,affine_plan.py,zero3_state.py(new files; no existing file changes).RelayoutPlan: both sides are declared per rank as affine pieces. Source and destination ranks may be disjoint, equal or overlapping, and a weight may have several holders. Planning runs identically on every rank from the gathered descriptions; execution is oneall_to_all_singleper bucket of weights and stage.topology="relay": a block that crosses nodes is fetched once per node and copied to the node's other ranks in a second stage.spread=Truehas the replicas of a node fetch bands of each large block and exchange them, so the traffic uses every NIC of the node. Over buckets the stages are pipelined: stage one of a bucket travels with stage two of the bucket before.planner="affine"(affine_plan.py): the same moves planned byplan_transfer. A relay is two plans through a relay map whose shards are bands of the destinations themselves, so there is no relay buffer. Two cases still fall back, per weight, to the module's own rectangle planner: holders in two layouts (waiting for Describe a parameter held in several layouts as one affine map #9052) and partial targets (RFC [RFC] Two-stage relay for cross-node weight sync, priced by a calibrated cost model #9026, open question 1).Zero3StateMove(src_engine, dst_engine): moves the training state throughzero_optimizer_storage_affine_maps([AffineIR] Affine storage maps for live ZeRO parameters and optimizer states #8885).release_training_state/restore_training_stategive back and re-allocate the inactive engine's memory.[rollout]native/and the tests. A small tensor-parallel decoder whose weights are views of one buffer per rank, written in place, so CUDA graphs captured over one version of the weights stay valid for the next.NativeRolloutis the destination the tests move weights into.RolloutBatchgains an optionalresponse_logprobs.[rollout]NativeRolloutleavesbuild_rollout, which is as on master again. The tests constructNativeRolloutdirectly.How it relates to open work
AffineWeightSync) is the same weight move on the IR alone (plan_transfers+execute_transfers), validated on one node. This draft holds what [rollout] Affine weight-sync building block: ZeRO storage into a tensor-parallel inference copy, shard to shard #9045 leaves out: the relay, the pipelined execution, holders in two layouts, and the training state.native/arena.pyduplicates [rollout] Affine weight-sync building block: ZeRO storage into a tensor-parallel inference copy, shard to shard #9045'sWeightArena; only one of them should land.AutoTPRollout) is a tensor-parallel rollout engine already under review.NativeRolloutoverlaps with it and is not proposed to land next to it. It is here because the relayout and trainer-switch tests run through it.schedule_transfers) is what the pipelined execution should be built on: with the schedule exposed, stage two of one bucket can share a collective with stage one of the next. The executor inrelayout.pydoes this with its own bucketing today.Validation
One node with 4× RTX 3090 (24 GB), driver 595.58.03, torch 2.13.0+cu130, NCCL 2.29.7. Commands from
tests/, each aspython -m pytest -q -rfEs <files>. The two rollout rows were rerun on the head commit; the affine and ZeRO rows and the end-to-end run below are from the third commit (the fourth changes onlyrollout/__init__.pyand a docstring):unit/runtime/rollout/test_relayout.py,test_native_decoder.py,test_native_rollout.pyunit/runtime/rolloutunit/checkpoint/test_affine_shard_map.py,test_affine_transfer_plan.py,test_affine_transfer_executor.py,test_affine_transfer_executor_external_group.py,test_affine_replica_policy.py,test_zero_affine.pyunit/v1/zero/test_zero.py,unit/v1/zero/test_zero_init_pg_contradiction.py,unit/runtime/zero/test_zero_context.pytest_hybrid_engine_rollout.py::TestZero3SynchronizedEarlyStop. It needs thetransformer_inferenceextension, which does not build on this machine (the CUDA toolkit there has nocuda_profiler_api.h); the same cause skips one more test in that file. Neither touches this change.TestNativeRolloutApartandTestTrainerSwitchintest_native_rollout.py.TestTrainerSwitchalso compares training across two trainer layouts with a plain-torch AdamW reference.End to end, a ZeRO-3 trainer going DP4 → DP2 → DP4 inside the same four processes (Qwen3-0.6B, bf16, Adam; 310 parameters; 6.66 GiB of fp32 master weights and two moments). Three rounds per planner, a training step after every move:
plan_transfer)What I could not run, and known limits
get_accelerator()anddeepspeed.comm, but the native decoder's graph capture is CUDA-specific.keep_in_fp32_modules).NativeRolloutassumes one dtype and has no test for a mixed model.NativeRollout.bind(NotImplementedError).all_to_all_singlemessage. Replaying the same plans on CPU is exact for all 707 weights. A stress test of the bare collective (torch.distributed.all_to_all_singlewith self-describing data, no DeepSpeed, no model) reproduces the same signature at the message sizes of these moves: 6 misplaced 128 KiB blocks in 370 TB between two nodes, with NCCL's default settings (1 in 121 TB) as well as withNCCL_IB_QPS_PER_CONNECTION=4orNCCL_NCHANNELS_PER_NET_PEER=8; none in 159 TB in an earlier run whose messages all stayed at or below 256 MiB per pair. So the cause is below this executor (torch 2.13.0, NCCL 2.29.7, IB on that cluster); which component it is has not been established. The digest after each move is what detects it. An 8B model's digests were clean in all 39 two-node configurations I ran.affine-transfer.rstfrom [rollout] Affine weight-sync building block: ZeRO storage into a tensor-parallel inference copy, shard to shard #9045).Size, and the split I'd propose. 2,311 new lines under
deepspeed/and 1,052 in tests is too much for one review. For upstream I would not send it in this form:plan_transfers+execute_transfers, as [rollout] Affine weight-sync building block: ZeRO storage into a tensor-parallel inference copy, shard to shard #9045 does for weights), with DP4 → DP2 → DP4 as its test.plan_transferscalls through a relay map. The planner needs nothing new; the open questions are where the relay-map constructor lives and how the choice meets the cost model of [AffineIR] Calibrate affine transfer links per path (Plan analysis & calibration Part-2) #8966 / [AffineIR] Analyze affine transfer batches before execution (Plan analysis & calibration Part-1) #8967.NativeRolloutstays out; the destination upstream is feat(rollout): add AutoTP rollout engine with in-memory weight sync #9023's engine through [rollout] Affine weight-sync building block: ZeRO storage into a tensor-parallel inference copy, shard to shard #9045's sync.Two questions for the RFC: does this split look right, and is the relay-map formulation in
affine_plan.pywhat #9026 should describe?AI assistance: AI assistance was used. The code, tests and this description were drafted with an AI coding agent (Claude Code). The submitter reviewed every changed line and ran the tests above.
Maintenance contact: @LiRunGuo. cc @Achyuthan-S @0z5a @jinyouzhi