Skip to content

fix(moe): keep right_sorted on the SwitchGLU sorted prefill path - #2257

Merged
inureyes merged 1 commit into
mainfrom
fix/issue-2241-moe-right-sorted
Oct 10, 2026
Merged

inureyes merged 1 commit into
mainfrom
fix/issue-2241-moe-right-sorted

Conversation

@inureyes

Copy link
Copy Markdown
Member

Summary

  • The sorted prefill path of SwitchGLU::forward lost right_sorted when perf(moe): share gather indices across SwitchGLU projections #2137 shared one explicit identity lhs_indices across gate, up and down: upstream gather_qmm / gather_mm build the primitive with right_sorted = sorted_indices && !lhs_indices, and every backend's sorted MoE prefill kernel keys on that flag, so affine MoE prefill fell back to per-row kernels, 1.7x to 4.8x slower on gfx1151.

What changed

  • src/models/switch_layers.rs: the sorted path now shares only the uint32 expert ids and leaves lhs_indices to MLX, whose default is the same arange; the unsorted (decode) sharing of perf(moe): share gather indices across SwitchGLU projections #2137 is unchanged, so its decode-graph node cut stays. SwitchLinear::forward_indexed documents the constraint and debug-asserts it.
  • src/lib/mlxcel-core: new gather_sorted_flags bridge accessor walks an unevaluated graph and reports the left_sorted / right_sorted state of every GatherQMM / GatherMM node.
  • docs/environment-variables.md: the MLX_ROCM_GATHER_QMV_EXPERT_BATCHED row records that the op sets right_sorted only when the caller passes sorted_indices and no lhs_indices.
  • Adds the measurement data and driver under docs/benchmark_results/data/rocm-moe-prefill-right-sorted-gfx1151-2026-10-09/.

Benchmarks (gfx1151, pp512, medians of 3 guarded runs, arms alternated)

  • granite-4.0-h-tiny-4bit prefill 606.91 to 918.41 tok/s (2026-10-05 page: 918.59)
  • Mixtral-8x7B-Instruct-v0.1-4bit 25.33 to 122.21 tok/s (page: 125.63)
  • Qwen3-30B-A3B-4bit 291.26 to 318.36 tok/s
  • gpt-oss-20b-MXFP4-Q4 465.74 to 500.10 tok/s (unaffected path, GptOssSwitchGLU); decode unchanged on all four models
  • rocprofv3: gather_qmv_expert_batched_kernel back at 240 launches (42.7 percent of kernel time) where the pre-fix build had none

Reproduction

  • models::switch_layers::gather_index_tests::sorted_prefill_keeps_right_sorted_on_every_projection pins right_sorted on all three projections for quantized and regular experts without running a kernel. On the previous main it fails with "projection 0 lost right_sorted: the sorted path passed an explicit lhs_indices"; with the fix it passes (1 passed, 0 failed, 9071 filtered out, both runs on gfx1151).

Metal and CUDA cannot be measured on this Linux host; the flags are computed in the op constructor, backend-independent, and the test pins them for every backend.

Verified locally with make verify-rocm on gfx1151 at d3faa75: 12,199 passed, 0 failed, 403 ignored.

Closes #2241

PR #2137 shared one explicit identity `lhs_indices` across gate, up and down on the sorted prefill path of `SwitchGLU::forward`. Upstream `gather_qmm` and `gather_mm` build their primitive with `right_sorted = sorted_indices && !lhs_indices`, and every backend keys its sorted MoE prefill kernel on that flag (the ROCm expert-batched kernel and sorted-rhs schedule in `qmm.hip`, Metal's `gather_qmm_rhs` and sorted `gather_mm`, CUDA's grouped GEMM), so the explicit lhs silently moved affine MoE prefill back to the per-row kernels: 1.7x to 4.8x slower on gfx1151 (issue #2192).

The sorted path now shares only the `uint32` expert ids and leaves `lhs_indices` to MLX, whose default is the same arange; the unsorted (decode) sharing of #2137 is unchanged, so its decode-graph node cut stays. `SwitchLinear::forward_indexed` documents the constraint and debug-asserts it.

`mlxcel_core::gather_sorted_flags` walks an unevaluated graph and reports the `left_sorted` / `right_sorted` state of every `GatherQMM` / `GatherMM` node, so `sorted_prefill_keeps_right_sorted_on_every_projection` pins the flag on all three projections for quantized and regular experts on every backend without running a kernel; it fails on the previous `main`.

Measured on gfx1151 at pp512 against the pre-fix build, three guarded rounds with arms alternated (docs/benchmark_results/data/rocm-moe-prefill-right-sorted-gfx1151-2026-10-09/): granite-4.0-h-tiny-4bit prefill 606.91 to 918.41 tok/s at the median (2026-10-05 page: 918.59), Mixtral-8x7B 25.33 to 122.21 (page: 125.63), Qwen3-30B-A3B 291.26 to 318.36, gpt-oss-20b 465.74 to 500.10; decode unchanged. The rocprofv3 trace shows `gather_qmv_expert_batched_kernel` back at 240 launches (42.7 percent of kernel time) where the pre-fix build had none.

Refs #2241
@inureyes
inureyes merged commit c2523e5 into main Oct 10, 2026
26 of 27 checks passed
@inureyes
inureyes deleted the fix/issue-2241-moe-right-sorted branch October 10, 2026 14:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(moe): sorted gather_qmm prefill lost right_sorted since #2137, MoE prefill 1.7x to 4.8x slower

1 participant