Performance profile: where does all the time go?
Maintainers usually reply within 2 days
Nobody has claimed this yet.
Assessment
- Difficulty
- 5/5
- Estimated time
- Over a week
- Newbie friendliness
- 35/100
- Issue type
- Feature
- Clarity
- Mostly clear
- Activity status
- Active
- Domain
- machine-learning, performance
Research direction
Start by reviewing the MLX core quantized GEMM and gather_qmm paths, then use the project's benchmarking method: warm up and compare medians across repeated, like-for-like runs. Capture the execution shapes for the 2,048-token prefill chunks before assessing kernel coverage or tuning. Done means a reproducible improvement on the identified long-prompt workloads, with results outside the stated noise band.
Written by the indexing model from the issue text.
Description
I periodically run a sequence of 40-60 models using mlx/mlx-vlm on a large image, asking it to provide a caption for the image and, in some cases, giving it a fairly lengthy hint as to what the current image is.
My harness records performance data, so I asked my bot to tell me what that showed. It produced the analysis below. This sets out areas that might be profitable for performance improvement (and which would not be), for my workflow. I thought that it might be of wider interest.
A profile of the 30 October 2026 sweep (43 cached vision-language models, one
63 MP photograph, one metadata prompt, one generation per model) on an Apple
M5 Max, identifying where engineering effort would visibly reduce wall time
for this workflow — and where it would not. These are observations with
evidence, not requests; every number is from one timed generation per model,
so individual ratios carry the sweep's usual ±15% noise band.
Headline. The sweep spends 55% of its wall time prefilling prompts and
only 18% decoding. Eight models that tokenize the test image into ~16,600
tokens account for 282 s of prefill — 43% of the entire sweep — running at
280–550 tok/s through 4-bit and 2-bit weights. Long-image-prompt quantized
prefill is where essentially all the recoverable time lives. The image-token
budget itself is probably a larger lever, but it is a checkpoint/processor
property, not a runtime one, and is listed separately because changing it
changes the measurement.
Environment and workload
- Apple M5 Max (40-core GPU,
applegpu_g17s, Metal 4), 128 GB, macOS 27.2 - mlx 0.32.5.dev20261030+1ba7f969a (local build), mlx-vlm 0.7.11 @ 918763c68,
transformers 5.22.1, Python 3.14.7 - Input: one 6,656 x 9,483 JPEG (63.1 MP, 60.5 MB); prompt ~320 text tokens;
max_tokens 1,000; prefill_step_size 2,048; greedy for 25 of 43 models,
checkpoint sampling defaults elsewhere; one model per process-equivalent
cycle, no warm-up, no repeats - Neural-accelerator (NAX) quantized kernels were active: the detection gate
(macOS >= 26.2, GPU gen >= 17) passes on this machine, and mlx routes
quantized matmul toqmm_nax/qmv_naxvariants when it does
Where the time goes
Wall time 10 m 58 s (658 s), split by harness phase, with generation further
split using each model's recorded prompt and decode rates:
| Phase | Time | Share |
|---|---|---|
| Generation: prompt prefill | 359 s | 55% |
| Generation: token decode | 116 s | 18% |
| Model load | 86 s | 13% |
| Image preprocessing | 35 s | 5% |
| Cleanup + outside the model loop | 27 s | 4% |
Prefill concentration: the eight models whose processors tokenize the image
into more than 10,000 tokens consume 282 s of the 359 s prefill. For each of
them, prefill is 78–96% of generation time:
| Model | Prompt tok | Prefill | Rate | Decode |
|---|---|---|---|---|
| Ternary-Bonsai-2-27B-mlx-2bit | 16,594 | 59.5 s | 279 tok/s | 3.5 s |
| Qwen3.8-27B-nvfp4 | 16,594 | 49.9 s | 333 tok/s | 4.1 s |
| AREX-2-4bit | 16,594 | 46.2 s | 360 tok/s | 3.8 s |
| Qwen3-VL-8B-Instruct-4bit | 16,578 | 33.1 s | 501 tok/s | 1.3 s |
| Qwen3.5-35B-A3B-4bit | 16,594 | 31.4 s | 529 tok/s | 1.5 s |
| Qwen3-VL-30B-A3B-Instruct-4bit | 16,578 | 30.2 s | 548 tok/s | 2.0 s |
| Qwen3-Omni-30B-A3B-Instruct-4bit | 12,683 | 17.9 s | 708 tok/s | 1.4 s |
| X-Reasoner-7B-8bit | 16,589 | 13.5 s | 1,226 tok/s | 3.9 s |
Area 1 — quantized prefill at long sequence length (owner: mlx core)
This is the profitable area. The operations that matter for this workflow are
the quantized GEMMs of long prefills: dense 4-bit affine (group 32/64), 2-bit,
nvfp4/mxfp4, and MoE expert gathers (gather_qmm) for the A3B-style models.
Prompts here total 12–17k tokens, but that is not the GEMM M: the sweep
requested 2,048-token prefill chunks, and mlx-vlm splits prefill into chunks
for models that support it, so for those models each call probably sees
M ≤ 2,048. The execution shapes actually run were not captured; any kernel
recommendation needs them from a profiled run first.
Evidence worth weighing:
- Observed rates for these prompts are 280–550 tok/s for 4-bit 8–35B models
and 279 tok/s for the 2-bit 27B, against ~2,700–2,800 tok/s for the same
class of hardware on 2–3k-token prompts of small/medium models. Rates blend
vision-tower and language-model time, so they are workload rates, not pure
GEMM rates. - The NAX path is already in place and active here: staged
affine_qmm_t_nax
split-K kernels landed 20 October (PR 4682),qmv_mma/qmv_naxfor 6–16
rows on 22 October (PR 4712), an fp4 fast dequant route on 21 October
(PR 4709). Four sweeps spanning 23–30 October (two directly comparable
pairs) measured prefill flat to within the noise band across all of it. So
for these models the remaining profit is in coverage and tuning at the
shapes these prefills actually execute, not in enabling the hardware. - One suggestive but confounded pair: at the same ~16.6k-token prompt and
similar parameter count, an 8-bit model (X-Reasoner-7B, Qwen2.5-VL lineage)
prefills at 1,226 tok/s while a 4-bit one (Qwen3-VL-8B) does 501 tok/s.
Weight bandwidth predicts the opposite ordering. The two differ in vision
tower and architecture, so this is not attributable to dequantization cost
alone — but it bounds how much the 4-bit path might be leaving on the
table on long prompts. A counter-datapoint from an August sweep:
Ministral-3-14B in mxfp4 vs nvfp4 prefilled within 1% of each other, so
format alone does not dominate.
Profit for this workflow: these eight models' prefill is 43% of the sweep;
each 2x on it returns ~140 s (~21% of wall time).
Area 2 — decode at long context (owner: mlx core; open question)
Decode is only 18% of the sweep, so gains here are bounded. The context
effect is visible but confounded by architecture: two ~8B 4-bit models decode
at 101 tok/s (2k context) vs 67 tok/s (16.6k context), but a third 8B model at
1.3k context decodes at 71 tok/s. KV-cache bandwidth reduction (for example
8-bit KV) is the standard lever at 16k context; this data cannot isolate its
value. The one clear beneficiary of any decode improvement is
Muse-Glimmer-30B-OptiQ (1,000 tokens at 24 tok/s = 41 s, two-thirds of its
runtime).
Area 3 — long-output thinking models (owner: mlx-vlm)
Three models decode 750+ tokens (ERNIE-4.5-VL: 855 tok / 7.3 s;
Muse-Glimmer: 1,000 tok / 41 s; MiniCPM-V-4.6: 752 tok / 3.1 s). mlx-vlm
has speculative-decoding implementations with generation integration; this
sweep did not check which of these models have compatible support, so its
value here is untested. For this sweep it could save under a minute at most
(the three models decode for ~51 s in total); for interactive use of
thinking VLMs, speedups of 1.5–3x are commonly reported, but none were
measured here.
Area 4 — image preprocessing (owner: mlx-vlm / harness; minor)
35 s of CPU-side decode/resize of the 63 MP JPEG across 43 models (~0.8 s per
model, serial with everything else). Overlapping it with model load, or
caching the decoded image across models, would return most of it. Small but
unambiguous.
Not profitable
- Model load: 86 s total, median 1.3 s per model (max 15 s for the 73 GB
119B model). Already dominated by disk/page-in; little headroom. - Cleanup and loop overhead: 27 s combined.
The image-token budget is not a runtime optimization
Eight different checkpoints (Qwen3-VL/3.5/3.8/Omni family tokenizers, AREX,
Ternary-Bonsai, X-Reasoner) tokenize this 63 MP image into ~16,600 tokens,
far more than the other models in the sweep. The sweep did not record the
image dimensions their processors actually produced, so how close to native
resolution they run is not known. Capping image resolution for those
processors could cut their prefill substantially, possibly by more than
kernel work can, but this sweep does not measure by how much: that needs the
processed dimensions per model and a controlled comparison of the same models
on the same image at two or three resize levels, recording image-token count
and prefill time at each. It is excluded from the areas above deliberately:
changing the input tokens changes model outputs, so for a comparison harness
it is a measurement-policy decision, not an optimization. Any discussion of
prefill cost for these models should state the token count first.
Calibration: what this week's data already shows
Between 28 and 30 October (identical image, prompt and settings), mlx moved
11 commits, mlx-vlm 21 commits (0.7.10 → 0.7.11) and transformers
5.21 → 5.22.1: 42 of 43 models produced byte-identical text, generation
throughput ratio was 0.980 (median) and prefill 0.984 — flat within the
harness's ±15% single-generation noise band. The 23 and 25 October runs are
excluded from trend claims: the former is a recorded fast-decode outlier
sweep, the latter ran entirely on battery power. Any claim built on these
profiles should be re-verified with the project's benchmarking method
(median of N runs after warm-up, like-for-like A/B across builds) before
being treated as more than an observation.
- Dominant language
- C++
- Stars
- 29k
- Forks
- 2.4k
- Avg merge
- 3d 10h
- Merged PRs (30d)
- 115
Getting set up
- No Dockerfile or Docker Compose file
- Has a pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from ml-explore/mlx
-
[BUG] `mx.qqmm` docs: the Args section names `global_scale`, but the parameter is `global_scale_x`Possibly taken @XXXXRT666 claimed this 2 days ago. Open
Difficulty 1/5 Under an hour Newbie friendliness 92/100
ml-explore/mlx#4815 ·
Maintainers usually reply within 2 days
-
[BUG] Documented signatures of `mx.add` and 27 other ops omit the keyword-only `*`, so `stream` (and `equal_nan`) cannot be passed positionally as documentedPossibly taken @Redempt1onzzZZ claimed this 1 day ago. Open
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
ml-explore/mlx#4814 ·
Maintainers usually reply within 2 days
-
[BUG] FP4 quantization on CPU rounds ties toward zero while Metal rounds to evenPossibly taken @projekt11th claimed this 4 days ago. Open
Difficulty 2/5 1-3 hours Newbie friendliness 74/100
ml-explore/mlx#4753 ·
Maintainers usually reply within 2 days
-
Difficulty 4/5 3-5 days Newbie friendliness 35/100
ml-explore/mlx#4819 ·
Maintainers usually reply within 2 days
-
[BUG] Metal scaled_dot_product_attention is ~300x less accurate for 2-8 query tokens at 33-128 stacked rows since #4723 (sdpa_split)Possibly taken @STRML claimed this 3 days ago. Openbug
Difficulty 4/5 3-5 days Newbie friendliness 22/100
ml-explore/mlx#4808 · 2 comments ·
Maintainers usually reply within 2 days
Similar issues
-
bug core p3
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
RunanywhereAI/runanywhere-sdks#1258 ·
Maintainers usually reply within 1 day
-
enhancement
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
JdeRobot/RoboticsInfrastructure#843 ·
Maintainers usually reply within 1 day
-
good first issue
Difficulty 2/5 1-3 hours Newbie friendliness 90/100
ai-quokka-wannabe/tron-grid-lite#180 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
cython/cython#8110 · 2 comments ·
Maintainers usually reply within 1 day
-
PR bot doesn't recognize hip-tests as a unit test directoryPossibly taken A pull request linked to this issue is open or already merged. Open
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
ROCm/rocm-systems#14923 ·
Maintainers usually reply within 2 days