mlx-dspark: DSpark and DFlash Speculative Decoding on Apple Silicon
Up to 4× faster LLM decoding on Apple Silicon, lossless. Native MLX port of DeepSeek's DSpark & z-lab's DFlash speculative decoding — Gemma-4, Qwen3.8, Muse-Glimmer, Nemotron, LFM2.5, Ornith-1.0, ternary Bonsai-27B.
At a glance
- What is it?
- A native MLX port of two EAGLE-family speculative drafters, DeepSeek's DSpark and z-lab's DFlash, aimed at consumer-size models on Macs. The README publishes a per-model speedup table measured on an M4 Pro, and states plainly that this is not DeepSeek-V4 inference.
- Who is it for?
- Adopt mlx-dspark if you already run Gemma-4, Qwen3, LFM2.5, Muse-Glimmer, Ornith-1.0, Qwen3.6 or Qwen3.8 locally on Apple Silicon and want the same tokens out of the same weights with a shorter wall-clock wait. Skip it if your target is an MoE or Mamba hybrid where the README's own table tops out near 1.3x, if you need CUDA, or if you were hoping for DeepSeek-V4.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 5 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
The problem is token latency, not throughput
Local inference on a Mac is usually memory-bandwidth bound. Each decoded token requires reading the weights, so a 12B model at 8-bit produces tokens at a rate set by how fast the machine can stream those weights past the compute units. Batching helps servers and does nothing for a single interactive session. Speculative decoding attacks the same wall from a different angle: instead of reading the weights once per token, you guess several tokens ahead with a small model, then verify the guess in a single pass of the large model. If the guess is right, you paid one weight read for several tokens.
mlx-dspark is for people who already have a supported model running under MLX and want it to answer sooner. The README frames the targets as consumer-size models: Gemma-4, Qwen3, LFM2.5, Muse-Glimmer, Ornith-1.0, Qwen3.6, Qwen3.8, Nemotron and the ternary Bonsai-27B. The author is explicit about the boundary: this is not DeepSeek-V4 inference. DSpark's headline use case is V4 Flash and Pro, MoE models with batched serving, and none of that is what runs here.
Two drafters, one verify loop
The repository bundles two EAGLE-family drafters. DSpark comes from DeepSeek's DeepSpec codebase and is described as semi-autoregressive. DFlash comes from z-lab and is described as block diffusion. They differ in how the draft is produced, but both feed the same verification path, and the README states that they run under one verify loop. That is the architectural decision worth noting: the project is not two forks glued together, it is a shared speculative decoding harness with two drafter implementations behind it.
Losslessness is the property that makes the whole thing safe to adopt. The target model verifies every drafted token, so the output is identical to normal decoding. The README calls the drafters lossless and says the output is the same, just faster. This matters because it means you are not trading answer quality for speed. You are trading a small amount of extra memory for the drafter checkpoint and some compute for the draft passes.
The README also describes a drafter-free path. With --mode auto, any target at all gets lookup speculation, with no drafter checkpoint required. That path is what produces the copy-heavy editing numbers: 4.5x on Gemma-12B and 3.6x on Ornith-9B when the model re-emits or refactors code already in its context. The mechanism there is match-scaled lookup drafts, which is a different bet from a learned drafter. It pays off precisely when the output overlaps the input, which is the daily agent workload and not much else.
Drafter resolution and the auto-resolve registry
Passing --model is enough for the supported pairs. The README says every row in the table auto-resolves its drafter from --model, and that any quant of the target matches. The registry behind that is deliberately narrow: it is the set of pairs the author has measured and vouches for, and the README states that this is the only thing the registry is for. It is not the set of models that work.
The escape hatches are explicit. Any DeepSpec-native drafter runs against any compatible target via --drafter. Any target at all gets drafter-free speculation via --mode auto. Qwen3.8-27B is the interesting case: --mode auto, which is the default and what the Mac app uses, resolves to the DFlash 2 drafter incoai/Qwen3.8-27B-DFlash2 for both the 4-bit and 8-bit quants. The DSpark heads for that target remain reachable with --mode dspark. So mode selection is not cosmetic on that model, it changes which drafter you get.
One design detail deserves attention because it changes how you read the published numbers. With no --max-draft, the tool measures your machine's verify and drafter cost curves once, about five seconds, and caches the result per model, quant and MLX version. It then derives the draft cap from those curves. The README is direct that the table is not pinned to the author's M4 Pro: an M1 or an M5 gets its own optimum. --max-draft auto goes further and adapts the cap per round while generating.
Getting it running from PyPI
The project ships on PyPI as mlx-dspark, so installation is a pip install away. The README presents three surfaces: a CLI, a Python API, and an OpenAI-compatible server that LM Studio or any local tool can talk to. There is also an integration for driving Claude Code with a model on your own Mac.
The commands that appear in the material are the benchmark invocation and the mode and drafter flags. Benchmarking is described as mlx-dspark benchmark --trials 3, run over three prompts: chat, code and math. The table's medians of three come from that command, warm, on an M4 Pro. The Muse-Glimmer row is footnoted as per-content best rather than a median, which is a small but real inconsistency in how the rows were aggregated.
The configuration keys worth knowing before you run anything are --model, --drafter, --mode (with dspark and auto as named values), and --max-draft. The README does not spell out the full flag surface in the excerpt available here, so treat the server and Python API signatures as something to read from the repository rather than infer from this article. What the material does establish is that --mode auto is the default and the mode the Mac app uses.
Where the speedup does not show up
The README's own table is the best argument against over-promising. Ternary-Bonsai-27B, a 2-bit model, shows 1.13x on code and nothing else listed. Nemotron-3.5-Lightning-30B-A3B, a 4-bit MoE with Mamba layers, shows 1.34x math, 1.27x code and 1.07x chat. Qwen3.6-35B-A3B, another 4-bit MoE, shows 1.67x math, 1.24x code and 1.05x chat. On those architectures the drafter is close to a rounding error on conversational traffic.
There is a second, subtler failure mode in the quantization story. The README states that speedup ratios are non-monotone in bits and peak at 8-bit on current MLX. The Ornith sweep is given in full: 4-bit 1.38x, 8-bit 2.17x, bf16 1.54x on code. The stated reason is that MLX's unquantized matmul pays roughly a 2x cost cliff at verify width 2. So bf16 loses in both ratio and absolute speed. If you assumed that running the target unquantized would give the cleanest speedup, the material says otherwise.
A third constraint is the content dependence. The speed column in the table is a range across chat, code and math, with chat at the low end and code or math at the high end. The README's explanation is that decoding speed depends on what is being generated, because copy- and structure-heavy content accepts longer drafts. A benchmark on math problems will flatter this project relative to a benchmark on open-ended conversation, and both are honest readings of the same tool.
What it is not, and what to compare it against
The most useful comparison is not another speculative decoding library, it is plain MLX decoding. mlx-dspark is a layer on top of MLX, and the baseline in every published ratio is normal decoding of the same target at the same quant. That framing is honest and it also tells you the decision rule: if your current setup already runs under MLX and you are satisfied with the model, this is an additive change with a measurable upside and no change in output. If you are on llama.cpp or a CUDA stack, the comparison is not available, because the whole implementation is Apple Silicon and Metal specific.
Against other speculative decoding approaches, the distinguishing claim is the pairing of two drafter families under one verify loop. A single-drafter implementation forces you to pick between a semi-autoregressive method and a block diffusion method before you have measured either on your model. Here you can run them head-to-head, which the README names as an explicit goal: serve them, script them, or benchmark them head-to-head. The DSpark heads for Qwen3.8-27B versus the DFlash 2 heads are the concrete instance of that, selectable with --mode.
The honest limitation is that the drafter supply is the bottleneck. The auto-resolve registry covers the measured pairs, and anything outside it requires either a compatible DeepSpec-native drafter you supply yourself or a fall back to drafter-free lookup speculation. That fallback is not a consolation prize on code editing, but it is a much narrower bet than a trained drafter on general chat.
Maintenance cadence and the MIT licence
The release history in the material shows v0.18.0 on 2026-09-01, v0.18.1 on 2026-09-09, and v0.19.0 on 2026-09-10, with the last push to main at the same timestamp as v0.19.0. That is a fast minor-version cadence, roughly weekly at the point the data was captured. For a tool you install into an inference pipeline, that cuts both ways: fixes and new model support arrive quickly, and so does churn in flags and default behaviour. The --mode auto default resolving differently between drafter families is exactly the kind of thing that can shift under you between minor versions.
The cache key is worth flagging as an operational detail. The measured cost curves are cached per model, quant and MLX version. Upgrading MLX invalidates that cache and triggers a fresh roughly five-second measurement on the next run. That is cheap, but it means an MLX upgrade is a moment when your first invocation behaves differently from the steady state.
The licence is MIT, which is permissive and imposes no source disclosure obligation on your own code. This article does not give legal advice, and the licence text in the repository is the authority. The practical point for an engineering decision is narrower: the drafter checkpoints you pull from Hugging Face are separate artifacts with their own terms, and the MIT licence on this repository does not speak for them. Check the model card for whichever drafter you resolve to, particularly incoai/Qwen3.8-27B-DFlash2 if you run Qwen3.8-27B on the default mode.
Editorial conclusion
Adopt mlx-dspark if you already run Gemma-4, Qwen3, LFM2.5, Muse-Glimmer, Ornith-1.0, Qwen3.6 or Qwen3.8 locally on Apple Silicon and want the same tokens out of the same weights with a shorter wall-clock wait. Skip it if your target is an MoE or Mamba hybrid where the README's own table tops out near 1.3x, if you need CUDA, or if you were hoping for DeepSeek-V4. Before committing, run mlx-dspark benchmark --trials 3 on your own machine and your own prompt mix, because the draft cap is derived from measured verify and drafter cost curves cached per model, quant and MLX version, not from the M4 Pro numbers in the table.
Community notes