@mlx-bun/inference 0.0.0-stage → 1.0.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/LICENSE +21 -0
- package/README.md +1475 -2
- package/THIRD_PARTY_NOTICES.md +3404 -0
- package/dist/native/libmlx_bun_ane.dylib +0 -0
- package/dist/native/libmlx_bun_expert_io.dylib +0 -0
- package/dist/native/mlx-bun-frame-extract +0 -0
- package/examples/fit-model.ts +25 -0
- package/examples/qwen3-forward.ts +42 -0
- package/examples/qwen3-generate.ts +28 -0
- package/examples/trellis-expand.ts +23 -0
- package/package.json +129 -4
- package/src/adapters/available.ts +110 -0
- package/src/adapters/index.ts +3 -0
- package/src/adapters/manager.ts +316 -0
- package/src/artifacts/auxiliary-files.ts +45 -0
- package/src/artifacts/config-dialects.ts +94 -0
- package/src/artifacts/config.ts +341 -0
- package/src/artifacts/draft-vocabulary.ts +30 -0
- package/src/artifacts/expert-offload-build.ts +75 -0
- package/src/artifacts/expert-offload.ts +89 -0
- package/src/artifacts/experts/index.ts +3 -0
- package/src/artifacts/experts/io.ts +386 -0
- package/src/artifacts/experts/residency.ts +1134 -0
- package/src/artifacts/experts/usage.ts +415 -0
- package/src/artifacts/fingerprint.ts +44 -0
- package/src/artifacts/glm52/container.ts +305 -0
- package/src/artifacts/glm52/expert-layout.ts +249 -0
- package/src/artifacts/glm52/index.ts +7 -0
- package/src/artifacts/glm52/kernel-layout.ts +124 -0
- package/src/artifacts/glm52/layout.ts +152 -0
- package/src/artifacts/glm52/memory.ts +500 -0
- package/src/artifacts/glm52/resident-weights.ts +135 -0
- package/src/artifacts/glm52/weights.ts +503 -0
- package/src/artifacts/glm52-config.ts +262 -0
- package/src/artifacts/identity.ts +94 -0
- package/src/artifacts/index.ts +15 -0
- package/src/artifacts/mmap.ts +90 -0
- package/src/artifacts/safetensors-writer.ts +249 -0
- package/src/artifacts/safetensors.ts +164 -0
- package/src/artifacts/turboquant-fold.ts +42 -0
- package/src/artifacts/weight-audit.ts +43 -0
- package/src/artifacts/weights.ts +255 -0
- package/src/contracts/index.ts +2 -0
- package/src/contracts/mlx/attention.ts +8 -0
- package/src/contracts/mlx/cache.ts +510 -0
- package/src/contracts/mlx/checkpoint.ts +10 -0
- package/src/contracts/mlx/draft-checkpoint.ts +14 -0
- package/src/contracts/mlx/draft-projection.ts +27 -0
- package/src/contracts/mlx/draft-target.ts +84 -0
- package/src/contracts/mlx/drafter.ts +176 -0
- package/src/contracts/mlx/forward-work.ts +23 -0
- package/src/contracts/mlx/glm52-weights.ts +25 -0
- package/src/contracts/mlx/graph.ts +309 -0
- package/src/contracts/mlx/head.ts +6 -0
- package/src/contracts/mlx/index.ts +15 -0
- package/src/contracts/mlx/kv-codec.ts +9 -0
- package/src/contracts/mlx/media.ts +113 -0
- package/src/contracts/mlx/positions.ts +16 -0
- package/src/contracts/mlx/row-table.ts +2 -0
- package/src/contracts/mlx/trainable.ts +103 -0
- package/src/contracts/mlx/whisper.ts +96 -0
- package/src/contracts/portable/composition.ts +106 -0
- package/src/contracts/portable/denoising.ts +17 -0
- package/src/contracts/portable/execution.ts +79 -0
- package/src/contracts/portable/generation.ts +103 -0
- package/src/contracts/portable/graph.ts +80 -0
- package/src/contracts/portable/index.ts +15 -0
- package/src/contracts/portable/memory-plan.ts +26 -0
- package/src/contracts/portable/object-cache.ts +8 -0
- package/src/contracts/portable/prefill.ts +63 -0
- package/src/contracts/portable/prefix-cache.ts +33 -0
- package/src/contracts/portable/prompt.ts +2 -0
- package/src/contracts/portable/resources.ts +25 -0
- package/src/contracts/portable/rollback.ts +7 -0
- package/src/contracts/portable/sampling.ts +44 -0
- package/src/contracts/portable/scheduling.ts +54 -0
- package/src/contracts/portable/token-delivery.ts +4 -0
- package/src/contracts/portable/tsconfig.json +14 -0
- package/src/embeddings/index.ts +1 -0
- package/src/embeddings/text.ts +85 -0
- package/src/execution/admission.ts +59 -0
- package/src/execution/batch-group.ts +1240 -0
- package/src/execution/batch-types.ts +225 -0
- package/src/execution/cancellation.ts +38 -0
- package/src/execution/continuation-request.ts +45 -0
- package/src/execution/continuation-types.ts +10 -0
- package/src/execution/coordinator.ts +70 -0
- package/src/execution/denoising-group.ts +180 -0
- package/src/execution/engine.ts +56 -0
- package/src/execution/fill-group.ts +505 -0
- package/src/execution/fit.ts +184 -0
- package/src/execution/gateway-binding.ts +178 -0
- package/src/execution/grammar-group.ts +224 -0
- package/src/execution/index.ts +18 -0
- package/src/execution/methods.ts +75 -0
- package/src/execution/mixed-iteration.ts +96 -0
- package/src/execution/plan.ts +65 -0
- package/src/execution/prefill-cohort.ts +100 -0
- package/src/execution/prefill-rows.ts +263 -0
- package/src/execution/request-policy.ts +19 -0
- package/src/execution/row-sampling.ts +49 -0
- package/src/execution/scheduler.ts +92 -0
- package/src/execution/session.ts +247 -0
- package/src/execution/speculative-group.ts +797 -0
- package/src/execution/tasks.ts +44 -0
- package/src/generation/autoregressive.ts +840 -0
- package/src/generation/bindings/autoregressive.ts +104 -0
- package/src/generation/bindings/denoising.ts +60 -0
- package/src/generation/bindings/prefill-policy.ts +36 -0
- package/src/generation/checkpoint-identity.ts +60 -0
- package/src/generation/denoising.ts +85 -0
- package/src/generation/diffusion.ts +583 -0
- package/src/generation/fill/echo-index.ts +188 -0
- package/src/generation/fill/index.ts +3 -0
- package/src/generation/fill/proposal.ts +69 -0
- package/src/generation/fill/schema-rows.ts +506 -0
- package/src/generation/fill/session.ts +464 -0
- package/src/generation/fill/tool-boundary.ts +103 -0
- package/src/generation/fill-append.ts +58 -0
- package/src/generation/generate.ts +44 -0
- package/src/generation/grammar-step.ts +39 -0
- package/src/generation/index.ts +14 -0
- package/src/generation/phase-dispatch.ts +115 -0
- package/src/generation/prefill.ts +33 -0
- package/src/generation/result.ts +49 -0
- package/src/generation/scopes.ts +157 -0
- package/src/generation/speculative/acceptance.ts +41 -0
- package/src/generation/speculative/adaptive-draft-gate.ts +166 -0
- package/src/generation/speculative/bindings/assistant-rows.ts +102 -0
- package/src/generation/speculative/bindings/autoregressive-rows.ts +124 -0
- package/src/generation/speculative/bindings/binding.ts +106 -0
- package/src/generation/speculative/bindings/deepspec-rows.ts +33 -0
- package/src/generation/speculative/bindings/draft-target.ts +10 -0
- package/src/generation/speculative/bindings/glm52-mtp-rows.ts +128 -0
- package/src/generation/speculative/bindings/projected-context-rows.ts +78 -0
- package/src/generation/speculative/bindings/projected-draft-rows.ts +95 -0
- package/src/generation/speculative/bindings/qwen-mtp-rows.ts +336 -0
- package/src/generation/speculative/bindings/two-model-rows.ts +63 -0
- package/src/generation/speculative/cache-identity.ts +16 -0
- package/src/generation/speculative/draft-kind.ts +149 -0
- package/src/generation/speculative/draft-policy.ts +41 -0
- package/src/generation/speculative/draft-registry.ts +118 -0
- package/src/generation/speculative/draft-vocab.ts +110 -0
- package/src/generation/speculative/index.ts +17 -0
- package/src/generation/speculative/output.ts +49 -0
- package/src/generation/speculative/round.ts +307 -0
- package/src/generation/speculative/source.ts +144 -0
- package/src/generation/speculative/sources/assistant-source.ts +64 -0
- package/src/generation/speculative/sources/deepspec-source.ts +70 -0
- package/src/generation/speculative/sources/dflash-source.ts +84 -0
- package/src/generation/speculative/sources/dflash2-source.ts +86 -0
- package/src/generation/speculative/sources/glm52-mtp-source.ts +66 -0
- package/src/generation/speculative/sources/ngram-source.ts +165 -0
- package/src/generation/speculative/sources/qwen-mtp-source.ts +154 -0
- package/src/generation/speculative/sources/two-model.ts +75 -0
- package/src/generation/types.ts +194 -0
- package/src/index.ts +20 -0
- package/src/input/audio/audiotoolbox.ts +112 -0
- package/src/input/audio/decode.ts +133 -0
- package/src/input/audio/features.ts +202 -0
- package/src/input/audio/index.ts +6 -0
- package/src/input/audio/transcode.ts +67 -0
- package/src/input/audio/whisper-mel.ts +185 -0
- package/src/input/audio/whisper-tokenizer.ts +238 -0
- package/src/input/chat-template.ts +389 -0
- package/src/input/index.ts +6 -0
- package/src/input/media-fetch.ts +439 -0
- package/src/input/packed-mixed.ts +43 -0
- package/src/input/special-tokens.ts +75 -0
- package/src/input/token-groups.ts +49 -0
- package/src/input/tokenizer.ts +336 -0
- package/src/input/tool-call.ts +689 -0
- package/src/input/vision/index.ts +3 -0
- package/src/input/vision/preprocess.ts +247 -0
- package/src/input/vision/prompt.ts +398 -0
- package/src/input/vision/qwen3vl-preprocess.ts +409 -0
- package/src/input/vision/qwen3vl-prompt.ts +237 -0
- package/src/input/vision/video-frames.ts +100 -0
- package/src/kernels/ane/fill.ts +101 -0
- package/src/kernels/ane/linear.ts +416 -0
- package/src/kernels/attention/flash.ts +694 -0
- package/src/kernels/attention/kvq4-decode.ts +155 -0
- package/src/kernels/attention/masks.ts +40 -0
- package/src/kernels/attention/multi-query.ts +193 -0
- package/src/kernels/attention/paged.ts +130 -0
- package/src/kernels/delta/gated.ts +306 -0
- package/src/kernels/delta/index.ts +3 -0
- package/src/kernels/delta/silu-rms-heads.ts +69 -0
- package/src/kernels/delta/state.ts +74 -0
- package/src/kernels/gated-rms-norm.ts +64 -0
- package/src/kernels/glm52/layout.ts +37 -0
- package/src/kernels/glm52/streamed-experts.ts +360 -0
- package/src/kernels/logits.ts +38 -0
- package/src/kernels/quantization/affine-row.ts +154 -0
- package/src/kernels/quantization/affine-rows.ts +36 -0
- package/src/kernels/quantization/affine-split-mma.ts +144 -0
- package/src/kernels/quantization/affine3-mma.ts +117 -0
- package/src/kernels/quantization/affine3-rows.ts +103 -0
- package/src/kernels/quantization/affine4-mma.ts +113 -0
- package/src/kernels/quantization/affine4-rows.ts +102 -0
- package/src/kernels/residual-rms-norm.ts +65 -0
- package/src/kernels/sampling/index.ts +2 -0
- package/src/kernels/sampling/normalized-argmax.ts +116 -0
- package/src/kernels/sampling/token-bitmask.ts +52 -0
- package/src/kernels/softcap.ts +35 -0
- package/src/kernels/trellis/codebook.ts +123 -0
- package/src/kernels/trellis/down-codebook.ts +184 -0
- package/src/kernels/trellis/down-factored.ts +180 -0
- package/src/kernels/trellis/down-k3-interleaved-factored.ts +146 -0
- package/src/kernels/trellis/down-mma-wide.ts +195 -0
- package/src/kernels/trellis/down-mma.ts +145 -0
- package/src/kernels/trellis/expand.ts +42 -0
- package/src/kernels/trellis/gate-up-codebook-mma.ts +141 -0
- package/src/kernels/trellis/gate-up-codebook.ts +137 -0
- package/src/kernels/trellis/gate-up-factored.ts +197 -0
- package/src/kernels/trellis/gate-up-mma-wide.ts +147 -0
- package/src/kernels/trellis/gate-up-mma.ts +148 -0
- package/src/kernels/trellis/gate-up.ts +248 -0
- package/src/kernels/trellis/geometry.ts +22 -0
- package/src/kernels/trellis/index.ts +103 -0
- package/src/kernels/trellis/launch.ts +12 -0
- package/src/kernels/trellis/mixed-gate-up-factored.ts +119 -0
- package/src/kernels/trellis/mixed-gate-up.ts +146 -0
- package/src/kernels/trellis/qualified.ts +34 -0
- package/src/kernels/trellis/reduce.ts +150 -0
- package/src/kernels/trellis/scatter.ts +407 -0
- package/src/kernels/trellis/splitk-prefill.ts +152 -0
- package/src/kernels/trellis/tiled-prefill.ts +131 -0
- package/src/kernels/trellis/vector-expand.ts +58 -0
- package/src/kernels/trellis/wide-prefill.ts +89 -0
- package/src/kernels/turboquant/decode.ts +82 -0
- package/src/kernels/turboquant/index.ts +5 -0
- package/src/kernels/turboquant/inverse.ts +122 -0
- package/src/kernels/turboquant/ops.ts +860 -0
- package/src/kernels/turboquant/signs.ts +130 -0
- package/src/kernels/turboquant/tables.ts +124 -0
- package/src/layers/affine-verify-linear.ts +75 -0
- package/src/layers/ane-prefill-split.ts +283 -0
- package/src/layers/compiled-normalization.ts +41 -0
- package/src/layers/delta-heads.ts +23 -0
- package/src/layers/dense-embedding.ts +29 -0
- package/src/layers/dense-linear.ts +32 -0
- package/src/layers/dequantized-linear.ts +72 -0
- package/src/layers/folded-attention.ts +42 -0
- package/src/layers/geglu.ts +130 -0
- package/src/layers/helpers.ts +7 -0
- package/src/layers/index.ts +36 -0
- package/src/layers/kv4-decode-attention.ts +15 -0
- package/src/layers/layer-norm.ts +24 -0
- package/src/layers/loading.ts +92 -0
- package/src/layers/lora.ts +150 -0
- package/src/layers/normalization.ts +9 -0
- package/src/layers/prefix-layout.ts +33 -0
- package/src/layers/quantized-append-attention.ts +32 -0
- package/src/layers/quantized-attention.ts +353 -0
- package/src/layers/quantized-embedding.ts +39 -0
- package/src/layers/quantized-linear.ts +137 -0
- package/src/layers/quantized-switch-linear.ts +37 -0
- package/src/layers/qwen-append.ts +12 -0
- package/src/layers/qwen-mrope.ts +238 -0
- package/src/layers/rope-2d.ts +110 -0
- package/src/layers/rope.ts +389 -0
- package/src/layers/swiglu.ts +23 -0
- package/src/layers/trellis-codebook-down.ts +52 -0
- package/src/layers/trellis-codebook-gate-up.ts +42 -0
- package/src/layers/trellis-down.ts +117 -0
- package/src/layers/trellis-gate-up.ts +71 -0
- package/src/layers/trellis-linear.ts +211 -0
- package/src/layers/trellis-prompt.ts +102 -0
- package/src/layers/trellis-reduce-gate-up.ts +73 -0
- package/src/layers/trellis-scatter-down.ts +98 -0
- package/src/models/ane-install.ts +74 -0
- package/src/models/audio/conformer.ts +656 -0
- package/src/models/cache-composition.ts +44 -0
- package/src/models/capabilities.ts +89 -0
- package/src/models/chat-template.ts +10 -0
- package/src/models/composition.ts +107 -0
- package/src/models/diffusion-gemma/experts.ts +59 -0
- package/src/models/diffusion-gemma/family.ts +21 -0
- package/src/models/diffusion-gemma/model.ts +900 -0
- package/src/models/diffusion-gemma/vision.ts +549 -0
- package/src/models/drafter-loaders.ts +49 -0
- package/src/models/drafters.ts +39 -0
- package/src/models/factory.ts +351 -0
- package/src/models/families.ts +70 -0
- package/src/models/family.ts +125 -0
- package/src/models/gemma4/assistant-target.ts +46 -0
- package/src/models/gemma4/assistant.ts +327 -0
- package/src/models/gemma4/blocks.ts +755 -0
- package/src/models/gemma4/compiled-decode.ts +452 -0
- package/src/models/gemma4/family.ts +39 -0
- package/src/models/gemma4/gemma4-12b.ts +677 -0
- package/src/models/gemma4/gemma4-26b.ts +544 -0
- package/src/models/gemma4/gemma4-e4b.ts +599 -0
- package/src/models/gemma4/graphs.ts +147 -0
- package/src/models/gemma4/media.ts +88 -0
- package/src/models/gemma4/model.ts +860 -0
- package/src/models/gemma4/serving-blocks.ts +72 -0
- package/src/models/gemma4/trainable.ts +186 -0
- package/src/models/gemma4/written-blocks.ts +232 -0
- package/src/models/gemma4/written-forward.ts +159 -0
- package/src/models/gemma4/written-input.ts +29 -0
- package/src/models/glm52/atlas.ts +507 -0
- package/src/models/glm52/coupling.ts +388 -0
- package/src/models/glm52/dsa.ts +601 -0
- package/src/models/glm52/family.ts +20 -0
- package/src/models/glm52/mla.ts +812 -0
- package/src/models/glm52/model.ts +1127 -0
- package/src/models/glm52/moe.ts +223 -0
- package/src/models/glm52/mtp.ts +60 -0
- package/src/models/glm52/pilot.ts +537 -0
- package/src/models/glm52/reference.ts +445 -0
- package/src/models/glm52/residency.ts +557 -0
- package/src/models/glm52/streamed-experts.ts +447 -0
- package/src/models/graph.ts +59 -0
- package/src/models/implementation.ts +230 -0
- package/src/models/index.ts +15 -0
- package/src/models/media-input.ts +15 -0
- package/src/models/memory-plan.ts +39 -0
- package/src/models/minicpm5/family.ts +29 -0
- package/src/models/minicpm5/model.ts +394 -0
- package/src/models/minicpm5/trainable.ts +90 -0
- package/src/models/profile.ts +337 -0
- package/src/models/qwen/blocks.ts +658 -0
- package/src/models/qwen/checkpoint.ts +190 -0
- package/src/models/qwen/families.ts +57 -0
- package/src/models/qwen/media-input.ts +161 -0
- package/src/models/qwen/mtp.ts +101 -0
- package/src/models/qwen/qwen3-moe.ts +355 -0
- package/src/models/qwen/qwen3.ts +323 -0
- package/src/models/qwen/qwen38-27b-trellis-m1max.ts +1532 -0
- package/src/models/qwen/qwen38-27b-trellis-m4pro.ts +1092 -0
- package/src/models/qwen/qwen3_5.ts +433 -0
- package/src/models/qwen/written-layers.ts +285 -0
- package/src/models/row-table.ts +1 -0
- package/src/models/runtime.ts +73 -0
- package/src/models/speculative/deepspec-artifact.ts +10 -0
- package/src/models/speculative/deepspec.ts +744 -0
- package/src/models/speculative/dflash.ts +553 -0
- package/src/models/speculative/dflash2-artifact.ts +9 -0
- package/src/models/speculative/dflash2-operations.ts +1 -0
- package/src/models/speculative/dflash2.ts +576 -0
- package/src/models/speculative/loader.ts +29 -0
- package/src/models/support.ts +79 -0
- package/src/models/universal/archs.ts +365 -0
- package/src/models/universal/blocks.ts +479 -0
- package/src/models/universal/dense.ts +255 -0
- package/src/models/universal/family.ts +17 -0
- package/src/models/vision/qwen3vl.ts +566 -0
- package/src/models/vision/siglip.ts +520 -0
- package/src/models/vision/unified.ts +176 -0
- package/src/models/whisper/family.ts +19 -0
- package/src/models/whisper/fast.ts +734 -0
- package/src/models/whisper/model.ts +430 -0
- package/src/runtime/compiled-trace.ts +16 -0
- package/src/runtime/config.ts +77 -0
- package/src/runtime/expert-trace.ts +71 -0
- package/src/runtime/graph-forward.ts +65 -0
- package/src/runtime/integers.ts +6 -0
- package/src/runtime/native.ts +16 -0
- package/src/runtime/resources.ts +53 -0
- package/src/runtime/row-operations.ts +42 -0
- package/src/runtime/trace.ts +161 -0
- package/src/sampling/curve.ts +122 -0
- package/src/sampling/draft.ts +55 -0
- package/src/sampling/extras.ts +45 -0
- package/src/sampling/filters.ts +134 -0
- package/src/sampling/grammar.ts +630 -0
- package/src/sampling/hlg.ts +290 -0
- package/src/sampling/index.ts +9 -0
- package/src/sampling/processors.ts +142 -0
- package/src/sampling/sampler.ts +120 -0
- package/src/sampling/step.ts +343 -0
- package/src/sampling/types.ts +161 -0
- package/src/sampling/window-filters.ts +46 -0
- package/src/scoring/decisions.ts +96 -0
- package/src/scoring/full-sequence.ts +103 -0
- package/src/scoring/index.ts +9 -0
- package/src/scoring/kl.ts +29 -0
- package/src/scoring/perplexity.ts +165 -0
- package/src/scoring/response-logps.ts +65 -0
- package/src/state/affine-attention.ts +180 -0
- package/src/state/attachment.ts +68 -0
- package/src/state/attention-read.ts +215 -0
- package/src/state/batched-kv.ts +294 -0
- package/src/state/batched-mask.ts +445 -0
- package/src/state/batched-quant.ts +389 -0
- package/src/state/batched-quantized-kv.ts +209 -0
- package/src/state/batched-rotating-quant.ts +457 -0
- package/src/state/batched-rotating-state.ts +149 -0
- package/src/state/batched-rotating.ts +430 -0
- package/src/state/batched-row-storage.ts +179 -0
- package/src/state/batched-ssm.ts +54 -0
- package/src/state/batched-turboquant-kv.ts +144 -0
- package/src/state/bf16-first-kv.ts +663 -0
- package/src/state/cache-service.ts +592 -0
- package/src/state/capabilities.ts +65 -0
- package/src/state/checkpoint.ts +46 -0
- package/src/state/continuation-persistence.ts +126 -0
- package/src/state/decode-step-inputs.ts +31 -0
- package/src/state/decoded-kv-donor.ts +12 -0
- package/src/state/delayed-quantized-kv.ts +141 -0
- package/src/state/delayed-rotating-quantized-kv.ts +178 -0
- package/src/state/delayed-turboquant-kv.ts +123 -0
- package/src/state/durability.ts +214 -0
- package/src/state/encoder-cache.ts +26 -0
- package/src/state/full-kv-row-append.ts +33 -0
- package/src/state/full-kv-row-donor.ts +54 -0
- package/src/state/full-prefill-padding.ts +56 -0
- package/src/state/full-prefill-row.ts +112 -0
- package/src/state/full-transitioning-kv-rows.ts +70 -0
- package/src/state/glm52-cache.ts +783 -0
- package/src/state/grouped-heads-kv4-cache.ts +26 -0
- package/src/state/index.ts +38 -0
- package/src/state/kv-attention-view.ts +77 -0
- package/src/state/kv-budget.ts +14 -0
- package/src/state/kv-scheme.ts +315 -0
- package/src/state/kv-storage.ts +57 -0
- package/src/state/kv-tensor-rows.ts +212 -0
- package/src/state/kv.ts +324 -0
- package/src/state/kv4-head256-cache.ts +46 -0
- package/src/state/latent-legos.ts +895 -0
- package/src/state/layout.ts +277 -0
- package/src/state/leases.ts +26 -0
- package/src/state/paged/cache.ts +689 -0
- package/src/state/paged/codec.ts +36 -0
- package/src/state/paged/rows.ts +223 -0
- package/src/state/persistence-types.ts +173 -0
- package/src/state/persistence-worker.ts +83 -0
- package/src/state/persistence.ts +1036 -0
- package/src/state/persistence.worker.d.ts +3 -0
- package/src/state/persistence.worker.js +264 -0
- package/src/state/prefix-cache.ts +685 -0
- package/src/state/quantized-kv.ts +369 -0
- package/src/state/quantized-tensor.ts +26 -0
- package/src/state/request-policy.ts +48 -0
- package/src/state/retention-policy.ts +37 -0
- package/src/state/rollback.ts +131 -0
- package/src/state/rotating-kv-donor.ts +35 -0
- package/src/state/rotating-kv-layout.ts +341 -0
- package/src/state/rotating-kv.ts +529 -0
- package/src/state/rotating-quantized-kv.ts +616 -0
- package/src/state/rotating-row-transaction.ts +35 -0
- package/src/state/rows.ts +104 -0
- package/src/state/softcap-attention.ts +118 -0
- package/src/state/speculative/glm52-mtp-state.ts +38 -0
- package/src/state/speculative/qwen-mtp-state.ts +34 -0
- package/src/state/speculative-rotating-kv.ts +113 -0
- package/src/state/ssd-cache.ts +434 -0
- package/src/state/ssm-prefill-padding.ts +39 -0
- package/src/state/ssm.ts +422 -0
- package/src/state/target-layout.ts +7 -0
- package/src/state/tiered-prefix-cache.ts +45 -0
- package/src/state/training-cache.ts +300 -0
- package/src/state/transitioning-kv-rows.ts +267 -0
- package/src/state/turboquant-attention.ts +97 -0
- package/src/state/turboquant-codec.ts +90 -0
- package/src/state/turboquant-kv.ts +442 -0
- package/src/state/views.ts +26 -0
- package/src/transcription/index.ts +3 -0
- package/src/transcription/silero-vad.ts +379 -0
- package/src/transcription/whisper/decode.ts +773 -0
- package/src/transcription/whisper/format.ts +74 -0
- package/src/transcription/whisper/index.ts +4 -0
- package/src/transcription/whisper/timing.ts +274 -0
- package/src/transcription/whisper/transcribe.ts +394 -0
- package/src/transcription/whisper/types.ts +22 -0
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
// Whisper (encoder-decoder speech recognition) — an op-for-op port of
|
|
2
|
+
// mlx-whisper's whisper.py (mlx-community/whisper-* checkpoints: fp16
|
|
3
|
+
// weights in a single weights.safetensors + config.json ModelDimensions).
|
|
4
|
+
// L1 gate: encoder output and per-step decoder logits bit-exact vs the
|
|
5
|
+
// pinned mlx-whisper oracle (tests/parity/whisper-parity.test.ts, opt-in).
|
|
6
|
+
//
|
|
7
|
+
// Graph (dtype = weight dtype, fp16 for the shipped artifacts):
|
|
8
|
+
// encoder: mel [B,3000,n_mels] → conv1(k3,p1)+gelu → conv2(k3,s2,p1)+gelu
|
|
9
|
+
// → + sinusoids(1500, D) → N × block(self-attn, mlp) → ln_post
|
|
10
|
+
// decoder: tokens [B,T] → token_embedding + positional_embedding[offset:]
|
|
11
|
+
// → N × block(causal self-attn w/ cache, cross-attn on the
|
|
12
|
+
// encoder output w/ cross-KV cache, mlp) → ln → @ embeddingᵀ
|
|
13
|
+
// gelu: mlx.nn.gelu is `@partial(mx.compile, shapeless=True)` in the
|
|
14
|
+
// oracle — the FUSED kernel rounds differently from the four
|
|
15
|
+
// separate ops (2% of conv1 outputs differ by an ulp), so the same
|
|
16
|
+
// graph is compiled here too (bit-exact, verified 2026-09-15).
|
|
17
|
+
// attention: q,k scaled by (D/H)^-0.25 each, qk in the weight dtype,
|
|
18
|
+
// additive causal mask (-inf f16), precise softmax, no fused
|
|
19
|
+
// kernels — the FAITHFUL path. The optimized path lives in
|
|
20
|
+
// packages/inference/src/models/whisper/fast.ts and is gated token-exact against
|
|
21
|
+
// this one.
|
|
22
|
+
//
|
|
23
|
+
// mlx inline-temporary hazard (CLAUDE.md): every op result is held in a
|
|
24
|
+
// local and disposed; no nested `ops.foo(x.slice(...))` chains.
|
|
25
|
+
|
|
26
|
+
import { MlxArray } from "@mlx-bun/mlx/array";
|
|
27
|
+
import { CompiledFunction } from "@mlx-bun/mlx/compile";
|
|
28
|
+
import { Dtype } from "@mlx-bun/mlx/ffi";
|
|
29
|
+
import * as ops from "@mlx-bun/mlx/ops";
|
|
30
|
+
import type { ModelConfig } from "../../artifacts/config";
|
|
31
|
+
import type { Weights } from "../../artifacts/weights";
|
|
32
|
+
import type { WhisperDecoderCache, WhisperDims, WhisperGraph } from "../../contracts/mlx/whisper";
|
|
33
|
+
import { WhisperFastPath } from "./fast";
|
|
34
|
+
|
|
35
|
+
export function parseWhisperDims(raw: Record<string, unknown>): WhisperDims {
|
|
36
|
+
const n = (k: string): number => {
|
|
37
|
+
const v = raw[k];
|
|
38
|
+
if (typeof v !== "number") throw new Error(`whisper config.json: missing numeric ${k}`);
|
|
39
|
+
return v;
|
|
40
|
+
};
|
|
41
|
+
return {
|
|
42
|
+
nMels: n("n_mels"), nAudioCtx: n("n_audio_ctx"), nAudioState: n("n_audio_state"),
|
|
43
|
+
nAudioHead: n("n_audio_head"), nAudioLayer: n("n_audio_layer"), nVocab: n("n_vocab"),
|
|
44
|
+
nTextCtx: n("n_text_ctx"), nTextState: n("n_text_state"), nTextHead: n("n_text_head"),
|
|
45
|
+
nTextLayer: n("n_text_layer"),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const dispose = (old: MlxArray, next: MlxArray): MlxArray => {
|
|
50
|
+
old.dispose();
|
|
51
|
+
return next;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/** Per-layer decoder state: self-attention K/V [B, T, D] (un-split, un-scaled,
|
|
55
|
+
* exactly what the oracle concatenates) and the cross-attention K/V computed
|
|
56
|
+
* once from the encoder output. */
|
|
57
|
+
export interface WhisperLayerCache {
|
|
58
|
+
k: MlxArray | null;
|
|
59
|
+
v: MlxArray | null;
|
|
60
|
+
crossK: MlxArray | null;
|
|
61
|
+
crossV: MlxArray | null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export class WhisperKvCache implements WhisperDecoderCache {
|
|
65
|
+
readonly layers: WhisperLayerCache[];
|
|
66
|
+
constructor(nLayers: number) {
|
|
67
|
+
this.layers = Array.from({ length: nLayers }, () => ({ k: null, v: null, crossK: null, crossV: null }));
|
|
68
|
+
}
|
|
69
|
+
/** Tokens already in the self-attention cache. */
|
|
70
|
+
get offset(): number {
|
|
71
|
+
return this.layers[0]?.k?.shape[1] ?? 0;
|
|
72
|
+
}
|
|
73
|
+
/** Beam reorder: keep rows `indices` (mx.take along axis 0). Cross K/V
|
|
74
|
+
* rows are identical across beams and are left untouched when they were
|
|
75
|
+
* broadcast from one audio (shape[0] === 1). */
|
|
76
|
+
rearrange(indices: number[]): void {
|
|
77
|
+
if (indices.every((v, i) => v === i)) return;
|
|
78
|
+
const idx = ops.fromInt32(indices, [indices.length]);
|
|
79
|
+
for (const l of this.layers) {
|
|
80
|
+
if (l.k) l.k = dispose(l.k, ops.takeAxis(l.k, idx, 0));
|
|
81
|
+
if (l.v) l.v = dispose(l.v, ops.takeAxis(l.v, idx, 0));
|
|
82
|
+
if (l.crossK && l.crossK.shape[0] !== 1) l.crossK = dispose(l.crossK, ops.takeAxis(l.crossK, idx, 0));
|
|
83
|
+
if (l.crossV && l.crossV.shape[0] !== 1) l.crossV = dispose(l.crossV, ops.takeAxis(l.crossV, idx, 0));
|
|
84
|
+
}
|
|
85
|
+
idx.dispose();
|
|
86
|
+
}
|
|
87
|
+
arrays(): MlxArray[] {
|
|
88
|
+
const out: MlxArray[] = [];
|
|
89
|
+
for (const l of this.layers) for (const a of [l.k, l.v, l.crossK, l.crossV]) if (a) out.push(a);
|
|
90
|
+
return out;
|
|
91
|
+
}
|
|
92
|
+
dispose(): void {
|
|
93
|
+
for (const l of this.layers) {
|
|
94
|
+
l.k?.dispose(); l.v?.dispose(); l.crossK?.dispose(); l.crossV?.dispose();
|
|
95
|
+
l.k = l.v = l.crossK = l.crossV = null;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export class WhisperModel implements WhisperGraph {
|
|
101
|
+
readonly dims: WhisperDims;
|
|
102
|
+
readonly dtype: Dtype;
|
|
103
|
+
readonly #w: Weights;
|
|
104
|
+
/** [1500, D] encoder positional sinusoids (weight dtype). */
|
|
105
|
+
#encPos: MlxArray | null = null;
|
|
106
|
+
/** [n_text_ctx, n_text_ctx] additive causal mask (weight dtype, -inf). */
|
|
107
|
+
#causalMask: MlxArray | null = null;
|
|
108
|
+
/** Transposed weight views, cached per name. */
|
|
109
|
+
readonly #transposed = new Map<string, MlxArray>();
|
|
110
|
+
/** Compiled nn.gelu (see header). */
|
|
111
|
+
#gelu: CompiledFunction | null = null;
|
|
112
|
+
#fast: WhisperFastPath | null = null;
|
|
113
|
+
|
|
114
|
+
/** The optimized execution path (packages/inference/src/models/whisper/fast.ts), built lazily. */
|
|
115
|
+
get fast(): WhisperFastPath {
|
|
116
|
+
return this.#fast ??= new WhisperFastPath(this);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
constructor(weights: Weights, config: ModelConfig) {
|
|
120
|
+
this.#w = weights;
|
|
121
|
+
this.dims = parseWhisperDims(config.raw as Record<string, unknown>);
|
|
122
|
+
this.dtype = weights.tensor("decoder.token_embedding.weight").dtype;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** A fresh faithful-path decoder cache. */
|
|
126
|
+
makeCache(): WhisperKvCache {
|
|
127
|
+
return new WhisperKvCache(this.dims.nTextLayer);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
get isMultilingual(): boolean {
|
|
131
|
+
return this.dims.nVocab >= 51865;
|
|
132
|
+
}
|
|
133
|
+
get numLanguages(): number {
|
|
134
|
+
return this.dims.nVocab - 51765 - (this.isMultilingual ? 1 : 0);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** Alignment heads [(layer, head), …] from the checkpoint (word timestamps);
|
|
138
|
+
* falls back to every head of the last half of the decoder layers. */
|
|
139
|
+
get alignmentHeads(): [number, number][] {
|
|
140
|
+
if (this.#w.has("alignment_heads")) {
|
|
141
|
+
const a = this.#w.tensor("alignment_heads");
|
|
142
|
+
const flat = a.astype(Dtype.int32).toIntTokens();
|
|
143
|
+
const out: [number, number][] = [];
|
|
144
|
+
for (let i = 0; i + 1 < flat.length; i += 2) out.push([flat[i]!, flat[i + 1]!]);
|
|
145
|
+
return out;
|
|
146
|
+
}
|
|
147
|
+
const out: [number, number][] = [];
|
|
148
|
+
for (let l = Math.floor(this.dims.nTextLayer / 2); l < this.dims.nTextLayer; l++)
|
|
149
|
+
for (let h = 0; h < this.dims.nTextHead; h++) out.push([l, h]);
|
|
150
|
+
return out;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// --- primitives -----------------------------------------------------------
|
|
154
|
+
|
|
155
|
+
#t(name: string): MlxArray {
|
|
156
|
+
return this.#w.tensor(name);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** Raw checkpoint tensor (borrowed; the fast path composes its own graphs). */
|
|
160
|
+
weight(name: string): MlxArray {
|
|
161
|
+
return this.#t(name);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** Cached Wᵀ view (borrowed). */
|
|
165
|
+
weightT(name: string): MlxArray {
|
|
166
|
+
return this.#wT(name);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** The compiled nn.gelu (shared with the faithful path). */
|
|
170
|
+
gelu(x: MlxArray): MlxArray {
|
|
171
|
+
return this.#geluCompiled(x);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
layerNorm(x: MlxArray, prefix: string): MlxArray {
|
|
175
|
+
return this.#layerNorm(x, prefix);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** nn.Linear against a checkpoint weight (borrowed inputs, owned output). */
|
|
179
|
+
linear(x: MlxArray, prefix: string, bias = true): MlxArray {
|
|
180
|
+
return this.#linear(x, prefix, bias);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** Wᵀ view, cached (transpose is a strided view, no copy). */
|
|
184
|
+
#wT(name: string): MlxArray {
|
|
185
|
+
let t = this.#transposed.get(name);
|
|
186
|
+
if (!t) {
|
|
187
|
+
t = ops.transposeAxes(this.#t(name), [1, 0]);
|
|
188
|
+
this.#transposed.set(name, t);
|
|
189
|
+
}
|
|
190
|
+
return t;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** nn.Linear: addmm(bias, x, Wᵀ) with bias, x @ Wᵀ without. */
|
|
194
|
+
#linear(x: MlxArray, prefix: string, bias = true): MlxArray {
|
|
195
|
+
const wT = this.#wT(`${prefix}.weight`);
|
|
196
|
+
return bias ? ops.addmm(this.#t(`${prefix}.bias`), x, wT) : ops.matmul(x, wT);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/** nn.gelu — compiled, shapeless, same op graph as mlx.nn.activations.gelu:
|
|
200
|
+
* x * (1 + erf(x / sqrt(2))) / 2. */
|
|
201
|
+
#geluCompiled(x: MlxArray): MlxArray {
|
|
202
|
+
this.#gelu ??= new CompiledFunction(([a]) => {
|
|
203
|
+
const s2 = ops.scalarLike(Math.SQRT2, a!);
|
|
204
|
+
const xs = ops.div(a!, s2);
|
|
205
|
+
const e = ops.erf(xs);
|
|
206
|
+
const one = ops.scalarLike(1, a!);
|
|
207
|
+
const e1 = ops.add(one, e);
|
|
208
|
+
const xe = ops.mul(a!, e1);
|
|
209
|
+
const two = ops.scalarLike(2, a!);
|
|
210
|
+
return [ops.div(xe, two)];
|
|
211
|
+
}, true);
|
|
212
|
+
return this.#gelu.apply([x])[0]!;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
#layerNorm(x: MlxArray, prefix: string): MlxArray {
|
|
216
|
+
return ops.layerNorm(x, this.#t(`${prefix}.weight`), this.#t(`${prefix}.bias`), 1e-5);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/** MultiHeadAttention.__call__ + qkv_attention. Returns the projected
|
|
220
|
+
* output, the (un-split) k/v to cache, and optionally the raw qk. */
|
|
221
|
+
#attention(
|
|
222
|
+
x: MlxArray, prefix: string, nHead: number,
|
|
223
|
+
opts: { xa?: MlxArray; mask?: MlxArray; kv?: [MlxArray, MlxArray] | null; crossKv?: [MlxArray, MlxArray] | null; wantQk?: boolean },
|
|
224
|
+
): { out: MlxArray; k: MlxArray; v: MlxArray; qk: MlxArray | null } {
|
|
225
|
+
const q = this.#linear(x, `${prefix}.query`);
|
|
226
|
+
let k: MlxArray;
|
|
227
|
+
let v: MlxArray;
|
|
228
|
+
if (!opts.xa) {
|
|
229
|
+
k = this.#linear(x, `${prefix}.key`, false);
|
|
230
|
+
v = this.#linear(x, `${prefix}.value`);
|
|
231
|
+
if (opts.kv) {
|
|
232
|
+
k = dispose(k, ops.concatAxis([opts.kv[0], k], 1));
|
|
233
|
+
v = dispose(v, ops.concatAxis([opts.kv[1], v], 1));
|
|
234
|
+
}
|
|
235
|
+
} else if (!opts.crossKv) {
|
|
236
|
+
k = this.#linear(opts.xa, `${prefix}.key`, false);
|
|
237
|
+
v = this.#linear(opts.xa, `${prefix}.value`);
|
|
238
|
+
} else {
|
|
239
|
+
[k, v] = opts.crossKv;
|
|
240
|
+
}
|
|
241
|
+
const [B, T, D] = q.shape as [number, number, number];
|
|
242
|
+
// k/v keep THEIR batch (the oracle reshapes with k.shape): beam rows
|
|
243
|
+
// attend to one broadcast encoder output ([1, 1500, D]) via matmul
|
|
244
|
+
// broadcasting instead of copying cross-K/V per beam.
|
|
245
|
+
const Bk = k.shape[0]!;
|
|
246
|
+
const Tk = k.shape[1]!;
|
|
247
|
+
const hd = D / nHead;
|
|
248
|
+
const scale = ops.scalarLike(hd ** -0.25, q);
|
|
249
|
+
let qh = ops.reshape(q, [B, T, nHead, hd]);
|
|
250
|
+
q.dispose();
|
|
251
|
+
qh = dispose(qh, ops.transposeAxes(qh, [0, 2, 1, 3]));
|
|
252
|
+
qh = dispose(qh, ops.mul(qh, scale));
|
|
253
|
+
let kh = ops.reshape(k, [Bk, Tk, nHead, hd]);
|
|
254
|
+
kh = dispose(kh, ops.transposeAxes(kh, [0, 2, 3, 1]));
|
|
255
|
+
kh = dispose(kh, ops.mul(kh, scale));
|
|
256
|
+
scale.dispose();
|
|
257
|
+
let vh = ops.reshape(v, [Bk, Tk, nHead, hd]);
|
|
258
|
+
vh = dispose(vh, ops.transposeAxes(vh, [0, 2, 1, 3]));
|
|
259
|
+
let qk = ops.matmul(qh, kh);
|
|
260
|
+
qh.dispose();
|
|
261
|
+
kh.dispose();
|
|
262
|
+
if (opts.mask) {
|
|
263
|
+
const m = opts.mask.slice([0, 0], [T, T]);
|
|
264
|
+
qk = dispose(qk, ops.add(qk, m));
|
|
265
|
+
m.dispose();
|
|
266
|
+
}
|
|
267
|
+
const w = ops.softmaxAxis(qk, -1, true);
|
|
268
|
+
let o = ops.matmul(w, vh);
|
|
269
|
+
w.dispose();
|
|
270
|
+
vh.dispose();
|
|
271
|
+
o = dispose(o, ops.transposeAxes(o, [0, 2, 1, 3]));
|
|
272
|
+
o = dispose(o, ops.reshape(o, [B, T, D]));
|
|
273
|
+
const out = this.#linear(o, `${prefix}.out`);
|
|
274
|
+
o.dispose();
|
|
275
|
+
if (!opts.wantQk) {
|
|
276
|
+
qk.dispose();
|
|
277
|
+
return { out, k, v, qk: null };
|
|
278
|
+
}
|
|
279
|
+
return { out, k, v, qk };
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
#mlp(x: MlxArray, prefix: string): MlxArray {
|
|
283
|
+
let h = this.#layerNorm(x, `${prefix}.mlp_ln`);
|
|
284
|
+
h = dispose(h, this.#linear(h, `${prefix}.mlp1`));
|
|
285
|
+
h = dispose(h, this.#geluCompiled(h));
|
|
286
|
+
h = dispose(h, this.#linear(h, `${prefix}.mlp2`));
|
|
287
|
+
return h;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// --- encoder ----------------------------------------------------------------
|
|
291
|
+
|
|
292
|
+
#sinusoids(): MlxArray {
|
|
293
|
+
if (this.#encPos) return this.#encPos;
|
|
294
|
+
const { nAudioCtx: length, nAudioState: channels } = this.dims;
|
|
295
|
+
const half = channels / 2;
|
|
296
|
+
const inc = Math.log(10000) / (half - 1);
|
|
297
|
+
const ar = ops.arange(0, half, 1, Dtype.int32);
|
|
298
|
+
const negInc = MlxArray.fromFloat32(new Float32Array([-inc]), []);
|
|
299
|
+
let inv = ops.mul(ar, negInc);
|
|
300
|
+
ar.dispose();
|
|
301
|
+
negInc.dispose();
|
|
302
|
+
inv = dispose(inv, ops.exp(inv));
|
|
303
|
+
inv = dispose(inv, ops.reshape(inv, [1, half]));
|
|
304
|
+
let pos = ops.arange(0, length, 1, Dtype.int32);
|
|
305
|
+
pos = dispose(pos, ops.reshape(pos, [length, 1]));
|
|
306
|
+
const scaled = ops.mul(pos, inv);
|
|
307
|
+
pos.dispose();
|
|
308
|
+
inv.dispose();
|
|
309
|
+
const s = ops.sin(scaled);
|
|
310
|
+
const c = ops.cos(scaled);
|
|
311
|
+
scaled.dispose();
|
|
312
|
+
let pe = ops.concatAxis([s, c], 1);
|
|
313
|
+
s.dispose();
|
|
314
|
+
c.dispose();
|
|
315
|
+
pe = dispose(pe, pe.astype(this.dtype));
|
|
316
|
+
this.#encPos = pe;
|
|
317
|
+
return pe;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/** AudioEncoder: mel [B, 3000, n_mels] (weight dtype) → [B, 1500, D]. */
|
|
321
|
+
encode(mel: MlxArray, trace?: (name: string, a: MlxArray) => void): MlxArray {
|
|
322
|
+
let x = ops.conv1d(mel, this.#t("encoder.conv1.weight"), 1, 1);
|
|
323
|
+
x = dispose(x, ops.add(x, this.#t("encoder.conv1.bias")));
|
|
324
|
+
x = dispose(x, this.#geluCompiled(x));
|
|
325
|
+
trace?.("conv1", x);
|
|
326
|
+
x = dispose(x, ops.conv1d(x, this.#t("encoder.conv2.weight"), 2, 1));
|
|
327
|
+
x = dispose(x, ops.add(x, this.#t("encoder.conv2.bias")));
|
|
328
|
+
x = dispose(x, this.#geluCompiled(x));
|
|
329
|
+
if (x.shape[1] !== this.dims.nAudioCtx || x.shape[2] !== this.dims.nAudioState)
|
|
330
|
+
throw new Error(`whisper encoder: incorrect audio shape ${JSON.stringify(x.shape)}`);
|
|
331
|
+
x = dispose(x, ops.add(x, this.#sinusoids()));
|
|
332
|
+
trace?.("conv2pos", x);
|
|
333
|
+
for (let i = 0; i < this.dims.nAudioLayer; i++) {
|
|
334
|
+
const p = `encoder.blocks.${i}`;
|
|
335
|
+
const ln = this.#layerNorm(x, `${p}.attn_ln`);
|
|
336
|
+
const a = this.#attention(ln, `${p}.attn`, this.dims.nAudioHead, {});
|
|
337
|
+
ln.dispose();
|
|
338
|
+
a.k.dispose();
|
|
339
|
+
a.v.dispose();
|
|
340
|
+
x = dispose(x, ops.add(x, a.out));
|
|
341
|
+
a.out.dispose();
|
|
342
|
+
const m = this.#mlp(x, p);
|
|
343
|
+
x = dispose(x, ops.add(x, m));
|
|
344
|
+
m.dispose();
|
|
345
|
+
trace?.(`block${i}`, x);
|
|
346
|
+
}
|
|
347
|
+
x = dispose(x, this.#layerNorm(x, "encoder.ln_post"));
|
|
348
|
+
return x;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// --- decoder ----------------------------------------------------------------
|
|
352
|
+
|
|
353
|
+
#mask(): MlxArray {
|
|
354
|
+
if (this.#causalMask) return this.#causalMask;
|
|
355
|
+
const n = this.dims.nTextCtx;
|
|
356
|
+
const m = new Float32Array(n * n);
|
|
357
|
+
for (let i = 0; i < n; i++) for (let j = i + 1; j < n; j++) m[i * n + j] = -Infinity;
|
|
358
|
+
const f32 = MlxArray.fromFloat32(m, [n, n]);
|
|
359
|
+
const out = f32.astype(this.dtype);
|
|
360
|
+
f32.dispose();
|
|
361
|
+
this.#causalMask = out;
|
|
362
|
+
return out;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/** TextDecoder.__call__: tokens [B, T] int32, audioFeatures [B|1, 1500, D].
|
|
366
|
+
* Returns logits [B, T, n_vocab] (weight dtype) and, when requested, the
|
|
367
|
+
* per-layer raw cross-attention qk for alignment. Mutates `cache`. */
|
|
368
|
+
decode(
|
|
369
|
+
tokens: MlxArray, audioFeatures: MlxArray, cache: WhisperKvCache,
|
|
370
|
+
opts: { wantCrossQk?: boolean } = {},
|
|
371
|
+
): { logits: MlxArray; crossQk: (MlxArray | null)[] } {
|
|
372
|
+
const offset = cache.offset;
|
|
373
|
+
const T = tokens.shape[1]!;
|
|
374
|
+
const embed = this.#t("decoder.token_embedding.weight");
|
|
375
|
+
let x = ops.takeAxis(embed, tokens, 0);
|
|
376
|
+
const pos = this.#t("decoder.positional_embedding").slice([offset, 0], [offset + T, this.dims.nTextState]);
|
|
377
|
+
x = dispose(x, ops.add(x, pos));
|
|
378
|
+
pos.dispose();
|
|
379
|
+
const mask = this.#mask();
|
|
380
|
+
const crossQk: (MlxArray | null)[] = [];
|
|
381
|
+
for (let i = 0; i < this.dims.nTextLayer; i++) {
|
|
382
|
+
const p = `decoder.blocks.${i}`;
|
|
383
|
+
const layer = cache.layers[i]!;
|
|
384
|
+
const ln = this.#layerNorm(x, `${p}.attn_ln`);
|
|
385
|
+
const a = this.#attention(ln, `${p}.attn`, this.dims.nTextHead, {
|
|
386
|
+
mask, kv: layer.k && layer.v ? [layer.k, layer.v] : null,
|
|
387
|
+
});
|
|
388
|
+
ln.dispose();
|
|
389
|
+
layer.k?.dispose();
|
|
390
|
+
layer.v?.dispose();
|
|
391
|
+
layer.k = a.k;
|
|
392
|
+
layer.v = a.v;
|
|
393
|
+
x = dispose(x, ops.add(x, a.out));
|
|
394
|
+
a.out.dispose();
|
|
395
|
+
const cln = this.#layerNorm(x, `${p}.cross_attn_ln`);
|
|
396
|
+
const c = this.#attention(cln, `${p}.cross_attn`, this.dims.nTextHead, {
|
|
397
|
+
xa: audioFeatures,
|
|
398
|
+
crossKv: layer.crossK && layer.crossV ? [layer.crossK, layer.crossV] : null,
|
|
399
|
+
wantQk: opts.wantCrossQk,
|
|
400
|
+
});
|
|
401
|
+
cln.dispose();
|
|
402
|
+
if (!layer.crossK) {
|
|
403
|
+
layer.crossK = c.k;
|
|
404
|
+
layer.crossV = c.v;
|
|
405
|
+
}
|
|
406
|
+
x = dispose(x, ops.add(x, c.out));
|
|
407
|
+
c.out.dispose();
|
|
408
|
+
crossQk.push(c.qk);
|
|
409
|
+
const m = this.#mlp(x, p);
|
|
410
|
+
x = dispose(x, ops.add(x, m));
|
|
411
|
+
m.dispose();
|
|
412
|
+
}
|
|
413
|
+
x = dispose(x, this.#layerNorm(x, "decoder.ln"));
|
|
414
|
+
const logits = ops.matmul(x, this.#wT("decoder.token_embedding.weight"));
|
|
415
|
+
x.dispose();
|
|
416
|
+
return { logits, crossQk };
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
dispose(): void {
|
|
420
|
+
this.#encPos?.dispose();
|
|
421
|
+
this.#causalMask?.dispose();
|
|
422
|
+
for (const t of this.#transposed.values()) t.dispose();
|
|
423
|
+
this.#transposed.clear();
|
|
424
|
+
this.#fast?.dispose();
|
|
425
|
+
this.#gelu?.dispose();
|
|
426
|
+
this.#w.dispose();
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
export { type WhisperDims };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
// Compiled-decode trace mode (set by compiled-decode.ts around its trace,
|
|
4
|
+
// which runs synchronously on the serialized generation queue — same
|
|
5
|
+
// safety argument as LoraState). Inside a shapeless-compiled graph, mlx's
|
|
6
|
+
// Slice primitive cannot re-infer output shapes, so the two subrange
|
|
7
|
+
// slices on the decode path (per-layer-input split, MoE top-k) swap to
|
|
8
|
+
// DynamicSlice — identical values, shapeless-safe — ONLY while tracing.
|
|
9
|
+
// The uncompiled path keeps the exact oracle op sequence.
|
|
10
|
+
let compiledTrace = false;
|
|
11
|
+
export function setCompiledTrace(v: boolean): void {
|
|
12
|
+
compiledTrace = v;
|
|
13
|
+
}
|
|
14
|
+
export function isCompiledTrace(): boolean {
|
|
15
|
+
return compiledTrace;
|
|
16
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
|
|
3
|
+
export type RuntimeKey = `MLX_BUN_${string}`;
|
|
4
|
+
export type RuntimeOverrides = Readonly<Partial<Record<RuntimeKey, string | undefined>>>;
|
|
5
|
+
|
|
6
|
+
export interface RuntimeConfig {
|
|
7
|
+
readonly values: Readonly<Record<string, string | undefined>>;
|
|
8
|
+
value(name: RuntimeKey): string | undefined;
|
|
9
|
+
flag(name: RuntimeKey, defaultOn: boolean): boolean;
|
|
10
|
+
number(name: RuntimeKey, fallback: number): number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Build one immutable runtime snapshot. Only mlx-bun-owned keys cross this
|
|
14
|
+
* boundary; feature code never reads or mutates process.env. */
|
|
15
|
+
export function createRuntimeConfig(
|
|
16
|
+
source: Readonly<Record<string, string | undefined>>,
|
|
17
|
+
): RuntimeConfig {
|
|
18
|
+
const values = Object.freeze(Object.fromEntries(
|
|
19
|
+
Object.entries(source).filter(([key, value]) =>
|
|
20
|
+
key.startsWith("MLX_BUN_") && value !== undefined),
|
|
21
|
+
));
|
|
22
|
+
return Object.freeze({
|
|
23
|
+
values,
|
|
24
|
+
value: (name: RuntimeKey) => values[name],
|
|
25
|
+
flag: (name: RuntimeKey, defaultOn: boolean) => {
|
|
26
|
+
const value = values[name];
|
|
27
|
+
return value === "1" ? true : value === "0" ? false : defaultOn;
|
|
28
|
+
},
|
|
29
|
+
number: (name: RuntimeKey, fallback: number) => {
|
|
30
|
+
const value = values[name];
|
|
31
|
+
if (value === undefined) return fallback;
|
|
32
|
+
const parsed = Number(value);
|
|
33
|
+
return Number.isFinite(parsed) ? parsed : fallback;
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
let active = createRuntimeConfig(process.env);
|
|
39
|
+
const executionConfig = new AsyncLocalStorage<RuntimeConfig>();
|
|
40
|
+
|
|
41
|
+
/** Legacy kernels read through this port. A bound execution sees its own
|
|
42
|
+
* immutable snapshot across awaits, independently of later host settings. */
|
|
43
|
+
export function withRuntimeConfig<T>(config: RuntimeConfig, run: () => T): T {
|
|
44
|
+
return executionConfig.getStore() === config ? run() : executionConfig.run(config, run);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function runtimeConfig(): RuntimeConfig {
|
|
48
|
+
return executionConfig.getStore() ?? active;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function runtimeValue(name: RuntimeKey): string | undefined {
|
|
52
|
+
return runtimeConfig().value(name);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** An explicit "1"/"0" wins; other values retain the caller's default. */
|
|
56
|
+
export function runtimeFlag(name: RuntimeKey, defaultOn: boolean): boolean {
|
|
57
|
+
return runtimeConfig().flag(name, defaultOn);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function runtimeNumber(name: RuntimeKey, fallback: number): number {
|
|
61
|
+
return runtimeConfig().number(name, fallback);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function runtimeKey(name: string): RuntimeKey {
|
|
65
|
+
if (!name.startsWith("MLX_BUN_"))
|
|
66
|
+
throw new Error(`runtime flag must start with MLX_BUN_ (got ${name})`);
|
|
67
|
+
return name as RuntimeKey;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Replace selected values by installing a new frozen snapshot. The CLI uses
|
|
71
|
+
* this once while resolving flags; tests may use the returned restore hook.
|
|
72
|
+
* No environment mutation is involved. */
|
|
73
|
+
export function configureRuntime(overrides: RuntimeOverrides): () => void {
|
|
74
|
+
const previous = active;
|
|
75
|
+
active = createRuntimeConfig({ ...previous.values, ...overrides });
|
|
76
|
+
return () => { active = previous; };
|
|
77
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// E0 expert-routing tracer (expert offload).
|
|
2
|
+
//
|
|
3
|
+
// Records every MoE router decision as JSONL — one record per router call:
|
|
4
|
+
// { c: callSeq, l: layerIdx, s: [B, L, k], i: [flattened uint32 expert ids] }
|
|
5
|
+
// Analyse with analyze-expert-trace.ts (deleted 2026-08-23; git history) (coverage curve, working-set
|
|
6
|
+
// size, within-task stability, cross-task specialisation).
|
|
7
|
+
//
|
|
8
|
+
// Two ways to drive it:
|
|
9
|
+
// - env: `MLX_BUN_EXPERT_TRACE=<path> mlx-bun serve ...` (auto-starts here)
|
|
10
|
+
// - programmatic: beginExpertTrace(path) / endExpertTrace() per domain
|
|
11
|
+
// (run-expert-trace.ts (deleted 2026-08-23; git history) uses this to trace several domains from
|
|
12
|
+
// one model load).
|
|
13
|
+
//
|
|
14
|
+
// Recording adds a per-call GPU->host sync (it reads the routed indices back),
|
|
15
|
+
// so a traced run decodes slower — it's a MEASUREMENT tool, not a serving
|
|
16
|
+
// path. When no trace is active, isExpertTracing() is false and the hook in
|
|
17
|
+
// Router.forward is a single branch (zero overhead) — inert by default, same
|
|
18
|
+
// discipline as the slots flag.
|
|
19
|
+
|
|
20
|
+
import { Dtype } from "@mlx-bun/mlx/ffi";
|
|
21
|
+
import type { MlxArray } from "@mlx-bun/mlx/array";
|
|
22
|
+
import { runtimeValue } from "./config";
|
|
23
|
+
|
|
24
|
+
let sink: Bun.FileSink | null = null;
|
|
25
|
+
let seq = 0;
|
|
26
|
+
let written = 0;
|
|
27
|
+
|
|
28
|
+
export function beginExpertTrace(path: string): void {
|
|
29
|
+
if (sink) endExpertTrace();
|
|
30
|
+
sink = Bun.file(path).writer();
|
|
31
|
+
seq = 0;
|
|
32
|
+
written = 0;
|
|
33
|
+
sink.write(JSON.stringify({ meta: "mlx-bun-expert-trace-v1", note: "one record per router call" }) + "\n");
|
|
34
|
+
process.stderr.write(`[expert-trace] recording -> ${path}\n`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function endExpertTrace(): void {
|
|
38
|
+
if (!sink) return;
|
|
39
|
+
sink.flush();
|
|
40
|
+
sink.end();
|
|
41
|
+
process.stderr.write(`[expert-trace] wrote ${written} records (${seq} calls)\n`);
|
|
42
|
+
sink = null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Alias kept for the process-exit handler / env callers. */
|
|
46
|
+
export const finishExpertTrace = endExpertTrace;
|
|
47
|
+
|
|
48
|
+
export function isExpertTracing(): boolean {
|
|
49
|
+
return sink !== null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Record one router decision. `indices` is [B, L, k] uint32 (expert ids). */
|
|
53
|
+
export function recordRouting(layer: number, indices: MlxArray): void {
|
|
54
|
+
if (!sink) return;
|
|
55
|
+
const shape = indices.shape; // [B, L, k]
|
|
56
|
+
const f = indices.astype(Dtype.float32); // ids <=127 are exact in f32
|
|
57
|
+
const data = f.toFloat32();
|
|
58
|
+
f.dispose();
|
|
59
|
+
const ids = new Array<number>(data.length);
|
|
60
|
+
for (let i = 0; i < data.length; i++) ids[i] = Math.round(data[i]!);
|
|
61
|
+
sink.write(JSON.stringify({ c: seq++, l: layer, s: shape, i: ids }) + "\n");
|
|
62
|
+
if (++written % 2000 === 0) sink.flush();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const expertTracePath = runtimeValue("MLX_BUN_EXPERT_TRACE");
|
|
66
|
+
if (expertTracePath) beginExpertTrace(expertTracePath);
|
|
67
|
+
|
|
68
|
+
// The trace is flushed on process exit. No signal handler lives here: an
|
|
69
|
+
// importing application owns SIGINT/SIGTERM (graceful drain, cancellation),
|
|
70
|
+
// and a library handler that exits would preempt it.
|
|
71
|
+
process.on("exit", () => { try { endExpertTrace(); } catch { /* best-effort */ } });
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { MlxArray } from "@mlx-bun/mlx/array";
|
|
2
|
+
import * as ops from "@mlx-bun/mlx/ops";
|
|
3
|
+
type LayerCapture=(layer:number,hidden:MlxArray)=>void;
|
|
4
|
+
interface ForwardCache { state():MlxArray[]; readonly stateNeedsDispose?:boolean }
|
|
5
|
+
|
|
6
|
+
/** Owns intermediates of a written forward. The graph calls its layers itself;
|
|
7
|
+
* this scope only releases the previous output and records the new one. */
|
|
8
|
+
export class GraphForwardScope {
|
|
9
|
+
#hidden: MlxArray | null;
|
|
10
|
+
constructor(hidden: MlxArray, readonly capture: LayerCapture | null) { this.#hidden = hidden; }
|
|
11
|
+
get hidden(): MlxArray { return this.#hidden!; }
|
|
12
|
+
protected replace(next: MlxArray): void {
|
|
13
|
+
this.#hidden!.dispose();
|
|
14
|
+
this.#hidden = next;
|
|
15
|
+
}
|
|
16
|
+
accept(layer: number, next: MlxArray): void {
|
|
17
|
+
this.replace(next);
|
|
18
|
+
this.capture?.(layer, next);
|
|
19
|
+
}
|
|
20
|
+
bound(layer: number, next: MlxArray, cache: ForwardCache): void {
|
|
21
|
+
this.replace(next);
|
|
22
|
+
const state = cache.state();
|
|
23
|
+
try { ops.evalAll([next, ...state]); }
|
|
24
|
+
finally { if (cache.stateNeedsDispose) for (const array of state) array.dispose(); }
|
|
25
|
+
this.capture?.(layer, next);
|
|
26
|
+
}
|
|
27
|
+
submit(): void { ops.asyncEvalAll([this.#hidden!]); }
|
|
28
|
+
finish(layers: number, output: MlxArray): MlxArray {
|
|
29
|
+
this.#hidden!.dispose();
|
|
30
|
+
this.#hidden = null;
|
|
31
|
+
try { this.capture?.(layers, output); return output; }
|
|
32
|
+
catch (error) { output.dispose(); throw error; }
|
|
33
|
+
}
|
|
34
|
+
[Symbol.dispose](): void { this.#hidden?.dispose(); this.#hidden = null; }
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** The written prompt operation requests one materialization per layer. */
|
|
38
|
+
export class BoundedGraphForwardScope extends GraphForwardScope {
|
|
39
|
+
constructor(hidden: MlxArray, capture: LayerCapture | null, readonly caches: readonly ForwardCache[]) { super(hidden, capture); }
|
|
40
|
+
override accept(layer: number, next: MlxArray): void { this.bound(layer, next, this.caches[layer]!); }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Submission cadence only; projection and layer calls remain in the file. */
|
|
44
|
+
export class SubmittedGraphForwardScope extends GraphForwardScope {
|
|
45
|
+
constructor(hidden: MlxArray, capture: LayerCapture | null, readonly layers: number, readonly every: number) { super(hidden, capture); }
|
|
46
|
+
override accept(layer: number, next: MlxArray): void {
|
|
47
|
+
this.replace(next);
|
|
48
|
+
if ((layer + 1) % this.every === 0 && layer + 1 < this.layers) this.submit();
|
|
49
|
+
this.capture?.(layer, next);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
/** Queue a written layer and its cache state together before the next layer
|
|
55
|
+
* writes shared ANE buffers. Submission remains asynchronous. */
|
|
56
|
+
export class QueuedGraphForwardScope extends GraphForwardScope {
|
|
57
|
+
constructor(hidden: MlxArray, capture: LayerCapture | null, readonly caches: readonly ForwardCache[]) { super(hidden, capture); }
|
|
58
|
+
override accept(layer: number, next: MlxArray): void {
|
|
59
|
+
this.replace(next);
|
|
60
|
+
const cache = this.caches[layer]!, state = cache.state();
|
|
61
|
+
try { ops.asyncEvalAll([next, ...state]); }
|
|
62
|
+
finally { if (cache.stateNeedsDispose) for (const array of state) array.dispose(); }
|
|
63
|
+
this.capture?.(layer, next);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** `value` when it is a positive safe integer; otherwise a RangeError naming `label`. */
|
|
2
|
+
export function positiveInteger(value: number, label: string): number {
|
|
3
|
+
if (!Number.isSafeInteger(value) || value <= 0)
|
|
4
|
+
throw new RangeError(`${label} must be a positive safe integer`);
|
|
5
|
+
return value;
|
|
6
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
|
|
4
|
+
export const NATIVE_DIR = join(import.meta.dir, "..", "..", "dist", "native");
|
|
5
|
+
export const NATIVE_FILES = ["libmlx_bun_expert_io.dylib", "mlx-bun-frame-extract", "libmlx_bun_ane.dylib"] as const;
|
|
6
|
+
export function resolveInferenceNative(name: typeof NATIVE_FILES[number], override?: string): string {
|
|
7
|
+
if (override) return override;
|
|
8
|
+
if (import.meta.filename.startsWith("/$bunfs/")) {
|
|
9
|
+
const sibling = join(dirname(process.execPath), name);
|
|
10
|
+
if (existsSync(sibling)) return sibling;
|
|
11
|
+
}
|
|
12
|
+
return join(NATIVE_DIR, name);
|
|
13
|
+
}
|
|
14
|
+
export const EXPERT_IO_LIBRARY = resolveInferenceNative(NATIVE_FILES[0], process.env.MLX_BUN_EXPERT_IO_DYLIB);
|
|
15
|
+
export const FRAME_EXTRACT_BINARY = resolveInferenceNative(NATIVE_FILES[1], process.env.MLX_BUN_FRAME_EXTRACT);
|
|
16
|
+
export const ANE_LIBRARY = resolveInferenceNative(NATIVE_FILES[2], process.env.MLX_BUN_ANE_DYLIB);
|