@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,215 @@
|
|
|
1
|
+
import type { 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 { AttentionCache, AttentionRead, Mask } from "../contracts/mlx/cache";
|
|
5
|
+
import { bidirMask } from "../kernels/attention/masks";
|
|
6
|
+
import { foldedSdpa, foldedSdpaEligible } from "../layers/folded-attention";
|
|
7
|
+
import { disposeResources } from "../runtime/resources";
|
|
8
|
+
|
|
9
|
+
/** A mask held by one attention read until that read is disposed. */
|
|
10
|
+
export interface MaskLease {
|
|
11
|
+
readonly mask: Mask;
|
|
12
|
+
/** Drops this read's hold on the mask. Call exactly once. */
|
|
13
|
+
release(): void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** No mask: one query over every stored position. Nothing to release. */
|
|
17
|
+
export const unmaskedLease: MaskLease = { mask: { mode: "", arr: null }, release() {} };
|
|
18
|
+
/** The fused kernel's bottom-right-aligned causal mask. Nothing to release. */
|
|
19
|
+
export const causalLease: MaskLease = { mask: { mode: "causal", arr: null }, release() {} };
|
|
20
|
+
|
|
21
|
+
interface MaskSlot {
|
|
22
|
+
readonly key: string;
|
|
23
|
+
readonly source: object | null;
|
|
24
|
+
readonly mask: Mask;
|
|
25
|
+
/** This memo's hold plus one per unreleased lease. */
|
|
26
|
+
holders: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* The attention masks of one forward, built once and shared by every layer
|
|
31
|
+
* cache composed with this instance.
|
|
32
|
+
*
|
|
33
|
+
* A cache leases its mask here inside each named read, under a family and a
|
|
34
|
+
* key. The family names how the mask is built and is fixed when the cache is
|
|
35
|
+
* constructed: the cache's mask builder and its sliding window. The key holds
|
|
36
|
+
* every input that builder reads from the cache before the append: the query
|
|
37
|
+
* count and the row offsets, padding and ring position, plus `source`, compared
|
|
38
|
+
* by identity, for a mask built from a caller's tensor. The first lease of a key
|
|
39
|
+
* builds the mask. The other layers of the same forward stand at the same
|
|
40
|
+
* positions, so they lease the same mask. The next forward's appends have moved
|
|
41
|
+
* the offsets, so its first lease builds a new mask and drops this memo's hold
|
|
42
|
+
* on the old one. Each family keeps one mask, so a model's sliding and full
|
|
43
|
+
* layers do not evict each other. Equal keys always mean equal masks, so sharing
|
|
44
|
+
* an instance can only save builds, never change a result; caches at different
|
|
45
|
+
* positions that share one and alternate within a forward rebuild on each turn.
|
|
46
|
+
*
|
|
47
|
+
* A lease keeps its mask alive until the read holding it is disposed, so a read
|
|
48
|
+
* stays valid after a later forward replaces the mask. `clear()` drops only this
|
|
49
|
+
* memo's holds; a cache calls it from `dispose()`.
|
|
50
|
+
*
|
|
51
|
+
* Neither the graph nor the scheduler sees this. A model composes one instance
|
|
52
|
+
* into the caches its `makeCache()` makes (each cache constructor takes it; a
|
|
53
|
+
* cache built without one keeps its own); empty batches and the row layouts
|
|
54
|
+
* made from a cache (`state/layout`) take their source's instance. Not a module
|
|
55
|
+
* singleton: caches share masks only through an instance they were given.
|
|
56
|
+
*/
|
|
57
|
+
export class AttentionMasks {
|
|
58
|
+
readonly #slots = new Map<string, MaskSlot>();
|
|
59
|
+
#builds = 0;
|
|
60
|
+
|
|
61
|
+
/** Masks built so far (diagnostic; tests count it). */
|
|
62
|
+
get builds(): number { return this.#builds; }
|
|
63
|
+
|
|
64
|
+
/** The mask `build` makes for `family` at `key` (and `source`), built on the
|
|
65
|
+
* first lease of that key. The caller owns the lease, not the mask: release
|
|
66
|
+
* the lease, never dispose `lease.mask.arr`. */
|
|
67
|
+
lease(family: string, key: string, build: () => Mask, source: object | null = null): MaskLease {
|
|
68
|
+
let slot = this.#slots.get(family);
|
|
69
|
+
if (!slot || slot.key !== key || slot.source !== source) {
|
|
70
|
+
const mask = build();
|
|
71
|
+
this.#builds++;
|
|
72
|
+
if (slot) drop(slot);
|
|
73
|
+
slot = { key, source, mask, holders: 1 };
|
|
74
|
+
this.#slots.set(family, slot);
|
|
75
|
+
}
|
|
76
|
+
const held = slot;
|
|
77
|
+
held.holders++;
|
|
78
|
+
return { mask: held.mask, release: () => drop(held) };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Drop this memo's holds. Leased masks stay valid until released. */
|
|
82
|
+
clear(): void {
|
|
83
|
+
for (const slot of this.#slots.values()) drop(slot);
|
|
84
|
+
this.#slots.clear();
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function drop(slot: MaskSlot): void {
|
|
89
|
+
if (--slot.holders === 0) slot.mask.arr?.dispose();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** The encoder window's mask over an `L`-position first window
|
|
93
|
+
* (`EncoderAttentionCache.appendEncoderWindow`): causal, OR'd with the overlay
|
|
94
|
+
* of positions both flagged in `bidirectional`, with no sliding window, as one
|
|
95
|
+
* [1, 1, L, L] bool array (the shape the DiffusionGemma encoder built on the
|
|
96
|
+
* host). No cache's window enters it, so every cache of a model leases it
|
|
97
|
+
* under one family and a forward builds it once. */
|
|
98
|
+
export function encoderWindowMask(masks: AttentionMasks, L: number, bidirectional: MlxArray): MaskLease {
|
|
99
|
+
return masks.lease("encoder-window", `${L}`, () => {
|
|
100
|
+
const { arr } = bidirMask(L, null, bidirectional);
|
|
101
|
+
try { return { mode: "array", arr: ops.reshape(arr!, [1, 1, L, L]) }; }
|
|
102
|
+
finally { arr!.dispose(); }
|
|
103
|
+
}, bidirectional);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** DiffusionGemma's sliding-layer decoder mask (its `#makeDecoderMasks`) for a
|
|
107
|
+
* block read over a ring of `maxSize` holding `offset` positions:
|
|
108
|
+
* `storedLength` stored positions in temporal order, of which the newest
|
|
109
|
+
* `maxSize - 1` that hold a valid position are visible, then all `N` block
|
|
110
|
+
* positions, as one bool [1, 1, 1, storedLength + N] row every query shares.
|
|
111
|
+
* The rings of a model lease it under one family per window. */
|
|
112
|
+
export function rotatingBlockMask(masks: AttentionMasks, maxSize: number, offset: number, N: number,
|
|
113
|
+
storedLength: number): MaskLease {
|
|
114
|
+
const valid = Math.min(offset, storedLength);
|
|
115
|
+
return masks.lease(`rotating-block:${maxSize}`, `${N}:${storedLength}:${valid}`, () => {
|
|
116
|
+
const start = Math.max(0, valid - Math.max(maxSize - 1, 0));
|
|
117
|
+
const positions = ops.arange(0, storedLength, 1, Dtype.int32);
|
|
118
|
+
const startArr = ops.scalarLike(start, positions);
|
|
119
|
+
const validArr = ops.scalarLike(valid, positions);
|
|
120
|
+
const geStart = ops.greaterEqual(positions, startArr);
|
|
121
|
+
const ltValid = ops.less(positions, validArr);
|
|
122
|
+
const stored = ops.logicalAnd(geStart, ltValid);
|
|
123
|
+
positions.dispose();
|
|
124
|
+
startArr.dispose();
|
|
125
|
+
validArr.dispose();
|
|
126
|
+
geStart.dispose();
|
|
127
|
+
ltValid.dispose();
|
|
128
|
+
using ones = ops.fromInt32(new Array(N).fill(1), [N]);
|
|
129
|
+
const block = ones.astype(stored.dtype);
|
|
130
|
+
const row = ops.concatAxis([stored, block], 0);
|
|
131
|
+
stored.dispose();
|
|
132
|
+
block.dispose();
|
|
133
|
+
const reshaped = ops.reshape(row, [1, 1, 1, storedLength + N]);
|
|
134
|
+
row.dispose();
|
|
135
|
+
return { mode: "array", arr: reshaped };
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** A read over dense stored keys and values under a leased mask; it takes
|
|
140
|
+
* ownership of `keys`, `values` and `mask`. A bf16 storage reads its windows
|
|
141
|
+
* with the one its composition names (`KVCache.windowRead`). */
|
|
142
|
+
export type DenseRead = (keys: MlxArray, values: MlxArray, mask: MaskLease) => AttentionRead;
|
|
143
|
+
|
|
144
|
+
/** The read over dense stored keys and values: the stock fused SDPA kernel
|
|
145
|
+
* (`ops.sdpa`) under the leased mask. Takes ownership of `keys`, `values` and
|
|
146
|
+
* `mask`. */
|
|
147
|
+
export function sdpaRead(keys: MlxArray, values: MlxArray, mask: MaskLease): AttentionRead {
|
|
148
|
+
return {
|
|
149
|
+
attend: (q, scale) => ops.sdpa(q, keys, values, scale, mask.mask.mode, mask.mask.arr),
|
|
150
|
+
dispose() { keys.dispose(); values.dispose(); mask.release(); },
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** `sdpaRead`, reading each KV head's keys and values once for a causal window
|
|
155
|
+
* MLX's fused SDPA hands to its unfused fallback (up to 8 queries per row, more
|
|
156
|
+
* than 32 per KV head: `foldedSdpaEligible`), where that fallback reads them
|
|
157
|
+
* once per query head: such a window folds GQA into rows (`foldedSdpa`: the
|
|
158
|
+
* fallback's bits on `applegpu_g13s`, where it was measured, bf16 precision
|
|
159
|
+
* elsewhere); every other read is the stock fused SDPA. Takes ownership of
|
|
160
|
+
* `keys`, `values` and `mask`. */
|
|
161
|
+
export function foldedWindowRead(keys: MlxArray, values: MlxArray, mask: MaskLease): AttentionRead {
|
|
162
|
+
return {
|
|
163
|
+
attend: (q, scale) => mask.mask.mode === "causal" && foldedSdpaEligible(q, keys)
|
|
164
|
+
? foldedSdpa(q, keys, values, scale) : ops.sdpa(q, keys, values, scale, mask.mask.mode, mask.mask.arr),
|
|
165
|
+
dispose() { keys.dispose(); values.dispose(); mask.release(); },
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** A committed span (`CommittedAttentionCache.appendCommitted`) read one
|
|
170
|
+
* position at a time: each of the span's `L` positions appends through
|
|
171
|
+
* `cache.appendDecode` and keeps that one-position read, so query `i` attends
|
|
172
|
+
* its own causal prefix with the decode arithmetic and the storage holds what
|
|
173
|
+
* `L` decodes leave. The committed read of ring storage, whose window read is
|
|
174
|
+
* not the one-position arithmetic (a ring writes a window by concatenation and,
|
|
175
|
+
* once it wraps, reorders its keys). `k` and `v` are borrowed, as for every
|
|
176
|
+
* append. */
|
|
177
|
+
export function decodeByPosition(cache: AttentionCache, k: MlxArray, v: MlxArray): AttentionRead {
|
|
178
|
+
const L = k.shape[2]!;
|
|
179
|
+
const at = (a: MlxArray, i: number) => a.slice([0, 0, i, 0], [a.shape[0]!, a.shape[1]!, i + 1, a.shape[3]!]);
|
|
180
|
+
const reads: AttentionRead[] = [];
|
|
181
|
+
try {
|
|
182
|
+
for (let i = 0; i < L; i++) {
|
|
183
|
+
using key = at(k, i), value = at(v, i);
|
|
184
|
+
reads.push(cache.appendDecode(key, value));
|
|
185
|
+
}
|
|
186
|
+
} catch (error) { disposeResources(reads); throw error; }
|
|
187
|
+
return {
|
|
188
|
+
attend(q, scale) {
|
|
189
|
+
const outputs: MlxArray[] = [];
|
|
190
|
+
try {
|
|
191
|
+
for (const [i, read] of reads.entries()) {
|
|
192
|
+
using query = at(q, i);
|
|
193
|
+
outputs.push(read.attend(query, scale));
|
|
194
|
+
}
|
|
195
|
+
return outputs.length === 1 ? ops.contiguous(outputs[0]!) : ops.concatAxis(outputs, 2);
|
|
196
|
+
} finally { disposeResources(outputs); }
|
|
197
|
+
},
|
|
198
|
+
dispose() { disposeResources(reads); },
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/** Run a read's storage step under a mask leased before it, releasing the
|
|
203
|
+
* lease if the step throws. */
|
|
204
|
+
export function withLease(mask: MaskLease, read: (mask: MaskLease) => AttentionRead): AttentionRead {
|
|
205
|
+
try { return read(mask); }
|
|
206
|
+
catch (error) { mask.release(); throw error; }
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/** Retain an independent array handle for positions used across cache writes.
|
|
210
|
+
* The contiguous view preserves the integer values and shares their storage;
|
|
211
|
+
* callers release it with the fetched attention state. */
|
|
212
|
+
export function captureRopeOffsets(cache: { readonly ropeOffsetArr?: MlxArray }): MlxArray | undefined {
|
|
213
|
+
const offsets = cache.ropeOffsetArr;
|
|
214
|
+
return offsets ? ops.contiguous(offsets) : undefined;
|
|
215
|
+
}
|
|
@@ -0,0 +1,294 @@
|
|
|
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 { materializeCopy } from "@mlx-bun/mlx/materialize";
|
|
5
|
+
import { KVCache } from "./kv";
|
|
6
|
+
import { isPlainKvCache } from "./capabilities";
|
|
7
|
+
import { type AttentionRead, type BatchableCache, type Cache, type CommittedAttentionCache, type Mask, type PaddedPrefillCache, type PrefillPadding } from "../contracts/mlx/cache";
|
|
8
|
+
import { FullPrefillPadding } from "./full-prefill-padding";
|
|
9
|
+
import { AttentionMasks, causalLease, sdpaRead, unmaskedLease, withLease, type DenseRead, type MaskLease } from "./attention-read";
|
|
10
|
+
|
|
11
|
+
/** Full-attention storage with independently advancing row positions.
|
|
12
|
+
* Speculative rollback changes coverage, without moving the retained KV.
|
|
13
|
+
* Subsequent appends overwrite each row's rejected suffix in place when MLX
|
|
14
|
+
* can donate the buffer. Queue membership is outside this layout.
|
|
15
|
+
*
|
|
16
|
+
* Reads: append through `updateAndFetch`, then the stock fused SDPA, a window
|
|
17
|
+
* through `windowRead` (its source storage's). While every row is unpadded and
|
|
18
|
+
* at one position, decode passes no mask and a window the fused causal mask;
|
|
19
|
+
* otherwise both pass the per-row mask (causal, past each row's left padding)
|
|
20
|
+
* for their query count, built once per forward through `masks`. */
|
|
21
|
+
export class BatchedKVCache implements BatchableCache, PaddedPrefillCache, CommittedAttentionCache {
|
|
22
|
+
keys: MlxArray | null = null;
|
|
23
|
+
values: MlxArray | null = null;
|
|
24
|
+
rowOffsets: number[] = [];
|
|
25
|
+
leftPad: number[] = [];
|
|
26
|
+
#rope: MlxArray | undefined;
|
|
27
|
+
#beforeRound: number[] | undefined;
|
|
28
|
+
readonly #padding = new FullPrefillPadding();
|
|
29
|
+
|
|
30
|
+
constructor(
|
|
31
|
+
/** The per-forward masks of the model this layout serves. */
|
|
32
|
+
readonly masks = new AttentionMasks(),
|
|
33
|
+
/** The window read of the storage it was made from (`KVCache.windowRead`). */
|
|
34
|
+
readonly windowRead: DenseRead = sdpaRead,
|
|
35
|
+
) {}
|
|
36
|
+
|
|
37
|
+
appendDecode(k: MlxArray, v: MlxArray): AttentionRead {
|
|
38
|
+
return this.#read(k, v, this.#mask(1, unmaskedLease), sdpaRead);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
appendWindow(k: MlxArray, v: MlxArray): AttentionRead {
|
|
42
|
+
return this.#read(k, v, this.#mask(k.shape[2]!, causalLease), this.windowRead);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Fill spans run on this layout (the target layout of a full-attention
|
|
46
|
+
* layer). A bf16 committed span reads exactly as a window does. */
|
|
47
|
+
appendCommitted(k: MlxArray, v: MlxArray): AttentionRead {
|
|
48
|
+
return this.appendWindow(k, v);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
#read(k: MlxArray, v: MlxArray, mask: MaskLease, read: DenseRead): AttentionRead {
|
|
52
|
+
return withLease(mask, held => {
|
|
53
|
+
const [keys, values] = this.updateAndFetch(k, v);
|
|
54
|
+
return read(keys, values, held);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** `aligned` while every row is unpadded at one position; otherwise the
|
|
59
|
+
* per-row mask of an `N`-position append, before it. */
|
|
60
|
+
#mask(N: number, aligned: MaskLease): MaskLease {
|
|
61
|
+
if (this.#aligned()) return aligned;
|
|
62
|
+
return this.masks.lease("batched-kv", `${N}|${this.rowOffsets.join(",")}|${this.leftPad.join(",")}`,
|
|
63
|
+
() => ({ mode: "array", arr: this.#rowMask(N, null) }));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
restorePrefillEnds(ends: readonly number[] | undefined): void { this.#padding.restoreEnds(ends); }
|
|
67
|
+
preparePrefill(padding: PrefillPadding): void {
|
|
68
|
+
this.#padding.prepare(this, padding); this.#positionsChanged();
|
|
69
|
+
}
|
|
70
|
+
finalizePrefill(): void {
|
|
71
|
+
const next = this.#padding.finalize(this.state(), this);
|
|
72
|
+
if (!next) return;
|
|
73
|
+
this.keys?.dispose(); this.values?.dispose();
|
|
74
|
+
this.keys = next[0] ?? null; this.values = next[1] ?? null;
|
|
75
|
+
this.#positionsChanged();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
captureDonorRows(): import("../contracts/mlx/cache").KvDonorRows {
|
|
79
|
+
const B = this.rowOffsets.length, width = this.offset;
|
|
80
|
+
const keys = this.keys!.slice([0,0,0,0], [B,this.keys!.shape[1]!,width,this.keys!.shape[3]!]);
|
|
81
|
+
let values: MlxArray;
|
|
82
|
+
try { values = this.values!.slice([0,0,0,0], [B,this.values!.shape[1]!,width,this.values!.shape[3]!]); }
|
|
83
|
+
catch (error) { keys.dispose(); throw error; }
|
|
84
|
+
return { keys, values, offsets: [...this.rowOffsets], starts: [...this.leftPad], ends: this.#ends() };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
signature(): string { return "kv:batched-positions"; }
|
|
88
|
+
get batchSize(): number | null { return this.rowOffsets.length || null; }
|
|
89
|
+
get offset(): number { return Math.max(0, ...this.#ends()); }
|
|
90
|
+
#ends(): number[] { return this.rowOffsets.map((offset, row) => offset + this.leftPad[row]!); }
|
|
91
|
+
get ropeOffsetArr(): MlxArray | undefined {
|
|
92
|
+
// A common unpadded position uses the existing scalar RoPE operation.
|
|
93
|
+
if (this.leftPad.every(pad => pad === 0) && this.rowOffsets.every(offset => offset === this.rowOffsets[0])) return undefined;
|
|
94
|
+
return this.#rope ??= ops.fromInt32(this.rowOffsets, [this.rowOffsets.length]);
|
|
95
|
+
}
|
|
96
|
+
#positionsChanged(): void { this.#rope?.dispose(); this.#rope = undefined; }
|
|
97
|
+
makeEmptyBatch(): BatchedKVCache { return new BatchedKVCache(this.masks, this.windowRead); }
|
|
98
|
+
bytesPerToken(): number {
|
|
99
|
+
if (!this.keys || !this.values) return 0;
|
|
100
|
+
return (this.keys.nbytes + this.values.nbytes) / (this.rowOffsets.length * this.keys.shape[2]!);
|
|
101
|
+
}
|
|
102
|
+
projectedBytes(tokens: number): number { return this.bytesPerToken() * tokens; }
|
|
103
|
+
state(): MlxArray[] { return this.keys && this.values ? [this.keys, this.values] : []; }
|
|
104
|
+
isTrimmable(): boolean { return true; }
|
|
105
|
+
trim(count: number): void {
|
|
106
|
+
this.rowOffsets = this.rowOffsets.map(offset => offset - count);
|
|
107
|
+
this.#positionsChanged();
|
|
108
|
+
}
|
|
109
|
+
specRoundBegin(): void { this.#beforeRound = [...this.rowOffsets]; }
|
|
110
|
+
specRoundCommit(): void { this.#beforeRound = undefined; }
|
|
111
|
+
specRoundRollback(keep: number | readonly number[]): void {
|
|
112
|
+
this.rowOffsets = this.#beforeRound!.map((offset, row) => offset + (typeof keep === "number" ? keep : keep[row]!));
|
|
113
|
+
this.#beforeRound = undefined;
|
|
114
|
+
this.#positionsChanged();
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
makeMask(N: number, window: number | null): Mask {
|
|
118
|
+
if (window === null && this.#aligned()) return { mode: N === 1 ? "" : "causal", arr: null };
|
|
119
|
+
return { mode: "array", arr: this.#rowMask(N, window) };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** Every row unpadded and at one position: a windowless read needs no
|
|
123
|
+
* per-row mask. Layouts that keep their positions in this one read it. */
|
|
124
|
+
get aligned(): boolean { return this.#aligned(); }
|
|
125
|
+
|
|
126
|
+
#aligned(): boolean {
|
|
127
|
+
const ends = this.#ends();
|
|
128
|
+
return this.leftPad.every(pad => pad === 0) && ends.every(end => end === ends[0]);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** Causal, past each row's left padding, within `window`: [B, 1, N, S]. */
|
|
132
|
+
#rowMask(N: number, window: number | null): MlxArray {
|
|
133
|
+
const B = this.rowOffsets.length, ends = this.#ends(), S = this.offset + N;
|
|
134
|
+
using starts = ops.fromInt32(ends, [B, 1, 1, 1]);
|
|
135
|
+
using relative = ops.arange(0, N, 1, Dtype.int32);
|
|
136
|
+
using queries = ops.reshape(relative, [1, 1, N, 1]);
|
|
137
|
+
using position = ops.add(starts, queries);
|
|
138
|
+
using indices = ops.arange(0, S, 1, Dtype.int32);
|
|
139
|
+
using keys = ops.reshape(indices, [1, 1, 1, S]);
|
|
140
|
+
using pads = ops.fromInt32(this.leftPad, [B, 1, 1, 1]);
|
|
141
|
+
using causal = ops.lessEqual(keys, position);
|
|
142
|
+
using valid = ops.greaterEqual(keys, pads);
|
|
143
|
+
const mask = ops.logicalAnd(causal, valid);
|
|
144
|
+
if (window === null) return mask;
|
|
145
|
+
using windowSize = ops.fromInt32([window], []);
|
|
146
|
+
using limit = ops.add(keys, windowSize);
|
|
147
|
+
using within = ops.less(position, limit);
|
|
148
|
+
try { return ops.logicalAnd(mask, within); }
|
|
149
|
+
finally { mask.dispose(); }
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
updateAndFetch(k: MlxArray, v: MlxArray): [MlxArray, MlxArray] {
|
|
153
|
+
const B = this.rowOffsets.length, N = k.shape[2]!, ends = this.#ends();
|
|
154
|
+
const needed = Math.max(...ends) + N;
|
|
155
|
+
const grow = (array: MlxArray) => {
|
|
156
|
+
const [rows, heads, capacity, width] = array.shape as [number, number, number, number];
|
|
157
|
+
if (capacity >= needed) return array;
|
|
158
|
+
using padding = ops.zeros([rows, heads, Math.ceil(needed / 256) * 256 - capacity, width], array.dtype);
|
|
159
|
+
const grown = ops.concatAxis([array, padding], 2);
|
|
160
|
+
array.dispose();
|
|
161
|
+
return grown;
|
|
162
|
+
};
|
|
163
|
+
const allocate = (input: MlxArray) => ops.zeros(
|
|
164
|
+
[B, input.shape[1]!, Math.ceil(needed / 256) * 256, input.shape[3]!], input.dtype);
|
|
165
|
+
this.keys = this.keys ? grow(this.keys) : allocate(k);
|
|
166
|
+
this.values = this.values ? grow(this.values) : allocate(v);
|
|
167
|
+
const common = ends.every(end => end === ends[0]);
|
|
168
|
+
using positions = common ? null : ops.fromInt32(ends.flatMap(end =>
|
|
169
|
+
Array.from({ length: N }, (_, position) => end + position)), [B, 1, N, 1]);
|
|
170
|
+
const append = (array: MlxArray, values: MlxArray) => common
|
|
171
|
+
? ops.sliceUpdate(array, values, [0, 0, ends[0]!, 0], [B, array.shape[1]!, ends[0]! + N, array.shape[3]!])
|
|
172
|
+
: ops.putAlongAxis(array, positions!, values, 2);
|
|
173
|
+
const keys = append(this.keys, k);
|
|
174
|
+
let values: MlxArray;
|
|
175
|
+
try { values = append(this.values, v); }
|
|
176
|
+
catch (error) { keys.dispose(); throw error; }
|
|
177
|
+
this.keys.dispose(); this.values.dispose();
|
|
178
|
+
this.keys = keys; this.values = values;
|
|
179
|
+
this.rowOffsets = this.rowOffsets.map(offset => offset + N);
|
|
180
|
+
this.#positionsChanged();
|
|
181
|
+
return [keys.slice([0, 0, 0, 0], [B, keys.shape[1]!, needed, keys.shape[3]!]),
|
|
182
|
+
values.slice([0, 0, 0, 0], [B, values.shape[1]!, needed, values.shape[3]!])];
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
mergeRows(rows: readonly Cache[]): void {
|
|
186
|
+
const only = rows.length === 1 ? rows[0] : undefined;
|
|
187
|
+
if (only && (only instanceof BatchedKVCache || isPlainKvCache(only))) {
|
|
188
|
+
// Preserve allocated capacity when a request starts alone. Immutable
|
|
189
|
+
// aliases transfer ownership without copying its prefill storage.
|
|
190
|
+
const keys = only.keys ? ops.copyOf(only.keys) : null;
|
|
191
|
+
let values: MlxArray | null;
|
|
192
|
+
try { values = only.values ? ops.copyOf(only.values) : null; }
|
|
193
|
+
catch (error) { keys?.dispose(); throw error; }
|
|
194
|
+
const offsets = only instanceof BatchedKVCache ? [...only.rowOffsets] : [only.offset];
|
|
195
|
+
const pads = only instanceof BatchedKVCache ? [...only.leftPad] : [0];
|
|
196
|
+
this.dispose(); this.keys = keys; this.values = values;
|
|
197
|
+
this.rowOffsets = offsets; this.leftPad = pads;
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
const chunks: Array<{ keys: MlxArray | null; values: MlxArray | null; offsets: number[]; pads: number[]; width: number }> = [];
|
|
201
|
+
const held: MlxArray[] = [];
|
|
202
|
+
try {
|
|
203
|
+
for (const cache of rows) {
|
|
204
|
+
let keys: MlxArray | null, values: MlxArray | null, offsets: number[], pads: number[];
|
|
205
|
+
if (cache instanceof BatchedKVCache && cache.offset === 0) {
|
|
206
|
+
keys = values = null; offsets = [...cache.rowOffsets]; pads = [...cache.leftPad];
|
|
207
|
+
} else if (cache instanceof BatchedKVCache) {
|
|
208
|
+
const B = cache.rowOffsets.length, width = cache.offset;
|
|
209
|
+
keys = cache.keys!.slice([0, 0, 0, 0], [B, cache.keys!.shape[1]!, width, cache.keys!.shape[3]!]);
|
|
210
|
+
held.push(keys);
|
|
211
|
+
values = cache.values!.slice([0, 0, 0, 0], [B, cache.values!.shape[1]!, width, cache.values!.shape[3]!]);
|
|
212
|
+
held.push(values);
|
|
213
|
+
offsets = [...cache.rowOffsets]; pads = [...cache.leftPad];
|
|
214
|
+
} else if (isPlainKvCache(cache)) {
|
|
215
|
+
if (cache.offset === 0) keys = values = null;
|
|
216
|
+
else { [keys, values] = cache.temporalView(); held.push(keys, values); }
|
|
217
|
+
offsets = [cache.offset]; pads = [0];
|
|
218
|
+
} else throw new Error(`full-attention batch cannot merge ${cache.signature()}`);
|
|
219
|
+
chunks.push({ keys, values, offsets, pads, width: keys?.shape[2] ?? 0 });
|
|
220
|
+
}
|
|
221
|
+
const width = Math.max(0, ...chunks.map(chunk => chunk.width));
|
|
222
|
+
const padded = (array: MlxArray, count: number) => {
|
|
223
|
+
if (!count) return array;
|
|
224
|
+
using padding = ops.zeros([array.shape[0]!, array.shape[1]!, count, array.shape[3]!], array.dtype);
|
|
225
|
+
const result = ops.concatAxis([padding, array], 2); held.push(result); return result;
|
|
226
|
+
};
|
|
227
|
+
const merge = (field: "keys" | "values") => {
|
|
228
|
+
if (!width) return null; // Geometry arrives with the first append.
|
|
229
|
+
const prototype = chunks.find(chunk => chunk[field])![field]!;
|
|
230
|
+
return ops.concatAxis(chunks.map(chunk => {
|
|
231
|
+
const array = chunk[field];
|
|
232
|
+
if (array) return padded(array, width - chunk.width);
|
|
233
|
+
const empty = ops.zeros([chunk.offsets.length, prototype.shape[1]!, width, prototype.shape[3]!], prototype.dtype);
|
|
234
|
+
held.push(empty); return empty;
|
|
235
|
+
}), 0);
|
|
236
|
+
};
|
|
237
|
+
const keys = merge("keys");
|
|
238
|
+
let values: MlxArray | null;
|
|
239
|
+
try { values = merge("values"); }
|
|
240
|
+
catch (error) { keys?.dispose(); throw error; }
|
|
241
|
+
this.dispose();
|
|
242
|
+
this.keys = keys; this.values = values;
|
|
243
|
+
this.rowOffsets = chunks.flatMap(chunk => chunk.offsets);
|
|
244
|
+
this.leftPad = chunks.flatMap(chunk => chunk.pads.map(pad => pad + width - chunk.width));
|
|
245
|
+
} finally { for (const array of held) array.dispose(); }
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
filterRows(rows: readonly number[]): void {
|
|
249
|
+
if (!rows.length) { this.dispose(); return; }
|
|
250
|
+
const pads = rows.map(row => this.leftPad[row]!);
|
|
251
|
+
const sharedPadding = Math.min(...pads);
|
|
252
|
+
using indices = this.keys ? ops.fromInt32([...rows], [rows.length]) : null;
|
|
253
|
+
const select = (array: MlxArray | null): MlxArray | null => {
|
|
254
|
+
if (!array) return null;
|
|
255
|
+
const selected = ops.takeAxis(array, indices!, 0);
|
|
256
|
+
if (!sharedPadding) return selected;
|
|
257
|
+
// mlx-lm BatchKVCache.filter removes padding common to all survivors.
|
|
258
|
+
// Keeping it changes attention reduction geometry after retirement.
|
|
259
|
+
try { return selected.slice([0, 0, sharedPadding, 0], [...selected.shape]); }
|
|
260
|
+
finally { selected.dispose(); }
|
|
261
|
+
};
|
|
262
|
+
const keys = select(this.keys);
|
|
263
|
+
let values: MlxArray | null;
|
|
264
|
+
try { values = select(this.values); }
|
|
265
|
+
catch (error) { keys?.dispose(); throw error; }
|
|
266
|
+
this.keys?.dispose(); this.values?.dispose();
|
|
267
|
+
this.keys = keys; this.values = values;
|
|
268
|
+
this.rowOffsets = rows.map(row => this.rowOffsets[row]!);
|
|
269
|
+
this.leftPad = pads.map(pad => pad - sharedPadding);
|
|
270
|
+
this.#padding.filter(rows);
|
|
271
|
+
this.#positionsChanged();
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
extractRow(row: number): KVCache {
|
|
275
|
+
const start = this.leftPad[row]!, count = this.rowOffsets[row]!;
|
|
276
|
+
if (count === 0) return new KVCache();
|
|
277
|
+
const copy = (array: MlxArray) => {
|
|
278
|
+
using view = array.slice([row, 0, start, 0], [row + 1, array.shape[1]!, start + count, array.shape[3]!]);
|
|
279
|
+
return materializeCopy(view);
|
|
280
|
+
};
|
|
281
|
+
const keys = copy(this.keys!);
|
|
282
|
+
let values: MlxArray;
|
|
283
|
+
try { values = copy(this.values!); }
|
|
284
|
+
catch (error) { keys.dispose(); throw error; }
|
|
285
|
+
const cache = new KVCache(); cache.restoreState(keys, values, count); return cache;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
dispose(): void {
|
|
289
|
+
this.keys?.dispose(); this.values?.dispose(); this.#positionsChanged();
|
|
290
|
+
this.keys = this.values = null;
|
|
291
|
+
this.rowOffsets = []; this.leftPad = []; this.#beforeRound = undefined; this.#padding.clear();
|
|
292
|
+
this.masks.clear();
|
|
293
|
+
}
|
|
294
|
+
}
|