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
@@ -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` |
@@ -68,7 +70,7 @@ Library requests support delayed uniform or per-layer affine conversion with
68
70
  qualified shared drafting providers, including rotating caches. CLI affine
69
71
  quantization starts at zero.
70
72
 
71
- The unreleased shared executor supports qualified Qwen MTP and start-zero
73
+ The shared executor supports qualified Qwen MTP and start-zero
72
74
  TurboQuant at one or several active rows; `--batch 1` still selects the
73
75
  legacy serial control. No new CLI flag is required for the shared interfaces.
74
76
  Full-attention and rotating targets, including Llama, MiniCPM and Gemma, use shared bf16, uniform KV4/KV8, per-layer affine KV or TurboQuant
@@ -213,6 +215,65 @@ mlx-bun embed --instruct "Retrieve passages that answer the question" --text "wh
213
215
  | `[query]` / `--query <q>` | embedding model; omitted = the first downloaded embedding model (errors with a `get` suggestion when none) |
214
216
  | `--text "…"` | text to embed (omit to read one text per stdin line) |
215
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`) |
216
277
  | `--json` | one OpenAI-style `{object:"list",data:[…]}` object instead of bare arrays |
217
278
 
218
279
  Server equivalent: `mlx-bun serve <embedding-model>` then
@@ -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.
@@ -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.
@@ -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; unreleased 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. |
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,7 +57,7 @@ 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. In the unreleased shared executor, qualified plain, uniform,
60
+ companion. In the shared executor, qualified plain, uniform,
49
61
  per-layer affine and TurboQuant layouts compose with drafting through the
50
62
  target/provider interfaces. Positive affine conversion thresholds are also
51
63
  supported by library requests. The loaded model and provider determine