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
@@ -653,6 +653,122 @@ non-goals.
653
653
 
654
654
  ---
655
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
+
656
772
  ## 7. Top design decisions
657
773
 
658
774
  1. **Explicit descriptor table keyed by model_type (+ vendored