CoolFace
Modelpublic

drowzeys/keys-Mac-oMLX-0.7.0.dev2-DeepSeek-V4.1-Flash-oQ3e-2b27-CED-MTP

sourceHugging Faceupdated 14d agoView on Hugging Face
0likes1.7kdownloads
Model Card

keys-Mac-oMLX-0.7.0.dev2-DeepSeek-V4.1-Flash-oQ3e-2b27-CED-MTP

DeepSeek-V4.1-Flash (763B) running on a single 256 GB Mac Studio M3 Ultra.

The published `Jundot/DeepSeek-V4.1-Flash-oQ3e-mtp` does not fit this machine: even with Engram SSD-offload, 239.04 GiB of weights stay resident against 238.42 GiB of usable RAM. This checkpoint requantizes 27 of 40 MoE layers' routed experts from affine 3-bit to affine 2-bit at group_size 64, shedding 42.7 GiB and landing at 197.19 GB resident โ€” with real working headroom.

๐Ÿ“ฆ Recipe, patches, benchmarks and serving notes: [GitHub](https://github.com/drowzeys/keys-Mac-oMLX-0.7.0.dev2-DeepSeek-V4.1-Flash-oQ3e-2b27-CED-MTP)

BaseJundot/DeepSeek-V4.1-Flash-oQ3e-mtp
RuntimeoMLX 0.7.0.dev2 (commit 395ec2fd), MLX 0.32.2
Resident weights197.19 GB (Engram on NVMe, zero expert paging)
SpeculationDSpark MTP, preserved at source precision
Quantized layers3,4,5,6,7,9,10,11,12,13,15,16,17,18,19,21,22,23,25,26,27,29,30,31,33,34,35
Verified context404,805 tokens @ 805 tok/s prefill

โš ๏ธ Requirements โ€” read before serving

1. Raise the kernel wired limit. oMLX caps prefill at 90% of `iogpu.wired_limit_mb`. The stock 249036 on a 256 GB Mac puts that cap at 218.4 GB โ€” below this model โ€” so the server refuses every prefill (surfacing to clients as a misleading "Context length exceeded") or dies with a silent SIGKILL.

bash
sudo sysctl iogpu.wired_limit_mb=253952   # does not persist across reboot

2. Launch through `omlx.cli serve`, never `python -m omlx.server`. Only the CLI enables prefix reuse. The bare module silently logs oMLX cache disabled and no env var fixes it. For agent workloads that resend a large system prompt this is the difference between ~99 s and ~7 s per call.

bash
python -m omlx.cli serve --model-dir ~/.omlx/models --host 0.0.0.0 --port 11601 \
  --paged-ssd-cache-dir ~/kvcache --paged-ssd-cache-max-size 16GB \
  --initial-cache-blocks 8 --memory-guard safe

--initial-cache-blocks 8 matters: the default 256 reserves too much up front and SIGKILLs the load.

Model settings (~/.omlx/model_settings.json):

json
{ "mtp_enabled": true, "mtp_num_draft_tokens": 3,
  "deepseek_v41_engram_ssd_offload": true,
  "deepseek_v41_expert_ssd_offload": false,
  "deepseek_v41_ced_prefill_enabled": true }

Long context: 404,805 tokens verified

KV storage was never the constraint. Only 4 layers (kv_source_layer_ids) produce shared compressed KV โ€” about 2.0 KB/token, so even 1M context is ~2.15 GB, and each layer's local attention is a fixed 128-token window. The real constraint is the prefill working set (~505 KB/token): with too little headroom oMLX shrinks the prefill chunk to a 32-token floor and long prompts collapse. Quantizing 27 layers frees ~46 GB of headroom, so the chunk stays at 2048:

promptprefill
25,949 tok45.0 s @ 576 tok/s
80,996 tok97.4 s @ 831 tok/s
212,256 tok252.6 s @ 840 tok/s
404,805 tok502.7 s @ 805 tok/s

Prefill gets faster at longer context (chunk amortization), then holds ~800โ€“840 tok/s. 500K+ is reachable; budget ~8.4 min of prefill for 400K.

Performance

Mac15,14 / M3 Ultra / 256 GB / macOS 26.6.2. Single stream, 256 max_tokens, temp 0:

paged 3-bit14-layer 2b**this build**
prose15.4525.6925.08 tok/s
code10.1829.0829.85 tok/s
TTFT (short)3.53 s0.74 s0.78 s
residentโ€”217.77 GB197.19 GB

Concurrency (aggregate tok/s) โ€” saturates ~19โ€“23 because decode is not batched, so aggregate tracks the single-stream rate:

taskc=1c=2c=4c=8
prose agg23.3819.6419.0619.21
code agg27.3619.6419.0319.20
per-stream25.08 / 29.8510.274.902.49
TTFT0.78โ€“0.81 s1.14 s1.46 s3.89 s

All numbers at the shipped mtp_num_draft_tokens: 3. MTP acceptance 75.4% code / 59.1% prose.

Use mtp_num_draft_tokens: 3

More 2-bit layers desynchronize the target from the (untouched) DSpark drafter, so acceptance falls. The fix is shorter drafts โ€” a long draft from a weak drafter just wastes verification. Measured on prose at temp 0.3:

ktok/stok/cycleacceptance
322.692.0463.8%
520.741.7954.5%
719.771.7652.2%

End to end: prose 23.35 โ†’ 25.06 tok/s (+7.3%), acceptance 55.2 โ†’ 59.1%, code unaffected (29.64 โ†’ 29.78). No per-task tradeoff, so k=3 is the shipped default.

โš ๏ธ Acceptance also rises with temperature (54.3% at temp 0 โ†’ 63.8% at temp 1.0) โ€” rejection sampling accepts more from a flatter target โ€” but throughput does not follow, so it is not a lever.

Quality notes

Layer selection is structural, not numerical. Per-layer weight-cosine damage from 3bโ†’2b is flat across all 40 layers (spread 0.0037), so it cannot guide the choice โ€” and trusting it caused a real regression: an earlier attempt included layers 28 and 32, members of index_source_layer_ids, which produce the index keys for sparse attention. MTP acceptance collapsed 84% โ†’ 58.5%. Excluding them restored it.

Never quantized below 3-bit: kv_source_layer_ids, index_source_layer_ids, candidate_source_layer_id, engram_layer_ids, dspark_target_layer_ids, layer 0 and layer nโˆ’1. DSpark MTP weights are untouched โ€” a degraded drafter is what sank an earlier 2.4-bit V4 checkpoint on code despite being 1.8ร— smaller.

Measured MTP acceptance on this build: ~81โ€“85% on code, ~64% on prose (prose drafts worse; that is task-intrinsic).

Credits

Almost everything here is other people's work. This checkpoint changes 14 of 40 MoE layers and adds a set of measurements. The model, the quantization that made it tractable, the runtime and the kernels all come from the people below. Thank you.

DeepSeek-AI โ€” the model

**DeepSeek-V4.1-Flash**, the 763B MoE underneath all of this: CSA2 sparse attention, the Engram n-gram tables, the DSpark MTP drafter, sqrtsoftplus routing.

Jundot โ€” the runtime and the checkpoint

Two separate debts, either of which alone would have made this impossible.

  • โ€”**oMLX** (Apache-2.0) โ€” the whole serving stack: the deepseek_v41 implementation, Engram SSD offload, the custom Metal kernels, the paged KV cache, the oQ quantizer, DSpark MTP integration. Specifically #3574 (V4.1 Flash + MTP + Engram offload), #3607 (CED prefill โ€” +45% prefill, โˆ’31% TTFT), #2595 (MoE expert offload) and #3571.
  • โ€”**`Jundot/DeepSeek-V4.1-Flash-oQ3e-mtp`** โ€” the calibrated mixed-precision checkpoint this is derived from. Their oQ pipeline (sensitivity measurement, imatrix calibration, mixed-bit allocator) did the hard quantization work. 231.1 GiB of this repo's 309 GB is byte-identical to theirs. Their oQ4e card also supplied the residency analysis that framed the problem.

Apple / ml-explore

MLX (MIT), mlx-lm, mlx-vlm, the Metal toolchain, and the M3 Ultra's unified memory โ€” without which a 763B model on one desktop is not a conversation anyone gets to have.

Also in the chain

mlx-embeddings (Prince Canuma), nanobind (Wenzel Jakob), and Hugging Face โ€” transformers, tokenizers, huggingface_hub and the Hub. HF's xet deduplication meant publishing 309 GB cost ~9.5 GB of new data rather than re-uploading 231 GiB of Jundot's bytes.

Full attribution, including an honest "what is actually ours" accounting: CREDITS.md.

License

Model weights derive from Jundot/DeepSeek-V4.1-Flash-oQ3e-mtp and the underlying DeepSeek-V4.1-Flash; both carry their own licenses, which apply here unchanged. The serving stack is oMLX (Apache-2.0). Tooling in the companion GitHub repo is Apache-2.0. Any bugs here are ours, not upstream's.