Repository navigation
docs(benchmarks): Gemma3n decode profile on M5 Max - #358
Merged
Merged
Conversation
Companion to the M1 Ultra profile from #329 (PR #345), measured on Apple M5 Max (40-core GPU, 128 GB, macOS 26.5.1), current HEAD, MLX 0.31.2, mlx-vlm 0.4.4. The no-fusion conclusion holds on M5 Max, by a partly different route: - M5 Max runs these 4-bit checkpoints at 1.8 to 1.9x M1 Ultra (e2b-4bit 161.5, e4b-4bit 112.2 text / 107.0 image tok/s). - Decode is 92 to 96% GPU-bound; the Rust graph-build step where an FFI-collapsing fusion would land stays hidden behind async_eval. - The MLX_MAX_OPS_PER_BUFFER lever that recovers +11 to 13% on M1 Ultra is flat on M5 Max (at most +1.7%, within noise), so the knob must stay hardware-gated and there is no free win for a fusion to chase. - mlxcel still leads mlx-vlm 1.14 to 1.17x (the only Python runtime that loads these multimodal checkpoints). Also correct a stale note in src/models/gemma3n.rs. The comment recorded the #60 fused decode path as a -6.3% regression on M5-class hardware. Re-measured on this M5 Max with MLX 0.31.2 it is neutral (e4b-bf16 +0.84%, e4b-4bit +0.34%, both within noise, no bf16 JIT crash). The gate stays off NA hardware because the fused path has no upside on M5 and the per-op split path is the validated default; only the figure was out of date. Gating logic is unchanged. Docs and comments only; no inference code path changed.
3 tasks done
inureyes
added a commit
that referenced
this pull request
Jun 18, 2026
On pre-M5 Apple Silicon (M1 through M4, Metal GPU family 3) the default command-buffer op cap is small enough that high-op-density decode stalls the GPU at buffer boundaries. Raising MLX_MAX_OPS_PER_BUFFER to 1000 closes that dispatch-gap idle and recovers Gemma3n decode throughput (#329/#345). apply_metal_ops_per_buffer_default (hardware.rs) sets the variable to 1000 at process start on M1-M4 when it is unset, wired into the CLI, server, and bench-decode main() before any MLX op. M5+ (Neural Accelerator) and non-Apple (CUDA) are left on MLX's default: the same sweep is flat on M5 Max (#358) and an earlier M5 study found larger buffers slower there. An operator-set MLX_MAX_OPS_PER_BUFFER always wins, and the knob is documented in the CLI help. M1 Ultra re-bench (best-of-3): gemma3n-e2b 82.7 to 92.5 (+11.9%), gemma3n-e4b 63.0 to 68.4 (+8.6%) versus the recorded default. Dense qwen3-8b and MoE qwen3-30b-a3b also run faster at 1000 than at a starved cap, so the generation-wide gate does not regress them; the MoE decode-gap sweep (#268) was flat. GB10 is not benched in this environment and is left on MLX's default. Closes #353.
12 tasks
be-free-snowboy
pushed a commit
to freestylejs/snowbrain-mlxcel
that referenced
this pull request
Oct 27, 2026
Add M5 Max companion to the M1 Ultra Gemma3n decode profile (lablup#329/lablup#345) and correct the stale fused-path regression note in gemma3n.rs to neutral. Docs and comments only; gating logic unchanged.
be-free-snowboy
pushed a commit
to freestylejs/snowbrain-mlxcel
that referenced
this pull request
Oct 27, 2026
…lup#360) On pre-M5 Apple Silicon (M1 through M4, Metal GPU family 3) the default command-buffer op cap is small enough that high-op-density decode stalls the GPU at buffer boundaries. Raising MLX_MAX_OPS_PER_BUFFER to 1000 closes that dispatch-gap idle and recovers Gemma3n decode throughput (lablup#329/lablup#345). apply_metal_ops_per_buffer_default (hardware.rs) sets the variable to 1000 at process start on M1-M4 when it is unset, wired into the CLI, server, and bench-decode main() before any MLX op. M5+ (Neural Accelerator) and non-Apple (CUDA) are left on MLX's default: the same sweep is flat on M5 Max (lablup#358) and an earlier M5 study found larger buffers slower there. An operator-set MLX_MAX_OPS_PER_BUFFER always wins, and the knob is documented in the CLI help. M1 Ultra re-bench (best-of-3): gemma3n-e2b 82.7 to 92.5 (+11.9%), gemma3n-e4b 63.0 to 68.4 (+8.6%) versus the recorded default. Dense qwen3-8b and MoE qwen3-30b-a3b also run faster at 1000 than at a starved cap, so the generation-wide gate does not regress them; the MoE decode-gap sweep (lablup#268) was flat. GB10 is not benched in this environment and is left on MLX's default. Closes lablup#353.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Companion to the M1 Ultra Gemma3n decode profile from #329 (PR #345),
measured on Apple M5 Max (40-core GPU, 128 GB, macOS 26.5.1), current
HEAD, MLX 0.31.2, mlx-vlm 0.4.4. M5 Max is detected as Neural-Accelerator
hardware, so
use_fused_decode_path()is false and decode runs theper-op split path, a different code path than M1 Ultra. That is why the
profile is worth repeating here rather than assuming the M1 Ultra result
carries over.
The #329 conclusion (no compiled fusion is justified for Gemma3n decode)
holds on M5 Max, by a partly different route.
Findings (Apple M5 Max)
161.5, e4b-4bit 112.2 (text) / 107.0 (image) tok/s, e4b-bf16 40.4.
forward,0.47 to 0.66 ms/token), where an FFI-collapsing fusion would land,
stays hidden behind
async_eval.MLX_MAX_OPS_PER_BUFFERis flat on M5 Max (at most +1.7% across thesweep, within best-of-3 noise). The same sweep gains +11 to 13% on M1
Ultra, so that lever must stay hardware-gated and there is no free win
for a fusion to displace.
at 76%. The 4-bit idle is diffuse dispatch/structure overhead, not a
fusable kernel hotspot.
parity gap pulling for a kernel.
Stale comment corrected
src/models/gemma3n.rsrecorded the #60 fused decode path as a -6.3%regression on M5-class hardware. Re-measured on this M5 Max with MLX
0.31.2 (forced on/off through a temporary override, reverted after) it is
neutral: e4b-bf16 +0.84%, e4b-4bit +0.34%, both within noise, with no
bf16 JIT crash. The gate stays off NA hardware because the fused path has
no upside on M5 and the per-op split path is the validated default; only
the figure was out of date. Gating logic is unchanged.
What changed
docs/benchmark_results/gemma3n-decode-profile-m5max.mdwith thefull numbers, pipeline split, bandwidth floor, command-buffer sweep,
fused-vs-split re-measurement, text-vs-VLM, mlx-vlm parity, and
reproduce commands.
docs/benchmarks.md.src/models/gemma3n.rs.Test plan
e4b-bf16 via
mlxcel-bench-decode, text and image.MLXCEL_PROFILE_PIPELINE_DETAIL; op histogramsvia
MLXCEL_EXPORT_DECODE_DOT.MLX_MAX_OPS_PER_BUFFERsweep {default,100,200,1000,2000} on allthree checkpoints.
MLXCEL_FORCE_*_DECODEoverride,reverted; tree clean and binary rebuilt from HEAD afterward.
cargo fmt --checkpasses. Docs and comments only; no inferencecode path changed.