Repository navigation
Conversation
A TransferWindow whose group spans hosts is registered with torch's NCCL symmetric-memory backend. Operations between hosts whose source and target are packed regions of window buffers become one put_signal from the source process; other operations between hosts keep the staged protocol. Signed-off-by: iLeGend <824040212@qq.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.
Stacked on #8 (
affine-window-prepared).Why
A
TransferWindow(#7/#8) only maps peers that share a host, so every operation between hosts is staged through send/recv arenas. On torch builds whose symmetric memory has NCCL one-sided puts, the window can register with the NCCL backend and deliver packed regions to a remote window in place, with no staging copy on either side.What
deepspeed.comm:symm_mem_rendezvous(..., backend=),symm_mem_handle_at(a handle at a byte offset into a rendezvoused buffer) andsymm_mem_put_signal. A torch withoutput_signalgets a clear error.TransferWindow: learns which processes share a host; a window spanning hosts registers with NCCL (CUDA only) and warms up NCCL's one-sided state collectively. NCCL counts signals per peer on one channel, so READY/DONE share it and waits take a count.prepare_transfers: an operation between hosts is one put when both regions are packed window buffers; strided ones keep staging. Mapped (same host) copies are unchanged.run: puts are issued first on the window stream. The target acknowledges after all puts from a peer have landed, and the putting process waits for that beforerunreturns, because a put can still be reading its source after its stream has completed. Without the acknowledgement the 2×8 benchmark below saw the next step's source values in the target.Not duplicate
Follow-up to this fork's #7/#8; no upstream PR adds cross-host window transfers. deepspeedai#8966 calibrates link costs for the planner and does not move data; its link numbers are used below as a reference.
Hardware
2 hosts × 8× H20, RoCE 8×400G (
mlx5_bond_0..7), driver 580.178.04, torch 2.13.0+cu129, NCCL 2.29.7.Tests
Single host (8× H20),
pytest tests/unit/checkpoint/test_affine_transfer_executor.py tests/unit/checkpoint/test_affine_transfer_executor_external_group.py tests/unit/checkpoint/test_affine_transfer_plan.py tests/unit/checkpoint/test_affine_shard_map.py:TestWindowAcrossHosts, world sizes 2 and 4, packed and strided)test_affine_transfer_calibration.py+ executor)TestWindowAcrossHostsfakes hosts by patchingsocket.gethostname, so puts go over NVLink. It checks targets and the mapped/staged byte split. It does not catch the source-reuse race fixed above: on one host the put finishes reading before its signal, even at 8 MiB per process. That fix is covered only by the 2×8 benchmark, which checks every target exactly on every repetition.pre-commit run --files <changed files>passes.Benchmarks
Aggregate GB/s, median of 5 runs after 2 warm-up runs. Every target is checked exactly on every run;
!would mark a mismatch, and there are none.Methods:
stage=execute_transfers.stage_p=prepare_transfers(...).run()without a window.pull_p/push_p= prepared with a 4 GiB window.One host (8 GPUs), #8's fan/peer scenarios — unchanged by this PR
best / upstreamuses this PR's best method. #8 and this PR agree within noise in every cell (*one #8 outlier).Two hosts (16 GPUs), the same scenarios across hosts
Upstream and #8 have no window across hosts, so only their staged numbers are shown. "this" = this PR.
deepspeedai#8966 link calibration, same machines
calibrate_links+fit_linksat17aa897e6, NCCL send/recv, 2×8 ranks. Intra-node fans usefan_width=(1, 7). Cross-node fans usefan_width=(1, 4, 8)withfan_peerschoosing peers on the other host. Run times: 63 s intra-node, 101 s cross-node.peer_pairsHow the calibration lines up with the executor:
xpeerstaged 21.2 GB/s. A put reaches 22.5 GB/s with no per-message alpha.xfan_outstaged 77.0 GB/s. One GPU's sends are relayed over the host's other adapters. A put cannot use them and stays at one adapter (22.7 GB/s).xfan_in. Puts reach 33–45 GB/s because each remote sender's adapter writes directly into the receiver.Weight sync, as in the #8 / deepspeedai#8966 threads
These scenarios follow the GH200/Slingshot measurements discussed in deepspeedai#8966 (direct / relay / sharded / sharded + intra-node gather, incast, staging size).
Setup: 8 GiB of bf16 weights (32 parameters of 256 MiB) go from the trainer node (ranks 0–7) to 8 engines on the other node (ranks 8–15). Each cell is the median of 3 runs after 1 warm-up run, and every engine buffer is checked exactly. Upstream and #8 cannot map a window across hosts, so they only have staged numbers.
Time to sync 8 GiB to 8 engines, ms (lower is better):
all_gather_into_tensorIncast across nodes, 1 GiB per sender into rank 0, aggregate GB/s:
Staging budget per peer (
StagingBudget.bytes_per_peer), 8 GiB sync, ms:From 32 MiB up, all budgets are within ±3%. 8 MiB costs up to 17% per call on upstream, and less once prepared. So 64 MiB is a safe default on RoCE, as on IB and Slingshot.
Reading the numbers
push_pon xpairs/xring at 256 MiB (+18% / +26% overstage_p);Matrix
Validated:
Not validated:
ZeRO stage, offload and pipeline/tensor parallelism are not touched.
AI assistance
This change, its tests and the benchmarks were developed with AI assistance (GitHub Copilot). A human must review every changed line before this leaves draft.