Repository navigation
cuda: a reduce-scatter in rank order over NCCL send and receive - #642
Conversation
|
Thank you, @Bizuayeu. A reduce-scatter that adds in rank order, so every rank holds the bits an all-gather plus a rank-order sum gives, is exactly what GLM's TP path needs, and the receipts over RoCE are clear. Our gate for it needs two linked GB10s, and both of ours were busy through tonight's 1.0.6 landing window, so it isn't in 1.0.6. We'll run |
|
Independent receipt for a9b86c3 on two ThinkStation PGX (GB10), sm_121, over a direct ConnectX-7 link. Zig 0.17.0, driver 580.178.04, CUDA 13.0.2 (nvcc 13.0.88), NCCL 2.28.9+cuda13.0. NCCL reports NET/IB RoCE, with GDR 0.
Ranges span the medians of 20 measurements per rank. Reduce-scatter includes its sum; all-gather measures the exchange alone. These runs used CUDA 13.0.2 and NCCL 2.28.9, with NCCL reporting GDR 0. No matched-configuration before/after comparison was run, so these timings establish neither a regression nor a performance bound. TP=3 was not tested. |
|
Thank you, @cwschroeder. A second pair on different hardware (PGX, CUDA 13.0.2, NCCL 2.28.9) giving byte-equal slices on every row count and seed, including the uneven and zero-row cases, is exactly the receipt this needed next to ours. For @ashhart: #642 still merges cleanly on v1.0.7. |
|
Here's a receipt from a second GB10 pair, as asked in #593. The rank-scatter rows are byte-equal on both ranks in all four completed dual-rail runs. Setup.
Host tests on this branch, each with a fresh One GB10, on each Spark: Two ranks, joined by
Your table has 1.00–1.05 ms for the reduce-scatter and 2.33–2.41 ms for the all-gather at two ranks. Here the all-gather is similar or a little faster (1.94–2.30 ms), but the reduce-scatter takes 2.39–3.61 ms: longer than the all-gather on this pair, not about half. The reduce-scatter time includes its rank-order sum, and the all-gather column is the exchange alone. I haven't isolated the cause. If you set any NCCL variables on your pair, for example One setup note for anyone repeating this on the host: my first two-rank run, with both ranks as plain user processes, failed. Rank 0 stopped in NCCL's P2P preconnect with |
|
Thank you, @SvangenStudios. Byte-equal slices on a third setup (NCCL 2.30.7, two rails) is a useful receipt, and the memlock note will save the next person a failed first run. Our pair did set NCCL variables, and the PR didn't say so. Each rank ran in a container with memlock unlimited, NCCL 2.30.7 (the same as yours), and these from its rank file: NCCL reported |
|
Thanks for the settings. Adding just Setup. This is the same pair and the same commit (
Results. All 18 rank runs printed
Today's baseline was faster than yesterday's 2.39–3.61 ms, so the comparison above uses the baseline from these rounds. The four-channel overrides alone brought the reduce-scatter to about 1 ms in all three rounds. N4 and F had overlapping ranges, and the full list gave no clear further improvement. With only three rounds in a fixed order, I can't conclude that the other settings have no effect. The all-gather ranges overlap, with no consistent improvement across rounds and more variation in N4. I haven't profiled the parts of the reduce-scatter separately, or checked why the baseline reports 64 channels. |
|
Thanks, @SvangenStudios. That confirms the suspect the PR description named: with four channels your pair lands at our 1.00–1.05 ms, and the rest of our list made no clear difference on top of that. We haven't looked at why NCCL picks 64 channels on these pairs either. I'll change the description's last sentence from "isn't confirmed yet" to point at your three-arm receipt. The byte equality doesn't depend on any of these settings, as your 18 runs show. |
|
Thanks, @SvangenStudios and @Bizuayeu. The matched baseline and four-channel arms give us a concrete setting to compare on the PGX/GB10 pair used in my earlier receipt. For that comparison I will keep binary, NCCL version, topology and call sizes fixed, check byte-equal slices on both ranks, and report reduce-scatter and all-gather separately. The HCA-startup retest requested in #636 is a separate check. Model throughput will need its own measurement. |
|
For whoever merges: #636 adds the same |
…_scatter.zig) nccl.zig binds ncclSend and ncclRecv beside the collectives. rank_scatter.zig cuts a [rows][width] fp32 partial by whole rows (the first rows % world ranks one more, as canon.partition cuts equal ranks), sends each peer its rows and receives its own from each peer in one group, then rank_sum.cu adds the terms rank 0 first, rounding each add. Every rank so holds the bits an all-gather followed by a rank-order sum gives, with each rank receiving only its own rows. NCCL's ncclReduceScatter is not used: it adds in its ring's order, not rank order. tf-cuda-test rank-scatter <rank> <n> [id] checks every rank's rows against an all-gather plus a host rank-order sum (rows 1, 4, 64, 2047 and 2048 by 4096, three seeds), the ranks joined by NCCL_COMM_ID or by rank 0's unique id in a file, and times both exchanges.
a9b86c3 to
0a36982
Compare
… over hosts The family ashhart#593 tracks, on the shared NVFP4 layer (`qlinear`, `experts.Grouped`) and hand-written `.cu` for the rest: KDA and MLA/DSA attention, the hyper-connection glue, MoE routing, the MTP draft head and sampling. Rank 0 serves through `tensorfold-native serve` and the other ranks follow its steps; each rank holds its share of every axis (uneven where a width doesn't divide), and a prompt chunk's rows are exchanged with a rank-order reduce-scatter (ashhart#642). Drafts and concurrency go through the lanes core: `prefill_step` takes a prompt a chunk at a time between the other streams' rounds, and `hidden_rows` lets several streams' verify windows share one forward. The prompt cache keeps states on the family's 2,048-row chunk starts (`snap_grid`). Checks in `tf-cuda-test`: `glm-ref` holds every projection whose reduction order is the kernels' own against float64 on synthetic inputs; the lanes checks (cover, concurrent, shared) run at TP over hosts and, with `glm-synth`'s two-layer checkpoint, on one GPU.
@kelchm's #819 and @SvangenStudios's share of #642 land in 1.0.12.
|
Thank you, @Bizuayeu. A reduce-scatter that adds in rank order, so every rank holds the bits an all-gather and a rank-order sum would give while receiving only its own rows, is what an exact sum across tensor-parallel ranks needs. Thank you too, @cwschroeder and @SvangenStudios, for running it on your own pairs. It ships in TensorFold 1.0.12 under your name ( On one GB10, Our own two-rank run is still owed, so your receipts and the other two pairs' carry the exchange itself. GLM-5.3-Flash at TP over hosts (#804) is its first caller. Posted by Ash's AI Assistant Synthena |
|
@ashhart, one correction for the record: #804 doesn't call |
|
Confirmed by a source check on main Our two-GB10 receipt here covers the standalone rank-scatter test, not GLM end-to-end or #636's integrated collective. I will keep that scope explicit in our reports. Once #636 uses the rank-order reducer beyond two ranks, that caller will need its own integration gate. |
…-fabric build.zig keeps main's kernel-contract and video steps beside the TP steps; nccl.zig keeps ashhart#642's send/recv with the TP abort and async-error symbols. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t#861) into stack/13b-performance-device The shared corrections reach the device head: sessions null-unlimited / zero-none budgets and the Mac NVMe-test guard (ashhart#637), the TP graph-cache startup agreement (ashhart#636, graph_cache_env), main's ashhart#642 NCCL send/recv, and ashhart#861's lane core. - sessions_gpu.zig: TF_DSV41_SESSION_RESIDENT / _RESIDENT_GIB default to null (unlimited). The old 0 default would now mean "no idle chats kept". The store's policy comes from one `policy` function, and its test fails with the old default. - zig/build/cuda.zig: main's runner block with the DeepSeek-V4.1 image-aware runner, tool, server and sampler installs. - zig/build/dsv41.zig: the host compile check's options carry slide_test_hooks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The contributor's commit landed above with the same code; this keeps the PR's head in the history with no change to the tree.
…and ashhart#757 onto the candidate
@kelchm's ashhart#819 and @SvangenStudios's share of ashhart#642 land in 1.0.12.
Step 2 of #593, as claimed there: NCCL send/recv and an exact reduce-scatter in rank order. It doesn't depend on the weight format: it sums the fp32 partial rows a layer's attention and MLP (or experts) leave on every rank.
zig/src/cuda/nccl.zig:ncclSend/ncclRecvbeside the AllReduce, AllGather and Broadcast already there.zig/src/cuda/rank_scatter.zig:sharecuts[rows][width]by whole rows; the firstrows % worldranks take one more, ascanon.partitioncuts equal ranks.RankScatter.runputs the point-to-point sends and receives in one group: each peer gets its rows of this rank's partial, and this rank receives its rows from each peer. There's no transfer to itself, and a rank without rows only sends.rank_sum.cuthen adds the terms rank 0 first, rounding each add. Every rank so holds the bits an all-gather followed by a rank-order sum gives, while receiving only its own rows. NCCL'sncclReduceScatterisn't used because it adds in its ring's order, not rank order. It's also unrelated tocluster/exchange.zig'sreduceScatter, which cuts by columns and sums in a fixed binary tree.tf-cuda-test rank-scatter <rank> <n> [id]: every rank's rows are checked against an all-gather plus a host rank-order sum. Rows are 1, 4, 64, 2,047 and 2,048 by 4,096, with three seeds each. The ranks join byNCCL_COMM_IDor by rank 0's unique id in a file. The id file is for hosts whose bootstrap interfaces share no subnet with rank 0's address, as on our three-host ring. The test also times both exchanges.Receipts on GB10s (DGX Spark, sm_121, CUDA 13.3, NCCL 2.30.7), the ranks over direct RoCE links, each rank in a container with memlock unlimited:
zig build testandzig build test-cuda-host(300/300) pass.zig fmt --checkis clean for the touched files.lean_checkreports 542 problems, the same as main.One GB10:
tf-cuda-test ncclandrank-scatter 0 1pass.Every rank's rows are byte-equal to an all-gather plus a rank-order sum, for all five row counts and three seeds:
NCCL_COMM_IDTimes are the medians of 20 per rank. The reduce-scatter includes its sum; the all-gather column is the exchange alone, before any sum.
NCCL settings, from each rank's file:
NCCL_NET=IB,NCCL_IB_GID_INDEX=3,NCCL_IB_ROCE_VERSION_NUM=2,NCCL_IB_ADDR_FAMILY=AF_INET,NCCL_SOCKET_FAMILY=AF_INET,NCCL_MIN_NCHANNELS=4andNCCL_MAX_NCHANNELS=4.NCCL_SOCKET_IFNAMEnames the link's interface.NCCL_IB_HCAnames the link's two rails at TP=2, and all four ports at TP=3, which also setsNCCL_IB_SUBNET_AWARE_ROUTING=1andNCCL_NET_PLUGIN=none. At TP=2, NCCL reported 4 coll and 4 p2p channels (2 per peer) and GDR 0. The two other pairs' receipts in this thread measured 1.86-3.61 ms for the reduce-scatter. On one of those pairs,NCCL_MIN_NCHANNELS=4 NCCL_MAX_NCHANNELS=4alone brought it to 0.99-1.06 ms, against 2.30-2.73 ms with the 64 channels NCCL picked by default (receipt); the rest of the list made no clear further difference there.Against GLM-5.3-Flash and full GLM-5.3 on the native CUDA engine (tracker) #593's gates: the sums stay fp32 and are added in the order an all-gather plus a rank-order sum uses, so there's no new precision and no projection whose reduction order changes.
Where we use it: our GLM-5.3-Flash port reads
nvidia/GLM-5.3-Flash-NVFP4as stored (the NVFP4 family in #548). At TP=2, each prompt chunk's partial rows go through this exchange, and each rank finishes only its own rows; TP=3 is next.Written with Weave (My Symbiotic AI)