@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,389 @@
|
|
|
1
|
+
// Batched QUANTIZED KV for continuous batching — the quantized twins of
|
|
2
|
+
// batched-mask.ts's dynamic-B ops, over (packed, scales, biases) triples.
|
|
3
|
+
//
|
|
4
|
+
// Why this is safe surgery: mlx quantization packs along HEAD_DIM (the last
|
|
5
|
+
// axis; packed last-dim = D/(32/bits), scales/biases last-dim = D/groupSize),
|
|
6
|
+
// so the TOKEN axis (axis 2) stays token-granular in all three tensors —
|
|
7
|
+
// left-padding, concat, and row-filter at token granularity never touch a
|
|
8
|
+
// quantization group. Pad columns hold zeros; they are never attended (the
|
|
9
|
+
// padding mask covers them) and never dequantized into anything a real row
|
|
10
|
+
// reads. QuantizedKVCache.updateAndFetchQuantized already handles arbitrary
|
|
11
|
+
// starting widths (it trims to `offset` and regrows in STEP chunks), so a
|
|
12
|
+
// merged exact-width buffer needs no STEP alignment here.
|
|
13
|
+
//
|
|
14
|
+
// Composition contract (the L2 oracle binds per ROW): each joiner prefills
|
|
15
|
+
// alone through the per-layer conversion its bf16-first lego runs (the
|
|
16
|
+
// conversion at the scheme's start), so a row's quantized bytes are bit-exact
|
|
17
|
+
// vs the same request run alone under `--kv-quant config` by construction;
|
|
18
|
+
// this module only re-arranges those bytes across the batch axis. Gates: `02d723a:tests/parity/batched-kv-quant-parity.test.ts`.
|
|
19
|
+
|
|
20
|
+
import { MlxArray } from "@mlx-bun/mlx/array";
|
|
21
|
+
import * as ops from "@mlx-bun/mlx/ops";
|
|
22
|
+
import { QuantizedKVCache } from "./quantized-kv";
|
|
23
|
+
import { type AttentionCache, type AttentionRead, type BatchableCache, type Cache, type Mask } from "../contracts/mlx/cache";
|
|
24
|
+
import type { QuantizedAttentionState } from "./kv-storage";
|
|
25
|
+
import { buildBatchedDecodeMask } from "./batched-mask";
|
|
26
|
+
import { isQuantizedKvCache } from "./capabilities";
|
|
27
|
+
import { runtimeValue } from "../runtime/config";
|
|
28
|
+
import { AttentionMasks, withLease } from "./attention-read";
|
|
29
|
+
import { affineRead, rowLayoutKernels, type AffineKernel, type AffineKernels } from "./affine-attention";
|
|
30
|
+
|
|
31
|
+
export interface QuantRow {
|
|
32
|
+
keys: ops.QuantizedTensor;
|
|
33
|
+
values: ops.QuantizedTensor;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const disposeTriple = (t: ops.QuantizedTensor): void => {
|
|
37
|
+
t.packed.dispose();
|
|
38
|
+
t.scales.dispose();
|
|
39
|
+
t.biases.dispose();
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/** Apply an MlxArray op to each member of a triple. Each tensor keeps its
|
|
43
|
+
* OWN last-dim (packed vs scales/biases differ) — helpers must never share
|
|
44
|
+
* one `D` across the triple (recon hazard, 2026-07-05). */
|
|
45
|
+
const tripleMap = (
|
|
46
|
+
t: ops.QuantizedTensor, f: (a: MlxArray) => MlxArray,
|
|
47
|
+
): ops.QuantizedTensor => ({ packed: f(t.packed), scales: f(t.scales), biases: f(t.biases) });
|
|
48
|
+
|
|
49
|
+
/** Left-pad one tensor of a triple by `pad` tokens (axis 2) with zeros of
|
|
50
|
+
* its own dtype/last-dim. pad=0 returns a fresh full view (mirrors
|
|
51
|
+
* batched-mask.ts leftPadTo so ownership is uniform). */
|
|
52
|
+
const leftPadTo = (a: MlxArray, pad: number): MlxArray => {
|
|
53
|
+
if (pad === 0) return a.slice([0, 0, 0, 0], a.shape as number[]);
|
|
54
|
+
const [B, H, , D] = a.shape as [number, number, number, number];
|
|
55
|
+
const z = ops.zeros([B, H, pad, D], a.dtype);
|
|
56
|
+
const out = ops.concatAxis([z, a], 2);
|
|
57
|
+
z.dispose();
|
|
58
|
+
return out;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
/** Quantized twin of mergeKVRows: stack N single-row quantized KV slices
|
|
62
|
+
* into one left-padded [B,H,Smax,*] triple pair. Caller owns the result;
|
|
63
|
+
* input rows are not disposed. */
|
|
64
|
+
export function mergeQuantRows(rows: QuantRow[]): {
|
|
65
|
+
keys: ops.QuantizedTensor; values: ops.QuantizedTensor; leftPad: number[]; width: number;
|
|
66
|
+
} {
|
|
67
|
+
const lens = rows.map((r) => r.keys.packed.shape[2]!);
|
|
68
|
+
const width = Math.max(...lens);
|
|
69
|
+
const leftPad = lens.map((l) => width - l);
|
|
70
|
+
const padded = rows.map((r, i) => ({
|
|
71
|
+
keys: tripleMap(r.keys, (a) => leftPadTo(a, leftPad[i]!)),
|
|
72
|
+
values: tripleMap(r.values, (a) => leftPadTo(a, leftPad[i]!)),
|
|
73
|
+
}));
|
|
74
|
+
const cat = (pick: (r: QuantRow) => ops.QuantizedTensor): ops.QuantizedTensor => ({
|
|
75
|
+
packed: ops.concatAxis(padded.map((r) => pick(r).packed), 0),
|
|
76
|
+
scales: ops.concatAxis(padded.map((r) => pick(r).scales), 0),
|
|
77
|
+
biases: ops.concatAxis(padded.map((r) => pick(r).biases), 0),
|
|
78
|
+
});
|
|
79
|
+
const keys = cat((r) => r.keys);
|
|
80
|
+
const values = cat((r) => r.values);
|
|
81
|
+
for (const r of padded) { disposeTriple(r.keys); disposeTriple(r.values); }
|
|
82
|
+
return { keys, values, leftPad, width };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Quantized twin of extendKVRows: append ONE right-justified quantized row
|
|
86
|
+
* to an existing batched triple pair in one pad + one B-axis concat.
|
|
87
|
+
* Existing pads grow, never shrink (mlx-lm extend semantics). Caller owns
|
|
88
|
+
* the result; inputs are not disposed. */
|
|
89
|
+
export function extendQuantRows(
|
|
90
|
+
keys: ops.QuantizedTensor, values: ops.QuantizedTensor, leftPad: number[], row: QuantRow,
|
|
91
|
+
): { keys: ops.QuantizedTensor; values: ops.QuantizedTensor; leftPad: number[]; width: number } {
|
|
92
|
+
const S = keys.packed.shape[2]!;
|
|
93
|
+
const Lr = row.keys.packed.shape[2]!;
|
|
94
|
+
const width = Math.max(S, Lr);
|
|
95
|
+
const bk = tripleMap(keys, (a) => leftPadTo(a, width - S));
|
|
96
|
+
const bv = tripleMap(values, (a) => leftPadTo(a, width - S));
|
|
97
|
+
const rk = tripleMap(row.keys, (a) => leftPadTo(a, width - Lr));
|
|
98
|
+
const rv = tripleMap(row.values, (a) => leftPadTo(a, width - Lr));
|
|
99
|
+
const cat = (b: ops.QuantizedTensor, r: ops.QuantizedTensor): ops.QuantizedTensor => ({
|
|
100
|
+
packed: ops.concatAxis([b.packed, r.packed], 0),
|
|
101
|
+
scales: ops.concatAxis([b.scales, r.scales], 0),
|
|
102
|
+
biases: ops.concatAxis([b.biases, r.biases], 0),
|
|
103
|
+
});
|
|
104
|
+
const outK = cat(bk, rk);
|
|
105
|
+
const outV = cat(bv, rv);
|
|
106
|
+
for (const t of [bk, bv, rk, rv]) disposeTriple(t);
|
|
107
|
+
return {
|
|
108
|
+
keys: outK, values: outV,
|
|
109
|
+
leftPad: [...leftPad.map((p) => p + (width - S)), width - Lr],
|
|
110
|
+
width,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Quantized twin of extractKVRow (mlx-lm BatchKVCache.extract,
|
|
115
|
+
* models/cache.py:1080, over triples): pull row `i` past its left padding
|
|
116
|
+
* into a fresh SERIAL QuantizedKVCache with OWNED contiguous copies.
|
|
117
|
+
* Token-axis slicing is byte-safe because quantization packs along
|
|
118
|
+
* HEAD_DIM (file header) — a row's quantized bytes come out exactly as
|
|
119
|
+
* the serial oracle wrote them. */
|
|
120
|
+
export function extractQuantRow(
|
|
121
|
+
cache: QuantizedKVCache, leftPad: number, i: number, kernels: AffineKernels = cache.kernels,
|
|
122
|
+
): QuantizedKVCache {
|
|
123
|
+
if (!cache.keys || !cache.values) throw new Error("extractQuantRow: empty cache");
|
|
124
|
+
const S = cache.offset;
|
|
125
|
+
const cut = (t: ops.QuantizedTensor): ops.QuantizedTensor =>
|
|
126
|
+
tripleMap(t, (a) => {
|
|
127
|
+
const [, H, , D] = a.shape as [number, number, number, number];
|
|
128
|
+
const view = a.slice([i, 0, leftPad, 0], [i + 1, H, S, D]);
|
|
129
|
+
const own = ops.copyOf(view); // TRUE copy: contiguous(view) is a no-op VIEW when already contiguous — pins the source buffer (2026-08-20 DeltaNet conv leak class)
|
|
130
|
+
view.dispose();
|
|
131
|
+
return own;
|
|
132
|
+
});
|
|
133
|
+
const out = new QuantizedKVCache(cache.groupSize, cache.bits, kernels);
|
|
134
|
+
out.restoreState(cut(cache.keys), cut(cache.values), S - leftPad);
|
|
135
|
+
return out;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** Quantized twin of filterKVRows: keep `keep` rows along the batch axis.
|
|
139
|
+
* Caller owns the result; inputs are not disposed. */
|
|
140
|
+
export function filterQuantRows(
|
|
141
|
+
keys: ops.QuantizedTensor, values: ops.QuantizedTensor, keep: number[], trimLeft = 0,
|
|
142
|
+
): { keys: ops.QuantizedTensor; values: ops.QuantizedTensor } {
|
|
143
|
+
const idx = MlxArray.fromInt32(Int32Array.from(keep), [keep.length]);
|
|
144
|
+
const take = (t: ops.QuantizedTensor): ops.QuantizedTensor =>
|
|
145
|
+
tripleMap(t, (a) => {
|
|
146
|
+
const rows = ops.takeAxis(a, idx, 0);
|
|
147
|
+
if (trimLeft === 0) return rows;
|
|
148
|
+
const trimmed = rows.slice([0, 0, trimLeft, 0], rows.shape);
|
|
149
|
+
rows.dispose();
|
|
150
|
+
return trimmed;
|
|
151
|
+
});
|
|
152
|
+
const k = take(keys);
|
|
153
|
+
const v = take(values);
|
|
154
|
+
idx.dispose();
|
|
155
|
+
return { keys: k, values: v };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** Per-step cache wrapper for batched QUANTIZED decode with left-padded
|
|
159
|
+
* rows — the quantized twin of BatchedDecodeMaskCache. Subclasses
|
|
160
|
+
* QuantizedKVCache so attention's `instanceof` dispatch routes to
|
|
161
|
+
* quantizedSdpa exactly as for a serial quantized cache; delegates KV
|
|
162
|
+
* storage to the persistent batched inner and overrides the two things a
|
|
163
|
+
* padded batch needs: the padding-aware decode mask and per-row RoPE
|
|
164
|
+
* positions. Ephemeral — the scheduler builds a fresh wrapper each step
|
|
165
|
+
* and calls releaseRopeArr() after (never dispose(), which would free the
|
|
166
|
+
* inner). Rope is captured ONCE per step before updateAndFetch (attention
|
|
167
|
+
* reads it pre-write; see packages/inference/src/models/minicpm5/model.ts
|
|
168
|
+
* LlamaAttention.forward), so an
|
|
169
|
+
* eagerly-built [B] position array is exact.
|
|
170
|
+
*
|
|
171
|
+
* Reads: append to the inner storage, then the inner cache's kernel for the
|
|
172
|
+
* read under the padding-aware mask (`buildBatchedDecodeMask`) for the query
|
|
173
|
+
* count, built once per forward through the inner cache's `masks`. That mask
|
|
174
|
+
* is an array, so a window takes the masked-window kernel. */
|
|
175
|
+
export class BatchedQuantDecodeMaskCache extends QuantizedKVCache {
|
|
176
|
+
override readonly ropeOffsetArr: MlxArray;
|
|
177
|
+
|
|
178
|
+
constructor(
|
|
179
|
+
private readonly inner: QuantizedKVCache,
|
|
180
|
+
private readonly B: number,
|
|
181
|
+
private readonly leftPad: number[],
|
|
182
|
+
) {
|
|
183
|
+
super(inner.groupSize, inner.bits, inner.kernels, inner.masks);
|
|
184
|
+
this.offset = inner.offset;
|
|
185
|
+
const data = new Int32Array(B);
|
|
186
|
+
for (let b = 0; b < B; b++) data[b] = inner.offset - leftPad[b]!;
|
|
187
|
+
this.ropeOffsetArr = MlxArray.fromInt32(data, [B]);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
override appendDecode(k: MlxArray, v: MlxArray): AttentionRead { return this.#read(k, v, this.kernels.decode); }
|
|
191
|
+
|
|
192
|
+
override appendWindow(k: MlxArray, v: MlxArray): AttentionRead { return this.#read(k, v, this.kernels.maskedWindow); }
|
|
193
|
+
|
|
194
|
+
#read(k: MlxArray, v: MlxArray, kernel: AffineKernel): AttentionRead {
|
|
195
|
+
const N = k.shape[2]!, S = this.inner.offset + N;
|
|
196
|
+
const mask = this.masks.lease("padded-kv", `${N}|${S}|${this.leftPad.join(",")}`,
|
|
197
|
+
() => ({ mode: "array", arr: buildBatchedDecodeMask(this.B, N, S, this.leftPad, null) }));
|
|
198
|
+
return withLease(mask, held => {
|
|
199
|
+
const [keys, values] = this.updateAndFetchQuantized(k, v);
|
|
200
|
+
return affineRead(keys, values, held, kernel);
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
override updateAndFetchQuantized(k: MlxArray, v: MlxArray): [ops.QuantizedTensor, ops.QuantizedTensor] {
|
|
205
|
+
const r = this.inner.updateAndFetchQuantized(k, v);
|
|
206
|
+
this.offset = this.inner.offset;
|
|
207
|
+
return r;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
override makeMask(N: number, _windowSize: number | null): Mask {
|
|
211
|
+
// Same convention as BatchedDecodeMaskCache: built pre-write, S spans
|
|
212
|
+
// offset + N; quantizedSdpaUnfused expands it for the 5-D GQA scores.
|
|
213
|
+
const S = this.inner.offset + N;
|
|
214
|
+
return { mode: "array", arr: buildBatchedDecodeMask(this.B, N, S, this.leftPad, null) };
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/** Free the per-step RoPE array WITHOUT touching the persistent inner —
|
|
218
|
+
* name and signature must match the scheduler's structural
|
|
219
|
+
* `(c as { releaseRopeArr?: () => void })` cast. */
|
|
220
|
+
releaseRopeArr(): void {
|
|
221
|
+
this.ropeOffsetArr.dispose();
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
override state(): MlxArray[] {
|
|
225
|
+
return this.inner.state();
|
|
226
|
+
}
|
|
227
|
+
override isTrimmable(): boolean {
|
|
228
|
+
return this.inner.isTrimmable();
|
|
229
|
+
}
|
|
230
|
+
override trim(n: number): void {
|
|
231
|
+
this.inner.trim(n);
|
|
232
|
+
}
|
|
233
|
+
override dispose(): void {
|
|
234
|
+
this.inner.dispose();
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/** The quantized twin of PaddedKVRows: a running batch's affine-quantized
|
|
239
|
+
* full-attention KV as (packed, scales, biases) triples with right-aligned rows.
|
|
240
|
+
* Its rows were converted by the serial per-layer maintenance before they
|
|
241
|
+
* joined, so their quantized bytes are the serial cache's; this layout only
|
|
242
|
+
* re-arranges them along the batch axis (`extendQuantRows`, or the re-merge
|
|
243
|
+
* with MLX_BUN_BATCH_EXTEND=0) and, while any row is padded, supplies the
|
|
244
|
+
* padding-aware mask and per-row RoPE positions. Sources are borrowed: a
|
|
245
|
+
* running layout or one serial QuantizedKVCache, then one per joining row.
|
|
246
|
+
*
|
|
247
|
+
* Reads: while no row is padded, the serial cache's reads; while one is, the
|
|
248
|
+
* step wrapper's (`BatchedQuantDecodeMaskCache`): the same kernels under the
|
|
249
|
+
* padding-aware mask, with the masked-window kernel for a window. The kernels
|
|
250
|
+
* are `kernels` while the layout holds one unpadded row and its `multiRow` set
|
|
251
|
+
* otherwise (`rowLayoutKernels`), chosen when the rows change; rows it
|
|
252
|
+
* extracts keep `kernels`. */
|
|
253
|
+
export class PaddedQuantKVRows implements AttentionCache, BatchableCache {
|
|
254
|
+
#inner: QuantizedKVCache;
|
|
255
|
+
#leftPad: number[] = [];
|
|
256
|
+
#padded = false;
|
|
257
|
+
#view: BatchedQuantDecodeMaskCache | null = null;
|
|
258
|
+
|
|
259
|
+
constructor(
|
|
260
|
+
readonly groupSize: number,
|
|
261
|
+
readonly bits: number,
|
|
262
|
+
/** The kernel of each read; empty batches and extracted rows keep them. */
|
|
263
|
+
readonly kernels: AffineKernels,
|
|
264
|
+
/** The per-forward masks of the model this layout serves. */
|
|
265
|
+
readonly masks = new AttentionMasks(),
|
|
266
|
+
) { this.#inner = this.#storage([]); }
|
|
267
|
+
|
|
268
|
+
appendDecode(k: MlxArray, v: MlxArray): AttentionRead {
|
|
269
|
+
return (this.#padded ? this.#step() : this.#inner).appendDecode(k, v);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
appendWindow(k: MlxArray, v: MlxArray): AttentionRead {
|
|
273
|
+
return (this.#padded ? this.#step() : this.#inner).appendWindow(k, v);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/** Empty serial storage that reads with this layout's masks and the kernels for rows at `leftPad`. */
|
|
277
|
+
#storage(leftPad: readonly number[]): QuantizedKVCache {
|
|
278
|
+
return new QuantizedKVCache(this.groupSize, this.bits, rowLayoutKernels(this.kernels, leftPad.length <= 1 && !leftPad[0]), this.masks);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
signature(): string { return `kv:padded-rows-quant:${this.bits}:${this.groupSize}`; }
|
|
282
|
+
get batchSize(): number | null { return this.#leftPad.length || null; }
|
|
283
|
+
get leftPad(): readonly number[] { return this.#leftPad; }
|
|
284
|
+
get rowOffsets(): readonly number[] { return this.#leftPad.map((pad) => this.#inner.offset - pad); }
|
|
285
|
+
get offset(): number { return this.#inner.offset; }
|
|
286
|
+
/** Attention reads and appends the quantized planes directly. */
|
|
287
|
+
get quantizedAttention(): QuantizedAttentionState { return this.#inner; }
|
|
288
|
+
/** Present only while a row is padded: each row's own position. */
|
|
289
|
+
get ropeOffsetArr(): MlxArray | undefined { return this.#padded ? this.#step().ropeOffsetArr : undefined; }
|
|
290
|
+
makeEmptyBatch(): PaddedQuantKVRows { return new PaddedQuantKVRows(this.groupSize, this.bits, this.kernels, this.masks); }
|
|
291
|
+
|
|
292
|
+
bytesPerToken(): number {
|
|
293
|
+
return this.#leftPad.length ? this.#inner.bytesPerToken() / this.#leftPad.length : 0;
|
|
294
|
+
}
|
|
295
|
+
projectedBytes(tokens: number): number { return this.bytesPerToken() * tokens; }
|
|
296
|
+
|
|
297
|
+
/** One wrapper per step: its RoPE positions are fixed at construction, before the step's writes. */
|
|
298
|
+
#step(): BatchedQuantDecodeMaskCache {
|
|
299
|
+
return this.#view ??= new BatchedQuantDecodeMaskCache(this.#inner, this.#leftPad.length, [...this.#leftPad]);
|
|
300
|
+
}
|
|
301
|
+
updateAndFetch(k: MlxArray, v: MlxArray): [MlxArray, MlxArray] { return this.#inner.updateAndFetch(k, v); }
|
|
302
|
+
makeMask(N: number, windowSize: number | null): Mask {
|
|
303
|
+
return this.#padded ? this.#step().makeMask(N, windowSize) : this.#inner.makeMask(N, windowSize);
|
|
304
|
+
}
|
|
305
|
+
/** Free this step's RoPE positions after the step's graph is built; the KV stays. */
|
|
306
|
+
releaseRopeArr(): void { this.#view?.releaseRopeArr(); this.#view = null; }
|
|
307
|
+
state(): MlxArray[] { return this.#inner.state(); }
|
|
308
|
+
isTrimmable(): boolean { return this.#inner.isTrimmable(); }
|
|
309
|
+
trim(n: number): void { this.#inner.trim(n); }
|
|
310
|
+
|
|
311
|
+
#set(inner: QuantizedKVCache, leftPad: number[]): void {
|
|
312
|
+
this.releaseRopeArr();
|
|
313
|
+
this.#inner = inner; this.#leftPad = leftPad; this.#padded = leftPad.some((pad) => pad !== 0);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
mergeRows(rows: readonly Cache[]): void {
|
|
317
|
+
if (this.#leftPad.length) throw new Error("PaddedQuantKVRows.mergeRows needs an empty batch");
|
|
318
|
+
if (!rows.length) return;
|
|
319
|
+
const held: ops.QuantizedTensor[] = [];
|
|
320
|
+
const own = (triple: ops.QuantizedTensor): ops.QuantizedTensor => { held.push(triple); return triple; };
|
|
321
|
+
type Part = { row: QuantRow; pads: number[] };
|
|
322
|
+
const partOf = (cache: Cache): Part => {
|
|
323
|
+
if (cache instanceof PaddedQuantKVRows) {
|
|
324
|
+
const [keys, values] = cache.#inner.temporalView();
|
|
325
|
+
return { row: { keys: own(keys), values: own(values) }, pads: [...cache.#leftPad] };
|
|
326
|
+
}
|
|
327
|
+
if (!isQuantizedKvCache(cache)) throw new Error(`quantized full-attention batch cannot merge ${cache.signature()}`);
|
|
328
|
+
const [keys, values] = cache.temporalView();
|
|
329
|
+
return { row: { keys: own(keys), values: own(values) }, pads: [0] };
|
|
330
|
+
};
|
|
331
|
+
const cut = (triple: ops.QuantizedTensor, b: number, pad: number): ops.QuantizedTensor => own(tripleMap(triple, (a) =>
|
|
332
|
+
a.slice([b, 0, pad, 0], [b + 1, a.shape[1]!, a.shape[2]!, a.shape[3]!])));
|
|
333
|
+
const extend = runtimeValue("MLX_BUN_BATCH_EXTEND") !== "0";
|
|
334
|
+
let final: { keys: ops.QuantizedTensor; values: ops.QuantizedTensor } | undefined;
|
|
335
|
+
try {
|
|
336
|
+
let acc = partOf(rows[0]!);
|
|
337
|
+
if (rows.length === 1) {
|
|
338
|
+
const single = mergeQuantRows(acc.pads.map((pad, b) => ({ keys: cut(acc.row.keys, b, pad), values: cut(acc.row.values, b, pad) })));
|
|
339
|
+
acc = { row: { keys: own(single.keys), values: own(single.values) }, pads: single.leftPad };
|
|
340
|
+
}
|
|
341
|
+
for (const source of rows.slice(1)) {
|
|
342
|
+
const next = partOf(source);
|
|
343
|
+
if (next.pads.length !== 1) throw new Error("PaddedQuantKVRows joins one solo row at a time");
|
|
344
|
+
let joined: { keys: ops.QuantizedTensor; values: ops.QuantizedTensor; leftPad: number[] };
|
|
345
|
+
if (extend) {
|
|
346
|
+
joined = extendQuantRows(acc.row.keys, acc.row.values, acc.pads, next.row);
|
|
347
|
+
} else {
|
|
348
|
+
const rowsOut = acc.pads.map((pad, b) => ({ keys: cut(acc.row.keys, b, pad), values: cut(acc.row.values, b, pad) }));
|
|
349
|
+
joined = mergeQuantRows([...rowsOut, next.row]);
|
|
350
|
+
}
|
|
351
|
+
acc = { row: { keys: own(joined.keys), values: own(joined.values) }, pads: joined.leftPad };
|
|
352
|
+
}
|
|
353
|
+
final = acc.row;
|
|
354
|
+
held.splice(held.indexOf(final.keys), 1); held.splice(held.indexOf(final.values), 1);
|
|
355
|
+
const cache = this.#storage(acc.pads);
|
|
356
|
+
cache.restoreState(final.keys, final.values, final.keys.packed.shape[2]!);
|
|
357
|
+
final = undefined;
|
|
358
|
+
this.#set(cache, acc.pads);
|
|
359
|
+
} finally {
|
|
360
|
+
for (const triple of held) disposeTriple(triple);
|
|
361
|
+
if (final) { disposeTriple(final.keys); disposeTriple(final.values); }
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
extractRow(row: number): Cache {
|
|
366
|
+
return this.#inner.keys ? extractQuantRow(this.#inner, this.#leftPad[row]!, row, this.kernels)
|
|
367
|
+
: new QuantizedKVCache(this.groupSize, this.bits, this.kernels);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
filterRows(keep: readonly number[]): void {
|
|
371
|
+
if (!keep.length) { this.dispose(); return; }
|
|
372
|
+
const kept = keep.map((row) => this.#leftPad[row]!);
|
|
373
|
+
const shared = Math.min(...kept); // padding every survivor shares
|
|
374
|
+
const [keys, values] = this.#inner.temporalView();
|
|
375
|
+
let filtered: { keys: ops.QuantizedTensor; values: ops.QuantizedTensor };
|
|
376
|
+
try { filtered = filterQuantRows(keys, values, [...keep], shared); }
|
|
377
|
+
finally { disposeTriple(keys); disposeTriple(values); }
|
|
378
|
+
const pads = kept.map((pad) => pad - shared);
|
|
379
|
+
const cache = this.#storage(pads);
|
|
380
|
+
cache.restoreState(filtered.keys, filtered.values, this.#inner.offset - shared);
|
|
381
|
+
this.#inner.dispose();
|
|
382
|
+
this.#set(cache, pads);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
dispose(): void {
|
|
386
|
+
this.#inner.dispose();
|
|
387
|
+
this.#set(this.#storage([]), []);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import type { MlxArray } from "@mlx-bun/mlx/array";
|
|
2
|
+
import * as ops from "@mlx-bun/mlx/ops";
|
|
3
|
+
import { cleanupFailure, disposeResources } from "../runtime/resources";
|
|
4
|
+
import { BatchedKVCache } from "./batched-kv";
|
|
5
|
+
import { KVCache } from "./kv";
|
|
6
|
+
import { QuantizedKVCache } from "./quantized-kv";
|
|
7
|
+
import { isQuantizedKvCache } from "./capabilities";
|
|
8
|
+
import { type AttentionRead, type BatchableCache, type Cache, type CommittedAttentionCache, type Mask, type PaddedPrefillCache, type PrefillPadding } from "../contracts/mlx/cache";
|
|
9
|
+
import type { QuantizedAttentionState } from "./kv-storage";
|
|
10
|
+
|
|
11
|
+
import { KvTensorRows } from "./kv-tensor-rows";
|
|
12
|
+
import { quantizedDonorAttention } from "./kv-attention-view";
|
|
13
|
+
import { dequantizedFetch } from "./quantized-tensor";
|
|
14
|
+
import { AttentionMasks, unmaskedLease, withLease, type MaskLease } from "./attention-read";
|
|
15
|
+
import { affineCausalLease, affineRead, committedAffineRead, rowLayoutKernels, type AffineKernel, type AffineKernels } from "./affine-attention";
|
|
16
|
+
|
|
17
|
+
const FIELDS = ["packed", "scales", "biases"] as const;
|
|
18
|
+
|
|
19
|
+
/** Affine KV uses the same row-position storage operations for its packed
|
|
20
|
+
* values, scales and biases. No dequantization or requantization is needed
|
|
21
|
+
* for merge, rollback, retirement or extraction. Inputs share this codec.
|
|
22
|
+
*
|
|
23
|
+
* Reads: append through `updateAndFetchQuantized`, then the kernel `kernels`
|
|
24
|
+
* fixed for the read at construction. While every row is unpadded and at one
|
|
25
|
+
* position, decode passes no mask and a window the bottom-right causal matrix,
|
|
26
|
+
* as `makeMask` leads the graphs to today; otherwise both pass the per-row mask
|
|
27
|
+
* (causal, past each row's left padding) for their query count, built once per
|
|
28
|
+
* forward through `masks`, and a window takes the masked-window kernel. The
|
|
29
|
+
* kernels are `kernels` while the layout holds one unpadded row and its
|
|
30
|
+
* `multiRow` set otherwise (`rowLayoutKernels`); rows it extracts keep
|
|
31
|
+
* `kernels`. The committed read is `quantizedAppendAttention`, for one row. */
|
|
32
|
+
export class BatchedQuantizedKVCache implements BatchableCache, QuantizedAttentionState, PaddedPrefillCache, CommittedAttentionCache {
|
|
33
|
+
#planes = FIELDS.map(() => new BatchedKVCache());
|
|
34
|
+
#minimum: number[] = [];
|
|
35
|
+
get minimumReusableOffset(): number { return Math.max(0, ...this.#minimum); }
|
|
36
|
+
constructor(
|
|
37
|
+
readonly groupSize: number,
|
|
38
|
+
readonly bits: number,
|
|
39
|
+
/** The kernel of each read; empty batches and extracted rows keep them. */
|
|
40
|
+
readonly kernels: AffineKernels,
|
|
41
|
+
/** The per-forward masks of the model this layout serves. */
|
|
42
|
+
readonly masks = new AttentionMasks(),
|
|
43
|
+
) {}
|
|
44
|
+
|
|
45
|
+
appendDecode(k: MlxArray, v: MlxArray): AttentionRead {
|
|
46
|
+
return this.#read(k, v, this.#planes[0]!.aligned ? unmaskedLease : this.#rowMask(1), this.#kernels().decode);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
appendWindow(k: MlxArray, v: MlxArray): AttentionRead {
|
|
50
|
+
const L = k.shape[2]!;
|
|
51
|
+
return this.#planes[0]!.aligned
|
|
52
|
+
? this.#read(k, v, affineCausalLease(this.masks, L, this.offset + L), this.#kernels().window)
|
|
53
|
+
: this.#read(k, v, this.#rowMask(L), this.#kernels().maskedWindow);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** `kernels` while this layout holds one unpadded row, its `multiRow` set otherwise. */
|
|
57
|
+
#kernels(): AffineKernels {
|
|
58
|
+
return rowLayoutKernels(this.kernels, this.batchSize === 1 && this.#planes[0]!.aligned);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Fill spans run on this layout (the target layout of a full-attention
|
|
62
|
+
* layer): the graph's independent-rows path. */
|
|
63
|
+
appendCommitted(k: MlxArray, v: MlxArray): AttentionRead {
|
|
64
|
+
const [keys, values] = this.updateAndFetchQuantized(k, v);
|
|
65
|
+
return committedAffineRead(keys, values, this.groupSize, this.bits);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
#read(k: MlxArray, v: MlxArray, mask: MaskLease, kernel: AffineKernel): AttentionRead {
|
|
69
|
+
return withLease(mask, held => {
|
|
70
|
+
const [keys, values] = this.updateAndFetchQuantized(k, v);
|
|
71
|
+
return affineRead(keys, values, held, kernel);
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** The per-row mask of an `N`-position append, before it: the bf16 layout's
|
|
76
|
+
* (`BatchedKVCache`), whose positions every plane shares. */
|
|
77
|
+
#rowMask(N: number): MaskLease {
|
|
78
|
+
return this.masks.lease("batched-kv", `${N}|${this.rowOffsets.join(",")}|${this.leftPad.join(",")}`,
|
|
79
|
+
() => this.#planes[0]!.makeMask(N, null));
|
|
80
|
+
}
|
|
81
|
+
restorePrefillEnds(ends: readonly number[] | undefined): void { for (const plane of this.#planes) plane.restorePrefillEnds(ends); }
|
|
82
|
+
preparePrefill(padding: PrefillPadding): void {
|
|
83
|
+
for (const plane of this.#planes) plane.preparePrefill(padding);
|
|
84
|
+
if (!this.#minimum.length) this.#minimum = padding.lengths.map(() => 0);
|
|
85
|
+
}
|
|
86
|
+
finalizePrefill(): void { for (const plane of this.#planes) plane.finalizePrefill(); }
|
|
87
|
+
get quantizedAttention(): QuantizedAttentionState { return this; }
|
|
88
|
+
captureDonorAttention(): import("../contracts/mlx/cache").KvDonorAttention {
|
|
89
|
+
const views: import("../contracts/mlx/cache").KvDonorRows[] = [];
|
|
90
|
+
try {
|
|
91
|
+
for (const plane of this.#planes) views.push(plane.captureDonorRows());
|
|
92
|
+
const field = (name: "keys" | "values") => ({
|
|
93
|
+
packed: views[0]![name], scales: views[1]![name], biases: views[2]![name],
|
|
94
|
+
});
|
|
95
|
+
const { offsets, starts, ends } = views[0]!;
|
|
96
|
+
// The kernels this layout reads with for its rows (a one-sequence set
|
|
97
|
+
// reads several rows with its `multiRow` set).
|
|
98
|
+
return quantizedDonorAttention(field("keys"), field("values"), { offsets, starts, ends }, this.#kernels());
|
|
99
|
+
} catch (error) { disposeResources(views.flatMap(view => [view.keys, view.values])); throw error; }
|
|
100
|
+
}
|
|
101
|
+
signature(): string { return `kv:batched-quant:${this.bits}:${this.groupSize}`; }
|
|
102
|
+
get rowOffsets(): readonly number[] { return this.#planes[0]!.rowOffsets; }
|
|
103
|
+
get leftPad(): readonly number[] { return this.#planes[0]!.leftPad; }
|
|
104
|
+
get batchSize(): number | null { return this.#planes[0]!.batchSize; }
|
|
105
|
+
get offset(): number { return this.#planes[0]!.offset; }
|
|
106
|
+
get ropeOffsetArr(): MlxArray | undefined { return this.#planes[0]!.ropeOffsetArr; }
|
|
107
|
+
makeEmptyBatch(): BatchedQuantizedKVCache { return new BatchedQuantizedKVCache(this.groupSize, this.bits, this.kernels, this.masks); }
|
|
108
|
+
bytesPerToken(): number { return this.#planes.reduce((n, plane) => n + plane.bytesPerToken(), 0); }
|
|
109
|
+
projectedBytes(tokens: number): number { return this.bytesPerToken() * tokens; }
|
|
110
|
+
makeMask(tokens: number, window: number | null): Mask { return this.#planes[0]!.makeMask(tokens, window); }
|
|
111
|
+
isTrimmable(): boolean { return true; }
|
|
112
|
+
trim(count: number): void { for (const plane of this.#planes) plane.trim(count); }
|
|
113
|
+
specRoundBegin(): void { for (const plane of this.#planes) plane.specRoundBegin(); }
|
|
114
|
+
specRoundCommit(): void { for (const plane of this.#planes) plane.specRoundCommit(); }
|
|
115
|
+
specRoundRollback(keep: number | readonly number[]): void {
|
|
116
|
+
for (const plane of this.#planes) plane.specRoundRollback(keep);
|
|
117
|
+
}
|
|
118
|
+
/** The dense fetch: appends, then the rows' stored windows dequantized. */
|
|
119
|
+
updateAndFetch(k: MlxArray, v: MlxArray): [MlxArray, MlxArray] {
|
|
120
|
+
return dequantizedFetch(this.updateAndFetchQuantized(k, v), this.groupSize, this.bits);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
updateAndFetchQuantized(k: MlxArray, v: MlxArray): [ops.QuantizedTensor, ops.QuantizedTensor] {
|
|
124
|
+
using inputs = new DisposableStack();
|
|
125
|
+
const key = ops.quantize(k, this.groupSize, this.bits);
|
|
126
|
+
for (const field of FIELDS) inputs.use(key[field]);
|
|
127
|
+
const value = ops.quantize(v, this.groupSize, this.bits);
|
|
128
|
+
for (const field of FIELDS) inputs.use(value[field]);
|
|
129
|
+
const held: MlxArray[] = [], keys: Partial<ops.QuantizedTensor> = {}, values: Partial<ops.QuantizedTensor> = {};
|
|
130
|
+
try {
|
|
131
|
+
for (const [index, field] of FIELDS.entries()) {
|
|
132
|
+
const [kk, vv] = this.#planes[index]!.updateAndFetch(key[field], value[field]);
|
|
133
|
+
held.push(kk, vv); keys[field] = kk; values[field] = vv;
|
|
134
|
+
}
|
|
135
|
+
return [keys as ops.QuantizedTensor, values as ops.QuantizedTensor];
|
|
136
|
+
} catch (error) { return cleanupFailure(error, () => disposeResources(held)); }
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** Borrow prepared request state. All planes stage replacements before
|
|
140
|
+
* releasing the current group, preserving donor ownership on failure. */
|
|
141
|
+
mergeRows(rows: readonly Cache[]): void {
|
|
142
|
+
const next: BatchedKVCache[] = [];
|
|
143
|
+
try {
|
|
144
|
+
for (const [index, field] of FIELDS.entries()) {
|
|
145
|
+
const held: KVCache[] = [], inputs: Cache[] = [];
|
|
146
|
+
try {
|
|
147
|
+
for (const row of rows) {
|
|
148
|
+
if (row instanceof BatchedQuantizedKVCache) inputs.push(row.#planes[index]!);
|
|
149
|
+
else if (isQuantizedKvCache(row)) {
|
|
150
|
+
const view = new KVCache(); held.push(view);
|
|
151
|
+
if (row.offset > 0) {
|
|
152
|
+
const key = ops.copyOf(row.keys![field]);
|
|
153
|
+
let value: MlxArray;
|
|
154
|
+
try { value = ops.copyOf(row.values![field]); }
|
|
155
|
+
catch (error) { key.dispose(); throw error; }
|
|
156
|
+
view.restoreState(key, value, row.offset);
|
|
157
|
+
}
|
|
158
|
+
inputs.push(view);
|
|
159
|
+
} else throw new Error(`quantized row layout cannot merge ${row.signature()}`);
|
|
160
|
+
}
|
|
161
|
+
const plane = new BatchedKVCache(); next.push(plane); plane.mergeRows(inputs);
|
|
162
|
+
} finally { disposeResources(held); }
|
|
163
|
+
}
|
|
164
|
+
} catch (error) { return cleanupFailure(error, () => disposeResources(next)); }
|
|
165
|
+
const previous = this.#planes; this.#planes = next; disposeResources(previous);
|
|
166
|
+
this.#minimum = rows.flatMap(row => row instanceof BatchedQuantizedKVCache ? row.#minimum : [row.minimumReusableOffset ?? 0]);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
alignRows(leftPad: readonly number[]): void {
|
|
170
|
+
if (leftPad.every((pad, row) => pad === this.leftPad[row])) return;
|
|
171
|
+
const aligned = new KvTensorRows();
|
|
172
|
+
try {
|
|
173
|
+
aligned.mergeRows([{ planes: this.#planes.flatMap(plane => plane.keys ? [plane.keys, plane.values!] : []),
|
|
174
|
+
rowOffsets: this.rowOffsets, leftPad: this.leftPad }]);
|
|
175
|
+
aligned.alignRows(leftPad);
|
|
176
|
+
for (const plane of this.#planes) {
|
|
177
|
+
plane.keys?.dispose(); plane.values?.dispose();
|
|
178
|
+
plane.keys = aligned.planes.shift() ?? null; plane.values = aligned.planes.shift() ?? null;
|
|
179
|
+
plane.leftPad = [...leftPad];
|
|
180
|
+
}
|
|
181
|
+
} finally { aligned.dispose(); }
|
|
182
|
+
}
|
|
183
|
+
filterRows(keep: readonly number[]): void {
|
|
184
|
+
for (const plane of this.#planes) plane.filterRows(keep);
|
|
185
|
+
this.#minimum = keep.map(row => this.#minimum[row]!);
|
|
186
|
+
}
|
|
187
|
+
state(): MlxArray[] {
|
|
188
|
+
if (!this.#planes[0]!.keys) return [];
|
|
189
|
+
return [...this.#planes.map(plane => plane.keys!), ...this.#planes.map(plane => plane.values!)];
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/** Return the existing persisted representation, owning compact arrays. */
|
|
193
|
+
extractRow(row: number): QuantizedKVCache {
|
|
194
|
+
const offset = this.rowOffsets[row]!, result = new QuantizedKVCache(this.groupSize, this.bits, this.kernels);
|
|
195
|
+
if (this.#minimum[row]) result.minimumReusableOffset = this.#minimum[row];
|
|
196
|
+
if (offset === 0) return result;
|
|
197
|
+
const held: KVCache[] = [];
|
|
198
|
+
try {
|
|
199
|
+
for (const plane of this.#planes) held.push(plane.extractRow(row));
|
|
200
|
+
result.restoreState(
|
|
201
|
+
{ packed: held[0]!.keys!, scales: held[1]!.keys!, biases: held[2]!.keys! },
|
|
202
|
+
{ packed: held[0]!.values!, scales: held[1]!.values!, biases: held[2]!.values! }, offset);
|
|
203
|
+
for (const cache of held) cache.keys = cache.values = null;
|
|
204
|
+
return result;
|
|
205
|
+
} finally { disposeResources(held); }
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
dispose(): void { disposeResources(this.#planes); this.#minimum = []; this.masks.clear(); }
|
|
209
|
+
}
|