A structured database of every measured Core AI–vs–MLX decode comparison we have, plus the
causal decomposition of the gap. All LLM rows are same M4 Max, same protocol as
mlx-lm benchmark (Apple’s llm-benchmark is explicitly modeled on it): 512 prompt /
1024 generation / 5 trials, release build. MLX side = mlx-lm 0.31.3, mlx-community 4-bit.
| # | Model | Arch class | Core AI | MLX | CA/MLX | Winner | Engine path | Dominant factor |
|---|---|---|---|---|---|---|---|---|
| 1 | qwen3-0.6b | dense | 484 | 432 | 1.12 | CA +12% | pipelined | dispatch-bound, not BW-bound → MLX 4-bit edge doesn’t cash in |
| 2 | qwen3-4b | dense | 145.4 | 145.8 | 1.00 | tie | pipelined | — |
| 3 | qwen3-8b | dense | 94.1 | 90.0 | 1.05 | CA +5% | pipelined | — |
| 4 | gemma3-4b-it | dense | 141.5 | 136.3 | 1.04 | CA +4% | pipelined | — |
| 5 | gemma3-12b-it | dense | 55.0 | 55.1 | 1.00 | tie | pipelined | biggest dense → BW starts to matter → MLX 4-bit pulls even |
| 6 | mistral-7b-v0.3 | dense | 101.7 | 97.5 | 1.04 | CA +4% | pipelined | — |
| 7 | gpt-oss-20b | MoE | 78.1 | 100.2 | 0.78 | MLX +28% | pipelined, stock GatherMM | expert dispatch: GatherMM reads ALL experts/token (over-read-bound) |
| 8 | Qwen3.6-35B-A3B | MoE (256e/top-8) | 30.9 | ~55–70 | ~0.5 | MLX | stock GatherMM | 32× expert over-read |
| 8b | Qwen3.6-35B-A3B | MoE + gather_qmm kernel | 64.9 | ~55–70 | ~1.0 | tie/CA | custom Metal sym8 gather | kernel reads only routed experts → gap closes |
| 9 | LFM2.5-8B-A1B | MoE (32e/top-4) | 39 → 141 | — | — | (3.6× self) | stock → gather_qmm | same over-read fix |
| 10 | GLM-4.7-Flash | MoE + MLA | 20.3 → 52.4 | — | — | (2.6× self) | stock → gather_qmm | MoE fixed by kernel; MLA on all 47 layers keeps it < qwen3.6 |
| 11 | Qwen3-Coder-Next-80B-A3B | MoE (512e) | ~24 | “MLX-competitive” | ~1.0 | tie | gather_qmm | BW-bound on 79GB cold weight, not GDN |
| 12 | Qwen3-ASR-1.7B (audio) | dense decoder, ANE | WhisperKit-ANE | MLX 2.6× | — | MLX | ANE (CoreML) | ANE = energy-not-speed; MLX-GPU wins raw tok/s + WER (1.52 vs 1.71) |
Sources: rows 1–7 apple-models-bench.md (head-to-head matrix); 8–11 project_gather_qmm_kernel.md,
project_qwen36_moe_port.md, project_gather_qmm_next_target.md; 12 project_audio_understanding_qwen_omni.md.
The difference is operator/architecture coverage on the engine — NOT the core engine. On standard dense transformers Core AI’s pipelined engine ties or beats MLX. Core AI only loses where the model uses an op-class the stock engine lowers naively:
SwitchGLU/GatherMM lowers to a dense matmul over ALL experts every token
(32× over-read for 256-expert top-8). MLX has real sparse expert gather. → MLX +28% on gpt-oss.c_kv must be threadgroup-staged across heads, which our kernel didn’t do).When we replace the naive lowering with a custom Metal kernel (gather_qmm, reads only the
routed experts), the MoE gap closes: Qwen3.6-35B-A3B 30.9 → 64.9 ≈ MLX. So the gap was
dispatch/over-read, not the engine.
The historical “MLX is ~2× faster, structural” verdict was measured on a hand-rolled per-token
fn.run() loop (~11% of BW peak, ~1000 Metal dispatches/token). That was the loop’s ceiling,
not Core AI’s. Apple’s coreai-pipelined engine runs the same weights ~3.5× faster (qwen3.5
58.5 → 204 tok/s, ~2× MLX) with zero custom kernels — which is why the dense rows above tie/win.
The remaining gap, where it survives, decomposes into three independent multipliers:
| Factor | Size | Helps MLX when… | Notes |
|---|---|---|---|
| Kernel coverage / dispatch | ~2× | the model has uncovered op-classes (MoE gather, MLA) | dense is covered by the pipelined engine; MoE/MLA aren’t → that’s the whole gap |
| Quantization byte-class | ~1.5–2× | bandwidth-bound (big models, long ctx) | MLX = 4-bit affine g64; CA ships int8 (int4 flips argmax for non-QAT). Only pays off once BW-bound — see gemma3-12b tie vs 0.6b +12% win |
| Host / framework / OS-runtime tax | ~1.3× | always (uncontrollable) | the irreducible ~15–25% you don’t own |
Decision rule that predicts the winner:
litertlm-convert/reports/coreai-ane-gpu-parity-addendum.md):
decode ANE 83.3 / CA-GPU 75.9 / MLX 73.0 tok/s; energy ANE 6,144 / MLX 5,662 /
CA-GPU 4,506 tok per 1% battery. The ANE-vs-GPU delta sign-flips across sibling models →
throughput parity, not an ANE speed win. And the ANE energy edge over MLX-GPU is only
~+8.5% (it’s +36% over CA’s own GPU — MLX’s GPU path is energy-efficient); the robust
ANE win is GPU exclusivity (UI/rendering don’t contend). MLX DOES run on iPhone (GPU,
via mlx-swift) — correction 2026-07-24: an earlier note here claimed it “can’t run on
ANE/iPhone at all”; only the ANE is closed to MLX.
(Foundation Models integration is NOT an exclusive: the LanguageModel protocol is public
and MLX plugs in via MLXLanguageModel — see fm-provider.md; CA’s edge is only the official
zero-code adapter. And the ANE LLM path is static-shape + palettization-required +
host-driven — no pipelined loop exists on ANE, see compute-units-and-authoring.md.)gather_qmm custom Metal kernel up front, or you ship at ~0.5–0.78×
MLX. With the kernel you reach parity (the ceiling), not a win.Claims in earlier revisions of this doc, re-checked against artifacts. Net: the technical differential for LLM execution is thin; most of the advertised deployment gap was illusory.
Only the graph compiler + executor (CoreAI.framework) is OS-resident. The LLM runtime —
EngineFactory, the coreai-pipelined engine, LanguageBundle, on-GPU sampling, KV growth —
is Swift code from coreai-models that you compile into the app (proof: we patch it —
apps/coreai-pipelined-extra-states.patch; you can’t patch an OS framework). MLX draws the
same line one layer lower: it bundles its tensor framework + kernels on top of OS-resident
Metal/MPS. The binary-size delta is tens of MB against GB-class model assets — noise.
OS-residency is also double-edged (all incidents verified in this repo): beta seed-to-seed
ABI churn kills TestFlight launches (FoundationModels must be weak-linked, see
reference_fm_beta_abi_churn); the OS reclaims model assets into 6-byte zeroed stubs; the
~O(p²) prefill scratch lives in the closed compiler and cannot be fixed app-side (MLX’s
stack is fully open — every layer is fixable). “The OS owns it” = “you can’t repair it.”
Cold GPU specialization is Core AI’s own cost (0.8B ≈ 4.8 s, 2.3 GB ≈ 29 s on iPhone;
pipelined-engine.md). AOT / AIModelCache gives control over that first-run cost; MLX’s
runtime kernel JIT is light enough that it never had the problem. Deterministic first-launch
is a genuine product knob, but don’t sell it as an advantage over MLX.
FM guided generation (@Generable) needs engine logits, and the GPU-pipelined fast path
does not expose logits (fm-provider.md). MLX exposes logits trivially → structured
generation, logprobs tooling, and sampler experiments are easier on MLX than on Core AI’s
fast path.