[moe] Fuse per-slot routed-expert launches: 8 kernel launches per layer to 2 (+15.4%) - #1
Merged
MRPRESIDENT66 merged 1 commit intoAug 6, 2026
Conversation
MRPRESIDENT66
changed the base branch from
moe-single-token
to
moe-routed-expert-fusion
August 6, 2026 15:55
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 top of
moe-single-token(beehive-lab#144) — exactly one commit on top ofb227bc2, so the diff here is only the optimisation.Opening it as offered in the discussion on #144. Merge it, rewrite it, or close it — no attribution needed. It is your subsystem; this is just the prototype with the numbers attached.
What changes
The layer graph currently emits two tasks per routed slot:
At
expert_used_count = 4that is 8 kernel launches per layer — 192 per token across 24 layers. This collapses them to two:fusedRoutedExpertsGateUpSwiGLUQ8_0Allfolds the slot index into the work-group id (slot = groupIdx / moeHiddenDim), so one launch covers every slot.Qwen2MoEState.wrapExpertGategrows frommoeHiddenDimtomoeHiddenDim * numberOfExpertsUsedso the slots write disjoint windows — 22 KB at this configuration.routedExpertsDownProjectAndAccumulateQ8_0Allloops the slots inside one launch, with lane 0 carrying a running residual accumulator. Besides the launch saving, the residual is read and written once per row instead of once per slot.8 launches per layer → 2. The per-slot kernels are left in place, untouched.
Numerics are preserved deliberately
My first version summed all slots into one partial before the reduction. That reorders the arithmetic, and the generated text diverged from baseline after ~40 tokens.
This version reduces each slot with the same tree and folds it into the residual in the same order as the per-slot kernels, so the result should be arithmetically identical rather than merely close. Lane 0 seeds its accumulator from
residual.get(rowId)and writes back once, which reproduces the per-slot read-modify-write sequence exactly.Numbers
RTX 4090, JDK 21.0.2, TornadoVM
develop@ba28f1526(CUDA backend),Qwen1.5-MoE-A2.7B-Chat.Q8_0.gguf, 256 tokens,--temperature 0 --seed 42. Medians of 5 runs, with the two jars interleaved run-by-run so GPU clock drift hits both arms equally:moe-single-token--cuda-graphsSpreads: baseline 61.73–62.31 and 85.48–85.59; this PR 71.42–71.70 and 91.70–92.22.
Combined with
--cuda-graphs(off by default,-Dllama.cudaGraphs): 61.98 → 92.00 tok/s, +48.4 % over today's default.The gain is smaller with graphs enabled, as expected — graphs already amortise launch cost, so what remains is the single residual accumulation instead of four.
Correctness
At 24 tokens the output is identical between baseline and this PR, and stable across 3 runs of each.
Longer generations cannot be compared this way: the existing MoE path is non-deterministic beyond roughly 40 tokens — three runs of the unmodified
moe-single-tokenbranch, same jar,--temperature 0 --seed 42, 200 tokens, produce three different outputs, with and without--cuda-graphs. That is pre-existing and not introduced here, but it is worth fixing before this lands, since it makes any subsequent A/B harder to trust. Details in the #144 thread.Reproducing
Note
--max-tokens 1024does not fit on a 24 GB card with this model:TornadoOutOfMemoryException: Unable to allocate 330612752 bytes.