mlx-bun 0.3.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 (231) hide show
  1. package/README.md +57 -32
  2. package/docs/README.md +58 -42
  3. package/docs/archive/investigations/benchmark-ledger-through-v0.4.0.md +3617 -0
  4. package/docs/archive/investigations/openjarvis-fit.md +198 -0
  5. package/docs/archive/investigations/qwen38-27b-campaign-log.md +1092 -0
  6. package/docs/archive/planning/release-notes-v0.4.0.md +45 -0
  7. package/docs/archive/planning/release-notes-v0.5.0.md +37 -0
  8. package/docs/design/batching.md +1535 -50
  9. package/docs/design/decode-speed-program.md +394 -1171
  10. package/docs/design/generic-model-support.md +195 -3
  11. package/docs/design/kv-cache.md +538 -225
  12. package/docs/design/speculative-decoding.md +552 -50
  13. package/docs/design/turboquant.md +140 -17
  14. package/docs/design/unified-engine-frontier-plan.md +259 -150
  15. package/docs/planning/PRODUCT_ROADMAP.md +21 -6
  16. package/docs/planning/public-readiness-handoff.md +244 -0
  17. package/docs/planning/public-readiness.md +149 -0
  18. package/docs/reference/benchmarks.md +3537 -1112
  19. package/docs/reference/cli.md +102 -2
  20. package/docs/reference/distribution.md +45 -13
  21. package/docs/reference/environment.md +159 -13
  22. package/docs/reference/library-api.md +18 -4
  23. package/docs/reference/memory.md +1 -1
  24. package/docs/reference/models.md +28 -7
  25. package/docs/reference/server-api.md +257 -33
  26. package/docs/reference/server-config.md +372 -151
  27. package/docs/reference/troubleshooting.md +13 -21
  28. package/package.json +2 -2
  29. package/src/audio/audiotoolbox.ts +112 -0
  30. package/src/audio/conformer.ts +8 -0
  31. package/src/audio/mic-capture.ts +98 -0
  32. package/src/audio/silero-vad.ts +379 -0
  33. package/src/audio/whisper-decode.ts +774 -0
  34. package/src/audio/whisper-fast.ts +732 -0
  35. package/src/audio/whisper-format.ts +73 -0
  36. package/src/audio/whisper-mel.ts +185 -0
  37. package/src/audio/whisper-timing.ts +274 -0
  38. package/src/audio/whisper-tokenizer.ts +239 -0
  39. package/src/audio/whisper-transcribe.ts +410 -0
  40. package/src/backends/mlx/assistant-target.ts +46 -0
  41. package/src/backends/mlx/autoregressive-draft-rows.ts +120 -0
  42. package/src/backends/mlx/autoregressive.ts +22 -1
  43. package/src/backends/mlx/batch-group.ts +464 -236
  44. package/src/backends/mlx/cache-attachment.ts +67 -0
  45. package/src/backends/mlx/cache-layout.ts +31 -0
  46. package/src/backends/mlx/checkpoint-state.ts +53 -0
  47. package/src/backends/mlx/continuation-persistence.ts +107 -0
  48. package/src/backends/mlx/continuation-request.ts +43 -0
  49. package/src/backends/mlx/continuation.ts +77 -0
  50. package/src/backends/mlx/draft-target.ts +7 -22
  51. package/src/backends/mlx/encoder-cache.ts +25 -0
  52. package/src/backends/mlx/fill-append.ts +21 -8
  53. package/src/backends/mlx/fill-group.ts +497 -0
  54. package/src/backends/mlx/full-kv-row-append.ts +32 -0
  55. package/src/backends/mlx/full-kv-row-donor.ts +54 -0
  56. package/src/backends/mlx/full-transitioning-kv-rows.ts +65 -0
  57. package/src/backends/mlx/gateway-binding.ts +122 -19
  58. package/src/backends/mlx/kv-codec.ts +9 -0
  59. package/src/backends/mlx/kv-maintenance.ts +161 -0
  60. package/src/backends/mlx/kv-tensor-rows.ts +212 -0
  61. package/src/backends/mlx/mixed-iteration.ts +62 -0
  62. package/src/backends/mlx/model-prompt.ts +46 -32
  63. package/src/backends/mlx/model-serving.ts +5 -4
  64. package/src/backends/mlx/paged-cache-codec.ts +27 -0
  65. package/src/backends/mlx/prefill-cohort.ts +100 -0
  66. package/src/backends/mlx/prefill-policy.ts +26 -0
  67. package/src/backends/mlx/prefill-rows.ts +244 -0
  68. package/src/backends/mlx/prefill.ts +1 -1
  69. package/src/backends/mlx/projected-context-rows.ts +72 -0
  70. package/src/backends/mlx/prompt-input.ts +29 -0
  71. package/src/backends/mlx/qwen-prompt-input.ts +40 -0
  72. package/src/backends/mlx/request-state-policy.ts +54 -0
  73. package/src/backends/mlx/rollback.ts +45 -4
  74. package/src/backends/mlx/row-sampling.ts +49 -0
  75. package/src/backends/mlx/serial-executor.ts +51 -73
  76. package/src/backends/mlx/speculative-group.ts +438 -0
  77. package/src/backends/mlx/speculative-round.ts +161 -0
  78. package/src/backends/mlx/speculative.ts +24 -7
  79. package/src/backends/mlx/state-rows.ts +104 -0
  80. package/src/backends/mlx/state-views.ts +21 -1
  81. package/src/backends/mlx/target-layout-capability.ts +35 -0
  82. package/src/backends/mlx/transitioning-kv-rows.ts +173 -0
  83. package/src/cli.ts +333 -36
  84. package/src/contracts/execution.ts +21 -0
  85. package/src/contracts/object-cache.ts +8 -0
  86. package/src/contracts/pi-provider.ts +1 -1
  87. package/src/contracts/pi.ts +3 -1
  88. package/src/contracts/prefix-cache.ts +33 -0
  89. package/src/contracts/resources.ts +7 -0
  90. package/src/contracts/sampling.ts +23 -0
  91. package/src/contracts/scheduling.ts +27 -0
  92. package/src/engine/execution-plan.ts +38 -25
  93. package/src/engine/execution-tasks.ts +44 -0
  94. package/src/engine/namespaced-cache.ts +13 -0
  95. package/src/engine/resources.ts +12 -1
  96. package/src/engine/scheduler.ts +32 -7
  97. package/src/fill/fill-session.ts +14 -3
  98. package/src/fit.ts +29 -15
  99. package/src/generate.ts +105 -195
  100. package/src/grammar.ts +13 -0
  101. package/src/inference/draft-acceptance.ts +41 -0
  102. package/src/inference/draft-output.ts +49 -0
  103. package/src/inference/prefill.ts +6 -0
  104. package/src/inference/rollback.ts +2 -2
  105. package/src/kv-scheme.ts +92 -12
  106. package/src/kv-store.ts +183 -93
  107. package/src/lab/README.md +5 -1
  108. package/src/lab/paged-kv/paged-attention.ts +117 -0
  109. package/src/lab/paged-kv/paged-kv-rows.ts +111 -0
  110. package/src/lab/paged-kv/paged-kv.ts +126 -46
  111. package/src/md-modules.d.ts +5 -0
  112. package/src/mlx/array.ts +39 -1
  113. package/src/mlx/ffi.ts +19 -0
  114. package/src/mlx/normalized-argmax.ts +116 -0
  115. package/src/mlx/ops.ts +97 -10
  116. package/src/model/artifact-identity.ts +73 -0
  117. package/src/model/batched-kv.ts +235 -0
  118. package/src/model/batched-mask.ts +12 -4
  119. package/src/model/batched-quant.ts +8 -2
  120. package/src/model/batched-quantized-kv.ts +144 -0
  121. package/src/model/batched-rotating-quant.ts +76 -15
  122. package/src/model/batched-rotating-state.ts +76 -0
  123. package/src/model/batched-rotating.ts +64 -22
  124. package/src/model/batched-row-storage.ts +52 -0
  125. package/src/model/batched-ssm.ts +52 -0
  126. package/src/model/batched-turboquant-kv.ts +94 -0
  127. package/src/model/compiled-decode.ts +7 -1
  128. package/src/model/decoded-kv-donor.ts +12 -0
  129. package/src/model/delayed-quantized-kv.ts +72 -0
  130. package/src/model/delayed-rotating-quantized-kv.ts +93 -0
  131. package/src/model/delayed-turboquant-kv.ts +57 -0
  132. package/src/model/factory.ts +26 -0
  133. package/src/model/fingerprint.ts +12 -0
  134. package/src/model/full-prefill-padding.ts +56 -0
  135. package/src/model/full-prefill-row.ts +105 -0
  136. package/src/model/gemma4-base.ts +183 -82
  137. package/src/model/gemma4.ts +116 -28
  138. package/src/model/generated/gemma4-12b.ts +4 -1
  139. package/src/model/generated/gemma4-26b.ts +4 -1
  140. package/src/model/generated/gemma4-e4b.ts +4 -1
  141. package/src/model/glm52-cache.ts +80 -4
  142. package/src/model/glm52-mla.ts +12 -6
  143. package/src/model/glm52.ts +19 -40
  144. package/src/model/kv-attention-view.ts +70 -0
  145. package/src/model/minicpm5.ts +11 -7
  146. package/src/model/profile.ts +29 -4
  147. package/src/model/quantized-append-attention.ts +31 -0
  148. package/src/model/qwen3-delta-state.ts +74 -0
  149. package/src/model/qwen3-delta.ts +59 -36
  150. package/src/model/qwen3-mrope.ts +7 -9
  151. package/src/model/qwen3_5.ts +177 -75
  152. package/src/model/rotating-kv-donor.ts +35 -0
  153. package/src/model/rotating-kv-layout.ts +262 -0
  154. package/src/model/rotating-row-transaction.ts +35 -0
  155. package/src/model/speculative-rotating-kv.ts +101 -0
  156. package/src/model/ssm-prefill-padding.ts +39 -0
  157. package/src/model/support.ts +8 -0
  158. package/src/model/token-groups.ts +66 -0
  159. package/src/model/trellis-linear.ts +4 -4
  160. package/src/model/turboquant-codec.ts +89 -0
  161. package/src/model/universal/dense.ts +14 -13
  162. package/src/model/whisper.ts +435 -0
  163. package/src/native/mic_capture.swift +92 -0
  164. package/src/native-pack.ts +6 -5
  165. package/src/pi-web.ts +9 -1
  166. package/src/preflight.ts +31 -15
  167. package/src/prompt-cache.ts +306 -86
  168. package/src/runtime-config.ts +1 -5
  169. package/src/safetensors.ts +9 -2
  170. package/src/sampler.ts +37 -25
  171. package/src/serve/audio-routes.ts +323 -0
  172. package/src/serve/chat-request.ts +2 -0
  173. package/src/serve/chat-stage.ts +19 -14
  174. package/src/serve/completion-executor.ts +9 -1
  175. package/src/serve/discovery-routes.ts +17 -1
  176. package/src/serve/generated-token-history.ts +57 -0
  177. package/src/serve/generation-gateway.ts +88 -38
  178. package/src/serve/model-binding.ts +7 -1
  179. package/src/serve/model-host.ts +3 -4
  180. package/src/serve/preparation.ts +3 -3
  181. package/src/serve/prompt-response-trace.ts +14 -0
  182. package/src/serve/request-plan.ts +6 -3
  183. package/src/serve/request-prep.ts +40 -25
  184. package/src/serve/text-completion-stage.ts +2 -2
  185. package/src/serve/transcription-server.ts +64 -0
  186. package/src/serve/transcription-service.ts +502 -0
  187. package/src/server.ts +185 -261
  188. package/src/spec/assistant-rows.ts +93 -0
  189. package/src/spec/assistant-source.ts +28 -18
  190. package/src/spec/cache-identity.ts +22 -0
  191. package/src/spec/deepspec-rows.ts +29 -0
  192. package/src/spec/deepspec-source.ts +12 -10
  193. package/src/spec/dflash-source.ts +28 -4
  194. package/src/spec/drafter.ts +153 -142
  195. package/src/spec/dspark/deepspec-module.ts +95 -120
  196. package/src/spec/dspark/module-dflash.ts +126 -170
  197. package/src/spec/glm52-mtp-graph.ts +29 -0
  198. package/src/spec/glm52-mtp-rows.ts +132 -0
  199. package/src/spec/glm52-mtp-source.ts +54 -136
  200. package/src/spec/glm52-mtp-state.ts +33 -0
  201. package/src/spec/ngram-source.ts +115 -21
  202. package/src/spec/projected-draft-rows.ts +73 -0
  203. package/src/spec/qwen-mtp-module.ts +171 -0
  204. package/src/spec/qwen-mtp-rows.ts +230 -0
  205. package/src/spec/qwen-mtp-source.ts +84 -226
  206. package/src/spec/qwen-mtp-state.ts +30 -0
  207. package/src/spec/serve-loop.ts +73 -69
  208. package/src/spec/source.ts +93 -15
  209. package/src/spec/two-model-rows.ts +62 -0
  210. package/src/spec/two-model.ts +45 -103
  211. package/src/ssd-cache.ts +107 -36
  212. package/src/ssd-durability.ts +29 -27
  213. package/src/storage/host-buffer.ts +24 -0
  214. package/src/storage/kv-writer.ts +83 -0
  215. package/src/storage/kv-writer.worker.js +264 -0
  216. package/src/storage/retention-policy.ts +37 -0
  217. package/src/tiered-prompt-cache.ts +45 -0
  218. package/src/vision/embedder.ts +8 -0
  219. package/src/vision/prompt.ts +101 -65
  220. package/src/vision/qwen3vl-prompt.ts +101 -64
  221. package/src/vision/qwen3vl-tower.ts +11 -0
  222. package/src/vision/siglip.ts +8 -0
  223. package/src/web/app.html +12 -0
  224. package/src/web/app.js +217 -57
  225. package/src/web/src/chat.ts +4 -1
  226. package/src/web/src/composer.ts +3 -0
  227. package/src/web/src/voice.ts +141 -0
  228. package/src/web/sw.js +1 -1
  229. package/src/backends/mlx/memory-guard.ts +0 -40
  230. package/src/spec/prefix-state.ts +0 -45
  231. /package/docs/{planning → archive/planning}/release-notes-v0.3.0.md +0 -0
@@ -29,6 +29,8 @@ each verb).
29
29
  | `harness pi` | connect your own pi install to the local server | — |
30
30
  | `generate` (`gen`) | one-shot generation, no server | `mlx_lm.generate` |
31
31
  | `embed` | text embeddings, no server | — |
32
+ | `transcribe` | speech-to-text from an audio file, no server | — (`mlx_whisper` CLI is the nearest) |
33
+ | `dictate` | push-to-talk microphone dictation → print / clipboard / typed keystrokes | — |
32
34
  | `get` | download a model (resumable, verified) | `mlx_lm.manage` (partial) |
33
35
  | `scan` | re-index the Hugging Face cache | `mlx_lm.manage --scan` |
34
36
  | `ls` | list downloaded models | `mlx_lm.manage --scan` |
@@ -64,6 +66,24 @@ mlx-bun serve GLM-5.2 --context-length 4096
64
66
  semantics, and which combinations compose live in
65
67
  **[server-config.md](server-config.md)** — this page only names them.
66
68
 
69
+ Library requests support delayed uniform or per-layer affine conversion with
70
+ qualified shared drafting providers, including rotating caches. CLI affine
71
+ quantization starts at zero.
72
+
73
+ The shared executor supports qualified Qwen MTP and start-zero
74
+ TurboQuant at one or several active rows; `--batch 1` still selects the
75
+ legacy serial control. No new CLI flag is required for the shared interfaces.
76
+ Full-attention and rotating targets, including Llama, MiniCPM and Gemma, use shared bf16, uniform KV4/KV8, per-layer affine KV or TurboQuant
77
+ prompt lookup and standalone drafting, including generated RAM/SSD state.
78
+ Gemma assistant drafting also shares these target layouts and caches its last true target
79
+ hidden alongside target KV through the same RAM/SSD interfaces.
80
+ DeepSpec and DSpark use the same executor and persist their projected context through those
81
+ interfaces. DeepSpec accepts bf16 or affine-quantized weights; DSpark retains
82
+ its Markov/RNN head and checkpoint confidence policy.
83
+
84
+ Shared Qwen MTP retains completed decode state through the existing
85
+ `--prompt-cache` and `--ssd-cache` settings.
86
+
67
87
  | Group | Flags |
68
88
  |---|---|
69
89
  | Network | `--host`, `--port`, `--no-open`, `--allow-private-media` |
@@ -71,13 +91,17 @@ semantics, and which combinations compose live in
71
91
  | Process model | `--isolate`, `--model-pool`, `--unix` (internal — the engine half of `--isolate`) |
72
92
  | Scheduling | `--batch` (`--decode-concurrency` accepted as the mlx_lm.server alias) |
73
93
  | KV cache | `--kv-quant`, `--paged-kv`, `--paged-kv-block-size` |
74
- | Adapters | `--adapter` (`--adapter-path` alias) |
75
- | Speculative decoding | `--draft-model`, `--draft-kind`, `--num-draft-tokens`, `--ngram-max`, `--ngram-min` |
94
+ | Adapters (compatible sets can batch) | `--adapter` (`--adapter-path` alias) |
95
+ | Speculative decoding (Qwen methods and full-attention/rotating lookup/standalone and Gemma assistant/DeepSpec/DSpark drafting share execution) | `--draft-model`, `--draft-kind`, `--num-draft-tokens`, `--ngram-max`, `--ngram-min` |
76
96
  | GLM-5.2 | `--mtp`, `--context-length` |
77
97
  | Sampling defaults | `--temperature` (`--temp` alias), `--top-p`, `--top-k`, `--max-tokens`, `--thinking`, `--hlg-sampling`, `--hlg-width`, `--hlg-shoulder`, `--hlg-toe`, `--hlg-pivot-offset` |
78
98
  | Parity tier | `--l1`, `--l2` (`--l3` errors — removed) |
79
99
  | Kill switches | `--compiled-decode`, `--compiled-activations`, `--fused-sdpa`, `--force-wire`, `--expert-offload` |
80
100
 
101
+ Serving treats memory estimates as advisory by default and attempts the request.
102
+ `--memory-budget` opts into estimated admission limits; `--kv-budget` opts
103
+ into aggregate KV limits. `--max-tokens` supplies an explicit output cap.
104
+
81
105
  Endpoints: `/v1/chat/completions`, `/v1/completions`, `/v1/messages`,
82
106
  `/v1/responses`, `/v1/embeddings`, `/v1/models`, `/v1/adapters`, `/health`,
83
107
  `/stats`, `/fit`, `/library`, `/downloads`; the web app at `/` (`/chat`,
@@ -191,6 +215,65 @@ mlx-bun embed --instruct "Retrieve passages that answer the question" --text "wh
191
215
  | `[query]` / `--query <q>` | embedding model; omitted = the first downloaded embedding model (errors with a `get` suggestion when none) |
192
216
  | `--text "…"` | text to embed (omit to read one text per stdin line) |
193
217
  | `--instruct "…"` | Qwen3-Embedding query instruction; omit for plain document embeddings |
218
+
219
+ ### `transcribe` — speech-to-text
220
+
221
+ Whisper transcription of one audio file with a local `mlx-community/whisper-*`
222
+ checkpoint, no server. WAV decodes natively; other containers CoreAudio can
223
+ read (mp3/m4a/aac/flac/ogg/aiff, mp4/mov audio…) decode in-process through
224
+ AudioToolbox. Model support
225
+ and the oracle contract: [models.md](models.md#supported-models).
226
+
227
+ ```sh
228
+ mlx-bun transcribe meeting.m4a # first downloaded Whisper, language auto-detected
229
+ mlx-bun transcribe talk.wav --language en --format srt > talk.srt
230
+ mlx-bun transcribe note.wav --beam-size 5 --prompt "Sotto, SwiftUI, Metal" --verbose
231
+ ```
232
+
233
+ | Flag | Meaning |
234
+ |---|---|
235
+ | `<audio-file>` | required positional |
236
+ | `[query]` / `--model <path\|query>` | Whisper model; omitted = the first downloaded `whisper` checkpoint (errors with a `get` suggestion when none) |
237
+ | `--language <code\|name>` | `en`, `japanese`, …; `auto` or omitted detects from the first 30 s |
238
+ | `--task translate` | translate to English |
239
+ | `--beam-size <n>` | beam search (default greedy) |
240
+ | `--temperature <t>` | one temperature; default is mlx-whisper's `(0, 0.2, …, 1.0)` fallback ladder gated by compression-ratio / log-prob thresholds |
241
+ | `--no-fallback` | temperature 0 only |
242
+ | `--prompt "…"` | initial prompt (vocabulary hints, up to 223 tokens are kept) |
243
+ | `--no-timestamps` | text-only decoding |
244
+ | `--no-condition` | do not feed the previous window's text as the next prompt |
245
+ | `--vad`, `--vad-threshold <t>`, `--vad-model <path>` | Silero VAD gate: no detected speech → empty output, Whisper never runs. Weights: `ggml-org/whisper-vad` in the HF cache (885 KB) or an explicit ggml path |
246
+ | `--word-timestamps` | word-level timing in `verbose_json` (`words` per segment) |
247
+ | `--faithful` | oracle-parity graph instead of the fast path (fused attention, compiled decoder step) |
248
+ | `--audio-ctx <n>` | Lab: encode only n of the 1500 encoder positions (whisper.cpp `-ac`); degrades below ~1024 |
249
+ | `--format <f>` | `text` (default) · `json` · `verbose_json` · `srt` · `vtt` |
250
+ | `--verbose` | per-segment time ranges and a realtime-factor summary on stderr |
251
+
252
+ ### `dictate` — push-to-talk from the microphone
253
+
254
+ Streams the mic (AVAudioEngine sidecar `mlx-bun-mic-capture`, built from
255
+ `src/native/mic_capture.swift` on first use with swiftc, or shipped beside
256
+ the binary) into a transcription session while you speak, so every
257
+ finished 30 s window is transcribed during capture and the result lands
258
+ about one window after you stop. Silence never runs Whisper (Silero VAD).
259
+ macOS prompts for Microphone permission; `--hotkey` needs Input Monitoring
260
+ and `--type` needs Accessibility for your terminal app.
261
+
262
+ ```sh
263
+ mlx-bun dictate # Enter starts/stops a take; prints the text
264
+ mlx-bun dictate --hotkey --copy # hold Right Option, transcript → clipboard
265
+ mlx-bun dictate --hotkey --type # …typed into the frontmost app
266
+ mlx-bun dictate --server http://localhost:8090 --vocabulary "Sotto,SwiftUI,Metal"
267
+ ```
268
+
269
+ | Flag | Meaning |
270
+ |---|---|
271
+ | `[query]` / `--model <path\|query>` | Whisper model for in-process transcription (default: first downloaded `whisper` checkpoint) |
272
+ | `--server <url>` | use a running server's `/v1/audio/sessions` instead |
273
+ | `--hotkey [keycode]` | hold-to-talk on a macOS virtual keycode (default 61, Right Option) instead of Enter toggling |
274
+ | `--language`, `--beam-size`, `--prompt`, `--vocabulary "a,b"`, `--no-vad` | decoding, as for `transcribe` |
275
+ | `--idle-unload <s>` / `--resident` | release the weights after idle seconds (0 = after every take; default 30) or keep them loaded |
276
+ | `--copy` / `--type` / `--type-delay <s>` | clipboard / System Events keystrokes into the frontmost app (delay defaults to 1 s in Enter mode so you can Cmd-Tab, 0 with `--hotkey`) |
194
277
  | `--json` | one OpenAI-style `{object:"list",data:[…]}` object instead of bare arrays |
195
278
 
196
279
  Server equivalent: `mlx-bun serve <embedding-model>` then
@@ -469,6 +552,11 @@ and optiq reference servers and writes a dated report:
469
552
  ```sh
470
553
  bun scripts/bench-serve.ts all
471
554
  bun scripts/bench-serve.ts all --models cpm5,e4b --arms mlx-bun,mlx-lm --out report.md
555
+ bun scripts/bench-serve.ts all --models qwen27b --arms mlx-bun-serial,mlx-bun --logprobs --top-logprobs 3
556
+ # Same KV settings on both executors
557
+ bun scripts/bench-serve.ts all --models qwen27b --arms mlx-bun-serial,mlx-bun --kv-quant 4
558
+ # Same adapter settings on both execution arms:
559
+ bun scripts/bench-serve.ts all --models e4b --arms mlx-bun-serial,mlx-bun --adapter fixtures/adapters/upper
472
560
  ```
473
561
 
474
562
  Curated results: [benchmarks.md](benchmarks.md).
@@ -544,3 +632,15 @@ behind each "not covered" row is in
544
632
  Native verbs with no mlx-lm counterpart — `pi`, `harness pi`, `embed`,
545
633
  `fit`, `train-watch`, `memory`, `evals` — are the surface that makes mlx-bun
546
634
  more than a drop-in.
635
+
636
+
637
+ `--kv-quant turbo` composes with grouped lookup, standalone, Gemma assistant,
638
+ DeepSpec and DSpark drafting on supported targets. Sliding layers stay bf16.
639
+ See [batching design](../design/batching.md) for composition evidence and limits.
640
+
641
+ Paged KV retains the configured `--batch` size. Gemma4 bf16 requests use the shared executor and bypass the prompt cache; quantized KV and draft combinations remain unavailable. See [server configuration](server-config.md).
642
+
643
+ `--generation-checkpoint N` requires `--ssd-cache` and queues owned snapshots
644
+ for eligible ordinary requests. Shared continuation supports eligible Qwen, Llama, MiniCPM and Gemma4 requests with or without adapters, excluding media, grammar, fill, paging, logprobs or speculation.
645
+ Repeat the identical request to replay and resume; flush before shutdown for
646
+ eventual queued snapshots to become durable.
@@ -15,9 +15,33 @@ Three install channels ship from one release:
15
15
  | Direct download (`curl -fsSL https://mlx-bun.dev/install.sh \| sh`) | the same bundle, into `~/.mlx-bun` | one tarball, via curl |
16
16
  | npm / bunx (`bunx mlx-bun`, `bun install -g mlx-bun`) | a launcher + the TypeScript source, run under the user's Bun | the package, then the native runtime pack on first run |
17
17
 
18
- [docs/reference/distribution.md](./distribution.md) covers the *sidecar* use of the same bundle
19
- inside a Tauri/Electron app; the build and signing facts it relies on are
20
- documented here.
18
+ The same bundle can run as a local worker inside a desktop application.
19
+ See [Embedding in a desktop application](#embedding-in-a-desktop-application).
20
+
21
+ ## Embedding in a desktop application
22
+
23
+ Include the complete release bundle in the application's resources and launch
24
+ its executable by absolute path. Keep the native libraries, Metal library,
25
+ helpers, and Pi assets together as listed below. Copying only the executable
26
+ does not produce a self-contained installation.
27
+
28
+ For a Bun host, the [library API](library-api.md) documents `openIsolatedHost`
29
+ and its explicit executable command. A desktop host in another runtime can
30
+ manage the worker process and use the [HTTP API](server-api.md). Keep transport
31
+ and worker lifecycle in the application; request scheduling belongs to the
32
+ engine. Stop the worker when the application no longer owns it.
33
+
34
+ The model weights are separate from the executable bundle. Select an existing
35
+ artifact or use the [model download commands](models.md) before starting the
36
+ worker with that model. Follow the documented cache-location settings when
37
+ the application manages its own storage.
38
+
39
+ Repackaging within an application requires that application's signing and
40
+ notarization workflow. The executable's hardened-runtime entitlements and
41
+ native signing requirements are documented below. Include the third-party
42
+ attributions with the distributed application. Test the installed application
43
+ bundle, including worker launch and runtime assets, rather than relying only
44
+ on a source-checkout run.
21
45
 
22
46
  ## What ships in the bundle
23
47
 
@@ -32,6 +56,7 @@ dir), ~80 MB compressed; `mlx.metallib` is the bulk of the installed size.
32
56
  | `libmlx.dylib`, `libjaccl.dylib`, `mlx.metallib` | MLX core, its distributed-comm dependency, the Metal kernels (libmlx loads the metallib from its own directory) | `/opt/homebrew/opt/mlx/lib` (override `MLX_LIB_DIR`) |
33
57
  | `libmlx_bun_expert_io.dylib` | bounded streamed-expert I/O for GLM-5.2 MoE serving | [scripts/build-expert-io.sh](../../scripts/build-expert-io.sh) |
34
58
  | `mlx-bun-frame-extract` | AVFoundation frame-extraction helper for video input (a Mach-O *executable*, not a dylib) | [scripts/build-frame-extract.sh](../../scripts/build-frame-extract.sh) → `swiftc -O src/native/frame_extract.swift` |
59
+ | `mlx-bun-mic-capture` | AVAudioEngine microphone capture + CGEvent hold-key helper for `mlx-bun dictate` (Mach-O executable; the only native piece of the Whisper work — the web composer uses the browser mic) | [scripts/build-mic-capture.sh](../../scripts/build-mic-capture.sh) → `swiftc -O src/native/mic_capture.swift` |
35
60
  | `photon_rs_bg.wasm` | pi's image codec, resolved next to the executable by the web chat's `read`-on-image path (best-effort; pi degrades to a text note without it) | `node_modules/@silvia-odwyer/photon-node` (override `PHOTON_WASM`) |
36
61
  | `theme/*.json`, `assets/*.png`, `export-html/`, `package.json`, `CHANGELOG.md`, `native/darwin/prebuilds/<arch>/darwin-modifiers.node` | assets the embedded pi **terminal** (`mlx-bun pi`) resolves by path: themes, startup art, `/export` template, version banner + changelog, native modifier-key helper (degrades if absent) | `node_modules/@earendil-works/pi-coding-agent/dist`, `pi-tui` |
37
62
 
@@ -133,7 +158,7 @@ does, in order. Steps 1–3 are yours; the rest is the scripts.
133
158
  (`BUILD_DIR` overrides `dist`). Release builds are arm64-only; the script
134
159
  exits on any other host arch.
135
160
  5. **Sign nested code first** — every `*.dylib`, `*.node`, and the
136
- `mlx-bun-frame-extract` helper, matched by name under the build dir, gets
161
+ `mlx-bun-frame-extract` / `mlx-bun-mic-capture` helpers, matched by name under the build dir, get
137
162
  `codesign --force --timestamp --options runtime -s "<identity>"`.
138
163
  Nested-before-container is required. The metallib, wasm, JSON, and PNG
139
164
  assets are data, not code, and are skipped.
@@ -186,6 +211,13 @@ npm if the version is live. Env overrides: `MLX_BUN_SIGN_IDENTITY`,
186
211
  `NOTARY_PROFILE`, `BUILD_DIR`, `OUT_DIR` (release); `OUT_DIR`, `REPO`,
187
212
  `TAP_REPO` (publish).
188
213
 
214
+ For an SSH-driven release, a local keychain unlock may still leave
215
+ `notarytool` reporting `keychainLocked` in the SSH session. The v0.3.0
216
+ release succeeded by running `release-binary.sh` in the M1 Max's local
217
+ Terminal, then `publish-release.sh` over SSH. Both steps used the same clean
218
+ merged source and `OUT_DIR`. Check `npm whoami` from the actual release
219
+ checkout too: a project `.npmrc` can override the user's configuration.
220
+
189
221
  ### The notarization lesson (v0.2.0, 2026-08-24)
190
222
 
191
223
  The first v0.2.0 submission came back **Invalid**. The release added
@@ -311,7 +343,7 @@ sh scripts/build-native-pack.sh <ver> dist-native
311
343
  gh release create native-v<ver> \
312
344
  dist-native/mlx-bun-native-v<ver>-arm64.tar.gz \
313
345
  dist-native/mlx-bun-native-v<ver>-arm64.tar.gz.sha256 \
314
- --title "mlx-bun native runtime v<ver>" --notes "<what changed>"
346
+ --latest=false --title "mlx-bun native runtime v<ver>" --notes "<what changed>"
315
347
  ```
316
348
 
317
349
  `build-native-pack.sh` stages the same six files with the same
@@ -323,13 +355,13 @@ package. Pre-publish smoke: a fresh-cache extraction plus a real MLX
323
355
  `dlopen` (and, when the helpers changed, an expert read or a video decode).
324
356
  The pack is ad-hoc signed only — `build-native-pack.sh` never touches a
325
357
  Developer ID and nothing notarizes it; it is `dlopen`ed by the user's own Bun
326
- process, not by our signed executable. The most recent pack
327
- (`native-v0.3.0`, published 2026-08-22) added `mlx-bun-frame-extract`.
358
+ process, not by our signed executable. Use `--latest=false` so a native pack
359
+ does not replace the application release behind the direct installer's URL.
328
360
 
329
- The working tree targets native pack 0.4.0 with MLX 0.32.2 and MLX-C
330
- `c74db5307cc8ce122f48d97ef951b30578674e7f`. Its six-file macOS 14 candidate
331
- has been built and verified locally. It is not published. Release the native
332
- asset before publishing any package that uses this manifest. Runtime bindings
361
+ Native pack 0.4.0 was published on 2026-09-08 with MLX 0.32.2 and MLX-C
362
+ `c74db5307cc8ce122f48d97ef951b30578674e7f`. Its six-file macOS 14 archive
363
+ was uploaded from the M1 Max and downloaded again; its checksum matches the
364
+ package manifest. It was published before mlx-bun v0.3.0. Runtime bindings
333
365
  match this one C API, and startup checks the linked core version. The old
334
366
  runtime and source controls remain separate for benchmark comparisons.
335
367
 
@@ -338,8 +370,8 @@ CI builds that pinned C wrapper with
338
370
  `MLX_BUN_LIBMLXC` to its `lib/libmlxc.dylib`. The script verifies the official
339
371
  macOS 14 MLX-Metal archive hash and checks out the exact MLX-C commit. It
340
372
  extracts native libraries and headers without running Python or compiling
341
- Metal sources. This also reproduces local native-pack build inputs before
342
- the candidate is published; Homebrew's C wrapper is not the CI runtime.
373
+ Metal sources. This also reproduces the native-pack build inputs;
374
+ Homebrew's C wrapper is not the CI runtime.
343
375
 
344
376
  Managed jobs in the compiled binary re-exec that binary through a private job
345
377
  entry, with the database/log paths passed in the environment. They do not depend
@@ -1,9 +1,8 @@
1
1
  # Reference environment and platform facts
2
2
 
3
- The canonical home for the oracle setup and the hard-won platform facts that
4
- agents and contributors need. CLAUDE.md points here; do not duplicate these
5
- facts elsewhere. Each fact is one paragraph with the evidence path that proves
6
- it — when you change the code, update the paragraph.
3
+ Oracle configuration, native-runtime compatibility, and machine conditions
4
+ for reproducing numerical and performance checks. Historical environment
5
+ records below identify the versions used for their original comparisons.
7
6
 
8
7
  User-facing symptoms live in [troubleshooting.md](./troubleshooting.md).
9
8
 
@@ -21,8 +20,8 @@ Before asserting anything about RAM, OOM, or bandwidth, run
21
20
  reference `serve.sh` (below) hard-codes a 28 GB wired limit "of 32 GB", so it
22
21
  is written for the M1 Max.
23
22
 
24
- For the current M4 Pro optimization campaign, Josh specifies internal SSD
25
- storage for active models. The external SSD stores artifacts to copy when
23
+ The M4 Pro optimization measurements use internal SSD storage for active
24
+ models. The external SSD stores artifacts to copy when
26
25
  needed; external-drive loading performance is outside normal acceptance for
27
26
  the 12–17 GB models. Stage each needed artifact under `~/models/`, verify
28
27
  every file against its archive, and record that path in both benchmark arms.
@@ -34,10 +33,24 @@ user-local eval DB (`~/.cache/mlx-bun/evals.sqlite`) and promoted to
34
33
  `docs/reference/benchmarks.md` deliberately. `bun scripts/bench-serve.ts all`
35
34
  is the benchmark harness; it writes Markdown and raw JSON under `reports/`
36
35
  as gitignored outputs. Numbers on a loaded machine are
37
- garbage — run-to-run spread is the stability signal, and the harness retries
36
+ diagnostic; run-to-run spread is the stability signal, and the harness retries
38
37
  unstable cells (`scripts/bench-serve.ts`; `benchmarks.md`, "Running the
39
38
  benchmark").
40
39
 
40
+ A performance decision must account for concurrent work during the timed
41
+ comparison. For unstable pairs, capture process activity, GPU utilization and
42
+ VM/page-in changes before and during the repeat; keep timestamps that align
43
+ those observations with each measured request or forward. A preflight snapshot
44
+ alone cannot identify interference during a run. CPU usage or retained swap
45
+ alone does not establish GPU contention or active paging. Preserve all original
46
+ samples and label unresolved comparisons inconclusive rather than attributing
47
+ an outlier to the candidate. GPU-wide counters and the last submitting PID are
48
+ observations, not complete per-process GPU attribution. The serving decode
49
+ samples use different nonce-bearing prompts; speculative acceptance and target
50
+ call counts can therefore vary within an arm. Compare matching request hashes
51
+ and work counters across arms before interpreting their TPS spread as temporal
52
+ instability.
53
+
41
54
  ## External storage loading
42
55
 
43
56
  On the M4 Pro with MLX 0.32.2, two RTN4 first-request warmups from
@@ -66,6 +79,65 @@ deferred under the active-storage policy above. Evidence under `reports/qwen38-r
66
79
  `fill-append-http-final-partial-review.json`, `rtn4-storage-control-manifest.json`,
67
80
  `fill-append-storage-control-review.json` and `weights-loading-http.json`.
68
81
 
82
+ ## Reproduce the MiniCPM5 logit comparison
83
+
84
+ From an Apple Silicon Mac running macOS 14 or later, install Bun 1.4.2 or
85
+ newer and [uv](https://docs.astral.sh/uv/getting-started/installation/), then
86
+ run these commands from a checkout of this repository:
87
+
88
+ ```sh
89
+ bun install --frozen-lockfile && \
90
+ oracle_env="$(bash scripts/oracle/setup.sh)" && \
91
+ eval "$oracle_env" && \
92
+ bash scripts/oracle/compare-minicpm5.sh --download
93
+ ```
94
+
95
+ The setup script creates an external Python 3.13.5 environment, installs the
96
+ complete [reference lock](../../scripts/oracle/requirements.lock), checks its
97
+ dependencies, and prints the `MLX_BUN_ORACLE_VENV` export. Its default location
98
+ is `~/.cache/mlx-bun/oracle-mlx-0.32.2-py3.13.5`. Pass a directory to `setup.sh`
99
+ to choose another location. Re-running it synchronizes that environment to
100
+ the lock, removing packages outside the pinned set. It does not install model
101
+ weights or run inference.
102
+
103
+ The lock pins MLX 0.32.2, mlx-lm 0.31.3, mlx-optiq 0.2.7 and their installed
104
+ dependencies. MLX-Metal uses the explicit macOS 14 wheel and SHA-256 from
105
+ [the upstream release](https://pypi.org/project/mlx-metal/0.32.2/#files),
106
+ matching native pack 0.4.0. Selecting only a package version could install a
107
+ different Metal build on newer macOS releases.
108
+
109
+ The comparison script downloads `mlx-community/MiniCPM5-1B-OptiQ-4bit` at
110
+ revision `664aabaed233c653f82716d8dc822234d0091f78` when `--download` is supplied.
111
+ Omit the flag to require a cached snapshot. Hugging Face cache environment
112
+ settings are respected. Set `MLX_BUN_TEST_MINICPM5` to use an existing model
113
+ directory instead; its config and weight hashes are recorded in the output.
114
+
115
+ Each run creates a fresh fixture directory under
116
+ `~/.cache/mlx-bun/comparisons/`, invokes the Python oracle through
117
+ `scripts/regen/minicpm5.ts`, then runs
118
+ `bun test tests/parity/minicpm5-parity.test.ts`. `MLX_BUN_GOLDEN_DIR` selects
119
+ that directory for both generation and comparison, with no fallback to older
120
+ fixtures. The manifest records the prompt, token IDs, Python/MLX/Metal/mlx-lm
121
+ versions, device, model hashes, lock hash and per-vector hashes. The command
122
+ prints the output directory and elapsed comparison time. Missing inputs,
123
+ failed generation or mismatched logits produce a nonzero exit; skipped tests
124
+ are not the acceptance result.
125
+
126
+ This test compares the complete vocabulary logit vector before sampling at
127
+ 100 steps, plus all 100 greedy token IDs. It uses bf16 KV, one short prompt
128
+ and single-row model forwards. It does not establish sampling-policy parity,
129
+ batched scheduling correctness, mixed-KV parity or server performance. The
130
+ [benchmark reference](benchmarks.md) links the separate
131
+ coverage for those configurations.
132
+
133
+ Fixtures must be generated on the comparison machine. The usual golden
134
+ resolver selects `goldens/<machine-key>/` overrides; the reference machine
135
+ uses the root `goldens/` directory. Metal kernels can differ across GPU
136
+ families and runtime builds. In particular, the fast-SDPA dispatch boundary
137
+ at sequence length L ≥ 16 can change floating-point operation order. This
138
+ short-prompt recipe does not test that prefill boundary. Longer-prefill and
139
+ batched comparisons need their corresponding same-machine oracle cases.
140
+
69
141
  ## The Python oracle
70
142
 
71
143
  Logit parity with mlx-lm is the correctness oracle; mixed-precision KV and the
@@ -85,6 +157,30 @@ bumped from 0.2.4 to 0.2.15 on 2026-07-06 and the mixed-KV goldens stayed
85
157
  byte-identical across the bump. The venv directory holds no project source —
86
158
  just the venv and `serve.sh`.
87
159
 
160
+ **Whisper oracle.** mlx-whisper 0.4.3 lives in its own venv,
161
+ `~/Code/mlx-whisper-oracle/.venv` (Python 3.12 via uv; `mlx==0.32.2` to match
162
+ `MLX_CORE_VERSION`, plus torch and numba, which must never enter the pinned
163
+ mlx-lm venv). Override with `MLX_BUN_WHISPER_ORACLE_VENV`. Regenerate with
164
+ `bun scripts/regen.ts whisper`; the parity test compares against
165
+ `goldens/whisper.json` and its untracked blobs. The mlx-community Whisper
166
+ checkpoint ships no tokenizer: `openai/whisper-large-v3-turbo`'s
167
+ `tokenizer.json` + `tokenizer_config.json` must be in the HF cache
168
+ (`hf_hub_download` of those two files; ~2.5 MB). whisper.cpp comparisons use
169
+ sotto's vendored v1.9.3 helper (`~/Code/sotto/build/server/helpers/sotto-engine`)
170
+ with `ggml-large-v3-turbo.bin` — same weights, different runtime.
171
+
172
+ Silero VAD parity uses the `silero-vad` 6.2.1 pip package (torch JIT model)
173
+ in the same venv (`scripts/oracle/gen-silero-golden.py` → tracked
174
+ `goldens/silero-vad.json`); the engine's weights are `ggml-org/whisper-vad`'s
175
+ `ggml-silero-v6.2.0.bin` in the HF cache (byte-identical to sotto's copy,
176
+ SHA-256 `2aa269b7…`). `coremltools` is installed there too for the Core ML
177
+ encoder experiment (benchmarks.md).
178
+
179
+ The repo's untracked `.env` may pin `MLX_BUN_ORACLE_VENV` to a directory that
180
+ no longer exists (it pointed at `~/Code/mlx-lm-example/.venv` on the M1 Max
181
+ on 2026-09-15); every parity test importing `tests/support/paths.ts` then
182
+ throws at import. Fix the path or run with `bun --env-file=/dev/null test …`.
183
+
88
184
  The Qwen MTP compatibility test accepts explicit local artifact paths through
89
185
  `MLX_BUN_TEST_MTP_TARGET` and `MLX_BUN_TEST_MTP_DRAFT`, alongside
90
186
  `MLX_BUN_TEST_QWEN38_MTP=1`. It compares speculative and ordinary greedy
@@ -101,7 +197,7 @@ Qwen artifacts are also present under
101
197
  `/Volumes/MLX-Models/models/{Qwen,mjriii}/`; header-only inventories under
102
198
  `reports/qwen38-rd/` are machine-local and do not load the GPU.
103
199
 
104
- **Authorized M4 Pro runtime update, staged 2026-09-07:** MLX 0.32.2 with
200
+ **M4 Pro runtime update, staged 2026-09-07:** MLX 0.32.2 with
105
201
  MLX-C `c74db5307cc8ce122f48d97ef951b30578674e7f` is built under
106
202
  `reports/qwen38-rd/mlx-upgrade-0.32.2/install/lib/`. Explicit
107
203
  `MLX_BUN_LIBMLXC=<that-directory>/libmlxc.dylib` selects it per process.
@@ -134,10 +230,10 @@ eleven skips on the old core. The local native-pack
134
230
  candidate under `native-pack-candidate/` extracts into `native-pack-extracted/`;
135
231
  all five Mach-O files target 14.0, have valid ad-hoc signatures and load their
136
232
  non-system dependencies from siblings. These checks ran on macOS 27, not an
137
- actual macOS 14 host. The production native-pack manifest now targets this
138
- 0.4.0 candidate and the verified local cache is
139
- `/Users/joshrossi/Library/Caches/mlx-bun/native-v0.4.0-arm64`. The candidate
140
- has not been published. The consolidated source passes 52 complete Qwen
233
+ actual macOS 14 host. This build is published as native pack 0.4.0 and the
234
+ verified local cache is
235
+ `/Users/joshrossi/Library/Caches/mlx-bun/native-v0.4.0-arm64`. Publication
236
+ details are in distribution.md. The consolidated source passes 52 complete Qwen
141
237
  forward/state/continuation cases and six Trellis generations, with fixed
142
238
  source/runtime/reference hashes and clean worker exits. The consolidated
143
239
  model-free suite passes 1,875 tests with ten skips; all typechecks pass.
@@ -179,6 +275,40 @@ against an M4 Pro output fixture. The test still requires exact output and
179
275
  state hashes; Python runs only the external reference. Trellis variant
180
276
  matrix tests allow 30 seconds for first-use kernel compilation on CI.
181
277
 
278
+ The M1 Max override was replayed on 2026-09-08 with the same inputs using
279
+ `/Users/joshrossi/Code/mlx-lm/.venv-mlx-0.32.2-macos14` (MLX/MLX-Metal
280
+ 0.32.2, MLX-LM 0.31.3). Bun matches its outputs and recurrent-state hashes
281
+ exactly. The review file is
282
+ `reports/qwen38-closeout/qwen-delta-m1-review.json`.
283
+
284
+ Qwen3-Embedding tests also resolve `goldenPath("qwen3-embed")`, including
285
+ machine overrides, and skip when their required binary blobs are absent.
286
+ Regenerate with the matching Python and
287
+ `scripts/oracle/gen-qwen3-embed-golden.py <artifact> --replay goldens/qwen3-embed/meta.json`;
288
+ `--output-dir` selects a review directory. Replay preserves the text and token
289
+ IDs and checks the tokenizer against them. Metadata records the device,
290
+ runtime, artifact config, original metadata and output hashes. The M1 Max
291
+ replay matches hidden states and pooled embeddings exactly. Cosine uses both
292
+ vector norms: bf16 normalization does not guarantee a unit norm when the
293
+ result is subsequently read as float32.
294
+
295
+ The same M1 validation found June MiniCPM logit blobs without runtime
296
+ provenance. Explicit `bun scripts/regen.ts minicpm5` with the matching venv
297
+ preserves the artifact, prompt IDs and greedy trajectory; the regenerated
298
+ logits match Bun exactly. The generator now records runtime/device/config
299
+ provenance and all blob hashes. The old files and reviewed differences remain
300
+ under `reports/qwen38-closeout/minicpm5-golden-before/` and
301
+ `minicpm5-golden-review.json`.
302
+
303
+ The Gemma bf16 and mixed-KV M1 replays also pass with unchanged prompt IDs
304
+ on MLX 0.32.2. This current M1 venv contains **mlx-optiq 0.2.7** by installed
305
+ distribution metadata, matching the documented M4 installation; it is not
306
+ the historical 0.2.15 environment. OptiQ's module `__version__` incorrectly
307
+ reports 0.2.5 there, so regeneration records distribution metadata. Old
308
+ fixtures remain under `reports/qwen38-closeout/{parity,mixed-kv}-golden-before/`.
309
+ These are current-stack parity checks, not a runtime-only A/B against the
310
+ older mixed-KV fixture's 0.31.2/0.2.15 combination.
311
+
182
312
  Readable oracle source, in that `site-packages/`: `mlx_lm/models/gemma3.py`
183
313
  and `mlx_lm/server.py` for the port targets; `mlx_lm/models/cache.py` for the
184
314
  cache classes; `mlx_lm/tokenizer_utils.py` for prompt rendering;
@@ -250,7 +380,19 @@ servers DO validate: the Bearer token must start with `sk-optiq-`
250
380
 
251
381
  ## Bun and FFI facts
252
382
 
253
- **Bun ≥ 1.4.0.** `package.json` `engines.bun` is `>=1.4.0`. `Bun.Image`
383
+ **Development and CI: Bun 1.4.2.** Build the committed frontend bundle with
384
+ that version so its byte-for-byte freshness check uses the same compiler.
385
+ Performance comparisons record the runtime and use the same version in both
386
+ arms; historical results retain their original version.
387
+
388
+ Non-login SSH on the M4 Pro can omit `/Users/joshrossi/.bun/bin` from `PATH`.
389
+ Put that directory on the remote command's PATH when running the test suite:
390
+ launching the parent Bun by absolute path alone does not let subprocess tests
391
+ find `bun`. The quantized-row validation's initial seven command-entry/job
392
+ failures disappear with that invocation corrected; no product code change was
393
+ needed for those failures.
394
+
395
+ **Runtime minimum: Bun ≥ 1.4.0.** `package.json` `engines.bun` is `>=1.4.0`. `Bun.Image`
254
396
  (native OS codecs via ImageIO, EXIF auto-orient, off-thread) is the vision
255
397
  decode path (`src/vision/preprocess.ts`); `Bun.Image.resize` is deliberately
256
398
  not used. The FFI ABI fix below is the other reason for the pin.
@@ -346,3 +488,7 @@ holds self-contained reproductions for upstream bug reports (the two Bun
346
488
  issues above, plus `optiq-mixed-kv-inert` and `vllm-metal-turboquant`); they
347
489
  are not part of the build. Committed absolute paths (the venv, the weights
348
490
  snapshot, `serve.sh`) are machine-specific on purpose — do not "correct" them.
491
+
492
+ The quantized-KV golden producer (`bun scripts/regen.ts kvq`) passes an absolute output directory to Python, since the child runs from `scripts/`. This preserves the selected machine directory and existing prompt IDs. Missing M4 blobs were regenerated from the pinned oracle into the isolated `delayed-affine-m4-oracle` override; numerical expectations were not produced by the inference implementation. Evidence: `reports/qwen38-closeout/composition-baseline/oracle-generator-path/`.
493
+
494
+ The M1 rotating-KV reference still identified MLX 0.31.2 and disagreed with both unchanged control and candidate on the KV8 greedy prefix. Regeneration with pinned MLX 0.32.2 retains the prompt IDs and passes exact logits plus both continuation checks. The matching M1 override and M4 reference caches are refreshed; previous manifests/blobs and provenance are retained with the generator-path evidence.
@@ -14,10 +14,13 @@ everything else under `src/` is internal and may change without notice.
14
14
  Every export is documented below, grouped the way `src/index.ts` groups
15
15
  them.
16
16
 
17
- One rule everywhere: **one generation at a time per process** (one GPU).
18
- The server serializes through a queue (or, under `--batch N`, its
19
- continuous scheduler — a server scheduling mechanism, not a library
20
- surface); direct library callers must do the same.
17
+ The server uses continuous batching by default for eligible requests.
18
+ Its scheduler coordinates work on shared model resources, including a single
19
+ active request. Direct callers of `generate` must coordinate access to the
20
+ same model and mutable state; launching independent generators does not
21
+ automatically create a batch. Use the serving integration for its scheduling
22
+ and request preparation, or compose the engine with explicit execution ports.
23
+ See [server configuration](server-config.md) for supported combinations.
21
24
 
22
25
  ## Export map
23
26
 
@@ -672,3 +675,14 @@ When embedding the high-level library in your own compiled application, pass
672
675
  `command: ["/path/to/mlx-bun"]` to `openIsolatedHost`. Source/package consumers
673
676
  use the installed CLI source automatically; compiled callers supply the worker
674
677
  executable explicitly because their own executable is not the mlx-bun CLI.
678
+
679
+ ### Cache session affinity
680
+
681
+ `GenerateOptions.cacheSessionId` carries application cache affinity through
682
+ serving execution. The `PrefixCache` contract accepts the optional session ID
683
+ on `take`, `prefetch` and `put`, and exposes optional `closeSession(id)`. The
684
+ cache owns the association and both storage tiers; the ID does not change the
685
+ numerical namespace or replace the request's full token history. Standalone
686
+ `generate()` calls that supply their own cache retain caller-owned state; an
687
+ ID alone does not create a process-wide cache. HTTP fields and lifecycle are
688
+ documented in [server-api.md](server-api.md#cache-session-affinity).
@@ -254,7 +254,7 @@ routes never touch the agent-tool surface above.
254
254
 
255
255
  ## Related
256
256
 
257
- - [cli.md](cli.md#memory--the-personal-wiki) — the `memory` command in the full CLI reference.
257
+ - [cli.md](cli.md#memory-alias-setup--the-personal-wiki) — the `memory` command in the full CLI reference.
258
258
  - [server-api.md](server-api.md#get-apimemory--post-apimemoryinit) — `/api/memory/*` wire format.
259
259
  - [dreaming-nightly-pipeline.md](../design/dreaming-nightly-pipeline.md) — the canonical pipeline design.
260
260
  - [docs/design/dreaming-nightly-pipeline.md](../design/dreaming-nightly-pipeline.md), [docs/design/dreaming-nightly-pipeline.md](../design/dreaming-nightly-pipeline.md) — synthesis internals and the local inference path.
@@ -4,6 +4,14 @@ The one supported-model list, plus how mlx-bun downloads, indexes, lists,
4
4
  and reclaims models. The store is the standard Hugging Face hub cache —
5
5
  nothing proprietary; `hf` and mlx-lm read/write the same tree.
6
6
 
7
+ ## Automatic model selection
8
+
9
+ On a fresh install with no supported downloaded model, `serve` downloads and
10
+ starts MiniCPM5-1B, then can download the device-recommended model in the
11
+ background. Gemma e4b is the preferred general model once available and within
12
+ the selection budget. `DEFAULT_REPO_ID` names that preference, not the
13
+ first-download artifact. Explicit model selection bypasses this starter flow.
14
+
7
15
  ## Supported models
8
16
 
9
17
  The source of truth is code, not this table: `src/model/profile.ts`
@@ -16,25 +24,29 @@ is the bug.
16
24
 
17
25
  Scope is deliberate: a few families held to explicit oracle contracts
18
26
  (L1 = bit-exact vs mlx-lm, L2 = bit-exact vs mlx-optiq, L3 = measured, no
19
- oracle), plus a declared Tier-0 generic surface. There is no alias table —
27
+ oracle), plus a declared Tier-0 generic surface. These are comparison
28
+ contracts, not interchangeable claims about every artifact. The
29
+ [coverage map](benchmarks.md#coverage-beyond-the-four-model-summary) identifies
30
+ the test and remaining evidence limits for each roster row. There is no alias table —
20
31
  the "query" column is a substring that `resolve()` matches against
21
32
  already-downloaded repos (it must match exactly one repo; see
22
33
  [Query resolution rules](#query-resolution-rules-per-verb)).
23
34
 
24
35
  | `serve` query | Validated artifact (HF repo) | Family · load path · tier | Modalities | Draft sources | KV schemes | Notes |
25
36
  | --- | --- | --- | --- | --- | --- | --- |
26
- | `e4b` | `mlx-community/gemma-4-e4b-it-OptiQ-4bit` | Gemma 4 (`gemma4*`) · dedicated `gemma4` graph with a **generated** specialization keyed on the config fingerprint · L1 | text · **image** (SigLIP tower in the bf16 `optiq_vision.safetensors` sidecar) · **audio** (Conformer tower in the same sidecar) | two-model · Gemma `-assistant` (L2) · locally-trained DSpark (`dspark.json`) · ngram | bf16 · uniform 4/8 · `config` (kv_config.json, batches) · turbo · `--paged-kv` | **Default model** (`DEFAULT_REPO_ID`, src/fit.ts). The only audio-capable artifact. ~7.0 GB on disk (benchmarks.md legend, 2026-06-15). Trains (LoRA / ORPO / SFT). |
37
+ | `e4b` | `mlx-community/gemma-4-e4b-it-OptiQ-4bit` | Gemma 4 (`gemma4*`) · dedicated `gemma4` graph with a **generated** specialization keyed on the config fingerprint · L1 | text · **image** (SigLIP tower in the bf16 `optiq_vision.safetensors` sidecar) · **audio** (Conformer tower in the same sidecar) | two-model · Gemma `-assistant` (L2) · locally-trained DSpark (`dspark.json`) · ngram | bf16 · uniform 4/8 · `config` (kv_config.json, batches) · turbo · `--paged-kv` | **Preferred model after download**; fresh installs first serve MiniCPM5. See [automatic selection](#automatic-model-selection). The only audio-capable artifact. ~7.0 GB on disk (benchmarks.md legend, 2026-06-15). Trains (LoRA / ORPO / SFT). |
27
38
  | `12B` | `mlx-community/gemma-4-12B-it-OptiQ-4bit` | Gemma 4 · generated specialization · L1 | text · **image** (encoder-free `gemma4_unified_vision` declared in config.json — no sidecar needed). No audio: the snapshot's sidecar is an audio **stub** (`embed_audio` only, no `audio_tower.*`) | two-model · `-assistant` · DSpark local · **DeepSpec released** (`deepseek-ai/dspark_gemma4_12b_block7`) · ngram | bf16 · uniform · `config` · turbo · `--paged-kv` | ~8.4 GB (2026-06-15). `largestRecommendedRepoId` pick for ≥ 24 GB RAM (src/fit.ts). Trains. |
28
39
  | `26B` | `mlx-community/gemma-4-26B-A4B-it-OptiQ-4bit` | Gemma 4 MoE · generated specialization · L1 | text · image **only if** the snapshot ships the SigLIP sidecar (registry `has_vision_sidecar`; the tower is `gemma4_vision`). No `audio_config` → no audio | two-model · `-assistant` · ngram | bf16 · uniform · `config` · turbo · `--paged-kv` | ~18 GB (2026-06-15). `largestRecommendedRepoId` pick for ≥ 48 GB RAM. |
29
- | any other `gemma4*` config (1B, e2b, 31B, …) | — | Gemma 4 · the `Gemma4Model` monolith (no generated specialization) · L1 by family | per the registry's vision/audio flags | same as the family | same as the family | Loads by `model_type`; **no artifact is pinned in tests/support/goldens for these**, so treat as family-supported, artifact-unvalidated. |
40
+ | any other `gemma4*` config (1B, e2b, 31B, …) | — | Gemma 4 · the `Gemma4Model` monolith (no generated specialization) · family support, artifact unvalidated | per the registry's vision/audio flags | same as the family | same as the family | Loads by `model_type`; **no artifact is pinned in tests/support/goldens for these**, so treat as family-supported, artifact-unvalidated. |
30
41
  | `MiniCPM5` | `mlx-community/MiniCPM5-1B-OptiQ-4bit` | MiniCPM5 · `model_type: llama` matched by exact shape (`isMiniCPM5Config`) → dedicated `minicpm5` graph · L1 / L2 | text | two-model · ngram | bf16 · uniform · `config` (batches — Phase 3.1) · turbo (quality curve measured 2026-07-06, benchmarks.md §3) | Sub-GB starter (benchmarks.md legend). Trains (segmented backward). Tool calling (XML). |
31
42
  | `Qwen3.5-4B` | `mlx-community/Qwen3.5-4B-OptiQ-4bit` | Qwen3.5 (`qwen3_5` / `qwen3_5_text`, dense hybrid gated-DeltaNet) · dedicated `qwen3.5` graph · L1 | text (image/video only when the artifact ships vision weights — see Qwen3.8) | two-model · ngram · `mtp` if a `qwen3_5_mtp` companion exists | bf16 (L1) · `config` (KV-ON parity passed 2026-06-15, PLAN 14e) | Thinking + tool calling. Batches on the SSM path (`MLX_BUN_BATCH_SSM=0` reverts). The MoE variant `qwen3_5_moe` is **not** supported. |
32
43
  | `Qwen3.8` | `mlx-community/Qwen3.8-27B-OptiQ-4bit` @ `b04599de95d7a9bfbd7f208d347c0f10d9432a42` | Qwen3.5 family · **exact artifact profile** `qwen3.8-27b-optiq-4bit` (safetensors + `qwen3.5` graph) · L1 | text · **image** · **video** (`image_url` / `video_url` parts; Qwen3-VL tower + mRoPE; video decodes via the AVFoundation sidecar `mlx-bun-frame-extract`, 2 fps, ≤ 768 frames). Video never combines with audio; no audio | **native MTP head** (`mlx-community/Qwen3.8-27B-MTP-bf16`, auto-detected `qwen3_5_mtp`; `--draft-kind mtp` with no `--draft-model` uses a bundled `mtp/` subfolder) · two-model · ngram | bf16 (L1) · `config` is **Lab** — policy copied from the same-topology Qwen3.6 artifact, no model-specific oracle (benchmarks.md, 2026-08-22) | `reasoning_effort` (xhigh/medium/low) + `preserve_thinking` + tool calling. `/v1/models` advertises vision. Shipped in v0.2.0. |
33
44
  | `Qwen3-Embedding` | `mlx-community/Qwen3-Embedding-4B-4bit-DWQ` | plain `qwen3` · dedicated `qwen3` graph · L1 | text | — | — | **Embeddings only**: `/v1/embeddings` + `mlx-bun embed`. Auto-picked by `embed` when no query is given; never the chat default. |
34
45
  | `Qwen3-30B` | `mlx-community/Qwen3-30B-A3B-Thinking-2507-4bit` | Qwen3-MoE (`qwen3_moe`) · dedicated `qwen3-moe` graph · L1 (opt-in slow parity test, `MLX_BUN_TEST_QWEN3_MOE=1`) | text | two-model · ngram | bf16 (the artifact ships no kv_config.json) | Sparse top-k SwitchGLU experts. |
35
46
  | `diffusiongemma` | `mlx-community/diffusiongemma-26B-A4B-it-OptiQ-4bit` | DiffusionGemma (`diffusion_gemma`) · dedicated graph + **denoising loop** (non-autoregressive) · L2 (optiq) | text · **image** (single image; its own inline-quantized SigLIP tower). No video / audio | — (no AR verify loop) | — (canvas model; the `--kv-quant` axis does not apply) | Serial lane always. Diffusion-native LoRA trains. |
47
+ | `whisper` | `mlx-community/whisper-large-v3-turbo` (fp16, 1.6 GB, `weights.safetensors`) | Whisper (`whisper`) · dedicated encoder-decoder graph + transcription loop · **L1 vs mlx-whisper 0.4.3** (mel, encoder output and per-step decoder logits bit-exact; `tests/parity/whisper.test.ts`) | **audio in → text out** only: `/v1/audio/transcriptions`, `/v1/audio/translations`, `mlx-bun transcribe`. Never the chat model. | — | — | Tokenizer files are not in the mlx-community repo; the loader resolves `openai/whisper-large-v3-turbo`'s `tokenizer.json` from the HF cache (2.4 MB, `mlx-bun get` it once). Beam search (`beam_size`) is ours — mlx-whisper has none; checked against whisper.cpp's beam-5 transcripts. Word timestamps not yet. Serves as a companion beside any chat model or alone (transcription-only server). Any `mlx-community/whisper-*` fp16 checkpoint loads (80-mel v1/v2 vocab resolves `openai/whisper-large-v2`). |
36
48
  | `GLM-5.2-colibri` | `mateogrgic/GLM-5.2-colibri-int4-with-int8-mtp` @ `3cc8db99b1b13fc79325d987ba3c1c430766b3b8` | GLM-5.2 (`glm_moe_dsa`) · **Colibri container loader** + dedicated `glm5.2` graph (streamed experts) · **exact artifact profile**, L3 | text | **checkpoint-native MTP** (`--mtp on`, the default; mutually exclusive with `--draft-model`) | checkpoint-native compressed **MLA/DSA** cache (its own scheme; SSD tier v3 persists it) | Fixed-context admission: `--context-length` (4096) and `--max-tokens` (128) are reserved by the header-only resource equation before weights open; `--memory-budget` defaults to min(25 GiB, RAM). `--mtp off` enables ordinary batching. Embeddings, vision/audio, adapters, and training are unsupported. See [below](#direct-container-artifacts-glm-5x). |
37
- | Tier-0 generic | validated manifest (`tests/support/universal-manifest.ts`): `Llama-3.2-1B-Instruct-4bit`, `Qwen2.5-0.5B-Instruct-4bit`, `Qwen3-0.6B-4bit`, `quantized-gemma-2b-it`, `gemma-2-2b-it-4bit`, `Phi-3.5-mini-instruct-4bit`, `GLM-4-9B-0414-4bit`, `granite-3.3-2b-instruct-4bit`, `starcoder2-3b-4bit`, `SmolLM3-3B-4bit` (all `mlx-community/`) and `ekryski/OLMo-2-0425-1B-Instruct-4bit` | 11 `model_type`s: `llama`, `smollm3`, `qwen2`, `qwen3`, `gemma`, `gemma2`, `phi3`, `olmo2`, `glm4`, `granite`, `starcoder2` (+ mlx-lm remaps `mistral`, `iquestcoder` → `llama`) · `universal-dense` graph · L1 | text | two-model · ngram | bf16 (the validated cell) | `ls` labels these `supported (generic)`. Batch only for plain full-attention archs (gated B=2 on Llama-3.2-3B); gemma2-family / sliding-window archs run serial. No compiled specialization. A `model_type` absent from the table refuses to load. |
49
+ | Tier-0 generic | validated manifest (`tests/support/universal-manifest.ts`): `Llama-3.2-1B-Instruct-4bit`, `Qwen2.5-0.5B-Instruct-4bit`, `Qwen3-0.6B-4bit`, `quantized-gemma-2b-it`, `gemma-2-2b-it-4bit`, `Phi-3.5-mini-instruct-4bit`, `GLM-4-9B-0414-4bit`, `granite-3.3-2b-instruct-4bit`, `starcoder2-3b-4bit`, `SmolLM3-3B-4bit` (all `mlx-community/`) and `ekryski/OLMo-2-0425-1B-Instruct-4bit` | 11 `model_type`s: `llama`, `smollm3`, `qwen2`, `qwen3`, `gemma`, `gemma2`, `phi3`, `olmo2`, `glm4`, `granite`, `starcoder2` (+ mlx-lm remaps `mistral`, `iquestcoder` → `llama`) · `universal-dense` graph · L1 | text | two-model · ngram | bf16; Llama full-attention uniform KV4/KV8 | `ls` labels these `supported (generic)`. Shared full-attention execution is qualified on Llama-3.2-3B at B1/B4, including speculative KV4/KV8 and generated RAM/SSD state; gemma2-family / sliding-window archs run serial. No compiled specialization. A `model_type` absent from the table refuses to load. |
38
50
 
39
51
  Rules that apply across rows (all enforced in `src/server.ts`):
40
52
 
@@ -45,8 +57,11 @@ Rules that apply across rows (all enforced in `src/server.ts`):
45
57
  MTP is its own row. Qwen MTP companions may store dense or affine-quantized
46
58
  projections; the loader uses each module's quantization metadata. Target
47
59
  verification is unchanged, while draft acceptance and speed depend on the
48
- companion. Any quantized KV scheme excludes the speculative lane
49
- (requests keep the scheme and decode serially without speculation).
60
+ companion. In the shared executor, qualified plain, uniform,
61
+ per-layer affine and TurboQuant layouts compose with drafting through the
62
+ target/provider interfaces. Positive affine conversion thresholds are also
63
+ supported by library requests. The loaded model and provider determine
64
+ which combinations can execute.
50
65
  - **Qwen3.5-family checkpoint generations.** Both ship the same graph and both
51
66
  load: the mlx-lm-converted naming (`language_model.model.*`,
52
67
  `language_model.lm_head`, `vision_tower.*`) and the transformers-5.8 export
@@ -60,7 +75,9 @@ Rules that apply across rows (all enforced in `src/server.ts`):
60
75
  - **KV schemes.** `--kv-quant 4|8` (uniform, L1) and `config` (per-layer
61
76
  `kv_config.json`, L2) apply to the autoregressive families that ship the
62
77
  file; `turbo` additionally requires a full-attention `head_dim` in
63
- {64, 128, 256, 512} (refused at startup otherwise) and is solo-only;
78
+ {64, 128, 256, 512} (refused at startup otherwise). Qualified TurboQuant
79
+ targets support shared ordinary and speculative execution; sliding layers
80
+ remain bf16;
64
81
  `--paged-kv` is Gemma 4 only, bf16, serial. Flag detail:
65
82
  [server-config.md](server-config.md).
66
83
  - **Modalities.** Image requests reach a Gemma 4 model only when
@@ -323,3 +340,7 @@ Safety rails:
323
340
 
324
341
  After a real deletion, gc re-scans so the registry drops the reaped
325
342
  rows immediately.
343
+
344
+
345
+ Non-Qwen grouped TurboQuant drafting, including assistant donor attention, uses
346
+ the shared cache interfaces. See [composition coverage](../design/batching.md#turboquant-donor-attention-and-grouped-draft-composition).