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.
- package/README.md +57 -32
- package/docs/README.md +55 -42
- package/docs/archive/investigations/benchmark-ledger-through-v0.4.0.md +3617 -0
- package/docs/archive/planning/release-notes-v0.5.0.md +37 -0
- package/docs/design/batching.md +220 -7
- package/docs/design/decode-speed-program.md +121 -66
- package/docs/design/generic-model-support.md +116 -0
- package/docs/design/kv-cache.md +380 -211
- package/docs/design/speculative-decoding.md +165 -26
- package/docs/design/turboquant.md +20 -7
- package/docs/design/unified-engine-frontier-plan.md +120 -13
- package/docs/planning/PRODUCT_ROADMAP.md +5 -6
- package/docs/planning/public-readiness-handoff.md +244 -0
- package/docs/planning/public-readiness.md +149 -0
- package/docs/reference/benchmarks.md +3463 -3489
- package/docs/reference/cli.md +62 -1
- package/docs/reference/distribution.md +29 -4
- package/docs/reference/environment.md +104 -8
- package/docs/reference/library-api.md +18 -4
- package/docs/reference/memory.md +1 -1
- package/docs/reference/models.md +17 -5
- package/docs/reference/server-api.md +181 -11
- package/docs/reference/server-config.md +232 -105
- package/package.json +2 -2
- package/src/audio/audiotoolbox.ts +112 -0
- package/src/audio/conformer.ts +8 -0
- package/src/audio/mic-capture.ts +98 -0
- package/src/audio/silero-vad.ts +379 -0
- package/src/audio/whisper-decode.ts +774 -0
- package/src/audio/whisper-fast.ts +732 -0
- package/src/audio/whisper-format.ts +73 -0
- package/src/audio/whisper-mel.ts +185 -0
- package/src/audio/whisper-timing.ts +274 -0
- package/src/audio/whisper-tokenizer.ts +239 -0
- package/src/audio/whisper-transcribe.ts +410 -0
- package/src/backends/mlx/autoregressive.ts +22 -1
- package/src/backends/mlx/batch-group.ts +111 -27
- package/src/backends/mlx/cache-layout.ts +3 -3
- package/src/backends/mlx/continuation-persistence.ts +5 -4
- package/src/backends/mlx/encoder-cache.ts +25 -0
- package/src/backends/mlx/fill-append.ts +21 -8
- package/src/backends/mlx/fill-group.ts +497 -0
- package/src/backends/mlx/gateway-binding.ts +56 -12
- package/src/backends/mlx/kv-maintenance.ts +30 -2
- package/src/backends/mlx/mixed-iteration.ts +62 -0
- package/src/backends/mlx/model-prompt.ts +46 -32
- package/src/backends/mlx/model-serving.ts +4 -3
- package/src/backends/mlx/paged-cache-codec.ts +27 -0
- package/src/backends/mlx/prefill-cohort.ts +15 -4
- package/src/backends/mlx/prefill-policy.ts +26 -0
- package/src/backends/mlx/prefill-rows.ts +57 -13
- package/src/backends/mlx/prompt-input.ts +29 -0
- package/src/backends/mlx/qwen-prompt-input.ts +40 -0
- package/src/backends/mlx/request-state-policy.ts +18 -5
- package/src/backends/mlx/rollback.ts +6 -3
- package/src/backends/mlx/serial-executor.ts +16 -7
- package/src/backends/mlx/speculative-group.ts +105 -26
- package/src/backends/mlx/speculative.ts +24 -7
- package/src/backends/mlx/target-layout-capability.ts +1 -1
- package/src/backends/mlx/transitioning-kv-rows.ts +8 -0
- package/src/cli.ts +322 -0
- package/src/contracts/execution.ts +10 -0
- package/src/contracts/object-cache.ts +8 -0
- package/src/contracts/pi-provider.ts +1 -1
- package/src/contracts/pi.ts +3 -1
- package/src/contracts/prefix-cache.ts +13 -3
- package/src/contracts/scheduling.ts +9 -0
- package/src/engine/execution-plan.ts +23 -12
- package/src/engine/execution-tasks.ts +44 -0
- package/src/engine/namespaced-cache.ts +13 -0
- package/src/engine/scheduler.ts +22 -2
- package/src/fill/fill-session.ts +14 -3
- package/src/generate.ts +28 -22
- package/src/grammar.ts +13 -0
- package/src/inference/prefill.ts +6 -0
- package/src/kv-scheme.ts +22 -0
- package/src/kv-store.ts +115 -79
- package/src/lab/README.md +5 -1
- package/src/lab/paged-kv/paged-attention.ts +117 -0
- package/src/lab/paged-kv/paged-kv-rows.ts +33 -5
- package/src/lab/paged-kv/paged-kv.ts +108 -42
- package/src/md-modules.d.ts +5 -0
- package/src/mlx/array.ts +37 -0
- package/src/mlx/ffi.ts +19 -0
- package/src/mlx/normalized-argmax.ts +116 -0
- package/src/mlx/ops.ts +97 -10
- package/src/model/batched-turboquant-kv.ts +6 -5
- package/src/model/delayed-quantized-kv.ts +3 -3
- package/src/model/delayed-rotating-quantized-kv.ts +1 -1
- package/src/model/delayed-turboquant-kv.ts +6 -4
- package/src/model/factory.ts +26 -0
- package/src/model/fingerprint.ts +12 -0
- package/src/model/full-prefill-row.ts +1 -1
- package/src/model/gemma4-base.ts +25 -6
- package/src/model/gemma4.ts +82 -2
- package/src/model/kv-attention-view.ts +4 -1
- package/src/model/profile.ts +29 -4
- package/src/model/quantized-append-attention.ts +31 -0
- package/src/model/qwen3-mrope.ts +7 -9
- package/src/model/qwen3_5.ts +99 -37
- package/src/model/support.ts +8 -0
- package/src/model/token-groups.ts +66 -0
- package/src/model/trellis-linear.ts +2 -2
- package/src/model/turboquant-codec.ts +6 -0
- package/src/model/whisper.ts +435 -0
- package/src/native/mic_capture.swift +92 -0
- package/src/native-pack.ts +6 -5
- package/src/pi-web.ts +9 -1
- package/src/prompt-cache.ts +265 -43
- package/src/runtime-config.ts +1 -5
- package/src/safetensors.ts +9 -2
- package/src/sampler.ts +5 -4
- package/src/serve/audio-routes.ts +323 -0
- package/src/serve/chat-request.ts +2 -0
- package/src/serve/chat-stage.ts +17 -12
- package/src/serve/completion-executor.ts +9 -1
- package/src/serve/discovery-routes.ts +17 -1
- package/src/serve/generated-token-history.ts +57 -0
- package/src/serve/generation-gateway.ts +52 -11
- package/src/serve/model-binding.ts +7 -1
- package/src/serve/preparation.ts +3 -3
- package/src/serve/prompt-response-trace.ts +14 -0
- package/src/serve/request-plan.ts +2 -0
- package/src/serve/request-prep.ts +38 -21
- package/src/serve/transcription-server.ts +64 -0
- package/src/serve/transcription-service.ts +502 -0
- package/src/server.ts +132 -164
- package/src/spec/assistant-rows.ts +1 -0
- package/src/spec/deepspec-rows.ts +1 -1
- package/src/spec/dflash-source.ts +1 -1
- package/src/spec/glm52-mtp-source.ts +1 -0
- package/src/spec/ngram-source.ts +27 -5
- package/src/spec/projected-draft-rows.ts +2 -2
- package/src/spec/qwen-mtp-rows.ts +11 -0
- package/src/spec/qwen-mtp-source.ts +3 -0
- package/src/spec/serve-loop.ts +5 -3
- package/src/spec/source.ts +14 -1
- package/src/spec/two-model-rows.ts +1 -0
- package/src/spec/two-model.ts +2 -2
- package/src/ssd-cache.ts +76 -17
- package/src/ssd-durability.ts +20 -22
- package/src/storage/host-buffer.ts +24 -0
- package/src/storage/kv-writer.ts +83 -0
- package/src/storage/kv-writer.worker.js +264 -0
- package/src/storage/retention-policy.ts +37 -0
- package/src/tiered-prompt-cache.ts +45 -0
- package/src/vision/embedder.ts +8 -0
- package/src/vision/prompt.ts +101 -65
- package/src/vision/qwen3vl-prompt.ts +101 -64
- package/src/vision/qwen3vl-tower.ts +11 -0
- package/src/vision/siglip.ts +8 -0
- package/src/web/app.html +12 -0
- package/src/web/app.js +160 -0
- package/src/web/src/chat.ts +4 -1
- package/src/web/src/composer.ts +3 -0
- package/src/web/src/voice.ts +141 -0
- package/src/web/sw.js +1 -1
- /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
|