@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,616 @@
|
|
|
1
|
+
import { MlxArray } from "@mlx-bun/mlx/array";
|
|
2
|
+
import { Dtype } from "@mlx-bun/mlx/ffi";
|
|
3
|
+
import * as ops from "@mlx-bun/mlx/ops";
|
|
4
|
+
import type { AttentionRead,BlockAttentionCache,CommittedAttentionCache,CompiledDecodeCache,DecodeSlot,DecodeStepInputs,DecodeStepPlan,DecodeTrace,KvDonorAttention,Mask } from "../contracts/mlx/cache";
|
|
5
|
+
import type { QuantizedAttentionState } from "./kv-storage";
|
|
6
|
+
import { createCausalMask } from "../kernels/attention/masks";
|
|
7
|
+
|
|
8
|
+
import { activeQuantViews,quantConcatDecodeSlot } from "./quantized-kv";
|
|
9
|
+
import { dequantizedFetch,disposeTriple,mapTriple } from "./quantized-tensor";
|
|
10
|
+
import { AttentionMasks, decodeByPosition, rotatingBlockMask, unmaskedLease, withLease, type MaskLease } from "./attention-read";
|
|
11
|
+
import { affineCausalLease, affineRead, unfusedAffineKernels, viewAffineKernel, type AffineKernel, type AffineKernels, type AffineKernelSet } from "./affine-attention";
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
/** Quantized rotating (sliding-window) KV cache — port of optiq
|
|
15
|
+
* runtime/kv/rotating.py RotatingQuantizedKVCache with keep=0
|
|
16
|
+
* (gemma4's configuration): RotatingKVCache's ring mechanics over
|
|
17
|
+
* (packed, scales, biases) triples, storage convention identical to
|
|
18
|
+
* QuantizedKVCache. Returns ACTIVE QUANTIZED SLICES — the oracle's
|
|
19
|
+
* module docstring claims dequantize-on-read, but its code does not
|
|
20
|
+
* (port follows the code). optiq's producer-registry
|
|
21
|
+
* + SDPA patches are unnecessary here: our SharedKv carries
|
|
22
|
+
* groupSize/bits through the donor→sharer plumbing explicitly.
|
|
23
|
+
*
|
|
24
|
+
* Built empty, it quantizes from token zero, unlike the served path's
|
|
25
|
+
* `Bf16FirstRotatingQuantizedKVCache`, so its first chunk's numbers differ.
|
|
26
|
+
*
|
|
27
|
+
* Reads: the sliding window is `maxSize`, the window every graph passes for
|
|
28
|
+
* the layers it gives this cache. Decode writes in place at the ring head and
|
|
29
|
+
* attends the whole ring with no mask (eviction enforces the window). A window
|
|
30
|
+
* writes by concatenation, in temporal order, under the bottom-right causal
|
|
31
|
+
* matrix with the window kernel, or `createCausalMask` with the sliding window
|
|
32
|
+
* and the masked-window kernel once the window binds. The kernels are fixed at
|
|
33
|
+
* construction. Today the graphs' `updateAndFetchQuantized` picks the in-place
|
|
34
|
+
* write for one position and `quantizedSdpa` picks the unfused port for one
|
|
35
|
+
* query, so a one-row window concatenates here (the same keys, in another
|
|
36
|
+
* order once the ring wraps) and takes the window kernel. */
|
|
37
|
+
export class RotatingQuantizedKVCache implements CompiledDecodeCache, CommittedAttentionCache, BlockAttentionCache {
|
|
38
|
+
get quantizedAttention(): QuantizedAttentionState { return this; }
|
|
39
|
+
declare minimumReusableOffset?: number;
|
|
40
|
+
static readonly STEP = 256;
|
|
41
|
+
/** Set only by compiled-decode trace adapters (see Cache). */
|
|
42
|
+
readonly ropeOffsetArr?: MlxArray;
|
|
43
|
+
keys: ops.QuantizedTensor | null = null;
|
|
44
|
+
values: ops.QuantizedTensor | null = null;
|
|
45
|
+
offset = 0;
|
|
46
|
+
/** Ring write index (oracle `_idx`); public so toQuantized replay
|
|
47
|
+
* and persistence can carry it. */
|
|
48
|
+
ringIdx = 0;
|
|
49
|
+
readonly maxSize: number;
|
|
50
|
+
|
|
51
|
+
constructor(
|
|
52
|
+
maxSize: number,
|
|
53
|
+
readonly groupSize: number,
|
|
54
|
+
readonly bits: number,
|
|
55
|
+
/** The kernel of each read. */
|
|
56
|
+
readonly kernels: AffineKernels,
|
|
57
|
+
/** The per-forward masks of the model this cache belongs to. Row layouts
|
|
58
|
+
* made from this cache (`state/layout`) share them. */
|
|
59
|
+
readonly masks = new AttentionMasks(),
|
|
60
|
+
) {
|
|
61
|
+
this.maxSize = maxSize;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** The kernel set affine storage restored at this cache's layer reads with
|
|
65
|
+
* (`state/persistence`): this cache's own kernels. */
|
|
66
|
+
get kernelSet(): AffineKernelSet { return () => this.kernels; }
|
|
67
|
+
|
|
68
|
+
signature(): string { return `kv:rotating-quant:${this.bits}:${this.groupSize}`; }
|
|
69
|
+
|
|
70
|
+
appendDecode(k: MlxArray, v: MlxArray): AttentionRead {
|
|
71
|
+
const [keys, values] = this.#updateInPlace(k, v);
|
|
72
|
+
return affineRead(keys, values, unmaskedLease, this.kernels.decode);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** The mask is `makeMask`'s with the window at `maxSize`, before the append:
|
|
76
|
+
* plain causal while the window has room, the window-bound array after. */
|
|
77
|
+
appendWindow(k: MlxArray, v: MlxArray): AttentionRead {
|
|
78
|
+
const N = k.shape[2]!, offset = Math.min(this.maxSize - 1, this.offset);
|
|
79
|
+
return offset + N <= this.maxSize
|
|
80
|
+
? this.#windowRead(k, v, affineCausalLease(this.masks, N, offset + N), this.kernels.window)
|
|
81
|
+
: this.#windowRead(k, v, this.masks.lease(`rotating:${this.maxSize}`, `${N}:${offset}`,
|
|
82
|
+
() => ({ mode: "array", arr: createCausalMask(N, offset, this.maxSize) })), this.kernels.maskedWindow);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** A committed span, one decode per position (`decodeByPosition`): the
|
|
86
|
+
* in-place ring write and the decode kernel each position would get. */
|
|
87
|
+
appendCommitted(k: MlxArray, v: MlxArray): AttentionRead {
|
|
88
|
+
return decodeByPosition(this, k, v);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** A block read (`BlockAttentionCache.readBlock`) as `RotatingKVCache`
|
|
92
|
+
* makes it: the live window in temporal order, cut to the newest
|
|
93
|
+
* `maxSize - 1` positions once it is full, then the block's keys and values
|
|
94
|
+
* quantized as this ring stores them and not appended, under the same mask
|
|
95
|
+
* (`rotatingBlockMask`), by the masked-window kernel. */
|
|
96
|
+
readBlock(k: MlxArray, v: MlxArray): AttentionRead {
|
|
97
|
+
let [storedK, storedV] = this.#temporalView();
|
|
98
|
+
const window = Math.max(this.maxSize - 1, 0);
|
|
99
|
+
const storedLength = storedK.packed.shape[2]!;
|
|
100
|
+
const L = k.shape[2]!;
|
|
101
|
+
const mask = storedLength <= window ? unmaskedLease : rotatingBlockMask(this.masks, this.maxSize, this.offset, L, storedLength);
|
|
102
|
+
let maskArr = mask.mask.arr;
|
|
103
|
+
const held: ops.QuantizedTensor[] = [storedK, storedV];
|
|
104
|
+
const blockK = ops.quantize(k, this.groupSize, this.bits), blockV = ops.quantize(v, this.groupSize, this.bits);
|
|
105
|
+
held.push(blockK, blockV);
|
|
106
|
+
try {
|
|
107
|
+
if (window && storedLength > window && this.offset >= storedLength) {
|
|
108
|
+
const newest = (a: MlxArray) => a.slice([0, 0, storedLength - window, 0], a.shape);
|
|
109
|
+
storedK = mapTriple(storedK, newest); held.push(storedK);
|
|
110
|
+
storedV = mapTriple(storedV, newest); held.push(storedV);
|
|
111
|
+
if (maskArr) {
|
|
112
|
+
const columns = maskArr.shape;
|
|
113
|
+
maskArr = maskArr.slice([0, 0, 0, columns[3]! - (window + L)], columns);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
const keys = concatTriples(storedK, blockK);
|
|
117
|
+
let values: ops.QuantizedTensor;
|
|
118
|
+
try { values = concatTriples(storedV, blockV); }
|
|
119
|
+
catch (error) { disposeTriple(keys); throw error; }
|
|
120
|
+
const sliced = maskArr !== mask.mask.arr ? maskArr : null;
|
|
121
|
+
const read: Mask = maskArr ? { mode: "array", arr: maskArr } : mask.mask;
|
|
122
|
+
return {
|
|
123
|
+
attend: (q, scale) => this.kernels.maskedWindow(q, keys, values, scale, read),
|
|
124
|
+
dispose() { disposeTriple(keys); disposeTriple(values); sliced?.dispose(); mask.release(); },
|
|
125
|
+
};
|
|
126
|
+
} catch (error) {
|
|
127
|
+
if (maskArr !== mask.mask.arr) maskArr?.dispose();
|
|
128
|
+
mask.release();
|
|
129
|
+
throw error;
|
|
130
|
+
} finally { for (const t of held) disposeTriple(t); }
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** The live window in temporal order: the newest min(offset, maxSize)
|
|
134
|
+
* positions (`RotatingKVCache.temporalView`). The caller owns both. */
|
|
135
|
+
#temporalView(): [ops.QuantizedTensor, ops.QuantizedTensor] {
|
|
136
|
+
if (!this.keys || !this.values) throw new Error("cache is empty");
|
|
137
|
+
const valid = Math.min(this.offset, this.maxSize);
|
|
138
|
+
const newest = (t: ops.QuantizedTensor): ops.QuantizedTensor => {
|
|
139
|
+
const ordered = this.#temporalOrder(t);
|
|
140
|
+
try {
|
|
141
|
+
return mapTriple(ordered, a => {
|
|
142
|
+
const [B, H, S, D] = a.shape as [number, number, number, number];
|
|
143
|
+
return a.slice([0, 0, Math.max(0, S - valid), 0], [B, H, S, D]);
|
|
144
|
+
});
|
|
145
|
+
} finally { disposeTriple(ordered); }
|
|
146
|
+
};
|
|
147
|
+
const keys = newest(this.keys);
|
|
148
|
+
try { return [keys, newest(this.values)]; }
|
|
149
|
+
catch (error) { disposeTriple(keys); throw error; }
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
#windowRead(k: MlxArray, v: MlxArray, mask: MaskLease, kernel: AffineKernel): AttentionRead {
|
|
153
|
+
return withLease(mask, held => {
|
|
154
|
+
const [keys, values] = this.#updateConcat(k, v);
|
|
155
|
+
return affineRead(keys, values, held, kernel);
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
bytesPerToken(): number {
|
|
160
|
+
const steps = this.keys?.packed.shape[2] ?? 0;
|
|
161
|
+
if (steps === 0 || !this.keys || !this.values) return 0;
|
|
162
|
+
const tensors = [
|
|
163
|
+
this.keys.packed, this.keys.scales, this.keys.biases,
|
|
164
|
+
this.values.packed, this.values.scales, this.values.biases,
|
|
165
|
+
];
|
|
166
|
+
return tensors.reduce((bytes, array) => bytes + array.nbytes, 0) / steps;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** The dense fetch: appends, then the stored ring (as the affine fetch
|
|
170
|
+
* hands it out) dequantized. */
|
|
171
|
+
updateAndFetch(k: MlxArray, v: MlxArray): [MlxArray, MlxArray] {
|
|
172
|
+
return dequantizedFetch(this.updateAndFetchQuantized(k, v), this.groupSize, this.bits);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
#seqLen(): number {
|
|
176
|
+
return this.keys ? this.keys.packed.shape[2]! : 0;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** Empty (packed, scales, biases) triple of T tokens (oracle _alloc_pair). */
|
|
180
|
+
#allocPair(B: number, H: number, T: number, dim: number, dtype: Dtype): ops.QuantizedTensor {
|
|
181
|
+
const elPerInt = 32 / this.bits;
|
|
182
|
+
return {
|
|
183
|
+
packed: ops.zeros([B, H, T, dim / elPerInt], Dtype.uint32),
|
|
184
|
+
scales: ops.zeros([B, H, T, dim / this.groupSize], dtype),
|
|
185
|
+
biases: ops.zeros([B, H, T, dim / this.groupSize], dtype),
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** Ring contents rearranged into temporal order, per component (keep=0). */
|
|
190
|
+
#temporalOrder(t: ops.QuantizedTensor): ops.QuantizedTensor {
|
|
191
|
+
const S = t.packed.shape[2]!;
|
|
192
|
+
const cut = (a: MlxArray, from: number, to: number): MlxArray => {
|
|
193
|
+
const [B, H, , D] = a.shape as [number, number, number, number];
|
|
194
|
+
return a.slice([0, 0, from, 0], [B, H, to, D]);
|
|
195
|
+
};
|
|
196
|
+
if (this.ringIdx === S) return mapTriple(t, (a) => cut(a, 0, S));
|
|
197
|
+
if (this.ringIdx < this.offset) {
|
|
198
|
+
return mapTriple(t, (a) => {
|
|
199
|
+
const tail = cut(a, this.ringIdx, S);
|
|
200
|
+
const head = cut(a, 0, this.ringIdx);
|
|
201
|
+
const out = ops.concatAxis([tail, head], 2);
|
|
202
|
+
tail.dispose();
|
|
203
|
+
head.dispose();
|
|
204
|
+
return out;
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
return mapTriple(t, (a) => cut(a, 0, this.ringIdx));
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
#trim(trimSize: number, t: ops.QuantizedTensor, append: ops.QuantizedTensor | null): ops.QuantizedTensor {
|
|
211
|
+
const part = (a: MlxArray, ap: MlxArray | null): MlxArray => {
|
|
212
|
+
const [B, H, S, D] = a.shape as [number, number, number, number];
|
|
213
|
+
const base = a.slice([0, 0, trimSize > 0 ? trimSize : 0, 0], [B, H, S, D]);
|
|
214
|
+
if (!ap) return base;
|
|
215
|
+
const out = ops.concatAxis([base, ap], 2);
|
|
216
|
+
base.dispose();
|
|
217
|
+
return out;
|
|
218
|
+
};
|
|
219
|
+
return {
|
|
220
|
+
packed: part(t.packed, append?.packed ?? null),
|
|
221
|
+
scales: part(t.scales, append?.scales ?? null),
|
|
222
|
+
biases: part(t.biases, append?.biases ?? null),
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
#updateConcat(k: MlxArray, v: MlxArray): [ops.QuantizedTensor, ops.QuantizedTensor] {
|
|
227
|
+
const S = k.shape[2]!;
|
|
228
|
+
const kq = ops.quantize(k, this.groupSize, this.bits);
|
|
229
|
+
const vq = ops.quantize(v, this.groupSize, this.bits);
|
|
230
|
+
if (!this.keys || !this.values) {
|
|
231
|
+
this.keys = kq;
|
|
232
|
+
this.values = vq;
|
|
233
|
+
} else {
|
|
234
|
+
const tk = this.#temporalOrder(this.keys);
|
|
235
|
+
const tv = this.#temporalOrder(this.values);
|
|
236
|
+
disposeTriple(this.keys);
|
|
237
|
+
disposeTriple(this.values);
|
|
238
|
+
this.ringIdx = tk.packed.shape[2]!;
|
|
239
|
+
const trimSize = this.ringIdx - this.maxSize + 1;
|
|
240
|
+
this.keys = this.#trim(trimSize, tk, kq);
|
|
241
|
+
this.values = this.#trim(trimSize, tv, vq);
|
|
242
|
+
for (const t of [tk, tv, kq, vq]) disposeTriple(t);
|
|
243
|
+
}
|
|
244
|
+
this.offset += S;
|
|
245
|
+
this.ringIdx = this.#seqLen();
|
|
246
|
+
return this.#activeSlices();
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
#updateInPlace(k: MlxArray, v: MlxArray): [ops.QuantizedTensor, ops.QuantizedTensor] {
|
|
250
|
+
const [B, H, S, D] = k.shape as [number, number, number, number];
|
|
251
|
+
const vD = v.shape[3]!;
|
|
252
|
+
const prev = this.offset;
|
|
253
|
+
|
|
254
|
+
if (!this.keys || (prev >= this.#seqLen() && this.#seqLen() < this.maxSize)) {
|
|
255
|
+
const newSize = Math.min(RotatingQuantizedKVCache.STEP, this.maxSize - prev);
|
|
256
|
+
const newK = this.#allocPair(B, H, newSize, D, k.dtype);
|
|
257
|
+
const newV = this.#allocPair(B, H, newSize, vD, v.dtype);
|
|
258
|
+
if (this.keys && this.values) {
|
|
259
|
+
const grow = (old: ops.QuantizedTensor, add: ops.QuantizedTensor): ops.QuantizedTensor => {
|
|
260
|
+
const cat = (a: MlxArray, b: MlxArray): MlxArray => {
|
|
261
|
+
const out = ops.concatAxis([a, b], 2);
|
|
262
|
+
a.dispose();
|
|
263
|
+
b.dispose();
|
|
264
|
+
return out;
|
|
265
|
+
};
|
|
266
|
+
return {
|
|
267
|
+
packed: cat(old.packed, add.packed),
|
|
268
|
+
scales: cat(old.scales, add.scales),
|
|
269
|
+
biases: cat(old.biases, add.biases),
|
|
270
|
+
};
|
|
271
|
+
};
|
|
272
|
+
this.keys = grow(this.keys, newK);
|
|
273
|
+
this.values = grow(this.values, newV);
|
|
274
|
+
} else {
|
|
275
|
+
this.keys = newK;
|
|
276
|
+
this.values = newV;
|
|
277
|
+
}
|
|
278
|
+
this.ringIdx = prev;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const trimSize = this.#seqLen() - this.maxSize;
|
|
282
|
+
if (trimSize > 0) {
|
|
283
|
+
const tk = this.#trim(trimSize, this.keys!, null);
|
|
284
|
+
const tv = this.#trim(trimSize, this.values!, null);
|
|
285
|
+
disposeTriple(this.keys!);
|
|
286
|
+
disposeTriple(this.values!);
|
|
287
|
+
this.keys = tk;
|
|
288
|
+
this.values = tv;
|
|
289
|
+
this.ringIdx = this.maxSize;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (this.ringIdx === this.maxSize) this.ringIdx = 0; // rotate (keep=0)
|
|
293
|
+
|
|
294
|
+
const kq = ops.quantize(k, this.groupSize, this.bits);
|
|
295
|
+
const vq = ops.quantize(v, this.groupSize, this.bits);
|
|
296
|
+
const writeAt = (dst: ops.QuantizedTensor, src: ops.QuantizedTensor): ops.QuantizedTensor => ({
|
|
297
|
+
packed: this.#assign(dst.packed, src.packed, S),
|
|
298
|
+
scales: this.#assign(dst.scales, src.scales, S),
|
|
299
|
+
biases: this.#assign(dst.biases, src.biases, S),
|
|
300
|
+
});
|
|
301
|
+
this.keys = writeAt(this.keys!, kq);
|
|
302
|
+
this.values = writeAt(this.values!, vq);
|
|
303
|
+
disposeTriple(kq);
|
|
304
|
+
disposeTriple(vq);
|
|
305
|
+
|
|
306
|
+
this.offset += S;
|
|
307
|
+
this.ringIdx += S;
|
|
308
|
+
return this.#activeSlices();
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
#assign(dst: MlxArray, src: MlxArray, S: number): MlxArray {
|
|
312
|
+
const [B, H, , D] = dst.shape as [number, number, number, number];
|
|
313
|
+
const out = ops.sliceUpdate(dst, src, [0, 0, this.ringIdx, 0], [B, H, this.ringIdx + S, D]);
|
|
314
|
+
dst.dispose();
|
|
315
|
+
return out;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/** Active window as quantized triples (oracle _active_slices; fresh
|
|
319
|
+
* view handles so callers own what they dispose). */
|
|
320
|
+
#activeSlices(): [ops.QuantizedTensor, ops.QuantizedTensor] {
|
|
321
|
+
const upTo = this.offset < this.maxSize ? this.offset : this.#seqLen();
|
|
322
|
+
const cut = (a: MlxArray): MlxArray => {
|
|
323
|
+
const [B, H, , D] = a.shape as [number, number, number, number];
|
|
324
|
+
return a.slice([0, 0, 0, 0], [B, H, upTo, D]);
|
|
325
|
+
};
|
|
326
|
+
return [mapTriple(this.keys!, cut), mapTriple(this.values!, cut)];
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/** Quantize incoming k/v and write into the ring; returns active
|
|
330
|
+
* quantized triples (S=1 in place, S>1 via temporal-order + concat). */
|
|
331
|
+
updateAndFetchQuantized(k: MlxArray, v: MlxArray): [ops.QuantizedTensor, ops.QuantizedTensor] {
|
|
332
|
+
return k.shape[2]! === 1 ? this.#updateInPlace(k, v) : this.#updateConcat(k, v);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/** Same mask formula as RotatingKVCache (inherited in the oracle). */
|
|
336
|
+
makeMask(N: number, windowSize: number | null): Mask {
|
|
337
|
+
const window = windowSize ?? this.maxSize;
|
|
338
|
+
if (N > 1) {
|
|
339
|
+
const offset = Math.min(this.maxSize - 1, this.offset);
|
|
340
|
+
if (offset + N > window)
|
|
341
|
+
return { mode: "array", arr: createCausalMask(N, offset, window) };
|
|
342
|
+
return { mode: "causal", arr: null };
|
|
343
|
+
}
|
|
344
|
+
return { mode: "", arr: null };
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
state(): MlxArray[] {
|
|
348
|
+
if (!this.keys || !this.values) return [];
|
|
349
|
+
return [
|
|
350
|
+
this.keys.packed, this.keys.scales, this.keys.biases,
|
|
351
|
+
this.values.packed, this.values.scales, this.values.biases,
|
|
352
|
+
];
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/** Ring rule (inherited semantics): trimmable only before wrap. */
|
|
356
|
+
isTrimmable(): boolean {
|
|
357
|
+
return this.offset < this.maxSize;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
trim(n: number, bypass = false): void {
|
|
361
|
+
const k = Math.min(this.offset, n);
|
|
362
|
+
if (!bypass) {
|
|
363
|
+
this.offset -= k;
|
|
364
|
+
this.ringIdx -= k;
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
// Spec-decode rollback after a >1 concat write — physically slice the last k
|
|
368
|
+
// off each (packed, scales, biases) component and keep ringIdx === seqLen.
|
|
369
|
+
this.offset -= k;
|
|
370
|
+
if (k > 0 && this.keys && this.values) {
|
|
371
|
+
const S = this.#seqLen();
|
|
372
|
+
const cut = (a: MlxArray): MlxArray => {
|
|
373
|
+
const [B, H, , D] = a.shape as [number, number, number, number];
|
|
374
|
+
return a.slice([0, 0, 0, 0], [B, H, S - k, D]);
|
|
375
|
+
};
|
|
376
|
+
const nk = mapTriple(this.keys, cut);
|
|
377
|
+
const nv = mapTriple(this.values, cut);
|
|
378
|
+
disposeTriple(this.keys);
|
|
379
|
+
disposeTriple(this.values);
|
|
380
|
+
this.keys = nk;
|
|
381
|
+
this.values = nv;
|
|
382
|
+
this.ringIdx = this.#seqLen();
|
|
383
|
+
} else {
|
|
384
|
+
this.ringIdx -= k;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/** Adopt persisted state (takes ownership of the triples' arrays) —
|
|
389
|
+
* ring order as-laid-out, ringIdx carried with it
|
|
390
|
+
* (packages/inference/src/state/persistence.ts). */
|
|
391
|
+
restoreState(keys: ops.QuantizedTensor, values: ops.QuantizedTensor, offset: number, idx: number): void {
|
|
392
|
+
this.dispose();
|
|
393
|
+
this.keys = keys;
|
|
394
|
+
this.values = values;
|
|
395
|
+
this.offset = offset;
|
|
396
|
+
this.ringIdx = idx;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/** The live window as donor attention (see temporalView). Caller owns the returned views. */
|
|
400
|
+
captureDonorAttention(): KvDonorAttention {
|
|
401
|
+
const [keys, values] = this.temporalView();
|
|
402
|
+
const B = keys.packed.shape[0]!, width = keys.packed.shape[2]!;
|
|
403
|
+
return { width, dtype: keys.scales.dtype, offsets: Array(B).fill(this.offset),
|
|
404
|
+
starts: Array(B).fill(0), ends: Array(B).fill(width),
|
|
405
|
+
attend: (q, scale, mask) => viewAffineKernel(this.kernels, q.shape[2]!, mask)(q, keys, values, scale, mask),
|
|
406
|
+
dispose() { disposeTriple(keys); disposeTriple(values); },
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/** The live window in chronological order: the newest min(offset, maxSize)
|
|
411
|
+
* positions, the quantized twin of RotatingKVCache.temporalView (the tail
|
|
412
|
+
* of an oversized multi-token block, never its head). */
|
|
413
|
+
temporalView(): [ops.QuantizedTensor, ops.QuantizedTensor] {
|
|
414
|
+
if (!this.keys || !this.values) throw new Error("cache is empty");
|
|
415
|
+
const valid = Math.min(this.offset, this.maxSize);
|
|
416
|
+
const cutValid = (t: ops.QuantizedTensor): ops.QuantizedTensor =>
|
|
417
|
+
mapTriple(t, (a) => {
|
|
418
|
+
const [B, H, S, D] = a.shape as [number, number, number, number];
|
|
419
|
+
return a.slice([0, 0, Math.max(0, S - valid), 0], [B, H, S, D]);
|
|
420
|
+
});
|
|
421
|
+
const tk = this.#temporalOrder(this.keys);
|
|
422
|
+
const tv = this.#temporalOrder(this.values);
|
|
423
|
+
const out: [ops.QuantizedTensor, ops.QuantizedTensor] = [cutValid(tk), cutValid(tv)];
|
|
424
|
+
disposeTriple(tk);
|
|
425
|
+
disposeTriple(tv);
|
|
426
|
+
return out;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/** Oracle: to_quantized on an already-quantized rotating cache is
|
|
430
|
+
* idempotent. */
|
|
431
|
+
toQuantized(): RotatingQuantizedKVCache {
|
|
432
|
+
return this;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/** Compiled decode: host-side bookkeeping of #updateInPlace (L=1) —
|
|
436
|
+
* growth, oversize trim, rotation — without quantize/write. */
|
|
437
|
+
prepareDecodeStep(): DecodeStepPlan {
|
|
438
|
+
const prev = this.offset;
|
|
439
|
+
if (!this.keys || !this.values) throw new Error("compiled decode on an empty cache");
|
|
440
|
+
if (prev >= this.#seqLen() && this.#seqLen() < this.maxSize) {
|
|
441
|
+
const [B, H, , pD] = this.keys.packed.shape as [number, number, number, number];
|
|
442
|
+
const elPerInt = 32 / this.bits;
|
|
443
|
+
const kD = pD * elPerInt;
|
|
444
|
+
const vD = this.values.packed.shape[3]! * elPerInt;
|
|
445
|
+
const dtype = this.keys.scales.dtype;
|
|
446
|
+
const newSize = Math.min(RotatingQuantizedKVCache.STEP, this.maxSize - prev);
|
|
447
|
+
const newK = this.#allocPair(B, H, newSize, kD, dtype);
|
|
448
|
+
const newV = this.#allocPair(B, H, newSize, vD, dtype);
|
|
449
|
+
const grow = (old: ops.QuantizedTensor, add: ops.QuantizedTensor): ops.QuantizedTensor => {
|
|
450
|
+
const cat = (a: MlxArray, b: MlxArray): MlxArray => {
|
|
451
|
+
const out = ops.concatAxis([a, b], 2);
|
|
452
|
+
a.dispose();
|
|
453
|
+
b.dispose();
|
|
454
|
+
return out;
|
|
455
|
+
};
|
|
456
|
+
return {
|
|
457
|
+
packed: cat(old.packed, add.packed),
|
|
458
|
+
scales: cat(old.scales, add.scales),
|
|
459
|
+
biases: cat(old.biases, add.biases),
|
|
460
|
+
};
|
|
461
|
+
};
|
|
462
|
+
this.keys = grow(this.keys, newK);
|
|
463
|
+
this.values = grow(this.values, newV);
|
|
464
|
+
this.ringIdx = prev;
|
|
465
|
+
}
|
|
466
|
+
const trimSize = this.#seqLen() - this.maxSize;
|
|
467
|
+
if (trimSize > 0) {
|
|
468
|
+
const tk = this.#trim(trimSize, this.keys, null);
|
|
469
|
+
const tv = this.#trim(trimSize, this.values, null);
|
|
470
|
+
disposeTriple(this.keys);
|
|
471
|
+
disposeTriple(this.values);
|
|
472
|
+
this.keys = tk;
|
|
473
|
+
this.values = tv;
|
|
474
|
+
this.ringIdx = this.maxSize;
|
|
475
|
+
}
|
|
476
|
+
if (this.ringIdx === this.maxSize) this.ringIdx = 0; // rotate (keep=0)
|
|
477
|
+
const fetch = prev + 1 < this.maxSize && this.ringIdx === prev ? "concat" : "ring";
|
|
478
|
+
return { fetch, writePos: this.ringIdx, activeLen: prev };
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/** Non-mutating twin of prepareDecodeStep's fetch choice (see RotatingKVCache). */
|
|
482
|
+
decodePhase(): DecodeStepPlan["fetch"] {
|
|
483
|
+
return this.offset + 1 < this.maxSize ? "concat" : "ring";
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
decodeSlot(plan: DecodeStepPlan): DecodeSlot {
|
|
487
|
+
return plan.fetch === "concat"
|
|
488
|
+
? quantConcatDecodeSlot(this.offset, this.groupSize, this.bits)
|
|
489
|
+
: quantRingDecodeSlot(this.offset, this.maxSize, this.groupSize, this.bits);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
decodeInputs(plan: DecodeStepPlan, step: DecodeStepInputs): MlxArray[] {
|
|
493
|
+
if (plan.fetch === "concat") return activeQuantViews(this.keys!, this.values!, plan.activeLen, step);
|
|
494
|
+
return [...this.state(), step.writePosition(plan.writePos)];
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
commitDecodeStep(slot: DecodeSlot, outputs: MlxArray[]): MlxArray[] {
|
|
498
|
+
// see RotatingKVCache.commitDecodeStep
|
|
499
|
+
if (slot.fetch === "ring") { this.adoptDecodeStep(outputs); return []; }
|
|
500
|
+
return this.writeDecodeStep(outputs);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/** Compiled decode, concat fetch: six sliceUpdates of the in-graph-
|
|
504
|
+
* quantized step row. Takes ownership; returns updated buffers. */
|
|
505
|
+
writeDecodeStep(rows: MlxArray[]): MlxArray[] {
|
|
506
|
+
const w = (d: MlxArray, srcRow: MlxArray): MlxArray => {
|
|
507
|
+
const [b, h, , dd] = d.shape as [number, number, number, number];
|
|
508
|
+
const u = ops.sliceUpdate(d, srcRow, [0, 0, this.ringIdx, 0], [b, h, this.ringIdx + 1, dd]);
|
|
509
|
+
d.dispose();
|
|
510
|
+
srcRow.dispose();
|
|
511
|
+
return u;
|
|
512
|
+
};
|
|
513
|
+
this.keys = {
|
|
514
|
+
packed: w(this.keys!.packed, rows[0]!),
|
|
515
|
+
scales: w(this.keys!.scales, rows[1]!),
|
|
516
|
+
biases: w(this.keys!.biases, rows[2]!),
|
|
517
|
+
};
|
|
518
|
+
this.values = {
|
|
519
|
+
packed: w(this.values!.packed, rows[3]!),
|
|
520
|
+
scales: w(this.values!.scales, rows[4]!),
|
|
521
|
+
biases: w(this.values!.biases, rows[5]!),
|
|
522
|
+
};
|
|
523
|
+
this.offset += 1;
|
|
524
|
+
this.ringIdx += 1;
|
|
525
|
+
return this.state();
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
/** Compiled decode, ring fetch: adopt the six in-graph-updated buffers
|
|
529
|
+
* (closure outputs, in state() order; ownership transfers here). */
|
|
530
|
+
adoptDecodeStep(bufs: MlxArray[]): MlxArray[] {
|
|
531
|
+
disposeTriple(this.keys!);
|
|
532
|
+
disposeTriple(this.values!);
|
|
533
|
+
this.keys = { packed: bufs[0]!, scales: bufs[1]!, biases: bufs[2]! };
|
|
534
|
+
this.values = { packed: bufs[3]!, scales: bufs[4]!, biases: bufs[5]! };
|
|
535
|
+
this.offset += 1;
|
|
536
|
+
this.ringIdx += 1;
|
|
537
|
+
return bufs;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
dispose(): void {
|
|
541
|
+
if (this.keys) disposeTriple(this.keys);
|
|
542
|
+
if (this.values) disposeTriple(this.values);
|
|
543
|
+
this.keys = this.values = null;
|
|
544
|
+
this.offset = 0;
|
|
545
|
+
this.ringIdx = 0;
|
|
546
|
+
this.masks.clear();
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
/** The rotating quantized cache at steady state inside a compiled decode trace:
|
|
551
|
+
* quantize plus six dynamic writes in graph, fetch the full updated buffers
|
|
552
|
+
* (mirrors the oracle's _update_in_place -> _active_slices). */
|
|
553
|
+
class TracedRingQuantizedKVCache extends RotatingQuantizedKVCache implements DecodeTrace {
|
|
554
|
+
override readonly ropeOffsetArr: MlxArray;
|
|
555
|
+
outs: MlxArray[] = [];
|
|
556
|
+
constructor(
|
|
557
|
+
offset: number, maxSize: number, groupSize: number, bits: number,
|
|
558
|
+
readonly bufKq: ops.QuantizedTensor,
|
|
559
|
+
readonly bufVq: ops.QuantizedTensor,
|
|
560
|
+
readonly writePosArr: MlxArray,
|
|
561
|
+
ropeOffsetArr: MlxArray,
|
|
562
|
+
) {
|
|
563
|
+
// A compiled step is one-token decode, unfused in every composition.
|
|
564
|
+
super(maxSize, groupSize, bits, unfusedAffineKernels(bits, groupSize, Dtype.bfloat16));
|
|
565
|
+
this.offset = offset;
|
|
566
|
+
this.ropeOffsetArr = ropeOffsetArr;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
override updateAndFetchQuantized(k: MlxArray, v: MlxArray): [ops.QuantizedTensor, ops.QuantizedTensor] {
|
|
570
|
+
const kq = ops.quantize(k, this.groupSize, this.bits);
|
|
571
|
+
const vq = ops.quantize(v, this.groupSize, this.bits);
|
|
572
|
+
const upd = (buf: ops.QuantizedTensor, row: ops.QuantizedTensor): ops.QuantizedTensor => ({
|
|
573
|
+
packed: ops.sliceUpdateDynamic(buf.packed, row.packed, this.writePosArr, [2]),
|
|
574
|
+
scales: ops.sliceUpdateDynamic(buf.scales, row.scales, this.writePosArr, [2]),
|
|
575
|
+
biases: ops.sliceUpdateDynamic(buf.biases, row.biases, this.writePosArr, [2]),
|
|
576
|
+
});
|
|
577
|
+
const updK = upd(this.bufKq, kq);
|
|
578
|
+
const updV = upd(this.bufVq, vq);
|
|
579
|
+
for (const t of [kq, vq])
|
|
580
|
+
for (const a of [t.packed, t.scales, t.biases]) a.dispose();
|
|
581
|
+
this.outs = [updK.packed, updK.scales, updK.biases, updV.packed, updV.scales, updV.biases];
|
|
582
|
+
const whole = (a: MlxArray): MlxArray => a.slice(a.shape.map(() => 0), a.shape);
|
|
583
|
+
return [
|
|
584
|
+
{ packed: whole(updK.packed), scales: whole(updK.scales), biases: whole(updK.biases) },
|
|
585
|
+
{ packed: whole(updV.packed), scales: whole(updV.scales), biases: whole(updV.biases) },
|
|
586
|
+
];
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
/** The decode read over the in-graph write: the whole updated ring with no
|
|
590
|
+
* mask and the decode kernel, as `RotatingQuantizedKVCache.appendDecode`
|
|
591
|
+
* reads the ring it writes in place. */
|
|
592
|
+
override appendDecode(k: MlxArray, v: MlxArray): AttentionRead {
|
|
593
|
+
const [keys, values] = this.updateAndFetchQuantized(k, v);
|
|
594
|
+
return affineRead(keys, values, unmaskedLease, this.kernels.decode);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
/** `a` then `b` along the sequence axis, per component; the caller owns the result. */
|
|
599
|
+
function concatTriples(a: ops.QuantizedTensor, b: ops.QuantizedTensor): ops.QuantizedTensor {
|
|
600
|
+
const packed = ops.concatAxis([a.packed, b.packed], 2);
|
|
601
|
+
try {
|
|
602
|
+
const scales = ops.concatAxis([a.scales, b.scales], 2);
|
|
603
|
+
try { return { packed, scales, biases: ops.concatAxis([a.biases, b.biases], 2) }; }
|
|
604
|
+
catch (error) { scales.dispose(); throw error; }
|
|
605
|
+
} catch (error) { packed.dispose(); throw error; }
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
function quantRingDecodeSlot(offset: number, maxSize: number, groupSize: number, bits: number): DecodeSlot {
|
|
609
|
+
const triple = (inputs: readonly MlxArray[], at: number): ops.QuantizedTensor =>
|
|
610
|
+
({ packed: inputs[at]!, scales: inputs[at + 1]!, biases: inputs[at + 2]! });
|
|
611
|
+
return {
|
|
612
|
+
key: `q-ring:${groupSize}:${bits}`, fetch: "ring", inputs: 7, outputs: 6,
|
|
613
|
+
trace: (inputs, ropeOffset) =>
|
|
614
|
+
new TracedRingQuantizedKVCache(offset, maxSize, groupSize, bits, triple(inputs, 0), triple(inputs, 3), inputs[6]!, ropeOffset),
|
|
615
|
+
};
|
|
616
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import * as ops from "@mlx-bun/mlx/ops";
|
|
2
|
+
import { BatchedRotatingCache } from "./batched-rotating";
|
|
3
|
+
import { BatchedRotatingQuantCache } from "./batched-rotating-quant";
|
|
4
|
+
import { rotatingRollbackPlan } from "./batched-rotating-state";
|
|
5
|
+
import { plainRowStorage, quantizedRowStorage, rowRollIndices } from "./batched-row-storage";
|
|
6
|
+
|
|
7
|
+
export type RotatingRing = BatchedRotatingCache | BatchedRotatingQuantCache;
|
|
8
|
+
|
|
9
|
+
/** Retain accepted columns without decoding or recomputing the ring. Ownership
|
|
10
|
+
* moves to the returned ring; aligned members preserve their shared rectangle. */
|
|
11
|
+
export function rollbackRotatingRing(ring: BatchedRotatingCache, before: readonly number[], keep: number | readonly number[], preserveColumns?: boolean): BatchedRotatingCache;
|
|
12
|
+
export function rollbackRotatingRing(ring: BatchedRotatingQuantCache, before: readonly number[], keep: number | readonly number[], preserveColumns?: boolean): BatchedRotatingQuantCache;
|
|
13
|
+
export function rollbackRotatingRing(ring: RotatingRing, before: readonly number[], keep: number | readonly number[], preserveColumns?: boolean): RotatingRing;
|
|
14
|
+
export function rollbackRotatingRing(ring: RotatingRing, before: readonly number[],
|
|
15
|
+
keep: number | readonly number[], preserveColumns = false): RotatingRing {
|
|
16
|
+
const plan = rotatingRollbackPlan(ring.positionSnapshot, before, keep, preserveColumns);
|
|
17
|
+
if ("trim" in plan) { ring.trim(plan.trim); ring.releaseRopeArr(); return ring; }
|
|
18
|
+
let next: RotatingRing;
|
|
19
|
+
if (ring instanceof BatchedRotatingQuantCache) {
|
|
20
|
+
using indices = rowRollIndices(ring.keys!.packed.shape[2]!, plan.shifts);
|
|
21
|
+
const keys = quantizedRowStorage.rollRows(ring.keys!, indices);
|
|
22
|
+
let values: ops.QuantizedTensor;
|
|
23
|
+
try { values = quantizedRowStorage.rollRows(ring.values!, indices); }
|
|
24
|
+
catch (error) { quantizedRowStorage.dispose(keys); throw error; }
|
|
25
|
+
next = BatchedRotatingQuantCache.adoptPhysical(keys, values, ring.groupSize, ring.bits, ring.kernels, plan.position, ring.masks);
|
|
26
|
+
} else {
|
|
27
|
+
using indices = rowRollIndices(ring.keys!.shape[2]!, plan.shifts);
|
|
28
|
+
const keys = plainRowStorage.rollRows(ring.keys!, indices);
|
|
29
|
+
let values: import("@mlx-bun/mlx/array").MlxArray;
|
|
30
|
+
try { values = plainRowStorage.rollRows(ring.values!, indices); }
|
|
31
|
+
catch (error) { keys.dispose(); throw error; }
|
|
32
|
+
next = BatchedRotatingCache.adoptPhysical(keys, values, plan.position, ring.masks);
|
|
33
|
+
}
|
|
34
|
+
ring.dispose(); return next;
|
|
35
|
+
}
|