@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,689 @@
|
|
|
1
|
+
// Paged KV storage behind the common cache and attention interfaces.
|
|
2
|
+
// Full-attention pools store bf16, affine (any bit width the composition
|
|
3
|
+
// names, per layer) or TurboQuant pages. The established
|
|
4
|
+
// gather + SDPA arm is the numerical control; direct Metal reads are Lab.
|
|
5
|
+
// Published views and SSD snapshots own immutable buffers independently of
|
|
6
|
+
// appends, row membership and RAM eviction.
|
|
7
|
+
|
|
8
|
+
import { disposeResources } from "../../runtime/resources";
|
|
9
|
+
import { pagedAttentionView } from "../../kernels/attention/paged";
|
|
10
|
+
import type { KvStorageScheme, PagedKvComposition } from "../../contracts/portable/composition";
|
|
11
|
+
import { MlxArray } from "@mlx-bun/mlx/array";
|
|
12
|
+
import { Dtype } from "@mlx-bun/mlx/ffi";
|
|
13
|
+
import * as ops from "@mlx-bun/mlx/ops";
|
|
14
|
+
import { bidirMask, createCausalMask } from "../../kernels/attention/masks";
|
|
15
|
+
import {
|
|
16
|
+
type AttentionRead, type BidirectionalAttentionCache, type BlockAttentionCache, type CommittedAttentionCache,
|
|
17
|
+
type CompiledDecodeCache, type DecodeSlot, type DecodeStepInputs, type DecodeStepPlan, type DecodeTrace,
|
|
18
|
+
type EncoderAttentionCache, type KvAttentionView, type KvDonorRows, type Mask,
|
|
19
|
+
} from "../../contracts/mlx/cache";
|
|
20
|
+
import { AttentionMasks, causalLease, encoderWindowMask, sdpaRead, unmaskedLease, withLease, type MaskLease } from "../attention-read";
|
|
21
|
+
import * as tq from "../../kernels/turboquant/ops";
|
|
22
|
+
import { TurboQuantCodec, type TurboQuantTensor } from "../turboquant-codec";
|
|
23
|
+
import { turboQuantDecodeSlot } from "../turboquant-kv";
|
|
24
|
+
import type { TurboQuantKernels } from "../turboquant-attention";
|
|
25
|
+
|
|
26
|
+
/** Typed pool-exhaustion error: a generation outgrew its pool. Sizing from
|
|
27
|
+
* prompt+maxTokens at construction makes this unreachable in practice;
|
|
28
|
+
* it exists so an accounting bug is a clear throw, never silent
|
|
29
|
+
* truncation or an allocator OOM (the byte-capped-not-count-capped
|
|
30
|
+
* lesson). */
|
|
31
|
+
export class PagedPoolExhausted extends Error {
|
|
32
|
+
constructor(numBlocks: number) {
|
|
33
|
+
super(`paged KV pool exhausted (${numBlocks} blocks) — capacity accounting bug`);
|
|
34
|
+
this.name = "PagedPoolExhausted";
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Affine pages: the bit width and group size every page of a layer stores. */
|
|
39
|
+
export interface PagedQuantization { bits: number; groupSize: number }
|
|
40
|
+
|
|
41
|
+
/** TurboQuant pages: the codec's key and value bit widths, its decode policy,
|
|
42
|
+
* and the read the layer's graph composed (`TurboQuantKernels`: dense or
|
|
43
|
+
* deferred values). */
|
|
44
|
+
export interface PagedTurboQuant {
|
|
45
|
+
readonly kBits: number;
|
|
46
|
+
readonly vBits: number;
|
|
47
|
+
readonly fusedDecode: boolean;
|
|
48
|
+
readonly kernels: TurboQuantKernels;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Per-layer block arena, mlx-owned (ops.zeros — no host-pointer
|
|
52
|
+
* alignment/dtor hazards), every plane shaped [numBlocks, H, blockSize, width].
|
|
53
|
+
* bf16 pages: `keys` and `values`. Affine pages: packed `keys` and `values`
|
|
54
|
+
* with their scales and biases. TurboQuant pages: the codec's five codes, key
|
|
55
|
+
* indices in `keys`, key scales and zero points in `keyScales` and
|
|
56
|
+
* `keyBiases`, value indices in `values`, value scales in `valueScales`.
|
|
57
|
+
* alloc()/free() manage physical slot indices; the owning PagedKVCache
|
|
58
|
+
* writes/gathers through the pool. */
|
|
59
|
+
|
|
60
|
+
export class BlockPool {
|
|
61
|
+
keyScales?: MlxArray; keyBiases?: MlxArray;
|
|
62
|
+
valueScales?: MlxArray; valueBiases?: MlxArray;
|
|
63
|
+
readonly headDim: number;
|
|
64
|
+
readonly vHeadDim: number;
|
|
65
|
+
readonly dtype: Dtype;
|
|
66
|
+
readonly quantization?: PagedQuantization;
|
|
67
|
+
/** TurboQuant pages' bit widths. */
|
|
68
|
+
readonly turbo?: { readonly kBits: number; readonly vBits: number; readonly fusedDecode: boolean };
|
|
69
|
+
readonly #codec?: TurboQuantCodec;
|
|
70
|
+
keys: MlxArray;
|
|
71
|
+
values: MlxArray;
|
|
72
|
+
numBlocks: number;
|
|
73
|
+
readonly blockSize: number;
|
|
74
|
+
#free: number[];
|
|
75
|
+
|
|
76
|
+
constructor(opts: {
|
|
77
|
+
numBlocks: number;
|
|
78
|
+
blockSize: number;
|
|
79
|
+
numKvHeads: number;
|
|
80
|
+
headDim: number;
|
|
81
|
+
/** V head dim when it differs from K's (attention_k_eq_v models don't
|
|
82
|
+
* reach v1's gemma4 scope, but the pool stays shape-honest). */
|
|
83
|
+
vHeadDim?: number;
|
|
84
|
+
dtype: Dtype;
|
|
85
|
+
quantization?: PagedQuantization;
|
|
86
|
+
turbo?: { readonly kBits: number; readonly vBits: number; readonly fusedDecode: boolean };
|
|
87
|
+
}, source?: BlockPool) {
|
|
88
|
+
this.headDim = opts.headDim; this.vHeadDim = opts.vHeadDim ?? opts.headDim;
|
|
89
|
+
this.dtype = opts.dtype; this.quantization = opts.quantization;
|
|
90
|
+
if (opts.turbo) {
|
|
91
|
+
this.turbo = Object.freeze({ kBits: opts.turbo.kBits, vBits: opts.turbo.vBits, fusedDecode: opts.turbo.fusedDecode });
|
|
92
|
+
this.#codec = new TurboQuantCodec(opts.turbo.kBits, opts.turbo.vBits, opts.turbo.fusedDecode);
|
|
93
|
+
}
|
|
94
|
+
this.numBlocks = opts.numBlocks;
|
|
95
|
+
this.blockSize = opts.blockSize;
|
|
96
|
+
const shape = (width: number) => [opts.numBlocks, opts.numKvHeads, opts.blockSize, width];
|
|
97
|
+
if (source) {
|
|
98
|
+
this.keys = ops.contiguous(source.keys); this.values = ops.contiguous(source.values);
|
|
99
|
+
const copy = (array?: MlxArray) => array ? ops.contiguous(array) : undefined;
|
|
100
|
+
this.keyScales = copy(source.keyScales); this.keyBiases = copy(source.keyBiases);
|
|
101
|
+
this.valueScales = copy(source.valueScales); this.valueBiases = copy(source.valueBiases);
|
|
102
|
+
} else if (this.turbo) {
|
|
103
|
+
// The codec's layout (TurboQuantKVCache's): index planes bit-packed below
|
|
104
|
+
// eight bits, one fp16 scale (and key zero point) per 32 positions of a head.
|
|
105
|
+
const { kBits, vBits } = this.turbo, vD = this.vHeadDim, groups = (dim: number) => dim / tq.TURBOQUANT_BLOCK_SIZE;
|
|
106
|
+
this.keys = ops.zeros(shape(tq.packedDim(opts.headDim, kBits)), kBits === 8 ? Dtype.int8 : Dtype.uint8);
|
|
107
|
+
this.keyScales = ops.zeros(shape(groups(opts.headDim)), Dtype.float16);
|
|
108
|
+
this.keyBiases = ops.zeros(shape(groups(opts.headDim)), Dtype.float16);
|
|
109
|
+
this.values = ops.zeros(shape(tq.packedDim(vD, vBits)), Dtype.uint8);
|
|
110
|
+
this.valueScales = ops.zeros(shape(groups(vD)), Dtype.float16);
|
|
111
|
+
} else {
|
|
112
|
+
this.keys = ops.zeros(shape(opts.headDim), opts.dtype);
|
|
113
|
+
this.values = ops.zeros(shape(this.vHeadDim), opts.dtype);
|
|
114
|
+
}
|
|
115
|
+
if (!source && this.quantization) {
|
|
116
|
+
const { bits, groupSize } = this.quantization;
|
|
117
|
+
const k = ops.quantize(this.keys, groupSize, bits), v = ops.quantize(this.values, groupSize, bits);
|
|
118
|
+
this.keys.dispose(); this.values.dispose();
|
|
119
|
+
this.keys = k.packed; this.keyScales = k.scales; this.keyBiases = k.biases;
|
|
120
|
+
this.values = v.packed; this.valueScales = v.scales; this.valueBiases = v.biases;
|
|
121
|
+
}
|
|
122
|
+
// LIFO free list, low indices first — deterministic layout for tests.
|
|
123
|
+
this.#free = source ? [...source.#free] : Array.from({ length: opts.numBlocks }, (_, i) => opts.numBlocks - 1 - i);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** Independent ownership and allocation metadata over immutable MLX buffers.
|
|
127
|
+
* Functional writes detach storage while another snapshot retains it. */
|
|
128
|
+
clone(): BlockPool {
|
|
129
|
+
return new BlockPool({ numBlocks: this.numBlocks, blockSize: this.blockSize,
|
|
130
|
+
numKvHeads: this.keys.shape[1]!, headDim: this.headDim,
|
|
131
|
+
vHeadDim: this.vHeadDim, dtype: this.dtype, quantization: this.quantization, turbo: this.turbo }, this);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
restoreArrays(arrays: MlxArray[], occupied: readonly number[]): void {
|
|
135
|
+
disposeResources(this.arrays());
|
|
136
|
+
[this.keys, this.values] = [arrays[0]!, arrays[1]!];
|
|
137
|
+
[this.keyScales, this.keyBiases, this.valueScales, this.valueBiases] = arrays.slice(2);
|
|
138
|
+
const used = new Set(occupied);
|
|
139
|
+
this.#free = Array.from({ length: this.numBlocks }, (_, i) => this.numBlocks - 1 - i).filter(i => !used.has(i));
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
grow(blocks: number): void {
|
|
143
|
+
if (blocks <= this.numBlocks) return;
|
|
144
|
+
const grow = (array: MlxArray) => {
|
|
145
|
+
using zeros = ops.zeros([blocks - this.numBlocks, ...array.shape.slice(1)], array.dtype);
|
|
146
|
+
const next = ops.concatAxis([array, zeros], 0); array.dispose(); return next;
|
|
147
|
+
};
|
|
148
|
+
this.keys = grow(this.keys); this.values = grow(this.values);
|
|
149
|
+
if (this.keyScales) this.keyScales = grow(this.keyScales);
|
|
150
|
+
if (this.keyBiases) this.keyBiases = grow(this.keyBiases);
|
|
151
|
+
if (this.valueScales) this.valueScales = grow(this.valueScales);
|
|
152
|
+
if (this.valueBiases) this.valueBiases = grow(this.valueBiases);
|
|
153
|
+
for (let i = blocks - 1; i >= this.numBlocks; i--) this.#free.push(i);
|
|
154
|
+
this.numBlocks = blocks;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
get freeBlocks(): number {
|
|
158
|
+
return this.#free.length;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
alloc(): number {
|
|
162
|
+
const idx = this.#free.pop();
|
|
163
|
+
if (idx === undefined) throw new PagedPoolExhausted(this.numBlocks);
|
|
164
|
+
return idx;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
free(physIdx: number): void {
|
|
168
|
+
this.#free.push(physIdx);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** Write `k`/`v` ([1, H, l, D], l ≤ blockSize − within) into physical
|
|
172
|
+
* block `physIdx` starting at row `within`. Functional slice_update:
|
|
173
|
+
* the pool tensors are single-referenced here, so mlx donates the
|
|
174
|
+
* buffer and the write is in-place in the steady state. */
|
|
175
|
+
writeBlock(physIdx: number, k: MlxArray, v: MlxArray, within: number): void {
|
|
176
|
+
if (this.#codec) {
|
|
177
|
+
const enc = this.#codec.encode(k, v);
|
|
178
|
+
const replace = (target: MlxArray, input: MlxArray): MlxArray => {
|
|
179
|
+
const result = ops.sliceUpdate(target, input, [physIdx, 0, within, 0],
|
|
180
|
+
[physIdx + 1, input.shape[1]!, within + input.shape[2]!, input.shape[3]!]);
|
|
181
|
+
target.dispose(); return result;
|
|
182
|
+
};
|
|
183
|
+
try {
|
|
184
|
+
this.keys = replace(this.keys, enc.kIdx); this.keyScales = replace(this.keyScales!, enc.kScales);
|
|
185
|
+
this.keyBiases = replace(this.keyBiases!, enc.kZeros);
|
|
186
|
+
this.values = replace(this.values, enc.vPacked); this.valueScales = replace(this.valueScales!, enc.vScales);
|
|
187
|
+
} finally { disposeResources([enc.kIdx, enc.kScales, enc.kZeros, enc.vPacked, enc.vScales]); }
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
if (this.quantization) {
|
|
191
|
+
const { bits, groupSize } = this.quantization;
|
|
192
|
+
const kq = ops.quantize(k, groupSize, bits), vq = ops.quantize(v, groupSize, bits);
|
|
193
|
+
const replace = (target: MlxArray, input: MlxArray): MlxArray => {
|
|
194
|
+
const result = ops.sliceUpdate(target, input, [physIdx, 0, within, 0],
|
|
195
|
+
[physIdx + 1, input.shape[1]!, within + input.shape[2]!, input.shape[3]!]);
|
|
196
|
+
target.dispose(); return result;
|
|
197
|
+
};
|
|
198
|
+
try {
|
|
199
|
+
this.keys = replace(this.keys, kq.packed); this.keyScales = replace(this.keyScales!, kq.scales); this.keyBiases = replace(this.keyBiases!, kq.biases);
|
|
200
|
+
this.values = replace(this.values, vq.packed); this.valueScales = replace(this.valueScales!, vq.scales); this.valueBiases = replace(this.valueBiases!, vq.biases);
|
|
201
|
+
} finally { disposeResources([kq.packed, kq.scales, kq.biases, vq.packed, vq.scales, vq.biases]); }
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
const l = k.shape[2]!;
|
|
205
|
+
const [, H, , kD] = this.keys.shape as [number, number, number, number];
|
|
206
|
+
const vD = this.values.shape[3]!;
|
|
207
|
+
const k2 = ops.sliceUpdate(
|
|
208
|
+
this.keys, k, [physIdx, 0, within, 0], [physIdx + 1, H, within + l, kD]);
|
|
209
|
+
this.keys.dispose();
|
|
210
|
+
this.keys = k2;
|
|
211
|
+
const v2 = ops.sliceUpdate(
|
|
212
|
+
this.values, v, [physIdx, 0, within, 0], [physIdx + 1, H, within + l, vD]);
|
|
213
|
+
this.values.dispose();
|
|
214
|
+
this.values = v2;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/** Gather `blockTable`'s blocks in logical order into one contiguous
|
|
218
|
+
* [1, H, nb·blockSize, D] pair (the fetch copy). Caller owns both. */
|
|
219
|
+
gather(blockTable: number[]): [MlxArray, MlxArray] {
|
|
220
|
+
if (this.#codec) throw new Error("TurboQuant pages gather through decode (gatherDecoded)");
|
|
221
|
+
const idx = MlxArray.fromInt32(Int32Array.from(blockTable), [blockTable.length]);
|
|
222
|
+
const pick = (pool: MlxArray): MlxArray => pickBlocks(pool, idx);
|
|
223
|
+
const read = (data: MlxArray, scales?: MlxArray, biases?: MlxArray) => {
|
|
224
|
+
const packed = pick(data);
|
|
225
|
+
if (!this.quantization) return packed;
|
|
226
|
+
using owned = packed; using s = pick(scales!), b = pick(biases!);
|
|
227
|
+
return ops.dequantize(owned, s, b, { ...this.quantization, mode: "affine" });
|
|
228
|
+
};
|
|
229
|
+
const k = read(this.keys, this.keyScales, this.keyBiases);
|
|
230
|
+
const v = read(this.values, this.valueScales, this.valueBiases);
|
|
231
|
+
idx.dispose();
|
|
232
|
+
return [k, v];
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/** TurboQuant pages: write encoded positions (taking ownership) into
|
|
236
|
+
* block `physIdx` from row `within`. */
|
|
237
|
+
writeCodes(physIdx: number, codes: TurboQuantTensor, within: number): void {
|
|
238
|
+
const replace = (target: MlxArray, input: MlxArray): MlxArray => {
|
|
239
|
+
const result = ops.sliceUpdate(target, input, [physIdx, 0, within, 0],
|
|
240
|
+
[physIdx + 1, input.shape[1]!, within + input.shape[2]!, input.shape[3]!]);
|
|
241
|
+
target.dispose(); return result;
|
|
242
|
+
};
|
|
243
|
+
try {
|
|
244
|
+
this.keys = replace(this.keys, codes.kIdx); this.keyScales = replace(this.keyScales!, codes.kScales);
|
|
245
|
+
this.keyBiases = replace(this.keyBiases!, codes.kZeros);
|
|
246
|
+
this.values = replace(this.values, codes.vPacked); this.valueScales = replace(this.valueScales!, codes.vScales);
|
|
247
|
+
} finally { disposeResources([codes.kIdx, codes.kScales, codes.kZeros, codes.vPacked, codes.vScales]); }
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/** TurboQuant pages: the first `length` positions of `blockTable`'s blocks,
|
|
251
|
+
* decoded by the codec with values in their own domain or left rotated
|
|
252
|
+
* (`TurboQuantKVCache`'s decode, per position). Caller owns both. */
|
|
253
|
+
gatherDecoded(blockTable: number[], length: number, rotated: boolean): [MlxArray, MlxArray] {
|
|
254
|
+
if (!this.#codec) throw new Error("only TurboQuant pages decode");
|
|
255
|
+
using idx = MlxArray.fromInt32(Int32Array.from(blockTable), [blockTable.length]);
|
|
256
|
+
const codes: TurboQuantTensor = {
|
|
257
|
+
kIdx: pickBlocks(this.keys, idx), kScales: pickBlocks(this.keyScales!, idx), kZeros: pickBlocks(this.keyBiases!, idx),
|
|
258
|
+
vPacked: pickBlocks(this.values, idx), vScales: pickBlocks(this.valueScales!, idx),
|
|
259
|
+
};
|
|
260
|
+
try { return this.#codec.decode(codes, length, this.headDim, rotated); }
|
|
261
|
+
finally { disposeResources([codes.kIdx, codes.kScales, codes.kZeros, codes.vPacked, codes.vScales]); }
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
arrays(): MlxArray[] {
|
|
265
|
+
return [this.keys, this.values, this.keyScales, this.keyBiases, this.valueScales, this.valueBiases].filter((a): a is MlxArray => !!a);
|
|
266
|
+
}
|
|
267
|
+
dispose(): void {
|
|
268
|
+
disposeResources([this.keyScales, this.keyBiases, this.valueScales, this.valueBiases].filter((a): a is MlxArray => !!a));
|
|
269
|
+
this.keys.dispose();
|
|
270
|
+
this.values.dispose();
|
|
271
|
+
this.#free = [];
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/** One plane's blocks `idx` in logical order, [1, H, blocks·blockSize, width]
|
|
276
|
+
* (the fetch copy). Caller owns it. */
|
|
277
|
+
function pickBlocks(pool: MlxArray, idx: MlxArray): MlxArray {
|
|
278
|
+
const took = ops.takeAxis(pool, idx, 0); // [nb, H, bs, D]
|
|
279
|
+
const [nb, H, bs, D] = took.shape as [number, number, number, number];
|
|
280
|
+
const t = ops.transposeAxes(took, [1, 0, 2, 3]); // [H, nb, bs, D] (view)
|
|
281
|
+
took.dispose();
|
|
282
|
+
const out = ops.reshape(t, [1, H, nb * bs, D]); // materializes seq order
|
|
283
|
+
t.dispose();
|
|
284
|
+
return out;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/** Whether the direct paged kernel reads this page format: bf16 pages, and
|
|
288
|
+
* affine pages whose codes never straddle a 32-bit word. TurboQuant pages and
|
|
289
|
+
* other widths read gathered. */
|
|
290
|
+
function directReadable(quantization: PagedQuantization | undefined, turbo: PagedTurboQuant | undefined): boolean {
|
|
291
|
+
return !turbo && (!quantization || 32 % quantization.bits === 0);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/** Whether the paged lego the composition's paging `paged` and KV scheme `kv`
|
|
295
|
+
* name has a compiled decode form: gathered pages do (a concat slot), and so
|
|
296
|
+
* do direct pages the kernel does not read (TurboQuant, affine widths that
|
|
297
|
+
* straddle a 32-bit word), which read gathered. Direct pages the kernel reads
|
|
298
|
+
* have none: the kernel's partitions follow the context length, which a
|
|
299
|
+
* shapeless recorded graph cannot hold, and the lego reads one way, so a
|
|
300
|
+
* composition with them decodes eagerly (`resolveComposition`). Per-layer
|
|
301
|
+
* affine schemes leave their unlisted layers bf16, which the kernel reads. */
|
|
302
|
+
export function pagedDecodeCompiles(paged: PagedKvComposition, kv: KvStorageScheme): boolean {
|
|
303
|
+
if (!paged.direct) return true;
|
|
304
|
+
return kv.kind === "turbo" || (kv.kind === "affine" && !directReadable({ bits: kv.bits, groupSize: kv.groupSize }, undefined));
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/** Cache implementation over a BlockPool. Same values as a plain KVCache
|
|
308
|
+
* at every step (parity-gated); only the physical arrangement differs.
|
|
309
|
+
* The pool allocates lazily on the first write (KVCache's lazy-alloc
|
|
310
|
+
* shape): head count / head dims / dtype come from the first k/v pair,
|
|
311
|
+
* so the wiring (maybePageKv) needs no per-model shape plumbing.
|
|
312
|
+
*
|
|
313
|
+
* Reads: each owns an immutable view of the blocks as of its append
|
|
314
|
+
* (`pagedAttentionView`). Decode attends its one query, and a window its
|
|
315
|
+
* queries under the causal mask, with the read `direct` (the composition's
|
|
316
|
+
* `pagedKv.direct`) fixes at construction: the direct Metal read from the
|
|
317
|
+
* blocks for at most eight queries, or the gathered blocks through the stock
|
|
318
|
+
* fused SDPA (decode with no mask, a window with the causal mask). A window of
|
|
319
|
+
* more than eight queries gathers under either.
|
|
320
|
+
*
|
|
321
|
+
* The other reads are `KVCache`'s over the gathered blocks, so bf16 pages read
|
|
322
|
+
* bit for bit as the bf16 lego they replace: a committed span is the window
|
|
323
|
+
* read (as `KVCache` qualifies it); the bidirectional and encoder windows
|
|
324
|
+
* append, gather and attend under `KVCache`'s masks; a block read attends the
|
|
325
|
+
* gathered positions and the block's own keys with no mask; the donor rows are
|
|
326
|
+
* the gathered positions. Affine pages gather dequantized.
|
|
327
|
+
*
|
|
328
|
+
* TurboQuant pages read as `TurboQuantKVCache` reads its storage: the
|
|
329
|
+
* window decoded per position by the codec, in the domain the layer's kernel
|
|
330
|
+
* set reads (`PagedTurboQuant.kernels`), attended by that set (no mask for
|
|
331
|
+
* decode, the causal mask for a window); the other reads take the window
|
|
332
|
+
* decoded to the values' own domain. The direct read takes bf16 pages and
|
|
333
|
+
* affine widths that divide 32 bits; other pages read gathered whatever the
|
|
334
|
+
* composition says.
|
|
335
|
+
*
|
|
336
|
+
* Compiled decode, gathered pages: a concat slot. The stored positions are
|
|
337
|
+
* gathered (and dequantized) outside the recorded graph, as the decode read
|
|
338
|
+
* gathers them; in it the new position is stored as the pages store it
|
|
339
|
+
* (quantized and dequantized again on affine pages) and joined to them, and
|
|
340
|
+
* the fused SDPA reads the joined window with no mask, the gathered decode
|
|
341
|
+
* read. The row leaves the graph as it was appended; the write after the step
|
|
342
|
+
* appends it to the pages. Direct pages have no compiled form
|
|
343
|
+
* (`pagedDecodeCompiles`): their read is a custom kernel whose partitions
|
|
344
|
+
* follow the context length, which a shapeless recorded graph cannot hold, so
|
|
345
|
+
* their composition decodes eagerly and this cache refuses a compiled slot. */
|
|
346
|
+
export class PagedKVCache implements CommittedAttentionCache, BidirectionalAttentionCache, EncoderAttentionCache, BlockAttentionCache, CompiledDecodeCache {
|
|
347
|
+
/** Distinct storage kind; the row adapter preserves its block pools. */
|
|
348
|
+
signature(): string { return "kv:paged"; }
|
|
349
|
+
/** Matches KVCache.STEP: v1's growth granularity is a permutation of
|
|
350
|
+
* today's 256-token step into fixed reusable slots, not a new tuning
|
|
351
|
+
* axis (--paged-kv-block-size overrides for experiments). */
|
|
352
|
+
static readonly DEFAULT_BLOCK_SIZE = 256;
|
|
353
|
+
/** Set only by compiled-decode trace adapters (see Cache); a real cache
|
|
354
|
+
* leaves it unset. */
|
|
355
|
+
readonly ropeOffsetArr?: MlxArray;
|
|
356
|
+
offset = 0;
|
|
357
|
+
/** Logical block order → physical pool slot. */
|
|
358
|
+
blockTable: number[] = [];
|
|
359
|
+
pool: BlockPool | null = null;
|
|
360
|
+
|
|
361
|
+
/** The direct Metal read, where the page format takes it (see the class). */
|
|
362
|
+
readonly direct: boolean;
|
|
363
|
+
|
|
364
|
+
constructor(
|
|
365
|
+
/** Tokens this cache must be able to hold (prompt + maxTokens). */
|
|
366
|
+
readonly capacityTokens: number,
|
|
367
|
+
readonly blockSize: number,
|
|
368
|
+
direct = false,
|
|
369
|
+
/** Affine pages; absent with `turbo` absent: bf16 pages. */
|
|
370
|
+
readonly quantization?: PagedQuantization,
|
|
371
|
+
/** The masks the bidirectional and encoder reads lease. */
|
|
372
|
+
readonly masks = new AttentionMasks(),
|
|
373
|
+
/** TurboQuant pages. */
|
|
374
|
+
readonly turbo?: PagedTurboQuant,
|
|
375
|
+
) {
|
|
376
|
+
if (quantization && turbo) throw new Error("pages store one format: affine or TurboQuant");
|
|
377
|
+
this.direct = direct && directReadable(quantization, turbo);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
clone(): PagedKVCache {
|
|
381
|
+
const copy = new PagedKVCache(this.capacityTokens, this.blockSize, this.direct, this.quantization, this.masks, this.turbo);
|
|
382
|
+
copy.offset = this.offset;
|
|
383
|
+
copy.blockTable = [...this.blockTable];
|
|
384
|
+
copy.pool = this.pool?.clone() ?? null;
|
|
385
|
+
return copy;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
get #blockSize(): number {
|
|
389
|
+
return this.blockSize;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/** Blocks needed to hold `n` tokens. */
|
|
393
|
+
#blocksFor(n: number): number {
|
|
394
|
+
return Math.ceil(n / this.#blockSize);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
appendDecode(k: MlxArray, v: MlxArray): AttentionRead {
|
|
398
|
+
this.append(k, v);
|
|
399
|
+
if (this.turbo) return this.#turboRead(unmaskedLease);
|
|
400
|
+
return pagedRead(pagedAttentionView(this.pool!, [...this.blockTable], this.offset, this.direct), { mode: "", arr: null });
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
appendWindow(k: MlxArray, v: MlxArray): AttentionRead {
|
|
404
|
+
this.append(k, v);
|
|
405
|
+
if (this.turbo) return this.#turboRead(causalLease);
|
|
406
|
+
return pagedRead(pagedAttentionView(this.pool!, [...this.blockTable], this.offset, this.direct), { mode: "causal", arr: null });
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/** TurboQuant pages: the layer's kernel set over the window it decodes. */
|
|
410
|
+
#turboRead(mask: MaskLease): AttentionRead {
|
|
411
|
+
const table = [...this.blockTable], length = this.offset, pool = this.pool!;
|
|
412
|
+
return this.turbo!.kernels.read({
|
|
413
|
+
decoded: () => pool.gatherDecoded(table, length, false),
|
|
414
|
+
rotated: () => {
|
|
415
|
+
const [keys, values] = pool.gatherDecoded(table, length, true);
|
|
416
|
+
return { keys, values, restore: tq.unrotateValues };
|
|
417
|
+
},
|
|
418
|
+
}, mask);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/** The window read: `KVCache`'s committed read, over the same positions. */
|
|
422
|
+
appendCommitted(k: MlxArray, v: MlxArray): AttentionRead {
|
|
423
|
+
return this.appendWindow(k, v);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/** `KVCache`'s vision prefill read: the causal mask OR'd with the image
|
|
427
|
+
* block overlay, over the gathered first window. */
|
|
428
|
+
appendBidirectional(k: MlxArray, v: MlxArray, bidirectional: MlxArray): AttentionRead {
|
|
429
|
+
const L = k.shape[2]!;
|
|
430
|
+
const mask = this.masks.lease("bidirectional:none", `${L}`, () => bidirMask(L, null, bidirectional), bidirectional);
|
|
431
|
+
return withLease(mask, held => {
|
|
432
|
+
const [keys, values] = this.appendAndGather(k, v);
|
|
433
|
+
return sdpaRead(keys, values, held);
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/** `KVCache`'s encoder read: the encoder window's mask over the gathered
|
|
438
|
+
* first window. */
|
|
439
|
+
appendEncoderWindow(k: MlxArray, v: MlxArray, bidirectional: MlxArray): AttentionRead {
|
|
440
|
+
return withLease(encoderWindowMask(this.masks, k.shape[2]!, bidirectional), mask => {
|
|
441
|
+
const [keys, values] = this.appendAndGather(k, v);
|
|
442
|
+
return sdpaRead(keys, values, mask);
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/** `KVCache`'s block read: the gathered positions in order, then the block's
|
|
447
|
+
* keys and values, with no mask. Nothing is appended. */
|
|
448
|
+
readBlock(k: MlxArray, v: MlxArray): AttentionRead {
|
|
449
|
+
const [storedK, storedV] = this.#gathered();
|
|
450
|
+
let keys: MlxArray, values: MlxArray;
|
|
451
|
+
try {
|
|
452
|
+
keys = ops.concatAxis([storedK, k], 2);
|
|
453
|
+
try { values = ops.concatAxis([storedV, v], 2); }
|
|
454
|
+
catch (error) { keys.dispose(); throw error; }
|
|
455
|
+
} finally { storedK.dispose(); storedV.dispose(); }
|
|
456
|
+
return sdpaRead(keys, values, unmaskedLease);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/** The gathered positions, as `KVCache` hands its donor rows out. */
|
|
460
|
+
captureDonorRows(): KvDonorRows {
|
|
461
|
+
const [keys, values] = this.#gathered();
|
|
462
|
+
return { keys, values, offsets: [this.offset], starts: [0], ends: [this.offset] };
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
get attentionState(): this | undefined { return this.direct || this.quantization ? this : undefined; }
|
|
466
|
+
appendAndFetch(k: MlxArray, v: MlxArray): KvAttentionView {
|
|
467
|
+
this.append(k, v);
|
|
468
|
+
return pagedAttentionView(this.pool!, [...this.blockTable], this.offset, this.direct);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
updateAndFetch(k: MlxArray, v: MlxArray): [MlxArray, MlxArray] { return this.appendAndGather(k, v); }
|
|
472
|
+
|
|
473
|
+
/** Append, then the keys and values gathered from the blocks up to the new
|
|
474
|
+
* offset (dequantized for affine pages), each [1, H, offset, D]; the caller
|
|
475
|
+
* owns both. What the joined window read of `PagedKvRows` attends. */
|
|
476
|
+
appendAndGather(k: MlxArray, v: MlxArray): [MlxArray, MlxArray] {
|
|
477
|
+
this.append(k, v);
|
|
478
|
+
return this.#gathered();
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/** The keys and values gathered from the blocks up to the offset
|
|
482
|
+
* (dequantized for affine pages), each [1, H, offset, D]; the caller owns
|
|
483
|
+
* both. */
|
|
484
|
+
#gathered(): [MlxArray, MlxArray] {
|
|
485
|
+
if (!this.pool) throw new Error("paged cache is empty");
|
|
486
|
+
if (this.turbo) return this.pool.gatherDecoded(this.blockTable, this.offset, false);
|
|
487
|
+
const H = this.pool.keys.shape[1]!, kD = this.pool.headDim, vD = this.pool.vHeadDim;
|
|
488
|
+
const [gk, gv] = this.pool.gather(this.blockTable);
|
|
489
|
+
const keys = gk.slice([0, 0, 0, 0], [1, H, this.offset, kD]);
|
|
490
|
+
const values = gv.slice([0, 0, 0, 0], [1, H, this.offset, vD]);
|
|
491
|
+
gk.dispose(); gv.dispose();
|
|
492
|
+
return [keys, values];
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
append(k: MlxArray, v: MlxArray): void {
|
|
496
|
+
const L = k.shape[2]!;
|
|
497
|
+
const [, H, , kD] = k.shape as [number, number, number, number];
|
|
498
|
+
const vD = v.shape[3]!;
|
|
499
|
+
if (!this.pool)
|
|
500
|
+
this.pool = new BlockPool({
|
|
501
|
+
numBlocks: poolBlocksFor(this.capacityTokens, this.blockSize),
|
|
502
|
+
blockSize: this.blockSize,
|
|
503
|
+
numKvHeads: H,
|
|
504
|
+
headDim: kD,
|
|
505
|
+
vHeadDim: vD,
|
|
506
|
+
dtype: k.dtype, quantization: this.quantization,
|
|
507
|
+
...(this.turbo ? { turbo: { kBits: this.turbo.kBits, vBits: this.turbo.vBits, fusedDecode: this.turbo.fusedDecode } } : {}),
|
|
508
|
+
});
|
|
509
|
+
// Write, splitting the incoming L along block boundaries. Blocks past
|
|
510
|
+
// the current tail allocate from the free list as they're reached.
|
|
511
|
+
let written = 0;
|
|
512
|
+
while (written < L) {
|
|
513
|
+
const pos = this.offset + written;
|
|
514
|
+
const bi = Math.floor(pos / this.#blockSize);
|
|
515
|
+
const within = pos % this.#blockSize;
|
|
516
|
+
while (this.blockTable.length <= bi) {
|
|
517
|
+
if (!this.pool.freeBlocks) this.pool.grow(Math.max(this.pool.numBlocks + 1, Math.ceil(this.pool.numBlocks * 1.5)));
|
|
518
|
+
this.blockTable.push(this.pool.alloc());
|
|
519
|
+
}
|
|
520
|
+
const l = Math.min(this.#blockSize - within, L - written);
|
|
521
|
+
// Full-range pieces skip the slice (fresh-view slice would be a
|
|
522
|
+
// gratuitous op); partial pieces slice the [written, written+l) rows.
|
|
523
|
+
if (l === L && written === 0) {
|
|
524
|
+
this.pool.writeBlock(this.blockTable[bi]!, k, v, within);
|
|
525
|
+
} else {
|
|
526
|
+
const kp = k.slice([0, 0, written, 0], [1, H, written + l, kD]);
|
|
527
|
+
const vp = v.slice([0, 0, written, 0], [1, H, written + l, vD]);
|
|
528
|
+
this.pool.writeBlock(this.blockTable[bi]!, kp, vp, within);
|
|
529
|
+
kp.dispose();
|
|
530
|
+
vp.dispose();
|
|
531
|
+
}
|
|
532
|
+
written += l;
|
|
533
|
+
}
|
|
534
|
+
this.offset += L;
|
|
535
|
+
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
/** Compiled decode: nothing to prepare on the host; the pages grow when the
|
|
539
|
+
* step's row is appended. */
|
|
540
|
+
prepareDecodeStep(): DecodeStepPlan {
|
|
541
|
+
if (!this.pool) throw new Error("compiled decode on an empty cache");
|
|
542
|
+
return { fetch: "concat", writePos: this.offset, activeLen: this.offset };
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
decodePhase(): DecodeStepPlan["fetch"] { return "concat"; }
|
|
546
|
+
|
|
547
|
+
/** TurboQuant pages take TurboQuant storage's slot (the codes leave the
|
|
548
|
+
* graph); bf16 and affine pages the paged one (the row leaves it). */
|
|
549
|
+
decodeSlot(_plan: DecodeStepPlan): DecodeSlot {
|
|
550
|
+
if (this.direct) throw new Error("direct pages have no compiled decode form; their composition decodes eagerly (pagedDecodeCompiles)");
|
|
551
|
+
return this.turbo ? turboQuantDecodeSlot(this.offset, this.turbo.kBits, this.turbo.vBits, this.turbo.kernels)
|
|
552
|
+
: pagedDecodeSlot(this.offset, this.quantization);
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
/** The stored positions gathered (dequantized on affine pages, decoded in the
|
|
556
|
+
* kernel set's domain on TurboQuant pages), owned by the step. */
|
|
557
|
+
decodeInputs(_plan: DecodeStepPlan, step: DecodeStepInputs): MlxArray[] {
|
|
558
|
+
const [keys, values] = this.turbo
|
|
559
|
+
? this.pool!.gatherDecoded(this.blockTable, this.offset, this.turbo.kernels.fetch === "rotated") : this.#gathered();
|
|
560
|
+
return [step.own(keys), step.own(values)];
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
/** The write half (taking ownership of `outputs`): bf16 and affine pages
|
|
564
|
+
* append the step's row as an ordinary append does; TurboQuant pages store
|
|
565
|
+
* the step's codes, packed as the codec packs them. Returns the pages to
|
|
566
|
+
* evaluate with the step. */
|
|
567
|
+
commitDecodeStep(_slot: DecodeSlot, outputs: MlxArray[]): MlxArray[] {
|
|
568
|
+
if (this.turbo) {
|
|
569
|
+
const [kCodes, kScales, kZeros, vCodes, vScales] = outputs as [MlxArray, MlxArray, MlxArray, MlxArray, MlxArray];
|
|
570
|
+
const kIdx = this.turbo.kBits < 8 ? tq.packBits(kCodes, this.turbo.kBits) : kCodes;
|
|
571
|
+
if (kIdx !== kCodes) kCodes.dispose();
|
|
572
|
+
const vPacked = this.turbo.vBits < 8 ? tq.packBits(vCodes, this.turbo.vBits) : vCodes;
|
|
573
|
+
if (vPacked !== vCodes) vCodes.dispose();
|
|
574
|
+
this.#store({ kIdx, kScales, kZeros, vPacked, vScales });
|
|
575
|
+
return this.pool!.arrays();
|
|
576
|
+
}
|
|
577
|
+
const [k, v] = outputs as [MlxArray, MlxArray];
|
|
578
|
+
try { this.append(k, v); } finally { k.dispose(); v.dispose(); }
|
|
579
|
+
return this.pool!.arrays();
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
/** One position's TurboQuant codes (taking ownership), stored at the offset. */
|
|
583
|
+
#store(codes: TurboQuantTensor): void {
|
|
584
|
+
const pool = this.pool!, bi = Math.floor(this.offset / this.blockSize), within = this.offset % this.blockSize;
|
|
585
|
+
while (this.blockTable.length <= bi) {
|
|
586
|
+
if (!pool.freeBlocks) pool.grow(Math.max(pool.numBlocks + 1, Math.ceil(pool.numBlocks * 1.5)));
|
|
587
|
+
this.blockTable.push(pool.alloc());
|
|
588
|
+
}
|
|
589
|
+
pool.writeCodes(this.blockTable[bi]!, codes, within);
|
|
590
|
+
this.offset += 1;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
/** Same mask policy as KVCache.makeMask (it reads only offset). */
|
|
594
|
+
makeMask(N: number, windowSize: number | null): Mask {
|
|
595
|
+
if (N === 1) return { mode: "", arr: null };
|
|
596
|
+
if (windowSize === null) return { mode: "causal", arr: null };
|
|
597
|
+
if (this.offset === 0 && N <= windowSize) return { mode: "causal", arr: null };
|
|
598
|
+
return { mode: "array", arr: createCausalMask(N, this.offset, windowSize) };
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
/** Owned planes materialized at prefill and immutable publication boundaries. */
|
|
602
|
+
state(): MlxArray[] {
|
|
603
|
+
return this.pool ? this.pool.arrays() : [];
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
isTrimmable(): boolean {
|
|
607
|
+
return true;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
/** Rewind the last n tokens and return now-unoccupied tail blocks to
|
|
611
|
+
* the free list. Stale bytes past offset are never read (fetch slices
|
|
612
|
+
* to offset; writes cover exact ranges) — the KVCache padding
|
|
613
|
+
* invariant, block-shaped. */
|
|
614
|
+
trim(n: number): void {
|
|
615
|
+
this.offset = Math.max(0, this.offset - n);
|
|
616
|
+
const need = this.#blocksFor(this.offset);
|
|
617
|
+
while (this.blockTable.length > need) this.pool!.free(this.blockTable.pop()!);
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
dispose(): void {
|
|
621
|
+
// The cache owns its pool in v1 (one sequence per pool).
|
|
622
|
+
this.pool?.dispose();
|
|
623
|
+
this.pool = null;
|
|
624
|
+
this.blockTable = [];
|
|
625
|
+
this.offset = 0;
|
|
626
|
+
this.masks.clear();
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
/** A read over one block view under a fixed string mask. Takes ownership of
|
|
631
|
+
* `view`. */
|
|
632
|
+
function pagedRead(view: KvAttentionView, mask: Mask): AttentionRead {
|
|
633
|
+
return { attend: (q, scale) => view.attend(q, scale, mask), dispose: () => view.dispose() };
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
/** Size a pool for one request: capacity tokens rounded up to whole
|
|
637
|
+
* blocks. Exported for the wiring in generate.ts (maybePageKv). */
|
|
638
|
+
export function poolBlocksFor(capacityTokens: number, blockSize: number): number {
|
|
639
|
+
return Math.max(1, Math.ceil(capacityTokens / blockSize));
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
/** A paged cache inside a compiled decode trace: the new position joins the
|
|
643
|
+
* gathered prefix as the pages store it (affine pages: quantized, then
|
|
644
|
+
* dequantized as the gather dequantizes them), read with no mask by the fused
|
|
645
|
+
* SDPA; the row as appended is the closure output. */
|
|
646
|
+
class TracedPagedKVCache implements DecodeTrace {
|
|
647
|
+
outs: MlxArray[] = [];
|
|
648
|
+
constructor(
|
|
649
|
+
readonly offset: number,
|
|
650
|
+
readonly quantization: PagedQuantization | undefined,
|
|
651
|
+
readonly prefixKeys: MlxArray,
|
|
652
|
+
readonly prefixValues: MlxArray,
|
|
653
|
+
readonly ropeOffsetArr: MlxArray,
|
|
654
|
+
) {}
|
|
655
|
+
|
|
656
|
+
appendDecode(k: MlxArray, v: MlxArray): AttentionRead {
|
|
657
|
+
// same-shape reshape: owned aliases that survive the caller's dispose of k and v
|
|
658
|
+
this.outs = [ops.reshape(k, k.shape), ops.reshape(v, v.shape)];
|
|
659
|
+
const stored = (row: MlxArray): MlxArray => {
|
|
660
|
+
if (!this.quantization) return ops.reshape(row, row.shape);
|
|
661
|
+
const { bits, groupSize } = this.quantization;
|
|
662
|
+
const q = ops.quantize(row, groupSize, bits);
|
|
663
|
+
try { return ops.dequantize(q.packed, q.scales, q.biases, { bits, groupSize, mode: "affine" }); }
|
|
664
|
+
finally { q.packed.dispose(); q.scales.dispose(); q.biases.dispose(); }
|
|
665
|
+
};
|
|
666
|
+
const join = (prefix: MlxArray, row: MlxArray): MlxArray => {
|
|
667
|
+
using held = stored(row);
|
|
668
|
+
return ops.concatAxis([prefix, held], 2);
|
|
669
|
+
};
|
|
670
|
+
const keys = join(this.prefixKeys, k);
|
|
671
|
+
let values: MlxArray;
|
|
672
|
+
try { values = join(this.prefixValues, v); } catch (error) { keys.dispose(); throw error; }
|
|
673
|
+
return sdpaRead(keys, values, unmaskedLease);
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
signature(): string { return "kv:paged-trace"; }
|
|
677
|
+
state(): MlxArray[] { return []; }
|
|
678
|
+
isTrimmable(): boolean { return false; }
|
|
679
|
+
trim(): void { throw new Error("a compiled decode trace is not trimmed"); }
|
|
680
|
+
dispose(): void {}
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
/** The concat slot of a paged cache at `offset` whose pages store `quantization` (bf16 when absent). */
|
|
684
|
+
export function pagedDecodeSlot(offset: number, quantization: PagedQuantization | undefined): DecodeSlot {
|
|
685
|
+
return {
|
|
686
|
+
key: quantization ? `paged-cat:${quantization.bits}:${quantization.groupSize}` : "paged-cat:bf16", fetch: "concat", inputs: 2, outputs: 2,
|
|
687
|
+
trace: (inputs, ropeOffset) => new TracedPagedKVCache(offset, quantization, inputs[0]!, inputs[1]!, ropeOffset),
|
|
688
|
+
};
|
|
689
|
+
}
|