Repository navigation
fix: Diagnose and restore Gemma 4 MTP exactness on Metal #1983
Copy link
Copy link
Closed
Labels
area:inferenceGeneration, sampling, decoding (incl. speculative, DRY)Generation, sampling, decoding (incl. speculative, DRY)platform:macosmacOS (Apple Silicon) specificmacOS (Apple Silicon) specificpriority:mediumMedium priorityMedium prioritystatus:doneCompletedCompletedtype:bugBug fixes, error corrections, or issue resolutionsBug fixes, error corrections, or issue resolutions
Description
Activity
- addedtype:bugBug fixes, error corrections, or issue resolutionsBug fixes, error corrections, or issue resolutionspriority:mediumMedium priorityMedium prioritystatus:readyReady to be worked onReady to be worked onplatform:macosmacOS (Apple Silicon) specificmacOS (Apple Silicon) specificarea:inferenceGeneration, sampling, decoding (incl. speculative, DRY)Generation, sampling, decoding (incl. speculative, DRY)status:in-progressCurrently being worked onCurrently being worked onand removedstatus:readyReady to be worked onReady to be worked on
on Sep 26, 2026 Cross-reference from #1279, which reports the same both-kernel decline on the non-QAT
gemma-4-31b-it-4bitpairing. Its data weakens candidate cause 1 in this issue, so read it before Phase 1:- On M5 Max the differing byte count is identical at
--draft-block-size2, 3 and 4 (245722 of 524288; M3 Ultra read 231782). The divergence does not depend on block width, which points at a T = 1 versus T > 1 path difference rather than a width-dependent kernel split. - The Gemma 4 Unified 12B pairing passes the probe (after the narrow retry) on the same gen 15+ hosts. Its config also has
global_head_dim512 andfinal_logit_softcapping30, so the head_dim 512 unfused attention path alone cannot explain why only the 31B fails. - What differs between the two: KV head layout (31B: 16 sliding / 4 global KV heads over 32 query heads; 12B: 8 / 1 over 16) and the non-Unified versus Unified forward. The per-layer localization in Phase 1 should report which of these it lands on; candidate 2 (8-bit MLP) only applies to the QAT checkpoint, while fix(speculative): the Gemma 4 31B pairing's exactness probe fails both kernels on M3 Ultra, vetoing the burst #1217 just enabled #1279 fails on the plain 4-bit one.
- Throughput the fix would unlock, from fix(speculative): the Gemma 4 31B pairing's exactness probe fails both kernels on M3 Ultra, vetoing the burst #1217 just enabled #1279: classic 27.64 tok/s versus 62.11 tok/s with
MLXCEL_MTP_ALLOW_INEXACT=1at block 4 on M5 Max (2.25x); 1.95x to 2.65x on M3 Ultra (perf(speculative): re-measure the batch-capable B=1 MTP hardware gate on M1 Ultra and M3 Ultra and adjust mtp_b1_default #1217).
If Phase 2 restores exactness, the same PR can close #1279 by updating
docs/benchmarks.md's 31B rows and themtp_b1_defaultcomment; if exactness is unreachable, #1279's documentation criteria are what remains.- On M5 Max the differing byte count is identical at
- addedstatus:reviewUnder reviewUnder reviewand removedstatus:in-progressCurrently being worked onCurrently being worked on
on Sep 26, 2026 - added a commit that references this issue
on Sep 26, 2026 - addedstatus:doneCompletedCompletedand removedstatus:reviewUnder reviewUnder review
on Sep 26, 2026 - added a commit that references this issue
on Sep 29, 2026
Metadata
Metadata
Assignees
Labels
area:inferenceGeneration, sampling, decoding (incl. speculative, DRY)Generation, sampling, decoding (incl. speculative, DRY)platform:macosmacOS (Apple Silicon) specificmacOS (Apple Silicon) specificpriority:mediumMedium priorityMedium prioritystatus:doneCompletedCompletedtype:bugBug fixes, error corrections, or issue resolutionsBug fixes, error corrections, or issue resolutions
Problem
After #1978,
gemma-4-31b-it-qat-4bit+gemma-4-31b-it-assistant-bf16(block_size 4) on Apple Silicon Metal (~261 GB limit) declines MTP at startup:GB10 CUDA diverges too (226171 bytes), so the M=4 verify block and the M=1 chain take different kernels somewhere other than
qmv_wide. Refs #1279 (same symptom on the non-QAT 31B, M3 Ultra, LM head hypothesis only; this issue adds a localization pass and two further candidates), #1188, anddocs/benchmark_results/speculative-decoding-m1ultra-2026-08-19.md(12B op-level exact, model-level divergent).Candidate causes (unconfirmed)
global_head_dim512 (src/models/gemma4.rs:311,head_dim_for_layer).sdpa_supports_fast_path_impl(src/lib/mlxcel-core/cpp/mlx_cxx_bridge.cpp:4241-4274) accepts only 64/96/128/256 (vector) and 64/80/128/256 (full), so those layers fall back to unfused matmul attention: matrix-vector at M=1, matrix-matrix at M=4, different reduction order. Sliding layers (head_dim 256, gqa 2) stay onsdpa_vectorfor both./home/inureyes/models/mlx/gemma-4-31b-it-qat-4bit/config.json); 8-bit dispatch may differ between M=1 and M=4.Phase 0: log hygiene
With
MLXCEL_MTP_ALLOW_INEXACT=1on the same host, every verify round logs this once per sliding layer (50 layers), flooding the log on long-prompt requests:Per the comment in
trim_mask_to_keys(src/models/gemma4.rs:2545-2590, #885/#891) this is the expected buffered MTPRotatingKVCacheverify layout (uncompactedsliding_live_len + query_lenkeys vs the window-capped mask), and falling back tocausal_attentionwith the layer window is intended. Keep the signal for unexpected cases but stop the flood: WARN once per process then DEBUG, or DEBUG when the caller is the MTP verify path. Add a test next totrim_mask_to_keys_discards_too_short_multi_token(src/models/gemma4.rs:6552) proving the fallback's effective mask for query_len=4 over the buffered chronological layout equals the causal plus sliding-window mask the chain applies, so the discard is shown exact rather than assumed.Round statistics from that run (prompt 6449 tokens, 513 generated, block_size 4): 247 rounds, acceptance_rate 0.359, emitted_per_verify 2.08, zero/partial/full accept rounds 94/113/40, verify_forward_ms 11031, draft_ms 1460, prefill_seed_ms 6151 (a 6.15 s single-tick head-of-line stall, burst_wall_ms 6153). Low acceptance on an inexact block is consistent with the divergence below, so record acceptance before and after Phase 2.
Phase 1: localize
When
probe_block_chain_exactness(src/models/gemma4.rs:4967) diverges, re-run it with per-layer capture and report the first diverging layer index, its kind (sliding/full), and the first diverging sub-op (attention output, MLP output, or final norm/LM head) in one WARN line and in the string returned byspeculative_exactness::decline_reason(src/models/speculative_exactness.rs:185). Opt-in viaMLXCEL_MTP_PROBE_TRACE=1or automatic on failure only; never on the request path. Extend the existing probe, not a parallel one. Unit-test the localization function on synthetic per-layer byte vectors.Phase 2: fix (per Phase 1's finding)
If head_dim 512 is the cause, compute those layers' verify-block attention per query row (M=1 shapes) in
Attention::forward(src/models/gemma4.rs:1858), gated to the MTP verify path only. If the 8-bit MLP or LM head is the cause, apply the analogous per-row or kernel-pinning fix, or document why exactness is unreachable.Acceptance Criteria
trim_mask_to_keysWARN per layer per round; unexpected short masks still surface; the effective-mask equivalence test passes. Lands in the same PR as Phase 1.Verification
Implement all three phases in one PR, run the local gate once at the end, and keep pushes minimal.