feat(ggml): in-graph collective node via a caller-registered callback - #700
Open
maikzz32 wants to merge 1 commit into
Open
feat(ggml): in-graph collective node via a caller-registered callback#700maikzz32 wants to merge 1 commit into
maikzz32 wants to merge 1 commit into
Conversation
Multi-device inference that partitions work across processes has to combine partial sums between the kernels that produce them and the kernels that consume them. Doing that outside the graph means a host round trip per combination: download, reduce, upload, synchronize. For a 43-layer MoE model that is 43 round trips per forward. ggml_cluster_allreduce(ctx, a, fn, user) adds a node whose value is `a` summed element-wise across the caller's process group. The CUDA/HIP backend runs it on the same stream as the surrounding kernels, so it is ordered after the producers and before the consumers with no host synchronization. ggml stays free of any collective library: the node carries a callback and a user pointer in op_params, and the caller decides how the sum is produced (NCCL, RCCL, MPI, a test double). There is no new dependency, no new build option and no new backend entry point. It is a sub-op of the existing GGML_OP_MOE_FUSED family, so supports_op needs no change. A graph containing such a node is not captured by default. Measured on gfx1151 with RCCL 2.30.4: capture is bit-exact there — a 128-token greedy completion stays byte-identical — but replaying a 5445-node graph that holds 43 collectives is 11 % SLOWER than launching it eagerly (21.5 -> 19.15 tok/s autoregressive, 29.45 -> 26.65 with speculative decode, two nodes over RoCE, medians of 3 runs at the same clocks). GGML_CUDA_COLLECTIVE_GRAPH_CAPTURE=1 allows it for anyone whose runtime behaves differently. Downstream measurement for the node itself, same hardware: it replaces a per-layer host round trip, and the whole 43-collective budget is then 2.7 ms of a 48 ms decode step (measured by returning early from the callback, which is wrong output but exact timing). Co-Authored-By: Claude Opus 5 <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.
Multi-device inference that partitions work across processes has to combine partial sums between the kernels that produce them and the kernels that consume them. Doing that outside the graph means a host round trip per combination: download, reduce, upload, synchronize. For a 43-layer MoE model that is 43 round trips per forward.
ggml_cluster_allreduce(ctx, a, fn, user)adds a node whose value isasummed element-wise across the caller's process group. The CUDA/HIP backend runs it on the same stream as the surrounding kernels, so it is ordered after the producers and before the consumers with no host synchronization.ggml gains no dependency
The node carries a callback and a user pointer in
op_params; the caller decides how the sum is produced (NCCL, RCCL, MPI, a test double):No new library, no new build option, no new backend entry point. It is a sub-op of the existing
GGML_OP_MOE_FUSEDfamily, sosupports_opneeds no change.Graph capture
A graph containing such a node is not captured by default, and the reason is measured rather than assumed.
On gfx1151 with RCCL 2.30.4, capture is bit-exact — a 128-token greedy completion stays byte-identical — and replaying a 5445-node graph that holds 43 collectives is slower than launching it eagerly:
Two nodes over RoCE v2, DeepSeek V4 Flash, medians of 3 runs, same binary and clocks,
GGML_CUDA_GRAPH_STATS=1confirming 23 replays per 25 forwards (so this is not capture churn).GGML_CUDA_COLLECTIVE_GRAPH_CAPTURE=1allows it for anyone whose runtime behaves differently.What the node itself is worth
Measured by returning early from the callback — wrong output, exact timing — the whole 43-collective budget is 2.7 ms of a 48 ms decode step on that hardware. The node's value is not the collective's speed but that it removes the per-layer host round trip and lets a rank keep using the fused whole-model graph; without it the same model falls back to 43 host-driven per-layer graphs.
Hardware note: all numbers are gfx1151 (Radeon 8060S, Ryzen AI Max 395, ROCm 10), not the sm_86+ hardware in CONTRIBUTING. Happy to have them re-run on a 3090 or the 395 box if that is useful.