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
@@ -23,8 +23,12 @@ Two tiers, one contract:
23
23
 
24
24
  - **Tier-0 GENERIC** — one config-driven "universal" module per architecture
25
25
  *family*. Runs any covered checkpoint at **L1 parity** (bit-exact vs mlx-lm
26
- on this machine's GPU). Monolith path only: no compiled decode, no
27
- kv-quant. Slow, never broken. Built: `src/model/universal/` (dense
26
+ on this machine's GPU). Monolith path only, without compiled decode.
27
+ Standard attention now uses the shared plain/affine KV port; the softcap
28
+ branch retains plain KV. Qualified full-attention targets compose delayed
29
+ affine conversion with standalone and lookup drafting through the same
30
+ cache transaction interface; rotating layouts use that interface for
31
+ per-layer conversion and assistant donors. Built: `src/model/universal/` (dense
28
32
  llama-family). Design only: UniversalMoE.
29
33
  - **Tier-1 TARGETED** — a model *graduates*: dedicated or generated forward,
30
34
  L2 (mlx-optiq) composition where an optiq artifact exists, Lab experiments
@@ -200,7 +204,9 @@ Key choices:
200
204
  or missing tensors are a LOAD ERROR naming them, never a silently-wrong
201
205
  model.
202
206
  - **KV cache** — `KVCache`/`RotatingKVCache` unchanged; `layer_types` /
203
- `sliding_window` already normalized. No kv-quant at Tier-0.
207
+ `sliding_window` already normalized. Standard attention consumes the shared
208
+ cache-attention interface; qualified Llama uniform KV4/KV8 serving and
209
+ generated-cache coverage are described in batching.md.
204
210
  - **RuntimeModel surface** — the minimal contract `Qwen3Model` implements
205
211
  (config, weightsBytes, loraState, makeCache, forwardHidden,
206
212
  logitsFromHidden, forward, generate, loraTargets) so serving/eval/LoRA
@@ -336,6 +342,72 @@ design-relevant facts; phase logs live in PLAN.md at the named anchor.
336
342
  - Training reference model for segmented backward and ORPO
337
343
  (docs/design/orpo-training.md, orpo-training.md).
338
344
 
345
+ #### 6.2.1 MiniCPM5-2B investigation
346
+
347
+ Metadata and template inspection completed 2026-09-10. No weights were
348
+ downloaded and no GPU inference was run. This checkpoint is a candidate,
349
+ not an artifact with established parity or local performance results.
350
+
351
+ Start with the official [OpenBMB MLX artifact](https://huggingface.co/openbmb/MiniCPM5-2B-MLX),
352
+ revision `3d00c3da500debfe345e60e25a87ed2669f5b1ee`. Its config uses standard
353
+ Llama blocks, 42 layers, hidden size 2048, intermediate size 6144, 16 query
354
+ and 2 KV heads of dimension 128, untied embeddings, vocab 130560 and
355
+ RoPE theta 5000000. Quantization is affine 4-bit with group size 64.
356
+ Preserve both EOS IDs, `1` and `130073`, and the separate
357
+ `chat_template.jinja`. The release is text-only and Apache-2.0.
358
+
359
+ Current integration findings:
360
+
361
+ - `isMiniCPM5Config` in `src/model/support.ts` matches the older 24-layer,
362
+ hidden-1536 artifact. The 2B config fails that predicate and qualifies for
363
+ generic Llama through `genericArgsFor`; `familyProfile` therefore selects
364
+ the universal graph. Do not widen the predicate before auditing dedicated
365
+ execution, training and cache assumptions against this shape.
366
+ - A CPU-only smoke using the pinned config and template passed
367
+ `loadModelConfig`, `genericArgsFor`, `ChatTemplate.load` and rendering a
368
+ user → assistant tool call → tool result → user conversation. Explicit
369
+ thinking true/false both render correctly; the XML function call survives.
370
+ This checks rendering, not generated tool-call parsing or tokenizer parity.
371
+ - `resolveEnableThinking` applies the MiniCPM no-thinking default through
372
+ the same exact-shape predicate. It does not apply to 2B. With no explicit
373
+ option, this artifact's prompt ends at the assistant header; true appends
374
+ an open think tag, false appends a closed empty think block. Set this
375
+ explicitly in comparisons. Template support for disabling thinking does
376
+ not establish no-thinking quality.
377
+
378
+ Evaluation sequence:
379
+
380
+ 1. Pin an 8-bit or BF16 control alongside the official 4-bit candidate.
381
+ Establish tokenizer/prompt identity and same-machine logits against the
382
+ pinned mlx-lm oracle, then test multi-turn tool calls, reasoning splits,
383
+ EOS, cache reuse and batched continuation. Keep quantization quality
384
+ comparisons separate from same-artifact runtime parity.
385
+ 2. Compare with MiniCPM5-1B and Gemma e4b on held-out Dreaming segmentation,
386
+ extraction and source-grounded synthesis, plus bounded repository/tool
387
+ tasks. Score boundary/label accuracy and completed tasks, not just valid
388
+ JSON or XML. Use explicit thinking modes and equal token/time budgets.
389
+ 3. Record first-answer latency, complete task time, output/reasoning tokens,
390
+ repetition and truncation rates, memory and context scaling. Local
391
+ measurements belong only in `docs/reference/benchmarks.md`, with a named
392
+ quiet machine and the required serving benchmark procedure.
393
+ 4. Investigate the [released DSpark draft](https://huggingface.co/openbmb/MiniCPM5-2B-DSpark)
394
+ after ordinary inference passes. Its config declares `Qwen3DSparkModel`,
395
+ five draft layers, block size seven, and target taps `[1,10,20,30,39]`.
396
+ Existing Gemma DSpark support does not establish compatibility. Audit
397
+ the draft's architecture, target taps, weights and verifier before a
398
+ paired task-time comparison.
399
+
400
+ Evidence to use when designing the quality checks:
401
+ [OpenBMB's model card](https://huggingface.co/openbmb/MiniCPM5-2B) distinguishes
402
+ internally reproduced scores from Artificial Analysis results. The
403
+ [independent September 7 evaluation](https://artificialanalysis.ai/articles/openbmb-releases-minicpm5-2b)
404
+ supports strong capability for its size but reports substantial reasoning
405
+ token use and weak absolute terminal-task success. These evaluations do not
406
+ validate MLX 4-bit quality. [Upstream issue 374](https://github.com/OpenBMB/MiniCPM/issues/374)
407
+ reports repetition sensitivity in GGUF coding runs. That is an unreproduced
408
+ cross-runtime hypothesis for our sweep, not evidence of the same MLX failure
409
+ or a reason to change repetition defaults.
410
+
339
411
  ### 6.3 Qwen3 / Qwen3-MoE / Qwen3.5-family (3.6, 3.8) — `qwen3.ts`, `qwen3-moe.ts`, `qwen3_5.ts`
340
412
 
341
413
  - **Qwen3** (`model_type: qwen3`): dense GQA with per-head q/k RMSNorm
@@ -581,6 +653,122 @@ non-goals.
581
653
 
582
654
  ---
583
655
 
656
+ ### 6.7 Whisper (speech-to-text) — `src/model/whisper.ts`, `src/audio/whisper-*.ts`, `src/serve/transcription-*.ts`
657
+
658
+ Audio in → text out, the first encoder-decoder family. Target artifact:
659
+ `mlx-community/whisper-large-v3-turbo` (fp16, 32 encoder / 4 decoder layers,
660
+ 128 mel bins, 51 866 vocab). Plan/evidence anchor: PLAN.md "Phase: Whisper
661
+ speech-to-text". Served surface: [server-api.md](../reference/server-api.md#post-v1audiotranscriptions--post-v1audiotranslations-speech-to-text),
662
+ [cli.md `transcribe`](../reference/cli.md#transcribe--speech-to-text).
663
+
664
+ - **Oracle**: mlx-whisper 0.4.3 (`ml-explore/mlx-examples/whisper`) in its
665
+ own venv (`~/Code/mlx-whisper-oracle/.venv`, mlx pinned to
666
+ `MLX_CORE_VERSION`; it drags torch + numba, which must not enter the
667
+ pinned mlx-lm venv). The profile declares `{ l1, mlx-whisper, bit-exact }`
668
+ — no mlx-lm arm exists. Generator `scripts/oracle/gen-whisper-golden.py`
669
+ (`bun scripts/regen.ts whisper`) → `goldens/whisper.json` + untracked
670
+ blobs; gate `tests/parity/whisper.test.ts`: mel (f32), decode-window
671
+ encoder output (f16 raw) and every per-step PRE-filter logit vector (f32)
672
+ bit-exact; text / segment tokens / timestamps identical. Clips: the
673
+ tracked `speech-fox.wav`, whisper.cpp's public-domain `jfk.wav` (sotto's
674
+ vendored checkout, optional), and a synthesized 33 s concatenation that
675
+ crosses a 30 s window.
676
+ - **Front end** (`whisper-mel.ts`): op-for-op `log_mel_spectrogram` on the
677
+ GPU — host reflect pad, `as_strided` frames, periodic Hann, `rfft` 400,
678
+ `|·|²`, slaney filter bank (librosa's float64 → float32 rounding order
679
+ reproduced, bit-exact vs the shipped `mel_filters.npz`), `log10`, 8-decade
680
+ clamp, `(x+4)/4`. 1.5 ms warm for a 41 s padded clip (M1 Max).
681
+ - **Model** (`whisper.ts`): faithful path = the oracle's graph, including
682
+ its quirks that change bits: `nn.gelu` is `mx.compile`d in mlx, and the
683
+ FUSED kernel rounds differently from four separate ops (2 % of conv1
684
+ outputs differ by an ulp) — so the same graph is compiled here
685
+ (`CompiledFunction`, shapeless); attention scales q and k by
686
+ `(D/H)^-0.25` each with fp16 `q@kᵀ`, additive `-inf` fp16 causal mask,
687
+ precise softmax; self-attention K/V are cached un-split `[B, T, D]` and
688
+ concatenated per step; cross-attention K/V are projected once per window
689
+ and kept `[1, 1500, D]` — beam rows attend to them by broadcasting, never
690
+ by copying.
691
+ - **Decoding** (`whisper-decode.ts`): DecodingTask port — sot sequence,
692
+ prompt/prefix budgets (`n_ctx/2 − 1 = 223` prompt tokens), suppress-blank,
693
+ non-speech suppression, timestamp rules, language detection, greedy /
694
+ sampled, MaximumLikelihoodRanker. **Two deliberate divergences from
695
+ mlx-whisper**, both documented in code: (1) the monotonic-timestamp rule
696
+ follows openai-whisper (mlx-whisper's port collects sequence indices and
697
+ slices an empty range, so its rule never fires) — pre-filter logits stay
698
+ bit-exact, `avg_logprob` moves ~1e-4; (2) `compression_ratio` uses Bun's
699
+ zlib, which emits one byte more than CPython's 1.2.12 for the same
700
+ level-6 stream — the 2.4 fallback threshold decision is asserted equal,
701
+ the value within 5 %. Beam search is ours (mlx-whisper raises
702
+ NotImplemented): openai-whisper's BeamSearchDecoder over cache rows, top-k
703
+ on device (`argpartition` on negated log-probs; the `[B, k]` slice is
704
+ materialized before host readback — reading a strided view linearly was a
705
+ real bug), finished sequences collected per audio, patience honoured.
706
+ Checked against whisper.cpp v1.9.3 beam-5 transcripts (identical on the
707
+ JFK clip with and without vocabulary hints).
708
+ - **Transcribe loop** (`whisper-transcribe.ts`): transcribe.py's seek loop —
709
+ 30 s windows over the zero-padded mel, temperature fallback ladder gated by
710
+ compression ratio / avg log-prob / no-speech probability, timestamp-driven
711
+ seeking, previous-text conditioning, `clip_timestamps`. Async only to
712
+ yield between windows (streaming, cancellation). Word timestamps (DTW on
713
+ the checkpoint's `alignment_heads`) are not ported yet.
714
+ - **Serving** (`transcription-service.ts`, `audio-routes.ts`,
715
+ `transcription-server.ts`): residency-managed service (load on first
716
+ request, dispose after `--whisper-idle-unload` seconds, FIFO queue, runs
717
+ under the gateway's exclusive lock beside chat), OpenAI multipart/JSON
718
+ routes with `json/verbose_json/text/srt/vtt` and SSE streaming, sotto's
719
+ vocabulary-hint fitting with included/omitted reporting, and a
720
+ transcription-only server for `serve <whisper checkpoint>`. Page-in after
721
+ an explicit unload: 37–45 ms on the M1 Max (weights are mmap'd; the OS
722
+ file cache still holds them).
723
+ - **Tokenizer**: the mlx-community repos ship no tokenizer; ids are identical
724
+ to HF's `openai/whisper-large-v3-turbo` tokenizer.json (checked against
725
+ tiktoken), which the loader resolves from the HF cache by `n_vocab`.
726
+ - **Fast path** (`whisper-fast.ts`, default; `faithful`/`--faithful` selects
727
+ the oracle graph): fused SDPA everywhere (whisper.cpp flash-attn); one
728
+ fused q/k/v gemm per encoder layer; cross-K/V for all four decoder layers
729
+ from ONE matmul pair per window, head-split once (whisper.cpp `kv_cross`,
730
+ vLLM encoder-prefill cross KV); the single-token decoder step — embed, 4
731
+ layers, ln, logits, every logit filter, argmax (greedy) or top-k (beam) —
732
+ is one shapeless `mx.compile`d closure with per-step state as int32 arrays
733
+ (the LLM compiled-decode pattern); greedy runs device-resident with the
734
+ token array flowing into the next step and a one-step-lag host read
735
+ (mlx-whisper's async_eval); beam reorders caches with `take`. Gate:
736
+ `tests/parity/whisper-fast.test.ts` — at most two differing tokens per
737
+ clip vs the oracle/faithful path (one near-tie flips "dog"→"dog." plus
738
+ its timestamp on the 33 s no-conditioning clip). Word timestamps
739
+ (`whisper-timing.ts`, timing.py port: alignment-head cross-attention →
740
+ median filter → DTW in JS → punctuation merge and duration heuristics)
741
+ ride the faithful decoder once per window.
742
+ - **Silero VAD** (`silero-vad.ts`): the v6.2 TorchScript graph ported
743
+ exactly (64-sample inter-chunk context, right reflect pad, STFT-as-conv,
744
+ 4 conv blocks, LSTMCell, sigmoid) — whisper.cpp's port drops the context
745
+ and pads both sides — plus `get_speech_timestamps`; STFT/convs/input
746
+ gates batched over all chunks in one MLX graph, the recurrence on the
747
+ host; streaming state carried across chunks (streaming == batch within
748
+ 1e-6). Gate: `tests/parity/silero-vad.test.ts` vs the pip reference
749
+ (probabilities within 5e-3 because the ggml weights are fp16; segments
750
+ identical). Used as the service's gate (`vad`) and inside sessions.
751
+ - **Streaming run** (`WhisperStreamingRun`): the seek loop as a resumable
752
+ object; `feed()` transcribes each window with 30 s of audio past the seek
753
+ point, `finish()` drains. Batch = one feed + finish, so parity covers it.
754
+ Sessions (`/v1/audio/sessions`) wrap it with the VAD stream and the
755
+ exclusive lock.
756
+ - **Voice input**: `mlx-bun dictate` (AVAudioEngine sidecar
757
+ `src/native/mic_capture.swift`, hold a keycode or Enter-toggle, print /
758
+ clipboard / System Events keystrokes) and the web composer's hold-to-talk
759
+ mic (`src/web/src/voice.ts`: 16 kHz AudioContext → session chunks → caret
760
+ insert; shown when the `ready` frame carries `transcription`). Both are
761
+ the streaming session over the same server code.
762
+ - **Decode input**: AudioToolbox `ExtAudioFile` over `bun:ffi`
763
+ (`audiotoolbox.ts`) for every non-WAV container — CoreAudio's decoder and
764
+ resampler in-process; WAV keeps the exact PCM parser.
765
+ - **What the measurements say (M1 Max 32 GB, 2026-09-15, benchmarks.md):**
766
+ the encoder is ~85 % of a short clip's time and is bound by its fp16
767
+ gemms at ~7–8 TFLOPS (padding rows to 1536 for tile alignment, fused
768
+ vs unfused GELU, batching two windows: all 0–5 %); decode is ~1.5 ms per
769
+ token. Whisper.cpp's `audio_ctx` truncation is the only lever left on
770
+ the encoder and it is a quality trade (`audio_ctx`, Lab, off).
771
+
584
772
  ## 7. Top design decisions
585
773
 
586
774
  1. **Explicit descriptor table keyed by model_type (+ vendored
@@ -627,3 +815,7 @@ non-goals.
627
815
  - 2026-08-23 — This doc consolidated: audio-input-plan,
628
816
  diffusion-gemma-port, minicpm5-decode-megakernel, and the Colibri archive
629
817
  summarized here; ports become sections.
818
+
819
+
820
+ Non-Qwen grouped TurboQuant drafting, including assistant donor attention, uses
821
+ the shared cache interfaces. See [composition coverage](batching.md#turboquant-donor-attention-and-grouped-draft-composition).