Skip to content

epic(moe): wire the remaining unwired MoE families to the fused decode-MoE kernel #307

Closed
7 of 7 issues completed

Description

@inureyes

Background

The fused single-token decode-MoE kernel (#268 series, steps 2b-3b) and the default-on flip (#282, PR #285) are wired into only four model paths: qwen3_moe, qwen3_next, dots1, gemma4. #286 wires qwen2_moe. Seven more MoE families reuse a SwitchGLU for the expert matmul but never call forward_fused_kernel, so they always fall back to gather_qmm regardless of MLXCEL_FUSED_MOE. This epic tracks wiring those remaining families.

The canonical one-branch pattern lives in src/models/qwen3_moe.rs, src/models/dots1.rs, and src/models/qwen3_next.rs: gate on single-token decode plus fused_moe_enabled() (src/models/switch_layers.rs:42), call experts.forward_fused_kernel(&x_flat, &topk_indices, &scores) (src/models/switch_layers.rs:276), and fall back to experts.forward(...) plus moe_weighted_sum(...) (src/models/switch_layers.rs:419). All target families use SwiGLU (SiLU) experts, so the kernel runs with the default swiglu activation (act=0); no GeGLU/relu2 template is needed. Kernel gating: affine 4/8-bit gate/up, 4/6/8-bit down, matching group_size, single-token decode only; it auto-falls-back for any unsupported config.

Two implementation classes

This is the key technical finding, verified in-tree.

Group A (direct one-branch wiring): the model's experts already use the shared crate::models::switch_layers::SwitchGLU, which exposes forward_fused_kernel. Wiring is the small one-branch change from #286/dots1.

  • minimax (imports crate::models::switch_layers::SwitchGLU).
  • lfm2 (imports super::switch_layers::SwitchGLU; only the LFM2-MoE variant has the MoE block).
  • qwen3_vl_moe (imports super::qwen3_moe::SwitchGLU, which has forward_fused_kernel).

Group B (needs SwitchGLU consolidation first): the model defines its OWN local SwitchGLU struct that does NOT expose forward_fused_kernel. These must migrate to the shared switch_layers::SwitchGLU (preferred: removes duplication) OR have a forward_fused_kernel method added to the local struct, BEFORE the one-branch dispatch works.

  • mixtral (local SwitchGLU at src/models/mixtral.rs:156).
  • olmoe (local SwitchGLU at src/models/olmoe.rs:173).
  • phimoe (local SwitchGLU at src/models/phimoe.rs:202).

Sub-issues

Out of scope / related

Definition of done

Activity

  1. added
    area:modelsModel architectures, weights, loading, metadata
    area:inferenceGeneration, sampling, decoding (incl. speculative, DRY)
    platform:macosmacOS (Apple Silicon) specific
    on Jun 16, 2026
  2. added and removed on Jun 16, 2026
  3. inureyes commented on Jun 16, 2026

    @inureyes
    MemberAuthor

    Epic implementation complete

    All sub-issues implemented, validated on real checkpoints (M1 Ultra), and merged. Executed as a single serial chain (one worktree) because every unit edits the same "Models covered" doc list, which would otherwise conflict under parallel merges.

    Sub-issues (6/6 merged)

    Issue Model PR Expert Dff Result
    #286 qwen2_moe #308 1408 fused dispatches, +5.8% decode
    #305 lfm2 (LFM2-MoE) #309 1792 fused dispatches, +6.6% decode
    #306 qwen3_vl_moe #310 768 fused dispatches, +15.4% decode
    #301 mixtral #311 14336 migrated; declines via Dff guard, no regression
    #303 phimoe #312 6400 migrated; declines via Dff guard, no regression
    #302 olmoe #314 1024 fused dispatches; perf-neutral (top_k=8); parity-preserving

    Key outcome: the fused kernel is not a universal win, so it is now self-gating

    Real-checkpoint measurement showed the two-kernel fused decode path wins only while gather_qmm underutilizes the GPU (small experts) and loses for large experts (phi-3.5-moe Dff 6400: -5.9%; mixtral Dff 14336: -21%) where gather_qmm already saturates. PR #311 added MLXCEL_FUSED_MOE_MAX_DFF (default 4096, env-tunable): forward_fused_kernel declines above it and the caller falls back to gather_qmm. The 4096 default was calibrated against measured break-even data and verified to keep every winner (including the pre-existing qwen3_next at Dff 2560, +8.7%) while excluding both regressions. Confirmed: with the guard mixtral/phimoe match their gather_qmm baselines, and raising the bound re-arms the slow path.

    Each Group B migration (qwen2_moe, mixtral, phimoe, olmoe) removed a duplicated local SwitchGLU/SwitchLinear in favor of the shared switch_layers one, which gained two additive loaders along the way: a per-expert stacking fallback for the experts.{idx} layout (#286) and overridable projection leaf names for Mixtral's w1/w2/w3 convention (#301).

    Pre-existing bugs found while validating (separate from the MoE work)

    Verification

    • Per-unit: release build + real-checkpoint greedy temp-0 fused-on vs off (parity within the documented f16 jitter class), decode tok/s with repeats, and a loader-regression control (qwen3-moe).
    • Post-merge contract tests on main HEAD: every_variant_is_registered_for_arch and the switch_layers suite (including the new stacking and projection-name tests) pass.

    Totals: 6 sub-issues across 1 wave; 1 follow-up bugfix merged (#317); 1 follow-up bug filed (#318).

  4. added this to the 0.3 milestone on Jun 21, 2026
  5. self-assigned this
    on Jun 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:inferenceGeneration, sampling, decoding (incl. speculative, DRY)area:modelsModel architectures, weights, loading, metadataplatform:macosmacOS (Apple Silicon) specificpriority:mediumMedium prioritystatus:doneCompletedtype:performancePerformance improvements

Type

No type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions