Skip to content

perf(models): generalize fused QKV+RMSNorm+RoPE to standard-RMSNorm QK-norm models #326

Closed

Description

@inureyes

Summary

Generalize the existing fused QKV-projection + split + Q/K-norm + RoPE kernel so models using a standard RMSNorm (not only GemmaRMSNorm) can use it, then wire it into Qwen3 and Qwen3-MoE first, with a staged rollout to the other QK-norm families.

Source: idea.md "2. Generic Fused QKV + RMSNorm + RoPE For QK-Norm Models" (P0, expected payoff medium, risk medium-high).

Current state

  • FusedQKVLinear::forward_split_norm_rope_quantized() exists but is specialized to GemmaRMSNorm and is documented as "Used by: Gemma3 dense attention path" (src/lib/mlxcel-core/src/layers.rs:1282).
  • Qwen3 attention uses a fused QKV projection, then separate reshape, q_norm, k_norm, transpose, and RoPE ops (src/models/qwen3.rs, attention forward around line 115).
  • Many models still apply Q/K RMSNorm as separate ops: Qwen3-MoE, Qwen3-VL, Qwen3-VL-MoE, OLMoE, OLMo2/3, Apertus, Exaone4, Exaone-MoE, and MiniMax.
  • The decode-gap investigation reports dense Llama/Granite near parity, while the gap grows with extra small ops such as QK-norm and xIELU (docs/benchmark_results/moe-decode-gap-investigation.md).

Proposal

  • Generalize the fused projection/split/norm/rope primitive so standard RMSNorm weights can use it, not only GemmaRMSNorm (for example, accept a norm trait/enum or a generic norm parameter).
  • Wire it first into Qwen3 and Qwen3-MoE, which already have a fused QKV layout and good benchmark coverage.
  • Then port compatible QK-norm families in small batches: Qwen3-VL text path, OLMoE/OLMo2/OLMo3, Apertus, Exaone4/Exaone-MoE, MiniMax. These follow-up family rollouts can be split into sub-issues once the primitive is proven.

Scope

  • Generalize forward_split_norm_rope_quantized and update its // Used by: comment per the shared-function convention in docs/code-guidelines.md.
  • Wire Qwen3 and Qwen3-MoE in this issue.

Validation

  • Correctness: RMS < 5e-3 against the existing graph path, plus greedy parity for fixed prompts where deterministic parity is expected.
  • Performance: mlxcel-bench-decode on qwen3-0.6b-4bit, qwen3-8b-4bit, qwen3-30b-a3b-4bit, and Apertus-8B-2509-4bit.
  • Regression guard: include a non-QK-norm dense model (for example Llama) to prove common attention behavior did not change.
  • Suggested first experiment (idea.md): prototype generic RMSNorm support on Qwen3 only first, to isolate the QK-norm hypothesis with minimal model spread.

Acceptance criteria

  • The fused primitive accepts standard RMSNorm Q/K norms, not just GemmaRMSNorm.
  • Qwen3 and Qwen3-MoE attention dispatch the generalized fused path on decode; the non-fused path remains as fallback.
  • RMS < 5e-3 vs the graph reference; greedy temp-0 parity (or documented jitter) on the listed checkpoints.
  • mlxcel-bench-decode numbers recorded; the non-QK-norm dense regression guard shows no change.
  • // Used by: comment updated; INTEGRATION into the real Qwen3/Qwen3-MoE forward path verified.

Risk and priority

idea.md priority P0; risk medium-high. Directly targets the "extra small ops per layer" decode gap and yields one shared primitive reusable by many recently added QK-norm families.

References

  • idea.md "2. Generic Fused QKV + RMSNorm + RoPE For QK-Norm Models".
  • Code: src/lib/mlxcel-core/src/layers.rs (forward_split_norm_rope_quantized), src/models/qwen3.rs.
  • docs/benchmark_results/moe-decode-gap-investigation.md, docs/code-guidelines.md (shared-function rule).

Activity

  1. added
    area:modelsModel architectures, weights, loading, metadata
    area:coremlxcel-core: MLX FFI, primitives, KV cache, layers
    and removed on Jun 17, 2026
  2. inureyes commented on Jun 18, 2026

    @inureyes
    MemberAuthor

    Deferred validation completed on GB10 / CUDA (PR #357)

    The two open acceptance items were the validation/benchmark pass. Done now on the one backend the fused QK-norm primitive was never measured on (the doc comment had it gated off "pending a per-backend win (e.g. CUDA)").

    Decode throughput (GB10 SM 12.1, same binary, MLXCEL_FUSED_QK_NORM=1 vs =0, best-of-3, --no-chat-template + 256 tok):

    Model fused ON graph OFF fused/graph
    qwen3-0.6b-4bit (wired dense) 372.8 387.2 0.96x
    qwen3-8b-4bit (wired dense) 52.6 52.4 1.00x
    qwen3-30b-a3b-4bit (wired MoE) 83.8 90.7 0.92x
    apertus-8b-2509-4bit (not wired) 43.0 43.2 flag inert
    llama-3.1-8b-4bit (regression guard) 48.0 48.2 flag inert

    CUDA is not a per-backend win: the fused path is slower on every wired model, worst on the 48-layer MoE. The primitive builds the same MLX ops as the graph path in one C++ call, so the GPU work is identical; it only cuts FFI crossings and norms after the head transpose. Decode is GPU/bandwidth-bound, so that buys nothing. Same direction as M1 Ultra (1-3.4% slower) and consistent with M5 Max preferring the split decode path. The flag stays default-OFF; the graph/split path is the decode default on every measured backend.

    Regression guard: apertus (QK-norm but not wired) and llama (no QK-norm) show the flag inert, so the common attention path is untouched.

    Correctness:

    • RMS < 5e-3 vs graph reference: fused_qkv_split_norm_rope_standard_rmsnorm_matches_graph unit test.
    • Greedy temp-0 over 256 tok: qwen3-8b byte-identical; qwen3-0.6b/30b diverge at a near-tie argmax into coherent equal-quality text. This is documented jitter, not a fused-path regression: on CUDA the graph path is itself non-deterministic run-to-run (GPU FP-reduction order) while the fused path is deterministic, so the divergence stays inside the graph baseline's own envelope.

    Full method, table, and determinism analysis: docs/benchmark_results/fused-qk-norm-decode-gb10.md (PR #357). No code behavior change.

  3. added this to the 0.3 milestone on Jun 21, 2026
  4. 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:coremlxcel-core: MLX FFI, primitives, KV cache, layersarea:modelsModel architectures, weights, loading, metadatapriority:highHigh prioritystatus:doneCompletedtype:performancePerformance improvements

Type

No type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions