Skip to content

perf(moe): wire olmoe to the fused decode-MoE kernel - #314

Merged
inureyes merged 1 commit into
mainfrom
perf/issue-302-olmoe-fused-kernel
Jun 16, 2026
Merged

inureyes merged 1 commit into
mainfrom
perf/issue-302-olmoe-fused-kernel

Conversation

@inureyes

Copy link
Copy Markdown
Member

Summary

Migrate OLMoE from its local SwitchGLU/SwitchLinear pair to the shared crate::models::switch_layers::SwitchGLU, then wire the one-branch fused single-token decode dispatch. OLMoE's expert intermediate (1024) is below MLXCEL_FUSED_MOE_MAX_DFF (4096), so the kernel dispatches on every single-token decode step and delivers a real throughput gain.

What changed

  • src/models/olmoe.rs: removed local SwitchLinear enum (68 lines) and local SwitchGLU struct with forward/gather_sort/scatter_unsort impls (117 lines). No other module imported either type.
  • SparseMoeBlock.experts retyped from the deleted local SwitchGLU to crate::models::switch_layers::SwitchGLU.
  • SparseMoeBlock::from_weights: construction now calls crate::models::switch_layers::SwitchGLU::from_weights(weights, "{layer}.switch_mlp", group_size, bits). Standard gate_proj/up_proj/down_proj leaf names match the shared loader directly; the existing sanitize_weights helper already stacks per-expert weights under that prefix.
  • Removed local impl SwitchGLU { from_weights } and impl SwitchLinear { from_weights } (40 lines). get_weight_copy and sanitize_weights are kept (still used by Attention and OlmoeModel::from_weights).
  • SparseMoeBlock::forward: bare experts.forward + moe_weighted_sum dispatch replaced with the standard fused-kernel branch (mirror of qwen3_moe/phimoe).
  • Added a narrow compile-time unit test confirming fused_moe_enabled() is callable from olmoe's scope.
  • docs/benchmark_results/fused-moe-decode-kernel-design.md: wired-path count bumped from nine to ten; olmoe entry moved from the not-wired list to the wired list; not-wired list now contains only minimax.

Test plan

  • cargo check --lib --tests --features metal,accelerate passes clean
  • cargo clippy --lib --tests --features metal,accelerate -- -D warnings passes clean
  • Real-checkpoint validation (mlx-community/OLMoE-1B-7B-Instruct-4bit) pending the orchestrator

Closes #302

@inureyes inureyes added type:refactor Code restructuring without changing functionality type:performance Performance improvements priority:medium Medium priority area:models Model architectures, weights, loading, metadata area:inference Generation, sampling, decoding (incl. speculative, DRY) platform:macos macOS (Apple Silicon) specific status:review Under review labels Jun 16, 2026
Migrate OLMoE from its local SwitchGLU/SwitchLinear pair to the shared crate::models::switch_layers::SwitchGLU, then add the one-branch fused single-token decode dispatch that already covers qwen3_moe, qwen3_next, dots.llm1, gemma4, qwen2_moe, mixtral, lfm2, qwen3_vl_moe, and phimoe.

What changed in src/models/olmoe.rs:

- Remove local SwitchLinear enum + forward impl (~68 lines) and local SwitchGLU struct + forward/gather_sort/scatter_unsort impls (~117 lines). Neither was imported by any other module.
- Retype SparseMoeBlock.experts from the deleted local SwitchGLU to crate::models::switch_layers::SwitchGLU.
- SparseMoeBlock::from_weights: replace SwitchGLU::from_weights(weights, args, prefix) with crate::models::switch_layers::SwitchGLU::from_weights(weights, prefix, args.group_size(), args.bits()). Prefix passed is {layer}.switch_mlp; standard gate_proj/up_proj/down_proj leaf names match the shared loader directly. The sanitize_weights helper already stacks per-expert weights under that prefix before from_weights runs.
- Remove local impl SwitchGLU { from_weights } and impl SwitchLinear { from_weights } (~40 lines). Helper functions get_weight_copy and sanitize_weights are kept (used by Attention and OlmoeModel::from_weights).
- SparseMoeBlock::forward: replace the bare experts.forward + moe_weighted_sum dispatch with the standard fused-kernel branch (mirror of qwen3_moe). OLMoE's expert intermediate is 1024, below MLXCEL_FUSED_MOE_MAX_DFF (4096), so the kernel dispatches unconditionally at single-token decode and produces a real throughput gain. No fallback note needed.
- Add a narrow compile-time unit test confirming fused_moe_enabled() is callable from olmoe's scope.

docs/benchmark_results/fused-moe-decode-kernel-design.md:

- Bump wired-path count from nine to ten.
- Move olmoe entry from the not-wired list to the wired list with a brief description of checkpoint layout and the fact that the kernel actually fires (intermediate 1024 < DFF cap).
- not-wired list now contains only minimax.
@inureyes

Copy link
Copy Markdown
Member Author

Rebased onto main (picks up the olmoe q_norm crash fix #316). Orchestrator validation on mlx-community/OLMoE-1B-7B-0125-Instruct-4bit (M1 Ultra): the fused dispatch is wired correctly and fires (fused-on differs from MLXCEL_FUSED_MOE=0 only within the documented f16 jitter class), and decode is perf-neutral (~250 tok/s both on and off; no gain at olmoe's top_k=8 / Dff 1024, and no regression). The migration removes the local SwitchGLU/SwitchLinear duplication. Note: olmoe still emits degenerate output on the gather_qmm baseline too, from a separate pre-existing defect (not q_norm, not the MoE path) tracked in #318; this PR neither causes nor worsens it. The fused wiring is parity-preserving relative to the baseline.

@inureyes
inureyes force-pushed the perf/issue-302-olmoe-fused-kernel branch from cf6481f to c979772 Compare June 16, 2026 14:33
@inureyes inureyes added status:done Completed and removed status:review Under review labels Jun 16, 2026
@inureyes
inureyes merged commit d9f88e0 into main Jun 16, 2026
5 checks passed
@inureyes
inureyes deleted the perf/issue-302-olmoe-fused-kernel branch June 16, 2026 14:35
@inureyes inureyes added this to the 0.3 milestone Jun 21, 2026
@inureyes inureyes self-assigned this Aug 31, 2026
@inureyes inureyes removed the type:refactor Code restructuring without changing functionality label Sep 7, 2026
be-free-snowboy pushed a commit to freestylejs/snowbrain-mlxcel that referenced this pull request Oct 27, 2026
Migrate OLMoE from its local SwitchGLU/SwitchLinear pair to the shared crate::models::switch_layers::SwitchGLU, then add the one-branch fused single-token decode dispatch that already covers qwen3_moe, qwen3_next, dots.llm1, gemma4, qwen2_moe, mixtral, lfm2, qwen3_vl_moe, and phimoe.

What changed in src/models/olmoe.rs:

- Remove local SwitchLinear enum + forward impl (~68 lines) and local SwitchGLU struct + forward/gather_sort/scatter_unsort impls (~117 lines). Neither was imported by any other module.
- Retype SparseMoeBlock.experts from the deleted local SwitchGLU to crate::models::switch_layers::SwitchGLU.
- SparseMoeBlock::from_weights: replace SwitchGLU::from_weights(weights, args, prefix) with crate::models::switch_layers::SwitchGLU::from_weights(weights, prefix, args.group_size(), args.bits()). Prefix passed is {layer}.switch_mlp; standard gate_proj/up_proj/down_proj leaf names match the shared loader directly. The sanitize_weights helper already stacks per-expert weights under that prefix before from_weights runs.
- Remove local impl SwitchGLU { from_weights } and impl SwitchLinear { from_weights } (~40 lines). Helper functions get_weight_copy and sanitize_weights are kept (used by Attention and OlmoeModel::from_weights).
- SparseMoeBlock::forward: replace the bare experts.forward + moe_weighted_sum dispatch with the standard fused-kernel branch (mirror of qwen3_moe). OLMoE's expert intermediate is 1024, below MLXCEL_FUSED_MOE_MAX_DFF (4096), so the kernel dispatches unconditionally at single-token decode and produces a real throughput gain. No fallback note needed.
- Add a narrow compile-time unit test confirming fused_moe_enabled() is callable from olmoe's scope.

docs/benchmark_results/fused-moe-decode-kernel-design.md:

- Bump wired-path count from nine to ten.
- Move olmoe entry from the not-wired list to the wired list with a brief description of checkpoint layout and the fact that the kernel actually fires (intermediate 1024 < DFF cap).
- not-wired list now contains only minimax.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:inference Generation, sampling, decoding (incl. speculative, DRY) area:models Model architectures, weights, loading, metadata platform:macos macOS (Apple Silicon) specific priority:medium Medium priority status:done Completed type:performance Performance improvements

Projects

None yet

Development

Successfully merging this pull request may close these issues.

perf(moe): wire olmoe to the fused decode-MoE kernel

1 participant