mlx-bun 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (158) hide show
  1. package/README.md +57 -32
  2. package/docs/README.md +55 -42
  3. package/docs/archive/investigations/benchmark-ledger-through-v0.4.0.md +3617 -0
  4. package/docs/archive/planning/release-notes-v0.5.0.md +37 -0
  5. package/docs/design/batching.md +220 -7
  6. package/docs/design/decode-speed-program.md +121 -66
  7. package/docs/design/generic-model-support.md +116 -0
  8. package/docs/design/kv-cache.md +380 -211
  9. package/docs/design/speculative-decoding.md +165 -26
  10. package/docs/design/turboquant.md +20 -7
  11. package/docs/design/unified-engine-frontier-plan.md +120 -13
  12. package/docs/planning/PRODUCT_ROADMAP.md +5 -6
  13. package/docs/planning/public-readiness-handoff.md +244 -0
  14. package/docs/planning/public-readiness.md +149 -0
  15. package/docs/reference/benchmarks.md +3463 -3489
  16. package/docs/reference/cli.md +62 -1
  17. package/docs/reference/distribution.md +29 -4
  18. package/docs/reference/environment.md +104 -8
  19. package/docs/reference/library-api.md +18 -4
  20. package/docs/reference/memory.md +1 -1
  21. package/docs/reference/models.md +17 -5
  22. package/docs/reference/server-api.md +181 -11
  23. package/docs/reference/server-config.md +232 -105
  24. package/package.json +2 -2
  25. package/src/audio/audiotoolbox.ts +112 -0
  26. package/src/audio/conformer.ts +8 -0
  27. package/src/audio/mic-capture.ts +98 -0
  28. package/src/audio/silero-vad.ts +379 -0
  29. package/src/audio/whisper-decode.ts +774 -0
  30. package/src/audio/whisper-fast.ts +732 -0
  31. package/src/audio/whisper-format.ts +73 -0
  32. package/src/audio/whisper-mel.ts +185 -0
  33. package/src/audio/whisper-timing.ts +274 -0
  34. package/src/audio/whisper-tokenizer.ts +239 -0
  35. package/src/audio/whisper-transcribe.ts +410 -0
  36. package/src/backends/mlx/autoregressive.ts +22 -1
  37. package/src/backends/mlx/batch-group.ts +111 -27
  38. package/src/backends/mlx/cache-layout.ts +3 -3
  39. package/src/backends/mlx/continuation-persistence.ts +5 -4
  40. package/src/backends/mlx/encoder-cache.ts +25 -0
  41. package/src/backends/mlx/fill-append.ts +21 -8
  42. package/src/backends/mlx/fill-group.ts +497 -0
  43. package/src/backends/mlx/gateway-binding.ts +56 -12
  44. package/src/backends/mlx/kv-maintenance.ts +30 -2
  45. package/src/backends/mlx/mixed-iteration.ts +62 -0
  46. package/src/backends/mlx/model-prompt.ts +46 -32
  47. package/src/backends/mlx/model-serving.ts +4 -3
  48. package/src/backends/mlx/paged-cache-codec.ts +27 -0
  49. package/src/backends/mlx/prefill-cohort.ts +15 -4
  50. package/src/backends/mlx/prefill-policy.ts +26 -0
  51. package/src/backends/mlx/prefill-rows.ts +57 -13
  52. package/src/backends/mlx/prompt-input.ts +29 -0
  53. package/src/backends/mlx/qwen-prompt-input.ts +40 -0
  54. package/src/backends/mlx/request-state-policy.ts +18 -5
  55. package/src/backends/mlx/rollback.ts +6 -3
  56. package/src/backends/mlx/serial-executor.ts +16 -7
  57. package/src/backends/mlx/speculative-group.ts +105 -26
  58. package/src/backends/mlx/speculative.ts +24 -7
  59. package/src/backends/mlx/target-layout-capability.ts +1 -1
  60. package/src/backends/mlx/transitioning-kv-rows.ts +8 -0
  61. package/src/cli.ts +322 -0
  62. package/src/contracts/execution.ts +10 -0
  63. package/src/contracts/object-cache.ts +8 -0
  64. package/src/contracts/pi-provider.ts +1 -1
  65. package/src/contracts/pi.ts +3 -1
  66. package/src/contracts/prefix-cache.ts +13 -3
  67. package/src/contracts/scheduling.ts +9 -0
  68. package/src/engine/execution-plan.ts +23 -12
  69. package/src/engine/execution-tasks.ts +44 -0
  70. package/src/engine/namespaced-cache.ts +13 -0
  71. package/src/engine/scheduler.ts +22 -2
  72. package/src/fill/fill-session.ts +14 -3
  73. package/src/generate.ts +28 -22
  74. package/src/grammar.ts +13 -0
  75. package/src/inference/prefill.ts +6 -0
  76. package/src/kv-scheme.ts +22 -0
  77. package/src/kv-store.ts +115 -79
  78. package/src/lab/README.md +5 -1
  79. package/src/lab/paged-kv/paged-attention.ts +117 -0
  80. package/src/lab/paged-kv/paged-kv-rows.ts +33 -5
  81. package/src/lab/paged-kv/paged-kv.ts +108 -42
  82. package/src/md-modules.d.ts +5 -0
  83. package/src/mlx/array.ts +37 -0
  84. package/src/mlx/ffi.ts +19 -0
  85. package/src/mlx/normalized-argmax.ts +116 -0
  86. package/src/mlx/ops.ts +97 -10
  87. package/src/model/batched-turboquant-kv.ts +6 -5
  88. package/src/model/delayed-quantized-kv.ts +3 -3
  89. package/src/model/delayed-rotating-quantized-kv.ts +1 -1
  90. package/src/model/delayed-turboquant-kv.ts +6 -4
  91. package/src/model/factory.ts +26 -0
  92. package/src/model/fingerprint.ts +12 -0
  93. package/src/model/full-prefill-row.ts +1 -1
  94. package/src/model/gemma4-base.ts +25 -6
  95. package/src/model/gemma4.ts +82 -2
  96. package/src/model/kv-attention-view.ts +4 -1
  97. package/src/model/profile.ts +29 -4
  98. package/src/model/quantized-append-attention.ts +31 -0
  99. package/src/model/qwen3-mrope.ts +7 -9
  100. package/src/model/qwen3_5.ts +99 -37
  101. package/src/model/support.ts +8 -0
  102. package/src/model/token-groups.ts +66 -0
  103. package/src/model/trellis-linear.ts +2 -2
  104. package/src/model/turboquant-codec.ts +6 -0
  105. package/src/model/whisper.ts +435 -0
  106. package/src/native/mic_capture.swift +92 -0
  107. package/src/native-pack.ts +6 -5
  108. package/src/pi-web.ts +9 -1
  109. package/src/prompt-cache.ts +265 -43
  110. package/src/runtime-config.ts +1 -5
  111. package/src/safetensors.ts +9 -2
  112. package/src/sampler.ts +5 -4
  113. package/src/serve/audio-routes.ts +323 -0
  114. package/src/serve/chat-request.ts +2 -0
  115. package/src/serve/chat-stage.ts +17 -12
  116. package/src/serve/completion-executor.ts +9 -1
  117. package/src/serve/discovery-routes.ts +17 -1
  118. package/src/serve/generated-token-history.ts +57 -0
  119. package/src/serve/generation-gateway.ts +52 -11
  120. package/src/serve/model-binding.ts +7 -1
  121. package/src/serve/preparation.ts +3 -3
  122. package/src/serve/prompt-response-trace.ts +14 -0
  123. package/src/serve/request-plan.ts +2 -0
  124. package/src/serve/request-prep.ts +38 -21
  125. package/src/serve/transcription-server.ts +64 -0
  126. package/src/serve/transcription-service.ts +502 -0
  127. package/src/server.ts +132 -164
  128. package/src/spec/assistant-rows.ts +1 -0
  129. package/src/spec/deepspec-rows.ts +1 -1
  130. package/src/spec/dflash-source.ts +1 -1
  131. package/src/spec/glm52-mtp-source.ts +1 -0
  132. package/src/spec/ngram-source.ts +27 -5
  133. package/src/spec/projected-draft-rows.ts +2 -2
  134. package/src/spec/qwen-mtp-rows.ts +11 -0
  135. package/src/spec/qwen-mtp-source.ts +3 -0
  136. package/src/spec/serve-loop.ts +5 -3
  137. package/src/spec/source.ts +14 -1
  138. package/src/spec/two-model-rows.ts +1 -0
  139. package/src/spec/two-model.ts +2 -2
  140. package/src/ssd-cache.ts +76 -17
  141. package/src/ssd-durability.ts +20 -22
  142. package/src/storage/host-buffer.ts +24 -0
  143. package/src/storage/kv-writer.ts +83 -0
  144. package/src/storage/kv-writer.worker.js +264 -0
  145. package/src/storage/retention-policy.ts +37 -0
  146. package/src/tiered-prompt-cache.ts +45 -0
  147. package/src/vision/embedder.ts +8 -0
  148. package/src/vision/prompt.ts +101 -65
  149. package/src/vision/qwen3vl-prompt.ts +101 -64
  150. package/src/vision/qwen3vl-tower.ts +11 -0
  151. package/src/vision/siglip.ts +8 -0
  152. package/src/web/app.html +12 -0
  153. package/src/web/app.js +160 -0
  154. package/src/web/src/chat.ts +4 -1
  155. package/src/web/src/composer.ts +3 -0
  156. package/src/web/src/voice.ts +141 -0
  157. package/src/web/sw.js +1 -1
  158. /package/docs/{planning → archive/planning}/release-notes-v0.4.0.md +0 -0
@@ -23,6 +23,39 @@ Sections: [Start flags](#start-flags) · [`--isolate` semantics](#--isolate-sema
23
23
  · [Fidelity tiers](#fidelity-tiers-and-the-decode-route---l1----l2) · [Feature matrix](#feature-matrix)
24
24
  · [Performance & recipes](#performance-characteristics--recipes) · [`GET /stats`](#observability--get-stats).
25
25
 
26
+ ## Configuration ownership
27
+
28
+ CLI presets and explicit flags resolve before model loading. Request preparation
29
+ combines server defaults with request overrides once; explicit request values
30
+ win. A bound execution keeps its immutable runtime snapshot across awaits.
31
+ The tables below own the individual defaults and effective-value rules; this
32
+ map identifies who consumes them.
33
+
34
+ | Settings | Owning interface / implementation | Binding and use |
35
+ |---|---|---|
36
+ | Model selection, host/port, isolation, native paths, shutdown | CLI and server host | Startup and process lifecycle; no model-forward policy |
37
+ | Batch capacity, queue/KV budget, prefill cohort budget, `MIXED_PREFILL`, `MIXED_TOKEN_BUDGET` | Scheduling policy and execution group | Choose admission and work size; never choose a codec or sample |
38
+ | Draft kind/model/depth, `DSPARK_MINCONF`, `GRAMMAR_DRAFT_TOKENS` | Draft provider and inference method | Bind provider policy; propose tokens through the shared verifier |
39
+ | `GRAMMAR_BATCH`, `BATCH_SSM`, `QWEN_SPEC_KV4`, compiled/grammar/fill eligibility | Model gateway and execution planner | Resolve supported composition before placement |
40
+ | Temperature, seed, penalties, probability filters, logprobs, `BATCH_VEC_SAMPLE` | Sampling session | Request-owned policy and RNG; selected-token output remains device-backed where supported |
41
+ | Thinking/template defaults, grammar schema, `GRAMMAR`, `TOKEN_MASK` | Chat renderer and grammar controller | Render and compile per request; no scheduling decisions |
42
+ | KV scheme/start/group size, `PAGED_KV`, `PAGED_ATTN`, `TURBOQUANT_FUSED_DECODE` | KV scheme, request-state policy and codec | Capture layout/kernel choice; preserve it through deferred construction and RAM copies |
43
+ | Prompt/SSD budgets, demotion, `SSD_*`, `CACHE_RETENTION`, `SESSION_CACHE`, `MTP_PROMPT_CACHE` | Prefix cache and persistence service | Cache owns residency and session lookup; persistence owns queued storage work |
44
+ | `COMPILED_DECODE`, `COMPILED_GEGLU`, `COMPILED_SWIGLU`, `NO_FUSED_SDPA`, `TRELLIS*`, `MIXED_PACKED_MLP` | Model backend and numerical kernels | Read bound execution policy; select implementations by supported tensor geometry |
45
+ | `RD_PREFILL_CHUNK`, `PREFILL_TAIL_SPLIT`, `EARLY_FIRST_TOKEN`, `BATCH_NO_PIPELINE`, `BATCH_EXTEND` | Prefill/step execution and row state | Preserve method boundaries, output ordering and state ownership; the batch-prefixed names remain compatibility controls |
46
+ | `FILL*`, `GRAMMAR_JUMP` | Fill session or grammar proposal provider | Capture request policy; supported methods consume proposals/appends independently of scheduling |
47
+ | Context limits, checkpoints, media access, trace/debug settings | Request preparation, checkpoint service, media adapter and diagnostics | Each service applies its own options; evaluation-only settings stay in the evaluation adapter |
48
+
49
+ Environment names in this map omit the `MLX_BUN_` prefix. Historical A/B switches
50
+ remain available while their control paths are supported. They are not alternate
51
+ configuration systems. Internal boolean reads use the same `runtimeFlag` port;
52
+ the old `flagOn` wrapper has been removed. CLI aliases remain compatible.
53
+
54
+ Paged request state captures both its cache namespace and its direct-attention
55
+ choice. Changing host settings after binding cannot change later prefetch, lookup,
56
+ publication or cache construction for that request. The same rule applies to
57
+ TurboQuant's fused codec across copies and delayed precision conversion.
58
+
26
59
  ## Start flags
27
60
 
28
61
  How flags are parsed (`src/cli.ts` `opt`/`flag`/`positional`):
@@ -76,26 +109,27 @@ When batch rows finish, full-attention caches drop padding shared by every
76
109
  surviving row, matching MLX-LM while preserving each row's absolute position.
77
110
 
78
111
  Qwen prefills longer than four tokens materialize each layer and its cache
79
- state before continuing, bounding deferred buffer lifetimes. Execution has
80
- no reservation checks between layers or during decode. Serial requests
81
- finish outstanding SSD snapshots before generation. The RAM prefix-cache
82
- cap controls retained entries; it does not promise that every workload fits.
112
+ state before continuing, bounding deferred buffer lifetimes. Cache maintenance
113
+ demotes least recently used snapshots under allocator pressure without
114
+ refusing the request. With SSD enabled, an unwritten victim stays in RAM until
115
+ its queued write completes. The RAM target can be exceeded while writes are
116
+ pending or failing;
117
+ it does not promise that every workload fits.
83
118
  Actual allocation failures use the ordinary error path when recoverable;
84
119
  a native Metal allocation failure can terminate the process.
85
- With `--ssd-cache`, an exact recurrent-cache hit persists its boundary before
86
- transferring the live state to the request. This keeps that boundary reusable
87
- after the recurrent state advances. Already-durable boundaries need no write;
88
- a failed required store retains the cache entry and refuses the transfer.
120
+ Recurrent-cache hits lend independently owned views before any donor is
121
+ reclaimed. The request can advance those views while the original boundary
122
+ remains in RAM or in a completed SSD snapshot. Persistence stays asynchronous.
89
123
 
90
124
  | Flag | Arg | Default | Lane/tier | What it does |
91
125
  | --- | --- | --- | --- | --- |
92
126
  | `--memory-budget` | GB (decimal, ×10⁹) | unset; memory estimates are advisory | both (per request) | Explicit opt-in budget. Sets the allocator limit and enforces the fit estimate: a prompt leaving no generation slot gets **400** (`type: memory_admission`); a broader completion cap is reduced to the remaining context. Estimates account for active KV quantization and prefill temporaries. Not an aggregate batch cap — see `--kv-budget`. GLM-5.2 also uses this value in its resource plan before loading. |
93
127
  | `--kv-budget` | GB (decimal) | off | batch | Aggregate KV budget across concurrently admitted batch rows: a joiner whose projected KV (prompt + `max_tokens`, window-capped) would exceed it **queues** until rows finish; a request over the budget alone is rejected. Without it, N large-context rows can collectively exceed memory. Reported in `/stats.batch.kv_budget_bytes`. |
94
- | `--prompt-cache` | GB (binary GiB) | `8` | both | RAM prefix-KV cache (byte-capped LRU). `--prompt-cache 0` disables it. RAM hits lend zero-copy views and leave the donor intact, including recurrent Qwen and method state. SSD persistence runs in the background; RAM reuse never waits for a write. Explicit cache flush and graceful shutdown wait for durability. Batch-lane joiners `take()` the longest usable prefix at admission; a row that finishes without ever merging `put()`s its caches back (merged rows' entries age out). |
95
- | `--ssd-cache` | dir | off | both | SSD cold tier under the prompt cache ([docs/design/kv-cache.md](../design/kv-cache.md)): prefix KV spills to disk on RAM eviction and idle demotion, is snapshotted after requests settle (debounced, each tensor step owns the generation lock so it cannot overlap inference; `MLX_BUN_SSD_WRITEBEHIND=0` disables), and **survives restarts**. Entries are keyed by model fingerprint + effective KV scheme + backend numerical identity + tokenizer hash + adapter namespace. MLX includes its runtime version and GPU architecture; older numerical identities use separate directories. `SIGINT`/`SIGTERM` drains active requests and flushes dirty snapshots before exit (`MLX_BUN_SHUTDOWN_TIMEOUT_MS`); `POST /admin/cache/flush` is the explicit boundary. Requires the RAM cache. |
128
+ | `--prompt-cache` | GB (binary GiB) | `8` | both | RAM prefix-KV residency target, managed by LRU. `--prompt-cache 0` disables it. RAM hits lend zero-copy views, including recurrent Qwen and method state. With SSD enabled, eviction persists an unwritten donor before removing it from RAM; pending writes can temporarily exceed the target. A successful write alone does not evict RAM. The target does not reserve that much memory. SSD persistence runs in the background; RAM reuse never waits for a write. Explicit cache flush and graceful shutdown report durability, including snapshots lost before persistence. Batch-lane joiners `take()` the longest usable prefix at admission; completed decode publishes reusable processed-token state through the same interface. |
129
+ | `--ssd-cache` | dir | off | both | SSD cold tier under the prompt cache ([docs/design/kv-cache.md](../design/kv-cache.md)): prefix KV spills to disk on RAM eviction and idle demotion, is also persisted after publication by a dedicated CPU worker while inference continues (`MLX_BUN_SSD_WRITEBEHIND=0` disables proactive writes), and **survives restarts**. Entries are keyed by model fingerprint + effective KV scheme + backend numerical identity + tokenizer hash + adapter namespace. MLX includes its runtime version and GPU architecture; older numerical identities use separate directories. `SIGINT`/`SIGTERM` drains active requests and flushes dirty snapshots before exit (`MLX_BUN_SHUTDOWN_TIMEOUT_MS`); `POST /admin/cache/flush` is the explicit boundary. Requires the RAM cache. |
96
130
  | `--ssd-cache-max` | GB (binary GiB; `0` = unlimited) | unlimited | both | Optional SSD tier byte cap; oldest-mtime entries are evicted only when a positive cap is configured. Warns and is ignored without `--ssd-cache`. |
97
131
  | `--ssd-cache-verify` | (bool) | off | both | Verify every tensor hash on restore (reads all bytes eagerly, defeating lazy fault-in) — integrity paranoia only; the header hash is always verified. Warns and is ignored without `--ssd-cache`. |
98
- | `--ssd-demote-idle` | seconds | `300` with `--ssd-cache`, else off | both | Prompt-cache entries unused this long spill to the SSD tier and free their GPU memory; the next hit restores them. Swept only when the engine is fully idle. `0` disables. Warns and is ignored without `--ssd-cache`. |
132
+ | `--ssd-demote-idle` | seconds | `300` with `--ssd-cache`, else off | both | Prompt-cache entries unused this long spill to the SSD tier and free their GPU memory; the next hit restores them. The cache sweeps by entry age independently of scheduler activity; RAM remains resident until SSD coverage exists. `0` disables. Warns and is ignored without `--ssd-cache`. |
99
133
  | `--generation-checkpoint` | output tokens | off | qualified requests | Queues an owned in-flight generation snapshot every N emitted tokens. Repeating the identical request after a restart replays the saved assistant prefix and continues from its already-sampled next token. Requires `--ssd-cache`. Shared execution qualifies ordinary requests with supported row-cache layouts, including Qwen, Llama, MiniCPM and Gemma4, with or without adapters, excluding media, grammar, fill, paging, logprobs or speculative methods; existing serial qualification remains available. Explicit cache flush drains queued writes. Completed generations remove their checkpoint, while client cancellation preserves the latest checkpoint. |
100
134
 
101
135
  Generation checkpoint identity includes stop strings, sampling/KV policy, the
@@ -107,9 +141,13 @@ identity. MLX runtime or GPU architecture changes select a fresh cache
107
141
  directory and leave the previous directory intact. A different backend codec
108
142
  identity refuses incompatible files. Adapter namespaces include the mounted weight contents and
109
143
  scale, so a changed adapter starts with a fresh prefix and checkpoint.
110
- Native media and grammar preparation waits for the generation execution lease
111
- and retains a reservation through completion. At most one media preparation and
112
- `--batch` grammar preparations can be retained. Generation and preparation
144
+ Native media and grammar preparation uses the backend execution lease between
145
+ decode iterations when the backend supports queued preparation. Exclusive model
146
+ changes retain drain priority. Media downloads and container transcoding run
147
+ outside that lease; a slow remote URL does not hold up decode. Preparations
148
+ retain a reservation through request
149
+ completion. Gemma4 and Qwen shared serving can retain up to `--batch` media preparations;
150
+ other media bindings retain one. Grammar preparation retains up to `--batch`. Generation and preparation
113
151
  queues each allow 64 waiting requests; overflow returns `429` before a response
114
152
  stream opens, or a terminal stream error after it opens. Disconnects release
115
153
  queued reservations and completed preparation resources.
@@ -129,11 +167,29 @@ Disconnected serial requests leave the admission queue immediately. Active AR
129
167
  requests check cancellation between prefill chunks and decode steps; a native
130
168
  operation already running completes before that boundary.
131
169
 
170
+
171
+ Qwen image/video and Gemma image/audio encoder features use the existing
172
+ prompt-cache RAM budget and, when configured, its SSD persistence layer.
173
+ Exact encoder-weight, computation-setting and input hashes identify reusable
174
+ features; each request still renders its own tokens, timestamps, masks and
175
+ positions. Qwen hashes prepared pixels and grids; Gemma hashes image/WAV bytes
176
+ within each encoder's versioned schema. No separate media-cache capacity is
177
+ allocated. `--prompt-cache 0` disables retention. CPU preprocessing and media
178
+ extraction still run before feature lookup, outside the native execution queue.
179
+ Encoder reuse is independent of batch/method selection. Experimental Gemma
180
+ media KV reuse is controlled by `MLX_BUN_MEDIA_PREFIX_CACHE` below. Cache counters
181
+ are documented in [server-api.md](server-api.md).
182
+
183
+
132
184
  ### Runtime isolation
133
185
 
134
186
  | Flag | Arg | Default | Lane/tier | What it does |
135
187
  | --- | --- | --- | --- | --- |
136
188
  | `--isolate` | (bool) | off | both | Run the inference engine as a **child process** on a unix socket while this process owns CPU application routes and proxies inference — instant under any GPU load, survives engine crashes (auto-respawn). Full semantics in [`--isolate` semantics](#--isolate-semantics). |
189
+ | `--whisper-model` | path \| query | first downloaded `whisper` checkpoint, resolved on the first `/v1/audio/*` request | both | Whisper checkpoint served beside the chat model for [audio transcription](server-api.md#post-v1audiotranscriptions--post-v1audiotranslations-speech-to-text). Serving a Whisper checkpoint as the MAIN model (`serve mlx-community/whisper-large-v3-turbo`) starts a **transcription-only server** (audio routes, `/v1/models`, `/health`, `/stats`; no chat model, no prompt cache). |
190
+ | `--whisper-idle-unload` | seconds | `0` | both | How long the Whisper weights (and MLX's allocator cache) stay after a take. Default `0`: released right after every take, so the chat model has the memory back before it prefills the dictated prompt; the process drops by ≈1.4 GB and the next take pages them back in from the OS file cache (a cold 11 s take is ≈520 ms vs 365 warm on the M1 Max, `TranscriptionService`). Reported per response in `mlx_bun.timings.load_ms`. |
191
+ | `--whisper-resident` | flag | off | both | Never dispose the Whisper weights (sotto's always-loaded behaviour). |
192
+ | `--preload` | flag | off | both | Transcription-only server: load the Whisper weights before listening instead of on the first request. |
137
193
  | `--model-pool` | n (≥1) | `1` | both | With `--isolate`: max **resident** model engines. A request whose `model` field is an **exact** `/v1/models` id spawns/routes to that model's own engine child (spawn-overlap: the new model loads while the old keeps serving). Over the cap the least-recently-used engine is drained (`POST /admin/drain` over its socket), demotes its prompt cache to the SSD tier, and exits; switching back respawns it with state restored from disk. Without `--isolate` the flag warns and is ignored. |
138
194
 
139
195
  ### Scheduling
@@ -147,22 +203,30 @@ operation already running completes before that boundary.
147
203
  | Flag | Arg | Default | Lane/tier | What it does |
148
204
  | --- | --- | --- | --- | --- |
149
205
  | `--kv-quant` | `config` \| `off` \| `4` \| `8` \| `turbo[:k<bits>v<bits>]` | `off` (bf16) | see row | KV-cache quantization. `off` = bf16 (the L1 default: quantized KV measured 5–20% slower decode than bf16 at ≤16k on both stacks, so it is an opt-in that buys **memory headroom**). `4`/`8` = **uniform** bits (group 64, start 0 — mlx-lm's `--kv-bits` scheme); with fused-sdpa off (the default for uniform) our unfused quantized SDPA is op-for-op mlx-lm's `quantized_scaled_dot_product_attention`, so uniform is **bit-exact L1**; uniform requests use shared B=1/B>1 execution where the loaded cache layout supports affine conversion. `config` = **per-layer mixed precision** from the model's `kv_config.json` — optiq-only → **L2**; per-layer configs **batch** on every shipped model (full-attention and rotating layers; gated bit-exact per row vs the serial composition). `turbo[:k<bits>v<bits>]` = **TurboQuant** ([docs/design/turboquant.md](../design/turboquant.md)): rotation-based KV quantization, default `k8v3`; `kBits` ∈ {2,4,5,8}, `vBits` ∈ {2,3,4,5,8}; a separate axis (mutually exclusive with `config`/`4`/`8`), uses the existing decoded or deferred rotated-value attention path, **full-attention layers only** (sliding-window layers stay bf16 with a one-time warning), refuses head dims outside `TURBOQUANT_HEAD_DIMS`, and supports shared ordinary and grouped drafting execution at B=1/B>1 including positive library conversion thresholds when the cache layout qualifies. Any explicit `--kv-quant` overrides the tier preset. |
150
- | `--paged-kv` | (bool) | off | shared / gated bit-exact | **Optional vLLM-style paged KV cache** ([docs/design/kv-cache.md](../design/kv-cache.md)): full-attention layers store K/V in fixed-size block pools (host-side block table, gather back to contiguous before the stock SDPA; no new attention math). Env mirror `MLX_BUN_PAGED_KV=1`. Paging retains the configured batch size; request state owns independent block pools through the shared cache-layout interface. `createServer` **refuses** any `--kv-quant` (incl. turbo), `--draft-model`, and non-Gemma4 models. Media and adapter requests run the plain cache path per request. Paged requests bypass the prompt cache and run uncompiled decode; pool exhaustion is a typed error, never truncation. Gated bit-exact vs the plain path (`tests/parity/paged-kv-parity.test.ts`). |
206
+ | `--paged-kv` | (bool) | off | shared / optional | Full-attention block pools through the common cache-layout and attention interfaces. Supports bf16 and uniform affine `--kv-quant 4|8`; affine pages encode from their first token. Default attention gathers into stock SDPA; `MLX_BUN_PAGED_ATTN=1` selects the experimental direct Metal reader. RAM/SSD reuse uses a separate numerical namespace. Gemma4-family only; draft models, per-layer KV schemes and TurboQuant pages remain unsupported. Media/adapter requests retain their existing non-paged behavior. Env mirror `MLX_BUN_PAGED_KV=1`. |
151
207
  | `--paged-kv-block-size` | n | `256` | shared | Tokens per KV block (`--paged-kv` only); 256 = the plain cache's growth step. |
152
208
 
153
209
  ### Adapters and speculative decoding
154
210
 
155
- In the current unreleased source, Qwen MTP publishes aligned target/draft
211
+ In v0.4.0, Qwen MTP publishes aligned target/draft
156
212
  prefill snapshots through the shared prompt cache by default. The RAM budget
157
213
  includes the draft KV and pending hidden row; configured SSD persistence
158
214
  queues those tensors with the target state and supports reuse after restart.
159
215
  RAM hits retain immutable snapshots for later requests. Target, draft-weight,
160
216
  adapter-revision and KV-policy identities separate incompatible state.
161
217
  `usage.cached_tokens` reports the reused target prefix.
162
- `MLX_BUN_MTP_PROMPT_CACHE=0` disables this reuse. Generated-output snapshots
163
- remain unfinished.
164
-
165
- In the current unreleased source, Qwen uniform affine KV4 with
218
+ `MLX_BUN_MTP_PROMPT_CACHE=0` disables this reuse. Completed decode also
219
+ publishes checkpoints for processed tokens through the same cache.
220
+ Long-conversation performance acceptance remains open.
221
+
222
+ Uniform KV4 attention automatically groups three query heads for the key
223
+ multiplication on M4 Pro when B≤2, L=3, GQA6/D256, N≥8192 and bf16/f32
224
+ match the qualified shape. Masking, softmax and value multiplication retain
225
+ their existing geometry. This internal selection needs no additional flag;
226
+ other shapes and machines keep the existing implementation. Exactness and
227
+ timing evidence are in [benchmarks.md](benchmarks.md#three-query-affine-attention-head-grouping).
228
+
229
+ In v0.4.0, Qwen uniform affine KV4 with
166
230
  `quantizedKvStart=0` supports configured speculative execution by default.
167
231
  `MLX_BUN_QWEN_SPEC_KV4=0` restores the ordinary-decode compatibility control. Recurrent state and draft KV keep their original precision. Shared Qwen MTP
168
232
  also supports bf16, uniform KV8 and TurboQuant at start zero; per-layer mixed
@@ -170,14 +234,25 @@ KV remains incompatible. Strict legacy serial speculation retains bf16/KV4.
170
234
  The KV4 control does not disable bf16, KV8 or TQ shared speculation. Six paired M4 Pro combined suites and two completed Kanban tasks support
171
235
  this default. Configuring a drafter and selecting KV4 remain explicit choices.
172
236
 
173
- For controlled experiments, `MLX_BUN_RD_PREFILL_CHUNK` sets the prefill chunk,
174
- default 2048. Shared ordinary and speculative methods use the group's captured
175
- default; an explicit group option overrides the environment and a library
176
- request's `prefillChunkSize` overrides that default for its request.
237
+ The shared prefill policy selects a chunk once per request. The usual size is
238
+ 2,048 tokens. For recurrent-attention models with materialized SDPA scores,
239
+ it halves that size until the estimated per-layer score workspace is at most
240
+ 1 GiB (minimum eight queries, where native fused decode attention applies).
241
+ Qwen3.8-27B therefore uses 2,048 at 10,398 prompt tokens and 256 at 78,678.
242
+ This changes the work size; it does not reject or shorten the request.
243
+ Ordinary and speculative execution consume the same bound policy.
244
+
245
+ `MLX_BUN_RD_PREFILL_CHUNK` selects an explicit fixed chunk. A group option
246
+ supersedes the environment, and a library request's `prefillChunkSize`
247
+ supersedes either. Explicit values bypass the automatic choice.
177
248
  `MLX_BUN_RD_CONTEXT_LIMIT` sets an explicit request context cap
178
- without enlarging it. Both require positive integers; the context cap is unset
179
- by default. These are benchmark controls, not changes to the published model
180
- profile or sampling policy.
249
+ without enlarging it. Both controls require positive integers; the context
250
+ cap is unset by default. Sampling and the published model context remain
251
+ independent of prefill chunk selection. To reproduce a seeded response, keep
252
+ the prefill chunk policy fixed along with the model, runtime, machine and
253
+ sampling settings. Changing chunk boundaries can change numerical results
254
+ and the sampled continuation. The [Kanban comparison](benchmarks.md#full-kanban-after-decode-integration)
255
+ records this effect on the same M4 Pro with identical initial request JSON.
181
256
 
182
257
  | Flag | Arg | Default | Lane/tier | What it does |
183
258
  | --- | --- | --- | --- | --- |
@@ -204,11 +279,18 @@ profile or sampling policy.
204
279
  | `--hlg-toe` | nats ∈ [0, 100] | `6` | serial | HLG shadow rolloff scale. Only with `--hlg-sampling on`. |
205
280
  | `--hlg-pivot-offset` | nats ∈ [0, 100] | `6` | serial | HLG pivot: nats below the top token. Only with `--hlg-sampling on`. |
206
281
 
282
+ Greedy sampling without requested logprobs uses a shared normalized-selection
283
+ kernel after applying penalties and grammar masks. It preserves dtype-rounded
284
+ scores and lowest-ID ties while avoiding a full normalized vocabulary output.
285
+ Independent speculative verification uses the same operation. Requests needing
286
+ logprobs, stochastic sampling or a custom sampler keep their existing operations;
287
+ no new flag is required. Measurements are in [benchmarks](benchmarks.md#fused-normalized-greedy-selection).
288
+
207
289
  ### Media and UX
208
290
 
209
291
  | Flag | Arg | Default | Lane/tier | What it does |
210
292
  | --- | --- | --- | --- | --- |
211
- | `--allow-private-media` | (bool) | off | serial (media requests) | Let `image_url`/`audio_url`/`video_url` parts fetch from **private/loopback/link-local** hosts. Off by default: a request's URL is attacker-controlled, so `src/media-fetch.ts` refuses non-http(s) schemes and private/loopback/link-local/CGNAT destinations — including hosts that *resolve* there and every redirect hop (SSRF guard). Independent of the flag, every remote fetch has a 10 s timeout and a 64 MB streaming-enforced cap; violations are clean `400`s. `data:` URLs are never policy-checked. Env mirror `MLX_BUN_ALLOW_PRIVATE_MEDIA=1`. |
293
+ | `--allow-private-media` | (bool) | off | both (model-qualified media) | Let `image_url`/`audio_url`/`video_url` parts fetch from **private/loopback/link-local** hosts. Off by default: a request's URL is attacker-controlled, so `src/media-fetch.ts` refuses non-http(s) schemes and private/loopback/link-local/CGNAT destinations — including hosts that *resolve* there and every redirect hop (SSRF guard). Independent of the flag, every remote fetch has a 10 s timeout and a 64 MB streaming-enforced cap; violations are clean `400`s. `data:` URLs are never policy-checked. Env mirror `MLX_BUN_ALLOW_PRIVATE_MEDIA=1`. |
212
294
  | `--no-open` | (bool) | off | both | Skip the automatic browser open. By default an interactive TTY opens `http://<host>:<port>/#/chat` once the server is ready; non-TTY runs never open. Parent-only under `--isolate`. |
213
295
 
214
296
  ### Parity tier and kill switches
@@ -367,7 +449,7 @@ chat stage (`src/serve/chat-stage.ts`). Full field list: [server-api.md](./serve
367
449
  All `MLX_BUN_*` variables are captured once at process start into an
368
450
  immutable snapshot (`src/runtime-config.ts`); CLI flags install overrides
369
451
  into the same snapshot before the model loads. Boolean levers read the
370
- literal strings `"1"`/`"0"` — `flagOn` treats any other value as unset.
452
+ literal strings `"1"`/`"0"` — `runtimeFlag` treats any other value as unset.
371
453
  Under `--isolate` the whole environment is inherited by the engine child.
372
454
 
373
455
  ### Serving levers (flag-backed and lane kill switches)
@@ -375,8 +457,12 @@ Under `--isolate` the whole environment is inherited by the engine child.
375
457
  | Env var | CLI flag | Default | Effect |
376
458
  | --- | --- | --- | --- |
377
459
  | `MLX_BUN_COMPILED_DECODE` | `--compiled-decode` | on (`"0"` disables) | Compiled decode graph replay (serial lane; batch lane at B=1). |
378
- | `MLX_BUN_EARLY_FIRST_TOKEN` | — | off (`=1`) | Serial/native generation yields token zero before constructing the next decode step. This can reduce first visible output latency when token zero contains visible text. Later decode remains pipelined. Serial fill, grammar, checkpoint resume and single-token budgets retain their existing order. When a native consumer stops at the first yield and retains caller-owned caches, a non-aborted return completes that token’s M=1 forward before returning the cache. This preserves the ordinary pipeline’s boundary for later prefix reuse. Aborted requests do not start another forward; caches owned and disposed by the generation need no alignment. Native M1 packed-Qwen/MiniCPM/Gemma continuation gates pass; M4 packed/affine and serving acceptance remain. The continuous scheduler also yields after preparation creates its first active row when no other request is queued, allowing prepared output to flush before decode. It then rechecks cancellation, admission and shutdown; queued short admissions still group together. The setting is captured by generation and the batch runtime. Experimental pending broader cached/pressure and quiet-machine acceptance. |
379
- | `MLX_BUN_TURBOQUANT_FUSED_DECODE` | — | off (`=1`) | Experimental packed K/V decode fusion for an existing `--kv-quant turbo:...` cache, captured when each cache is created. A shared Metal operation unpacks keys and values, applies the existing key zero/scale and Lloyd-Max value scale, and preserves the codec's eager or deferred inverse rotation. It accepts supported bit widths, head dimensions 64/128/256/512 and 32/64-element groups with fp16/bf16/f32 metadata. Eager k8v3 with B1/H4, head dimension 256, fp16 metadata, group32 and at least 8192 cached tokens also fuses inverse rotation; other shapes keep the existing rotation path. Unsupported inputs, CPU streams and shapeless traces retain ordinary operations. Quantization, stored cache format and serving eligibility are unchanged. Joint-decoder Qwen native/serial and repeated long-context HTTP gates pass. The inverse operation passes both integrated Qwen model gates and six HTTP pairs per quant. MiniCPM/Gemma deferred-consumer serving gates also pass. The unreleased shared codec/layout supports ordinary and supported drafting groups without changing this kernel selection. Gemma now owns pre-write row positions across cache appends; the fix passes native checks on both Macs. Combined settings, pressure and strict M4 Pro acceptance remain. |
460
+ | `MLX_BUN_MEDIA_PREFIX_CACHE` | (env only) | on (`=0` disables) | Reuse prepared Gemma image/audio or Qwen image/video conversation KV in the shared executor. Media contents, encoder identity and preceding tokens scope the ordinary RAM/SSD cache. Generated follow-ups reuse state; rewinds before a delayed-precision boundary recompute. Qwen recurrent layers require an exact retained prefix. Explicit serial retains encoder-only reuse. |
461
+ | `MLX_BUN_MIXED_PREFILL` | — | off (`=1`) | Lab mixed prefill/decode execution for Gemma 4 and Qwen3.5/3.8 text groups, including grouped speculative methods. The scheduler reserves running tokens before prompt work; the model packs feed-forward operations across real tokens while attention and KV state retain their original group geometry. Methods supply candidate-token demand and per-group hidden taps; speculative verification preserves its existing matmul geometry. Packed matmuls can select different kernels from solo decode; this does not claim solo-logit identity or a measured serving win. |
462
+ | `MLX_BUN_MIXED_PACKED_MLP` | — | on (`=0` disables) | Lab control inside mixed model work: disable feed-forward packing while keeping the same scheduling budget and attention geometry, to measure packing separately from chunk scheduling. No effect when mixed execution is off. |
463
+ | `MLX_BUN_MIXED_TOKEN_BUDGET` | — | `256` | Total real tokens in an enabled mixed iteration, including running decode rows. At least one token per participating row progresses. A lone preparation retains its existing chunk setting. Precision transitions and checkpoint endpoints remain method-owned. |
464
+ | `MLX_BUN_EARLY_FIRST_TOKEN` | — | on (`=0` disables) | Eligible ordinary generation yields token zero before constructing the next decode step; later decode remains pipelined. The continuous scheduler yields after preparation creates its first active row when no other request is queued, allowing prepared output to flush before decode. It then rechecks cancellation, admission and shutdown; queued short admissions still group together. Serial fill, grammar, checkpoint resume and single-token budgets retain their existing order. If a native consumer stops at the first yield and retains caller-owned caches, a non-aborted return completes that token’s M=1 forward before returning the cache, preserving the ordinary pipeline’s boundary for prefix reuse. Aborted requests do not start another forward; generation-owned caches need no alignment. The setting is captured by generation and the batch runtime. Native M1/M4 state and continuation, HTTP cancellation/overlap and cached/SSD gates pass. Monitored paired chat measurements support earlier visible output; they do not establish higher decode throughput, and an invisible token zero need not improve client latency. See benchmarks.md. |
465
+ | `MLX_BUN_TURBOQUANT_FUSED_DECODE` | — | on (`=0` disables) | Packed K/V decode fusion for an existing `--kv-quant turbo:...` cache, captured when each cache is created. A shared Metal operation unpacks keys and values, applies the existing key zero/scale and Lloyd-Max value scale, and preserves the codec's eager or deferred inverse rotation. It accepts supported bit widths, head dimensions 64/128/256/512 and 32/64-element groups with fp16/bf16/f32 metadata. Eager k8v3 with B1/H4, head dimension 256, fp16 metadata, group32 and at least 8192 cached tokens also fuses inverse rotation; other shapes keep the existing rotation path. Unsupported inputs, CPU streams and shapeless traces retain ordinary operations. Quantization, stored cache format and serving eligibility are unchanged. Joint-decoder Qwen native/serial and repeated long-context HTTP gates pass. The inverse operation passes both integrated Qwen model gates and six HTTP pairs per quant. MiniCPM/Gemma deferred-consumer serving gates also pass. The shared codec/layout supports ordinary and supported drafting groups without changing this kernel selection. Gemma now owns pre-write row positions across cache appends; the fix passes native checks on both Macs. The combined fused-k8v3/MTP3 pressure fixture passes on M4 and M1 with exact responses/usage and durable SSD writes. Codec policy is resolved once for ordinary, batched and delayed caches; `=0` retains the unfused control. This does not enable TurboQuant quantization itself: `--kv-quant` remains a separate choice. |
380
466
  | `MLX_BUN_NO_FUSED_SDPA` | `--fused-sdpa` (inverted) | follows `--kv-quant` | `=1` forces the stock unfused SDPA everywhere. |
381
467
  | `MLX_BUN_COMPILED_GEGLU` | `--compiled-activations` | on (`"0"` disables) | Gemma geglu via mlx-lm's `@mx.compile` closure. `=0` → uncompiled composition (same parity, slower). |
382
468
  | `MLX_BUN_COMPILED_SWIGLU` | `--compiled-activations` | on (`!== "0"`) | Compiled SwiGLU on MiniCPM5 decode and prefill, matching mlx-lm. An enclosing compiled graph owns its own fusion. qwen3/qwen3.5/universal compile unconditionally. |
@@ -384,14 +470,15 @@ Under `--isolate` the whole environment is inherited by the engine child.
384
470
  | `MLX_BUN_TRELLIS` | — | `kernel` (`=expand`) | Packed trellis-coded weights (`mode: "trellis"` modules, Q2b — design: `docs/design/turboquant.md`). `kernel` serves them through the Metal decode kernels (M≤4 matvec; larger M expands one tensor to bf16 and runs a stock matmul). `=expand` decodes every trellis tensor at LOAD into 8-bit g64 affine (+~4 GiB at 27B, the eval-carrier numerics) and serves it through the stock quantized path — the fallback for a machine where the kernels lose. |
385
471
  | `MLX_BUN_TRELLIS_VARIANT` | — | `13` | Trellis kernel variant (see [Q2b experiments](../design/turboquant.md)): `6` = code computed inline × reciprocal, weight served as f32 code×scale; `1` adds a residual step and bf16 rounding to reproduce the fake-quant artifact's weights. `0`/`2`/`3`/`4`/`5` are bench-only decoder variants. Variants `7`–`13` retain variant-6 decode values; `13` is the measured default. These variants tune work assignment; `11` tiles eligible short axis-1 prefills, `12` adds split-K axis-0 prefill at M=5..8, and `13` also vectorizes remaining bf16 expansion for k2/k3/k4, T=256, L=12. On MLX 0.32.2/M3+ the axis-1 M5..15 path instead uses direct packed decoding with native wide-matvec arithmetic when Qwen's RMSNorm establishes aligned row-contiguous inputs; callers without that layout proof use native expansion/matmul. Qwen27B additionally selects an integer codebook for its interleaved k3 down projection at M=3/4 with bf16 activations; other calls retain the computed decoder. Prefill rounds weights to the activation dtype. |
386
472
  | `MLX_BUN_TRELLIS_ASYNC_EXPAND` | — | off (`=1`) | Experimental variant-13 expansion scheduling. Submit an expanded projection asynchronously while MLX active allocation is below 75% of the device's recommended working set; retain blocking evaluation above it. Other variants are unchanged. Uses the execution's runtime-policy snapshot and preserves the caller's layer barriers. The threshold is not a total-memory cap. Qwen27B integrated native, repeated serial/continuous HTTP and both saved-agent pressure gates pass on M4 Pro 24 GB. Broader-model, combined-optimization and quiet M4 Pro acceptance remain. |
387
- | `MLX_BUN_PAGED_KV` | `--paged-kv` | off (`=1`) | Paged KV cache; the same refusals and prompt-cache bypass as the flag. |
473
+ | `MLX_BUN_PAGED_KV` | `--paged-kv` | off (`=1`) | Paged KV cache; the same eligibility and separate RAM/SSD namespace as the flag. |
388
474
  | `MLX_BUN_ALLOW_PRIVATE_MEDIA` | `--allow-private-media` | off (`=1`) | Permit media fetches to private/loopback/link-local hosts (timeout + size cap still apply). |
389
475
  | `MLX_BUN_EXPERT_OFFLOAD` | `--expert-offload` | off | `=<dir>` — the path of a built expert-offload file, activated at module load (`src/expert-offload.ts`) for scripts and library runs that never parse serve flags. The CLI flag builds the file and activates it itself. |
390
476
  | `MLX_BUN_PREFILL_TAIL_SPLIT` | — | on (`"0"` disables) | Oracle prefill convention: drain the prompt to len−1, then compute step-0 logits from a separate L=1 forward of the last prompt token (mlx-lm `generate_step` and its batched engine). Both lanes. The spec lane follows its own oracle's shape under the same flag (mlx-lm `speculative_generate_step`: target and draft drain to len−1, no separate step 0). `=0` restores the full-final-chunk convention everywhere — ulp-different at step 0, flips near-tie greedy streams vs mlx-lm. |
391
477
  | `MLX_BUN_GRAMMAR` | — | on (`"0"` disables) | `=0` disables structured-output mask compilation; requested constraints take the graceful-degrade route (chat prompt injection + a `Warning` header; raw completions header only). |
392
478
  | `MLX_BUN_TOKEN_MASK` | `host` \| `metal` | `host` | Experimental grammar-mask implementation. `metal` uploads packed bits and applies the same additive mask in a compiled shared Metal kernel. Captured when the grammar controller is created; serving performance gates remain open. |
393
- | `MLX_BUN_GRAMMAR_JUMP` | — | **off** (`=1`) | Jump-forward decoding for structured output (SGLang's technique via xgrammar's `findJumpForwardString`): when the grammar forces a unique continuation, the **serial** lane emits its retokenized ids with one multi-token forward. Lossless in string space and always grammar-valid, but the token stream can legally differ from an unjumped run — no oracle, hence opt-in. Skipped when `logprobs`/`top_logprobs` is requested; SentencePiece-family tokenizers that can't reproduce a mid-stream span never jump. The batch lane does not jump. |
394
- | `MLX_BUN_FILL` | — | **off** (`=strict` \| `=echo`) | **Token fast-forwarding** — lookup, not speculation. Spans of the assistant turn that the request's `tools` + the chat template already DETERMINE (tool-call open scaffold, the rest of a tool name after its first disambiguating token, a sole-required-key skeleton) are appended as committed context in bounded model forwards, and sampling resumes after them. No draft, no verify, no rollback — an injected token is context, indistinguishable to the model from one it sampled. Rows are compiled per request by diffing probe renderings of the model's OWN template, always sliced from a rendering carrying the REAL tool names and schema keys (so a span can never split a merged token such as Qwen3.5's `=get`), so a template that does not render `tool_calls` yields no rows and no fill. Key and call-close assertions require a closed object schema with exactly one required property and no pattern properties; optional/additional arguments stay available. If no property is required, the compiler preserves the empty-argument choice. Template-derived rows require a fresh request-local parser context. Tool-only output after closed reasoning qualifies; quoted/fenced examples, incomplete delimiters and ambiguous literals decline. Requests with more than 32 tools decline template-derived fills. Held-out identity coverage remains open. **Serial lane only** (it never forces a request off the batch lane) and refused for: fixed `seed`, `logprobs`/`top_logprobs`, structured output (grammar owns forced tokens), image/audio/video prompts, a mounted draft model, quantized/TurboQuant KV, and sliding-window models (one warning). Injection bypasses the sampler — a behavior-policy deviation at `temperature > 0` — hence opt-in. `=echo` additionally arms the **echo index** (Lab tier): spans copied from earlier in the same session, held under policy `verify` — the engine appends them in the same single forward, reads the argmax already in that forward's logits at every span position (free, no extra pass), keeps the prefix the model agrees with, and rewinds the rest through the same cache contract the spec lane's rounds use. A wrong echo costs a rewound forward, never a wrong token. Strict rows stay policy `assert` (no readback, no checkpoint). Telemetry: `usage.fill`. |
479
+ | `MLX_BUN_GRAMMAR_JUMP` | — | **off** (`=1`) | Grammar-supplied continuations. Eligible shared requests without a configured drafter propose xgrammar's forced string to the existing target verifier; the sampler validates every emitted token, including requested logprobs. Candidates leave the matcher unchanged until sampling. Affine/TurboQuant layouts compose through their speculative capabilities; paged requests keep ordinary grammar sampling. Explicit serial execution retains direct jump-forward: it emits retokenized forced spans with one multi-token forward, skips jumping with logprobs, and can choose different tokens than ordinary sampling. A configured drafter retains its own proposal policy. This remains opt-in; tokenization/verification width can change numerical trajectories. |
480
+ | `MLX_BUN_GRAMMAR_DRAFT_TOKENS` | — | `3` | Maximum grammar candidates per shared verification round when `MLX_BUN_GRAMMAR_JUMP=1`. Changes proposal depth, independently of scheduler capacity. |
481
+ | `MLX_BUN_FILL` | — | **off** (`=strict` \| `=echo`) | **Token fast-forwarding.** Strict mode appends tool-call spans determined by the request's schema and chat template, then resumes sampling. Request-local parsing excludes ambiguous literals and incomplete delimiters; closed, single-required-property schemas qualify for key/call-close assertions. Echo mode additionally proposes text copied from the session and verifies it with the request's sampler. Shared execution supports seeded requests and the qualified bf16/affine/TurboQuant row layouts. Homogeneous echo cohorts use the existing grouped target verifier; mixed proposed/unproposed rows retain one-position sampling. Rejected suffixes are rolled back before reusable state is published. Wider verification can change floating-point rounding relative to sequential decode, so sampled text need not be identical. Explicit serial fill still excludes fixed seeds and uses its existing model/cache eligibility. Fill is disabled for logprobs, structured output, media, a mounted draft model and shared paging. Strict appends bypass sampling and therefore change behavior policy at nonzero temperature. Both modes remain opt-in; see the shared-fill section below and `usage.fill`. |
395
482
  | `MLX_BUN_FILL_MAX_SPAN` | — | `32` (floor 2) | Hard cap on one injected span. |
396
483
  | `MLX_BUN_FILL_APPEND_CHUNK_SIZE` | — | `0` (model limit) | Execution chunk cap for assert-policy fill, captured by each FillSession. Zero selects the model's qualified limit; a positive value can lower that limit. The Qwen 27B text path on `applegpu_g16s`, validated on the M4 Pro, supports up to four positions with supported affine projections and optional Trellis MLPs. It rechecks the limit after each chunk and splits at MLX's attention arithmetic boundaries to preserve one-token numerics. Other configurations use one position per forward. The engine commits the complete span before emission and resumes sampling after it, with no intermediate vocabulary heads or verification. Verify-policy proposals keep a single forward for recurrent rollback. The injected-token cap is unchanged. Library callers can set `FillSession`'s `appendChunkSize` option. Held-out, broader-model and quiet M4 Pro acceptance remain. |
397
484
  | `MLX_BUN_FILL_K` | — | `8` (floor 2) | Echo anchor length: the k-gram that must match before a copied span is proposed (the corpus study's token-level threshold). |
@@ -402,9 +489,15 @@ Under `--isolate` the whole environment is inherited by the engine child.
402
489
  | `MLX_BUN_BATCH_SSM` | — | on (`"0"` forces serial) | `=0` excludes SSMCache (Qwen3.5 gated-DeltaNet hybrids) from the batch capability gate → those models route serial. |
403
490
  | `MLX_BUN_BATCH_EXTEND` | — | on (`"0"` reverts) | Joining rows append to the running batch's KV in one pad+concat (mlx-lm `BatchKVCache.extend`). `=0` reverts to whole-batch re-merge (numerically equivalent, O(B·S)). |
404
491
  | `MLX_BUN_BATCH_VEC_SAMPLE` | — | on (`"0"` reverts) | Vectorized greedy batch sampling; `=0` falls back to per-row sampling (bit-equal A/B). |
405
- | `MLX_BUN_BATCH_NO_PIPELINE` | — | off (`=1`) | Read each batch step's tokens synchronously instead of pipelined (A/B lever; numerically equivalent, slower). Read once at module load. |
406
- | `MLX_BUN_SSD_WRITEBEHIND` | — | on (`"0"` disables) | `--ssd-cache`'s debounced write-behind snapshot (restart survival). Each tensor write waits for an idle engine, then owns the generation lock for that step. Pending writes do not drain active batches or delay request admission for a full flush. `=0` disables it; eviction and demotion spills still write. |
407
- | `MLX_BUN_SSD_SPILL_QUEUE_GB` | — | `2` (GiB; any finite value ≥ 0) | Byte cap on pending write-behind spill clones (they pin evicted entries' GPU memory until the generation-locked flush runs). Over cap the oldest queued spill drops and frees immediately; the durability coordinator keeps its snapshot dirty so an explicit or shutdown flush can retry. `=0` keeps only the newest + in-flight clone pinned. Counters in `/stats.ssd_cache`. |
492
+ | `MLX_BUN_BATCH_NO_PIPELINE` | — | off (`=1`) | Read each batch step's tokens synchronously instead of pipelined (A/B lever; numerically equivalent, slower). Captured when the execution group is constructed. |
493
+ | `MLX_BUN_SSD_WRITEBEHIND` | — | on (`"0"` disables) | Proactive persistence after cache publication. A CPU worker packs, hashes and writes immutable state without a generation lock. Writing does not evict RAM. `=0` keeps eviction-triggered persistence; RAM victims remain resident until their SSD copy commits. |
494
+ | `MLX_BUN_SSD_LAYOUT` | — | `whole` | `blocks` writes immutable content-addressed blocks with atomic checkpoint manifests (format 5). Existing whole files remain readable. Shared blocks count once toward an explicit SSD cap and survive deletion of other referencing checkpoints. Experimental space/time tradeoff; see benchmarks. |
495
+ | `MLX_BUN_SSD_SEGMENTED` | — | on (`"0"` disables) | With block storage, read contiguous spans directly and pack arbitrary strides in at most 1 MiB scratch. `=0` retains whole-tensor CPU packing for paired measurements. |
496
+ | `MLX_BUN_SSD_PREFETCH` | — | on (`"0"` disables) | Prepare cold prefixes before execution, using a separate CPU read queue and owned page-aligned allocations. Concurrent identical reads coalesce. Cancellation releases request interest; the cache chooses residency. `=0` retains synchronous restore for comparison. |
497
+ | `MLX_BUN_SESSION_CACHE` | — | on (`"0"` disables) | Honor application session affinity for checkpoint lookup and soft RAM retention. `=0` ignores session metadata for paired HTTP measurements. Full input is still required; see server-api.md for body/header fields and session closure. |
498
+ | `MLX_BUN_CACHE_RETENTION` | — | `lru` | `cost-size` selects GreedyDual-style reuse/size retention, with reused prefix tokens as the processing-cost proxy. This changes RAM residency only; SSD durability remains independent. |
499
+ | `MLX_BUN_PAGED_ATTN` | — | off | With paged KV, `1` selects direct Metal reads for short causal queries (≤8 tokens), bf16 or affine KV4/KV8. Longer prefill and explicit masks use the established SDPA shape dispatch. Lab numerical contract: tolerance-tested against the same stored values, not an L1 bit-exact claim. |
500
+ | `MLX_BUN_SSD_SPILL_QUEUE_GB` | — | `2` (GiB; any finite value ≥ 0) | Pending snapshot cap for optional interrupted-generation checkpoints. Older queued intervals may be superseded; `=0` retains the newest plus any in-flight interval. Normal prompt-history persistence does not use this drop policy; its cache owns RAM residency and SSD demotion. Counters in `/stats.ssd_cache`. |
408
501
  | `MLX_BUN_SHUTDOWN_TIMEOUT_MS` | — | `120000` (any finite value > 0) | Maximum time `serve` gives active requests plus the SSD durability flush after `SIGINT`/`SIGTERM`; on timeout it logs the remaining snapshot/spill counters and exits. |
409
502
  | `MLX_BUN_DSPARK_MINCONF` | — | checkpoint-driven | Overrides the DSpark/DeepSpec draft scheduler's minimum-confidence threshold (draft-length pruning). Spec lane only ([docs/design/speculative-decoding.md](../design/speculative-decoding.md)). |
410
503
  | `MLX_BUN_MEMORY_BATCH` | — | `1` (integer ≥ 1) | Row width for `mlx-bun memory` model calls. 1 = serial (batching measured 1.7–1.9× slower on the heterogeneous extract/chunk workload — [docs/design/dreaming-nightly-pipeline.md](../design/dreaming-nightly-pipeline.md)). |
@@ -427,16 +520,27 @@ Under `--isolate` the whole environment is inherited by the engine child.
427
520
 
428
521
  | Env var | Value | Effect |
429
522
  | --- | --- | --- |
430
- | `MLX_BUN_P2R_TRACE` | `=1` | Per-request prompt→response phase trace (admission wait, prefill, token-zero, …) for `/v1/chat/completions` and `/v1/completions`; records print to stderr as JSON lines. The trace id is `x-mlx-bun-trace-id` when the request sends it. |
431
- | `MLX_BUN_P2R_SYNC` | `=1` (with `MLX_BUN_P2R_TRACE`) | Attribution mode: synchronizes the GPU at phase boundaries so each phase is charged its own work (MLX is lazy — without it, unsubmitted work lands in the next phase). Slows the traced request. |
523
+ | `MLX_BUN_P2R_TRACE` | `=1` | Per-request prompt→response phase trace (request-slot wait, execution admission wait, cache prefetch/restore, prefill forward/evaluation/maintenance/checkpoints, mixed-forward token counts, token-zero, bounded initial token routing, response writes) for `/v1/chat/completions` and `/v1/completions`; records print to stderr as JSON lines. The trace id is `x-mlx-bun-trace-id` when the request sends it. |
524
+ | `MLX_BUN_P2R_SYNC` | `=1` (with `MLX_BUN_P2R_TRACE`) | Token-zero attribution mode: synchronizes hidden/cache state, projection and sampling at the instrumented boundaries. This changes overlap and slows the traced request. Ordinary prefill traces add no synchronization: forward includes any backend evaluation, while evaluate measures the remaining state wait. |
525
+ | `MLX_BUN_SPEC_TRACE` | `=1` | Explicit serial speculative-loop round diagnostics, captured once at run setup. |
432
526
  | `MLX_BUN_LANE_DEBUG` | `=1` | Logs each request's scheduling placement (`mechanism` + shape) to stderr. |
433
- | `MLX_BUN_BATCH_STEP_TRACE` | `=1` | Per-step phase timing in the batch scheduler (build / read / emit / gap), read once at module load; summarized by `stepTraceReport()`. |
527
+ | `MLX_BUN_BATCH_STEP_TRACE` | `=1` | Per-step phase timing in the batch scheduler (build / read / emit / gap), captured when the execution group is constructed; summarized by `stepTraceReport()`. |
434
528
  | `MLX_BUN_GRAMMAR_DEBUG` | `=1` | Logs per-step grammar row state in the batch scheduler. |
435
529
  | `MLX_BUN_PREFILL_MEM_LOG` | `=1` | Logs active/peak memory after each serial prefill chunk. |
436
530
  | `MLX_BUN_EXPERT_TRACE` | `=<path>` | Records every MoE router decision as JSONL to that path (adds a per-call GPU→host sync — a measurement tool, not a serving path). |
437
531
  | `MLX_BUN_PI_DEBUG` | any non-empty | Extra `[pi-web]` logging (prompt fingerprint, tool/memory surface) for the web-chat pi session. |
438
532
  | `MLX_BUN_EVAL_DEBUG` | `=1` | HumanEval: pipe the sandbox's stderr and print failures. |
439
533
 
534
+ P2R records include a process-local `startedAtMs` origin to align concurrent
535
+ requests. Prefill `workId` attributes identify shared spans recorded on several
536
+ rows; these spans and their nested children must not be summed as separate
537
+ GPU work. The first eight token-routing spans stop at the first semantic event
538
+ and distinguish hidden channel markers from visible output. With tracing
539
+ enabled, `bench-serve.ts all` retains complete records by child PID in
540
+ `promptResponseTraces`, including records emitted during shutdown. Summarize a
541
+ saved report with `bun scripts/bench/prefill-trace.ts report.md.json --out
542
+ breakdown.json`. Clocks from different server processes are independent.
543
+
440
544
  ### Eval and training
441
545
 
442
546
  | Env var | Default | Effect |
@@ -506,7 +610,7 @@ GPU (one `AsyncMutex`).
506
610
 
507
611
  | Request property | Continuous scheduler? |
508
612
  | --- | --- |
509
- | vision / audio / video parts | ❌ serial — offset-0 single-sequence prefill + media masks |
613
+ | vision / audio / video parts | Gemma4 image/audio and Qwen image/video use atomic media prefill followed by shared decode; diffusion retains its separate serial method |
510
614
  | LoRA `adapter` (resolves to ≥1) | ✅ identical ordered adapter sets share a group; different sets wait for the next group |
511
615
  | `logprobs` / `top_logprobs` | ✅ ordinary and qualified MTP/lookup/standalone-draft groups capture per-request probabilities; other exclusions still apply |
512
616
  | explicit `seed` | ✅ request-local random stream; reproducibility also depends on model arithmetic and batch composition |
@@ -517,7 +621,7 @@ GPU (one `AsyncMutex`).
517
621
  | structured output (`response_format` / `guided_*`) | ✅ batches — per-row grammar matchers (`MLX_BUN_GRAMMAR_BATCH=0` forces serial) |
518
622
  | `temperature` / `top_p` / `top_k` | ✅ batches (each row samples with its own seed) |
519
623
  | `stop` sequences | ✅ batches (per-row `StopMatcher`) |
520
- | `tools` / `tool_choice` | ✅ batches (per-row tool router) |
624
+ | `tools` / `tool_choice` | ✅ batches (per-row tool router); opt-in `MLX_BUN_FILL=strict` uses the shared known-token method |
521
625
  | `--thinking` / `enable_thinking` | ✅ batches (template-render concern) |
522
626
  | multi-turn / long prompt | ✅ batches with prompt-cache reuse: a joiner restores the longest usable cached prefix and prefills only the suffix (`cached_tokens` reported) |
523
627
 
@@ -544,69 +648,85 @@ declared composition may still require the serial mechanism as shown above.
544
648
  | Option | serial (`--batch 1`) | `--batch N` (N>1) |
545
649
  | --- | --- | --- |
546
650
  | `--kv-quant config` | ✅ applied to all requests | ✅ batches where the loaded cache capability supports the per-layer scheme; otherwise routes serial |
547
- | `--kv-quant 4`/`8` | ✅ applied to all requests | ✅ server start=0 converts during prefill and batches on supported cache layouts; delayed library thresholds still use serial |
651
+ | `--kv-quant 4`/`8` | ✅ applied to all requests | ✅ server start=0 converts during prefill and batches on supported cache layouts; delayed library thresholds use qualified shared affine row layouts |
548
652
  | `--kv-quant turbo[:k<bits>v<bits>]` | ✅ ordinary decode; strict serial speculation remains excluded | ✅ ordinary TQ, including delayed library conversion; supported grouped drafting also supports delayed library conversion |
549
653
  | `--kv-quant off` / unset | ✅ bf16 (the L1 default) | ✅ bf16 |
550
- | `--paged-kv` | ✅ | ✅ Gemma4 bf16; prompt-cache bypass |
654
+ | `--paged-kv` | ✅ | ✅ Gemma4 bf16/affine4/8 with separate RAM/SSD namespace; experimental direct attention is opt-in |
551
655
  | `--memory-budget` | ✅ per-request admission | ✅ per-request admission — not aggregate (use `--kv-budget`) |
552
656
  | `--kv-budget` | n/a | ✅ aggregate queue/reject across rows |
553
- | `--prompt-cache` / `--ssd-cache` | ✅ prefix reuse + SSD restore | ✅ on both lanes: joiners `take()` at admission; never-merged rows `put()` back |
657
+ | `--prompt-cache` / `--ssd-cache` | ✅ prefix/output reuse + SSD restore | ✅ immutable prefill/output checkpoints, session lookup and queued SSD persistence |
554
658
  | `--temperature`/`--top-p`/`--top-k` | ✅ | ✅ (per-row) |
555
659
  | `--thinking` | ✅ | ✅ |
556
- | vision / audio / video request | ✅ | ✅ via serial lane |
660
+ | vision / audio / video request | ✅ | Gemma4 image/audio and Qwen image/video shared; diffusion via its serial method |
557
661
  | LoRA `adapter` | ✅ | ✅ compatible groups on adapter-capable backends |
558
662
  | `repetition_penalty` / `min_p` / `xtc_*` / `logit_bias` / presence+frequency | ✅ | ✅ (batches — per-row processors) |
559
663
  | `seed` | ✅ | ✅ request-local sampling |
560
664
  | `tools` / `stop` | ✅ | ✅ (batches) |
561
665
  | structured output (`response_format`/`guided_*`) | ✅ (mask in the decode loop) | ✅ (batches; per-row matchers) |
562
- | `--draft-model` / `--draft-kind` | ✅ eligible spec decode | ✅ qualified MTP/lookup/standalone-draft groups, including grammar/logprobs; other providers remain serial |
563
- | GLM `--mtp on` | ✅ native MTP spec decode | ⚠️ default-on MTP routes every request serial+spec; `--mtp off` exposes ordinary GLM batching |
666
+ | `--draft-model` / `--draft-kind` | ✅ eligible spec decode | ✅ qualified MTP, lookup, standalone, Gemma assistant/DeepSpec/DSpark groups, including grammar/logprobs |
667
+ | GLM `--mtp on` | ✅ native MTP spec decode | ✅ native MTP uses grouped state/verification; tiny-model and oracle tests pass, final Colibri artifact testing is deferred |
564
668
  | `--compiled-decode` | ✅ | ✅ at **B=1 only**: a lone request's adopted serial-class caches replay the same compiled step; B>1 steps run the plain graph |
565
- | `--fused-sdpa` / `--force-wire` | ✅ (serial decode route) | n/a — compat mode, no perf flags by design |
566
-
567
- ### `--batch N` is compat mode — perf flags don't apply by design
568
-
569
- The bit-parity guarantee (mlx-lm B=N) is the *whole point* of `--batch N`,
570
- and it requires running the plain forward path. The scheduler
571
- ([batch-scheduler.ts](../../src/serve/batch-scheduler.ts)) drives the
572
- model through `forwardHidden`/`logitsFromHidden` directly (not
573
- `generate()`), so:
574
-
575
- - **`--compiled-decode`** engages at **B=1 only** (adopt-don't-copy: the
576
- lone request's caches stay serial-class, so the scheduler replays the
577
- serial engine's compiled step — same kill switch). A second row ⇒ the
578
- plain batched graph.
579
- - **`--fused-sdpa`** never engages in the batched lane (an L2 serial-lane
580
- composition).
581
- - **`--force-wire`** doesn't wire (the scheduler bypasses `generate()`'s
582
- wired scope).
583
- - **Always-on bit-exact kernels still run.** The compiled activations are
584
- bit-exact with the spelled-out MLP, so they stay on in both lanes —
585
- "compat mode" means *no parity-breaking optionality*, not -O0.
586
-
587
- ## Known limitations under `--batch N`
588
-
589
- Deliberate v1 scope, not bugs:
590
-
591
- 1. **Other draft providers lack shared state.** Qwen MTP, prompt lookup and
592
- standalone drafting reuse aligned prefill and completed-output state through
593
- the common RAM/SSD cache. Supported full-attention targets also retain
594
- lookup and standalone-draft state. Gemma assistant drafting retains its target hidden
595
- and donor KV through the same cache. Other providers start fresh. Long-conversation timing and pressure
596
- acceptance remain open.
597
- 2. **Aggregate admission is opt-in** via `--kv-budget`; without it N
598
- large-context rows can collectively exceed memory.
599
- 3. **Coverage depends on composition.** Same-B Gemma oracle tests cover
600
- sliding-window wrap and late joins. Broader media, resume, paged and method
601
- combinations remain open in Phase 18.
602
- 4. **bf16 by contract; mixed-KV batching beyond it.** mlx-lm's batched
603
- path *is* bf16. Per-layer `config` batching is a beyond-mlx-lm
604
- composition verified per row against the optiq oracle. Batched
605
- uniform/TurboQuant now has shared layouts; additional same-B external
606
- oracle and feature-composition checks remain.
607
- 5. **`extend` join** appends a joining request to the running batch's
608
- full-attention KV in one pad+concat (`MLX_BUN_BATCH_EXTEND=0` reverts
609
- to whole-batch re-merge); sliding-window layers still re-merge on join.
669
+ | `--fused-sdpa` | ✅ eligible affine attention | ✅ kernel eligibility depends on dtype, quantization and mask geometry |
670
+ | `--force-wire` | ✅ serial wired scope | not applied by the shared executor |
671
+
672
+ ### Shared execution and kernel settings
673
+
674
+ The scheduler selects request work. The inference method advances it through
675
+ model-owned numerical operations. Specialized kernels and sampling policies
676
+ remain available wherever their backend capabilities qualify; batching does
677
+ not disable performance settings as a category.
678
+
679
+ - `--compiled-decode` uses the qualified compiled graph for one active,
680
+ unpadded request. Multi-row steps use the batched graph.
681
+ - `--fused-sdpa` follows affine kernel eligibility. Padded/array masks can
682
+ require the ordinary attention operation; this is an attention decision.
683
+ - `--force-wire` controls the explicit serial wired scope. The shared executor
684
+ does not enter that scope.
685
+ - Trellis, compiled activations, affine row reuse and TurboQuant kernel choices
686
+ belong to the numerical backend and remain independent of admission policy.
687
+
688
+ `MLX_BUN_FILL=strict` uses a grouped method for eligible tool requests. Each
689
+ row owns its fill session and sampler history. Known positions bypass sampling;
690
+ when all next positions are known, the method omits the vocabulary head.
691
+ Shared fill uses the model-qualified multi-position append when all active
692
+ rows have known spans and the model permits that geometry. Qualified Qwen B1
693
+ uses its specialized append; wider rows currently advance one position per step. The shared method supports
694
+ bf16, affine KV4/KV8 and supported TurboQuant layouts, including explicit seeds.
695
+ It does not use compiled replay, interrupted-generation checkpoints, paging,
696
+ probability metadata, media, grammar or a mounted draft provider.
697
+ `MLX_BUN_FILL=echo` uses the same shared method with sampled verification and
698
+ row-specific rollback. Ordinary rows can join with empty proposals; eligible
699
+ plain-greedy requests reuse independent verification sampling. With a Qwen MTP
700
+ companion, echo proposals use the speculative method and the provider consumes
701
+ accepted tokens before resuming learned drafting. Asserted template spans remain
702
+ sampled in this combination. Other draft providers are not yet qualified. Fill stays off by default;
703
+ per-model performance acceptance remains separate from numerical replay.
704
+
705
+ Executors capture an immutable runtime configuration. Later host configuration
706
+ changes do not change an active request's kernel choices. TurboQuant storage
707
+ carries its decode policy through RAM copies, row extraction and delayed
708
+ conversion; restored SSD state is opened under the receiving binding's policy.
709
+
710
+ ## Known limitations under shared execution
711
+
712
+ 1. Gemma4 image/audio uses shared decode after an indivisible embeddings prefill.
713
+ Qwen image/video positions stay with each request through shared decode; diffusion retains its separate serial method. Strict tool-call fill
714
+ and echo verification have shared methods. Qwen MTP supports verified echo;
715
+ asserted fill with a drafter, other providers and shared paging remain unsupported. Grammar has shared masking and opt-in verified
716
+ proposals; direct serial jump-forward is a different algorithm.
717
+ 2. Aggregate admission is opt-in through `--kv-budget`. Without it, concurrent
718
+ contexts can exceed available memory. Default fit estimates remain advisory.
719
+ 3. Native GLM MTP has tiny-model/same-B oracle coverage; final Colibri artifact
720
+ testing is deferred. DSpark/DFlash execution has fixture coverage, with no
721
+ trained checkpoint available for a performance claim.
722
+ 4. Paged Gemma storage supports bf16/affine4/8. TurboQuant pages, per-layer
723
+ paged KV and paged speculative methods remain unsupported. Qualified
724
+ ordinary resume and adapter resume are implemented; exact combinations are
725
+ tracked in [batching](../design/batching.md).
726
+ 5. Stock bf16, affine mixed-KV and TurboQuant retain separate correctness
727
+ contracts. A passing storage test does not qualify an untested model or
728
+ numerical geometry. Existing accepted matrices remain in the design and
729
+ benchmark references.
610
730
 
611
731
  ## Fidelity tiers and the decode route (`--l1` / `--l2`)
612
732
 
@@ -678,14 +798,14 @@ Everything mlx-bun serves, with its default, lane, fidelity tier, and knob.
678
798
  | Feature | Default | Lane | Tier | Knob |
679
799
  | --- | --- | --- | --- | --- |
680
800
  | Structured output (`response_format` json_object/json_schema) | on | both | L2 (oMLX) | request field; `MLX_BUN_GRAMMAR=0` kills |
681
- | Structured-output jump-forward (grammar-forced spans in one forward) | off | serial only | Lab | `MLX_BUN_GRAMMAR_JUMP=1` |
682
- | Token fast-forwarding for tool calls (template-determined spans in one forward) | off | serial only | opt-in; identity tested on covered fixtures, parser/held-out gates remain (`tests/parity/fill-strict.test.ts`) | `MLX_BUN_FILL=strict` |
683
- | Echo injection (session self-copy spans, verified against the same forward's logits) | off | serial only | Lab (paired A/B on task success + wall clock before any default) | `MLX_BUN_FILL=echo`, `MLX_BUN_FILL_K`, `MLX_BUN_FILL_CANDIDATES`, `MLX_BUN_FILL_INDEX_MAX` |
801
+ | Structured-output continuations (shared verified proposals / serial direct jump) | off | shared and serial | Lab | `MLX_BUN_GRAMMAR_JUMP=1` |
802
+ | Token fast-forwarding for tool calls (template-determined spans) | off | serial + shared strict method | opt-in; identity tested on covered fixtures, parser/held-out gates remain (`tests/parity/fill-strict.test.ts`) | `MLX_BUN_FILL=strict` |
803
+ | Echo injection (session self-copy spans, verified against the same forward's logits) | off | shared bf16/affine/TurboQuant; explicit serial eligibility remains separate | Lab (paired A/B on task success + wall clock before any default) | `MLX_BUN_FILL=echo`, `MLX_BUN_FILL_K`, `MLX_BUN_FILL_CANDIDATES`, `MLX_BUN_FILL_INDEX_MAX` |
684
804
  | `guided_grammar` (EBNF) / `guided_regex`¹ / `guided_choice` / `structured_outputs` | on | both | L2 | request fields |
685
805
  | Structured output × speculative decoding | on when both active | Qwen grouped MTP, lookup and standalone drafting / eligible serial methods | Lab | — |
686
806
  | Quantized KV × speculative decoding | Shared Qwen supports uniform KV4/KV8 and TQ with zero or positive conversion thresholds; strict serial supports qualified KV4; per-layer mixed KV remains excluded | Qwen grouped MTP, lookup and standalone drafting / eligible serial methods | scheme and method gates | `MLX_BUN_QWEN_SPEC_KV4=0` disables the KV4 composition |
687
807
  | Tool calling (Gemma sentinel / CPM+Qwen XML / GLM `arg_key`+`arg_value`) + `role:"tool"` loops | on | both | — | request `tools` |
688
- | Vision (`image_url`; PNG/JPEG/HEIC/AVIF/WebP/TIFF/GIF/BMP) | on for models with a tower; SSRF guard on remote URLs | serial | L1/L2 | `--allow-private-media` |
808
+ | Vision (`image_url`; PNG/JPEG/HEIC/AVIF/WebP/TIFF/GIF/BMP) | on for models with a tower; SSRF guard on remote URLs | Gemma4 shared; other media bindings serial | L1/L2 | `--allow-private-media` |
689
809
  | Video input (`video_url`/`video`; AVFoundation sidecar, 2 fps, ≤768 frames, 256 MB body cap; never with audio) | on for Qwen3.5-family | serial | mlx-vlm oracle | `--allow-private-media`, `MLX_BUN_FRAME_EXTRACT` |
690
810
  | Audio input (`input_audio`/`audio`/`audio_url`; WAV native, mp3/m4a/flac/ogg/aiff via CoreAudio; ≤30 s per clip; mixes with images) | on for models with `audio_config` + sidecar tower (e4b) | serial³ | L2 (greedy stream exact vs optiq's internal model) | `--allow-private-media` |
691
811
  | LoRA adapters (mount at start / hot-swap) | off | compatible groups | — | `--adapter <dir>`, `POST /v1/adapters` |
@@ -696,7 +816,7 @@ Everything mlx-bun serves, with its default, lane, fidelity tier, and knob.
696
816
  | Stop sequences / streaming / usage accounting | on | both | — | request fields |
697
817
  | HLG tone-curve sampling | off | serial | Lab | `--hlg-sampling on` |
698
818
  | Spec-decode telemetry (`usage.speculation`) | on with a draft | Qwen shared MTP, lookup and standalone drafting / eligible serial methods | — | — |
699
- | Token-fast-forwarding telemetry (`usage.fill`) | on with `MLX_BUN_FILL=strict` | serial | — | — |
819
+ | Token-fast-forwarding telemetry (`usage.fill`) | on with `MLX_BUN_FILL=strict` | serial + shared | — | — |
700
820
  | Per-turn lane telemetry (`usage.lane`: serial / serial+spec / batched) | on | both | — | — |
701
821
 
702
822
  ### Model coverage (per-model validated cells)
@@ -707,7 +827,7 @@ Everything mlx-bun serves, with its default, lane, fidelity tier, and knob.
707
827
  | Gemma 4 (1B/e4b/12B/26B, + vision e4b/12B, + audio e4b) | ✅ L1/L2 | ✅ | sliding+full interleaved; MoE 26B |
708
828
  | Qwen3.5 (gated-DeltaNet hybrid) | ✅ L1/L2 | ✅ (SSM path) | `MLX_BUN_BATCH_SSM=0` reverts |
709
829
  | Qwen3.8-27B (same qwen3_5 graph) | ✅ L1 | ✅ (SSM path) | native MTP head via `--draft-model`/`--draft-kind mtp` (lossless-gated; slower on a quiet box — opt-in); images and video serve (mlx-vlm oracle) |
710
- | GLM-5.2 / Colibri | ✅ chat/text, Messages, Responses, SSE, tools, grammar, logprobs | ✅ compressed MLA/DSA scheduler | native MTP defaults to serial+spec; `--mtp off` enables batching; embeddings, vision/audio, adapters, training unsupported |
830
+ | GLM-5.2 / Colibri | ✅ chat/text, Messages, Responses, SSE, tools, grammar, logprobs | ✅ compressed MLA/DSA scheduler | native MTP shares the executor; final Colibri artifact testing is deferred; embeddings, vision/audio, adapters, training unsupported |
711
831
  | DiffusionGemma-26B (non-autoregressive) | ✅ (own engine) | — serial always | first bit-exact non-AR port |
712
832
  | Tier-0 universal (llama/qwen2/qwen3/olmo2/…, 11 archs) | ✅ L1 | ✅ plain full-attention archs² | gemma2-family / sliding-window universal → serial |
713
833
 
@@ -753,8 +873,8 @@ composition cells in one run).
753
873
  On 12B+ add `--draft-model <small-same-tokenizer>`.
754
874
  - *Several clients at once (throughput):*
755
875
  `mlx-bun serve <model> --batch 4 --ssd-cache <dir> --kv-budget <GB>` —
756
- supports qualified start-zero TurboQuant and a grouped Qwen MTP companion.
757
- Other draft providers still use serial speculation.
876
+ supports the qualified grouped methods and cache layouts in the compatibility
877
+ matrix. Use the default cap eight unless a workload calls for another cap.
758
878
  - *UI must never lag / survive engine crashes:* add `--isolate`
759
879
  (`--model-pool 2` to keep two models resident).
760
880
  - *Reproducibility:* bare / `--l1` (≡ mlx-lm), `--l1 --batch 1` (strict
@@ -763,16 +883,23 @@ composition cells in one run).
763
883
  `--memory-budget <GB>` + `--ssd-cache <dir>`; MoE adds
764
884
  `--expert-offload`. Start-zero TurboQuant supports shared execution.
765
885
 
766
- **Remaining exclusions:** media, paged KV, generation resume, fill and draft
767
- providers without grouped implementations still need shared execution work.
886
+ **Remaining exclusions:** fill combined with media, speculation or paging, and unsupported provider/layout
887
+ combinations. Qualified ordinary resume, paged storage and grouped draft
888
+ providers are implemented; see the compatibility matrix above.
768
889
  Qwen ordinary, shared MTP and prompt lookup support positive library thresholds for uniform
769
890
  affine KV4/KV8 and TurboQuant. Ordinary Gemma supports positive affine KV4/KV8
770
891
  thresholds, including sliding-window layers. Other-model delayed affine remains
771
892
  open. Qwen MTP
772
893
  prefill and completed decode state use the common RAM/SSD cache. Generated
773
894
  checkpoints contain only processed tokens; persistence uses the existing
774
- queue. Strict serial deletion and complete feature parity
775
- remain acceptance work in Phase 6/18.
895
+ queue. Chat preparation preserves generated token IDs when their decoded text
896
+ exactly prefixes the next rendered request, including provenance recovered from
897
+ SSD headers. This avoids a full prefill when re-encoding identical generated
898
+ text would choose a different BPE segmentation. Edited text retains ordinary
899
+ tokenization. Input token counts can differ from encoding the full prompt again;
900
+ cache state is always matched by exact IDs and execution namespace.
901
+ Strict serial deletion is deferred. Remaining feature and performance work
902
+ is tracked in Phase 6/18.
776
903
 
777
904
  ## Observability — `GET /stats`
778
905