@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,895 @@
|
|
|
1
|
+
// The latent attention state (GLM-5.2's compressed MLA: the latent, the
|
|
2
|
+
// decoupled RoPE key and, on a sparse-attention layer, the DSA index key) under
|
|
3
|
+
// the composition's KV scheme and paging. `MLACache` is the f32 lego (bf16 KV's
|
|
4
|
+
// counterpart: the checkpoint-native state, stored as computed). The legos here
|
|
5
|
+
// store the same three families as the scheme names, by the rules the key/value
|
|
6
|
+
// legos follow:
|
|
7
|
+
// - `EncodedLatentCache`: affine (one bit width and group size for every
|
|
8
|
+
// family) or TurboQuant (every family through the codec's key encoding at
|
|
9
|
+
// the key width: the latent is what the keys are made from as well as the
|
|
10
|
+
// values, so it takes the key side's precision, and the state has no
|
|
11
|
+
// value-only store for the value width to name; measured on synthetic GLM
|
|
12
|
+
// weights, the latent through the 3-bit value encoding instead cost a logits
|
|
13
|
+
// KL of 0.0117 against 0.00007 through the 8-bit key encoding), f32 through
|
|
14
|
+
// each row's first append or its first `start` positions and encoded after
|
|
15
|
+
// (the bf16-first rule), read decoded;
|
|
16
|
+
// - `PagedLatentCache`: each row's positions in pages of `blockSize`, stored
|
|
17
|
+
// as the scheme names from the first position (as K/V pages are), read
|
|
18
|
+
// gathered and decoded.
|
|
19
|
+
// Both implement the whole latent contract (`LatentAttentionCache`): the read
|
|
20
|
+
// per append, the stored index keys, the row layout (`BatchableCache`), the
|
|
21
|
+
// speculative transaction, prefill padding, and the snapshot and SSD forms
|
|
22
|
+
// (`latentLegoCodecs`, the codec provider a graph composing these declares).
|
|
23
|
+
|
|
24
|
+
import type { MlxArray } from "@mlx-bun/mlx/array";
|
|
25
|
+
import { Dtype } from "@mlx-bun/mlx/ffi";
|
|
26
|
+
import * as ops from "@mlx-bun/mlx/ops";
|
|
27
|
+
import type {
|
|
28
|
+
BatchableCache, Cache, LatentAttentionCache, LatentAttentionState, PrefillPadding,
|
|
29
|
+
} from "../contracts/mlx/cache";
|
|
30
|
+
import type { KvStorageScheme } from "../contracts/portable/composition";
|
|
31
|
+
import * as tq from "../kernels/turboquant/ops";
|
|
32
|
+
import { disposeResources } from "../runtime/resources";
|
|
33
|
+
import { positiveInteger } from "../runtime/integers";
|
|
34
|
+
import { transitionDue } from "./bf16-first-kv";
|
|
35
|
+
import { rowRollIndices } from "./batched-row-storage";
|
|
36
|
+
import { MLACache, type MLACacheGeometry } from "./glm52-cache";
|
|
37
|
+
import { legacyCacheCodecs } from "./persistence";
|
|
38
|
+
import type { CacheCodec, CacheCodecProvider, CacheHeaderEntry, LatentLegoKind } from "./persistence-types";
|
|
39
|
+
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
// Family encodings
|
|
42
|
+
|
|
43
|
+
/** What one family of latent state stores: affine at a width and group size,
|
|
44
|
+
* or TurboQuant's key or value encoding at a width. */
|
|
45
|
+
export type LatentFormat =
|
|
46
|
+
| { readonly kind: "affine"; readonly bits: number; readonly groupSize: number }
|
|
47
|
+
| { readonly kind: "turbo"; readonly kBits: number; readonly vBits: number };
|
|
48
|
+
|
|
49
|
+
/** The latent state's three families, in stored order. */
|
|
50
|
+
type Family = 0 | 1 | 2;
|
|
51
|
+
|
|
52
|
+
/** One family's encoding: f32 positions [B, L, width] to owned planes
|
|
53
|
+
* [B, L, w_i] (positions along axis 1, so every row operation applies to
|
|
54
|
+
* each plane alike), and planes back to f32. Zero planes decode to zeros. */
|
|
55
|
+
interface FamilyEncoding {
|
|
56
|
+
readonly planes: number;
|
|
57
|
+
encode(values: MlxArray): MlxArray[];
|
|
58
|
+
decode(planes: readonly MlxArray[]): MlxArray;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function affineEncoding(bits: number, groupSize: number, width: number): FamilyEncoding {
|
|
62
|
+
if (width % groupSize !== 0)
|
|
63
|
+
throw new Error(`latent state width ${width} is not a multiple of the affine group size ${groupSize}`);
|
|
64
|
+
return {
|
|
65
|
+
planes: 3,
|
|
66
|
+
encode(values) { const q = ops.quantize(values, groupSize, bits); return [q.packed, q.scales, q.biases]; },
|
|
67
|
+
decode: ([packed, scales, biases]) => ops.dequantize(packed!, scales!, biases!, { bits, groupSize, mode: "affine" }),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** TurboQuant's key encoding (asymmetric affine per 32 positions of the width,
|
|
72
|
+
* `bits` wide), decoded in f32 (the codec decodes through bf16). */
|
|
73
|
+
function turboKeyEncoding(bits: number, width: number): FamilyEncoding {
|
|
74
|
+
if (width % tq.TURBOQUANT_BLOCK_SIZE !== 0)
|
|
75
|
+
throw new Error(`latent state width ${width} has no TurboQuant key encoding`);
|
|
76
|
+
const pack = (indices: MlxArray) => {
|
|
77
|
+
if (bits >= 8) return indices;
|
|
78
|
+
try { return tq.packBits(indices, bits); } finally { indices.dispose(); }
|
|
79
|
+
};
|
|
80
|
+
const unpack = (packed: MlxArray) => bits >= 8 ? packed : tq.unpackBits(packed, bits, width);
|
|
81
|
+
const wide = (decoded: MlxArray) => { try { return decoded.astype(Dtype.float32); } finally { decoded.dispose(); } };
|
|
82
|
+
return {
|
|
83
|
+
planes: 3,
|
|
84
|
+
encode(values) { const e = tq.encodeKeys(values, bits, bits === 8); return [pack(e.indices), e.scales, e.zeros]; },
|
|
85
|
+
decode([indices, scales, zeros]) {
|
|
86
|
+
const codes = unpack(indices!);
|
|
87
|
+
try { return wide(tq.decodeKeys(codes, scales!, zeros!)); } finally { if (codes !== indices) codes.dispose(); }
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** The encoding of a family of width `width` under `format` (every family alike). */
|
|
93
|
+
function familyEncoding(format: LatentFormat, width: number): FamilyEncoding {
|
|
94
|
+
return format.kind === "affine" ? affineEncoding(format.bits, format.groupSize, width) : turboKeyEncoding(format.kBits, width);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const formatKey = (format: LatentFormat | null) => !format ? "f32"
|
|
98
|
+
: format.kind === "affine" ? `affine:${format.bits}:${format.groupSize}` : `turbo:${format.kBits}:${format.vBits}`;
|
|
99
|
+
|
|
100
|
+
// ---------------------------------------------------------------------------
|
|
101
|
+
// Shared helpers
|
|
102
|
+
|
|
103
|
+
/** Geometry every latent lego of a layer shares (the `MLACache` geometry). */
|
|
104
|
+
export interface LatentGeometry {
|
|
105
|
+
readonly kvLoraRank: number;
|
|
106
|
+
readonly ropeHeadDim: number;
|
|
107
|
+
/** The DSA index key width, on a sparse-attention layer. */
|
|
108
|
+
readonly indexHeadDim?: number;
|
|
109
|
+
readonly maxTokens?: number;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const widthsOf = (g: LatentGeometry): number[] => [g.kvLoraRank, g.ropeHeadDim, ...(g.indexHeadDim ? [g.indexHeadDim] : [])];
|
|
113
|
+
|
|
114
|
+
function validateF32(name: string, array: MlxArray, expected: readonly number[]): void {
|
|
115
|
+
if (array.dtype !== Dtype.float32) throw new Error(`${name} must be float32`);
|
|
116
|
+
const shape = array.shape;
|
|
117
|
+
if (shape.length !== expected.length || shape.some((dimension, index) => dimension !== expected[index]))
|
|
118
|
+
throw new Error(`${name} shape ${JSON.stringify(shape)} != ${JSON.stringify(expected)}`);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Validate one append's inputs against the geometry and return [B, L]. */
|
|
122
|
+
function appendShape(g: LatentGeometry, latent: MlxArray, rope: MlxArray, indexKeys: MlxArray | null,
|
|
123
|
+
batch: number | null): [number, number] {
|
|
124
|
+
if (latent.shape.length !== 3) throw new Error(`latent state must have rank 3 (got ${latent.shape.length})`);
|
|
125
|
+
const [B, L] = latent.shape as [number, number, number];
|
|
126
|
+
positiveInteger(B, "latent batch size"); positiveInteger(L, "latent token count");
|
|
127
|
+
validateF32("latent", latent, [B, L, g.kvLoraRank]);
|
|
128
|
+
validateF32("RoPE key", rope, [B, L, g.ropeHeadDim]);
|
|
129
|
+
if (g.indexHeadDim) {
|
|
130
|
+
if (!indexKeys) throw new Error("this latent state stores DSA index keys");
|
|
131
|
+
validateF32("DSA index keys", indexKeys, [B, L, g.indexHeadDim]);
|
|
132
|
+
} else if (indexKeys) throw new Error("this latent state stores no DSA index keys");
|
|
133
|
+
if (batch !== null && B !== batch) throw new Error(`latent batch size ${B} != existing ${batch}`);
|
|
134
|
+
return [B, L];
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** Owned zeros shaped like `like` with `tokens` positions along axis 1. */
|
|
138
|
+
const zerosLike = (like: MlxArray, tokens: number, batch = like.shape[0]!) =>
|
|
139
|
+
ops.zeros([batch, tokens, ...like.shape.slice(2)], like.dtype);
|
|
140
|
+
|
|
141
|
+
/** `array` with `tokens` zero positions before it along axis 1 (owned). */
|
|
142
|
+
function padLeft(array: MlxArray, tokens: number): MlxArray {
|
|
143
|
+
if (tokens === 0) return ops.contiguous(array);
|
|
144
|
+
using padding = zerosLike(array, tokens);
|
|
145
|
+
return ops.concatAxis([padding, array], 1);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** Positions [from, to) of row `row` along axis 1, as an owned copy. */
|
|
149
|
+
function rowSlice(array: MlxArray, row: number, from: number, to: number): MlxArray {
|
|
150
|
+
using view = array.slice([row, from, 0], [row + 1, to, array.shape[2]!]);
|
|
151
|
+
return ops.copyOf(view);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** Per-row choice between two [B, T, W] arrays: `useFirst[b]` picks `first`. */
|
|
155
|
+
function pickRows(useFirst: readonly boolean[], first: MlxArray, second: MlxArray): MlxArray {
|
|
156
|
+
if (useFirst.every(Boolean)) return ops.contiguous(first);
|
|
157
|
+
if (!useFirst.some(Boolean)) return ops.contiguous(second);
|
|
158
|
+
using flags = ops.fromInt32(useFirst.map(Number), [useFirst.length, 1, 1]);
|
|
159
|
+
using mask = flags.astype(Dtype.bool);
|
|
160
|
+
return ops.where(mask, first, second);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// ---------------------------------------------------------------------------
|
|
164
|
+
// Encoded latent state (f32 first)
|
|
165
|
+
|
|
166
|
+
/** Persisted or extracted state of one encoded row (owned arrays). */
|
|
167
|
+
interface EncodedRowState {
|
|
168
|
+
/** Per family, its encoded planes [1, T, w_i]. */
|
|
169
|
+
readonly planes: MlxArray[][];
|
|
170
|
+
/** Per family, the f32 values [1, T, W] of a row not yet converted; null once converted. */
|
|
171
|
+
readonly shadow: MlxArray[] | null;
|
|
172
|
+
readonly offset: number;
|
|
173
|
+
readonly minimumReusableOffset: number;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* The latent attention state stored as `format` names, f32 first: the lego a
|
|
178
|
+
* quantized KV scheme composes for a GLM-5.2 layer.
|
|
179
|
+
*
|
|
180
|
+
* Every append encodes its positions. A row reads its stored state in f32
|
|
181
|
+
* through the append that leaves it holding at least `start` positions (the
|
|
182
|
+
* first append when `start` is 0), then converts: every later read of that row
|
|
183
|
+
* decodes all its stored positions. The encoding is per position, so encoding
|
|
184
|
+
* at append time stores what converting the f32 state at the start would. The
|
|
185
|
+
* f32 values a row still reads (the `shadow`) are kept only while some row has
|
|
186
|
+
* not converted. Conversion is irreversible: trimming or rolling a row back
|
|
187
|
+
* below the start keeps it converted. A nonzero start records the converted
|
|
188
|
+
* offset as the earliest reusable prefix (`minimumReusableOffset`).
|
|
189
|
+
*
|
|
190
|
+
* Rows: one row for a single sequence; a batch keeps its rows right-justified
|
|
191
|
+
* along the sequence axis, `rowOffsets[b]` positions in row `b` starting at
|
|
192
|
+
* `leftPad[b]` (`MLACache`'s layout). Reads return f32 [B, T, width] with zeros
|
|
193
|
+
* in the padding.
|
|
194
|
+
*/
|
|
195
|
+
export class EncodedLatentCache implements BatchableCache, LatentAttentionCache {
|
|
196
|
+
readonly kvLoraRank: number;
|
|
197
|
+
readonly ropeHeadDim: number;
|
|
198
|
+
readonly indexHeadDim: number | null;
|
|
199
|
+
readonly maxTokens: number;
|
|
200
|
+
readonly #encodings: FamilyEncoding[];
|
|
201
|
+
readonly #widths: number[];
|
|
202
|
+
#planes: MlxArray[][] | null = null;
|
|
203
|
+
#shadow: MlxArray[] | null = null;
|
|
204
|
+
#converted: boolean[] = [];
|
|
205
|
+
#minimum: number[] = [];
|
|
206
|
+
#beforeRound: number[] | undefined;
|
|
207
|
+
#ends: number[] | undefined;
|
|
208
|
+
offset = 0;
|
|
209
|
+
batchSize: number | null = null;
|
|
210
|
+
rowOffsets: number[] = [];
|
|
211
|
+
leftPad: number[] = [];
|
|
212
|
+
|
|
213
|
+
constructor(readonly geometry: LatentGeometry, readonly format: LatentFormat, readonly start: number) {
|
|
214
|
+
positiveInteger(geometry.kvLoraRank, "latent kvLoraRank");
|
|
215
|
+
positiveInteger(geometry.ropeHeadDim, "latent ropeHeadDim");
|
|
216
|
+
if (!Number.isSafeInteger(start) || start < 0) throw new Error("latent conversion start must be a non-negative integer");
|
|
217
|
+
this.kvLoraRank = geometry.kvLoraRank;
|
|
218
|
+
this.ropeHeadDim = geometry.ropeHeadDim;
|
|
219
|
+
this.indexHeadDim = geometry.indexHeadDim ?? null;
|
|
220
|
+
this.maxTokens = geometry.maxTokens ?? Number.MAX_SAFE_INTEGER;
|
|
221
|
+
this.#widths = widthsOf(geometry);
|
|
222
|
+
this.#encodings = this.#widths.map(width => familyEncoding(format, width));
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
signature(): string {
|
|
226
|
+
return `kv:latent:${this.indexHeadDim ? "dsa:" : ""}${formatKey(this.format)}:${this.start}`;
|
|
227
|
+
}
|
|
228
|
+
get minimumReusableOffset(): number { return Math.max(0, ...this.#minimum); }
|
|
229
|
+
/** A restored or cloned row's earliest reusable prefix, as persistence records it. */
|
|
230
|
+
set minimumReusableOffset(value: number | undefined) {
|
|
231
|
+
this.#minimum = this.#minimum.length ? this.#minimum.map(() => value ?? 0) : [value ?? 0];
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/** Every array this cache holds, encoded planes then the f32 shadow. */
|
|
235
|
+
#arrays(): MlxArray[] { return [...(this.#planes?.flat() ?? []), ...(this.#shadow ?? [])]; }
|
|
236
|
+
/** Rebuild the plane lists from a flat array list in `#arrays` order. */
|
|
237
|
+
#adopt(arrays: MlxArray[], shadow: boolean): void {
|
|
238
|
+
const planes: MlxArray[][] = [];
|
|
239
|
+
let at = 0;
|
|
240
|
+
for (const encoding of this.#encodings) { planes.push(arrays.slice(at, at + encoding.planes)); at += encoding.planes; }
|
|
241
|
+
this.#planes = planes;
|
|
242
|
+
this.#shadow = shadow ? arrays.slice(at, at + this.#encodings.length) : null;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
state(): MlxArray[] { return this.#arrays(); }
|
|
246
|
+
bytesPerToken(): number {
|
|
247
|
+
const arrays = this.#planes?.flat() ?? [];
|
|
248
|
+
const tokens = arrays[0] ? arrays[0].shape[0]! * arrays[0].shape[1]! : 0;
|
|
249
|
+
return tokens ? arrays.reduce((bytes, array) => bytes + array.nbytes, 0) / tokens
|
|
250
|
+
: this.#widths.reduce((bytes, width) => bytes + width * 4, 0);
|
|
251
|
+
}
|
|
252
|
+
projectedBytes(tokens: number): number {
|
|
253
|
+
return MLACache.projectedByteLength({ kvLoraRank: this.kvLoraRank, ropeHeadDim: this.ropeHeadDim,
|
|
254
|
+
...(this.indexHeadDim ? { dsa: { headDim: this.indexHeadDim } } : {}) }, 1, tokens);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/** Decode family `family`'s stored state, rows still in f32 read from the shadow. */
|
|
258
|
+
#read(family: Family, converted: readonly boolean[]): MlxArray {
|
|
259
|
+
const decoded = this.#encodings[family]!.decode(this.#planes![family]!);
|
|
260
|
+
if (!this.#shadow) return decoded;
|
|
261
|
+
try { return pickRows(converted, decoded, this.#shadow[family]!); } finally { decoded.dispose(); }
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
appendLatent(latent: MlxArray, rope: MlxArray, indexKeys: MlxArray | null): LatentAttentionState {
|
|
265
|
+
const [B, L] = appendShape(this.geometry, latent, rope, indexKeys, this.batchSize);
|
|
266
|
+
const offsets = this.rowOffsets.length ? this.rowOffsets : new Array<number>(B).fill(0);
|
|
267
|
+
if (Math.max(...offsets) > this.maxTokens - L)
|
|
268
|
+
throw new RangeError(`latent append would exceed maxTokens ${this.maxTokens}: ${Math.max(...offsets)} + ${L}`);
|
|
269
|
+
const converted = this.#converted.length ? [...this.#converted] : new Array<boolean>(B).fill(false);
|
|
270
|
+
const inputs = [latent, rope, ...(indexKeys ? [indexKeys] : [])];
|
|
271
|
+
// Stage every family before publishing any (a rejected append leaves the cache unchanged).
|
|
272
|
+
const planes: MlxArray[][] = [], shadow: MlxArray[] = [];
|
|
273
|
+
const keepShadow = converted.some(done => !done);
|
|
274
|
+
try {
|
|
275
|
+
for (const [family, input] of inputs.entries()) {
|
|
276
|
+
const encoded = this.#encodings[family]!.encode(input);
|
|
277
|
+
planes.push(encoded.map((plane, index) => {
|
|
278
|
+
const current = this.#planes?.[family]![index];
|
|
279
|
+
if (!current) return plane;
|
|
280
|
+
try { return ops.concatAxis([current, plane], 1); } finally { plane.dispose(); }
|
|
281
|
+
}));
|
|
282
|
+
if (keepShadow) {
|
|
283
|
+
const current = this.#shadow?.[family] ?? (this.offset ? ops.zeros([B, this.offset, this.#widths[family]!], Dtype.float32) : null);
|
|
284
|
+
shadow.push(current ? ops.concatAxis([current, input], 1) : ops.contiguous(input));
|
|
285
|
+
if (current && current !== this.#shadow?.[family]) current.dispose();
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
} catch (error) { disposeResources([...planes.flat(), ...shadow]); throw error; }
|
|
289
|
+
disposeResources(this.#arrays());
|
|
290
|
+
this.#planes = planes; this.#shadow = keepShadow ? shadow : null;
|
|
291
|
+
this.batchSize ??= B;
|
|
292
|
+
if (!this.rowOffsets.length) { this.rowOffsets = new Array<number>(B).fill(0); this.leftPad = new Array<number>(B).fill(0); }
|
|
293
|
+
this.rowOffsets = this.rowOffsets.map(offset => offset + L);
|
|
294
|
+
this.offset += L;
|
|
295
|
+
if (!this.#minimum.length) this.#minimum = new Array<number>(B).fill(0);
|
|
296
|
+
let state: LatentAttentionState;
|
|
297
|
+
const read: MlxArray[] = [];
|
|
298
|
+
try {
|
|
299
|
+
for (let family = 0; family < inputs.length; family++) read.push(this.#read(family as Family, converted));
|
|
300
|
+
state = { latent: read[0]!, rope: read[1]!, indexKeys: read[2] ?? null };
|
|
301
|
+
} catch (error) { disposeResources(read); throw error; }
|
|
302
|
+
this.#converted = converted.map((done, row) => done || transitionDue(this.rowOffsets[row]!, this.start));
|
|
303
|
+
for (const [row, done] of this.#converted.entries())
|
|
304
|
+
if (done && !converted[row] && this.start > 0) this.#minimum[row] = this.rowOffsets[row]!;
|
|
305
|
+
this.#dropShadowIfConverted();
|
|
306
|
+
return state;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
storedIndexKeys(): MlxArray | null {
|
|
310
|
+
if (!this.indexHeadDim || !this.#planes || this.offset === 0) return null;
|
|
311
|
+
return this.#read(2, this.#converted);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
#dropShadowIfConverted(): void {
|
|
315
|
+
if (this.#shadow && this.#converted.every(Boolean)) { disposeResources(this.#shadow); this.#shadow = null; }
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
isTrimmable(): boolean { return true; }
|
|
319
|
+
trim(n: number): void {
|
|
320
|
+
const shortest = this.rowOffsets.length ? Math.min(...this.rowOffsets) : this.offset;
|
|
321
|
+
if (!Number.isSafeInteger(n) || n < 0 || n > shortest)
|
|
322
|
+
throw new RangeError(`cannot trim ${n} tokens from latent offset ${this.offset}`);
|
|
323
|
+
if (n === 0) return;
|
|
324
|
+
const next = this.offset - n;
|
|
325
|
+
if (next === 0) { this.#clearStorage(); this.rowOffsets = this.rowOffsets.map(() => 0); this.leftPad = this.leftPad.map(() => 0); return; }
|
|
326
|
+
const arrays = this.#arrays().map(array => rowSliceAll(array, next));
|
|
327
|
+
const shadow = !!this.#shadow;
|
|
328
|
+
disposeResources(this.#arrays());
|
|
329
|
+
this.#adopt(arrays, shadow);
|
|
330
|
+
this.offset = next;
|
|
331
|
+
this.rowOffsets = this.rowOffsets.map(offset => offset - n);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
#clearStorage(): void {
|
|
335
|
+
disposeResources(this.#arrays());
|
|
336
|
+
this.#planes = null; this.#shadow = null; this.offset = 0;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// The speculative transaction (MLACache's): keep each row's accepted prefix,
|
|
340
|
+
// right-justified.
|
|
341
|
+
specRoundBegin(): void { this.#beforeRound = [...this.rowOffsets]; }
|
|
342
|
+
specRoundCommit(): void { this.#beforeRound = undefined; }
|
|
343
|
+
specRoundRollback(keep: number | readonly number[]): void {
|
|
344
|
+
const next = this.#beforeRound!.map((before, row) => before + (typeof keep === "number" ? keep : keep[row]!));
|
|
345
|
+
const rejected = this.rowOffsets.map((offset, row) => offset - next[row]!);
|
|
346
|
+
this.#beforeRound = undefined;
|
|
347
|
+
if (rejected.every(count => count === rejected[0])) { this.trim(rejected[0]!); return; }
|
|
348
|
+
const width = this.offset, pads = this.leftPad.map((pad, row) => pad + rejected[row]!);
|
|
349
|
+
const removable = Math.min(...pads);
|
|
350
|
+
using indices = ops.fromInt32(rejected.flatMap(count =>
|
|
351
|
+
Array.from({ length: width }, (_, column) => (column - count + width) % width)), [this.batchSize!, width, 1]);
|
|
352
|
+
const arrays: MlxArray[] = [];
|
|
353
|
+
try {
|
|
354
|
+
for (const array of this.#arrays()) {
|
|
355
|
+
using shifted = ops.takeAlongAxis(array, indices, 1);
|
|
356
|
+
arrays.push(shifted.slice([0, removable, 0], [this.batchSize!, width, array.shape[2]!]));
|
|
357
|
+
}
|
|
358
|
+
} catch (error) { disposeResources(arrays); throw error; }
|
|
359
|
+
const shadow = !!this.#shadow;
|
|
360
|
+
disposeResources(this.#arrays());
|
|
361
|
+
this.#adopt(arrays, shadow);
|
|
362
|
+
this.offset = width - removable;
|
|
363
|
+
this.rowOffsets = next;
|
|
364
|
+
this.leftPad = pads.map(pad => pad - removable);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// Prefill padding (MLACache's, over every plane).
|
|
368
|
+
preparePrefill(padding: PrefillPadding): void {
|
|
369
|
+
if (!this.rowOffsets.length) {
|
|
370
|
+
this.rowOffsets = padding.lengths.map(() => 0); this.leftPad = [...this.rowOffsets];
|
|
371
|
+
this.#converted = padding.lengths.map(() => false); this.#minimum = padding.lengths.map(() => 0);
|
|
372
|
+
this.batchSize = padding.lengths.length;
|
|
373
|
+
}
|
|
374
|
+
if (padding.leftPadding) {
|
|
375
|
+
this.leftPad = this.leftPad.map((pad, row) => pad + padding.leftPadding![row]!);
|
|
376
|
+
this.rowOffsets = this.rowOffsets.map((offset, row) => offset - padding.leftPadding![row]!);
|
|
377
|
+
}
|
|
378
|
+
this.#ends = padding.rightPadding?.some(pad => pad > 0)
|
|
379
|
+
? padding.lengths.map((length, row) => this.rowOffsets[row]! + length) : undefined;
|
|
380
|
+
}
|
|
381
|
+
finalizePrefill(): void {
|
|
382
|
+
const ends = this.#ends;
|
|
383
|
+
this.#ends = undefined;
|
|
384
|
+
if (!ends) return;
|
|
385
|
+
const padding = ends.map((end, row) => Math.max(0, this.rowOffsets[row]! - end));
|
|
386
|
+
if (!padding.some(pad => pad > 0)) return;
|
|
387
|
+
const arrays: MlxArray[] = [];
|
|
388
|
+
try {
|
|
389
|
+
if (this.#planes) {
|
|
390
|
+
using indices = rowRollIndices(this.offset, padding);
|
|
391
|
+
using compressed = ops.reshape(indices, [padding.length, this.offset, 1]);
|
|
392
|
+
for (const array of this.#arrays()) arrays.push(ops.takeAlongAxis(array, compressed, 1));
|
|
393
|
+
}
|
|
394
|
+
} catch (error) { disposeResources(arrays); throw error; }
|
|
395
|
+
if (this.#planes) { const shadow = !!this.#shadow; disposeResources(this.#arrays()); this.#adopt(arrays, shadow); }
|
|
396
|
+
this.rowOffsets = this.rowOffsets.map((offset, row) => offset - padding[row]!);
|
|
397
|
+
this.leftPad = this.leftPad.map((pad, row) => pad + padding[row]!);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
makeEmptyBatch(): EncodedLatentCache { return new EncodedLatentCache(this.geometry, this.format, this.start); }
|
|
401
|
+
|
|
402
|
+
/** Row `row`'s state (owned copies), its padding removed. */
|
|
403
|
+
rowState(row: number): EncodedRowState {
|
|
404
|
+
if (this.batchSize === null) throw new Error("latent state is empty");
|
|
405
|
+
const start = this.leftPad[row]!, end = this.offset, offset = this.rowOffsets[row]!;
|
|
406
|
+
const planes = offset ? this.#planes!.map(family => family.map(plane => rowSlice(plane, row, start, end))) : [];
|
|
407
|
+
const shadow = this.#converted[row] ? null
|
|
408
|
+
: offset ? this.#shadow!.map(values => rowSlice(values, row, start, end)) : [];
|
|
409
|
+
return { planes, shadow, offset, minimumReusableOffset: this.#minimum[row] ?? 0 };
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/** Adopt one row's state (taking ownership): what extraction and the SSD form restore. */
|
|
413
|
+
restoreRow(state: EncodedRowState): void {
|
|
414
|
+
this.dispose();
|
|
415
|
+
this.batchSize = 1; this.rowOffsets = [state.offset]; this.leftPad = [0];
|
|
416
|
+
this.#converted = [state.shadow === null]; this.#minimum = [state.minimumReusableOffset];
|
|
417
|
+
if (!state.offset) return;
|
|
418
|
+
this.#planes = state.planes; this.#shadow = state.shadow;
|
|
419
|
+
this.offset = state.offset;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/** Merge serial or batched rows of this geometry and format into this empty cache. */
|
|
423
|
+
mergeRows(rows: readonly Cache[]): void {
|
|
424
|
+
if (this.batchSize !== null || this.offset !== 0) throw new Error("latent mergeRows requires an empty destination cache");
|
|
425
|
+
if (!rows.length) throw new Error("latent mergeRows requires at least one row");
|
|
426
|
+
const states: EncodedRowState[] = [];
|
|
427
|
+
try {
|
|
428
|
+
for (const source of rows) {
|
|
429
|
+
if (!(source instanceof EncodedLatentCache) || source.signature() !== this.signature())
|
|
430
|
+
throw new Error(`latent mergeRows cannot merge ${source.signature()} into ${this.signature()}`);
|
|
431
|
+
const count = source.batchSize ?? 1;
|
|
432
|
+
for (let row = 0; row < count; row++)
|
|
433
|
+
states.push(source.batchSize === null ? { planes: [], shadow: [], offset: 0, minimumReusableOffset: 0 } : source.rowState(row));
|
|
434
|
+
}
|
|
435
|
+
const width = Math.max(...states.map(state => state.offset));
|
|
436
|
+
if (width > this.maxTokens) throw new RangeError(`latent merged width ${width} exceeds maxTokens ${this.maxTokens}`);
|
|
437
|
+
const converted = states.map(state => state.shadow === null);
|
|
438
|
+
if (width) {
|
|
439
|
+
const prototype = states.find(state => state.offset)!;
|
|
440
|
+
const family = (pick: (state: EncodedRowState) => MlxArray | undefined, columns: number, dtype: Dtype) => {
|
|
441
|
+
const padded = states.map(state => {
|
|
442
|
+
const array = pick(state);
|
|
443
|
+
return array ? padLeft(array, width - state.offset) : ops.zeros([1, width, columns], dtype);
|
|
444
|
+
});
|
|
445
|
+
try { return ops.concatAxis(padded, 0); } finally { disposeResources(padded); }
|
|
446
|
+
};
|
|
447
|
+
const planes = prototype.planes.map((group, f) =>
|
|
448
|
+
group.map((plane, i) => family(state => state.planes[f]?.[i], plane.shape[2]!, plane.dtype)));
|
|
449
|
+
const shadow = converted.every(Boolean) ? null
|
|
450
|
+
: this.#widths.map((w, f) => family(state => state.shadow?.[f], w, Dtype.float32));
|
|
451
|
+
this.#planes = planes; this.#shadow = shadow;
|
|
452
|
+
}
|
|
453
|
+
this.batchSize = states.length; this.offset = width;
|
|
454
|
+
this.rowOffsets = states.map(state => state.offset);
|
|
455
|
+
this.leftPad = states.map(state => width - state.offset);
|
|
456
|
+
this.#converted = converted;
|
|
457
|
+
this.#minimum = states.map(state => state.minimumReusableOffset);
|
|
458
|
+
} finally { disposeResources(states.flatMap(state => [...state.planes.flat(), ...(state.shadow ?? [])])); }
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
extractRow(row: number): EncodedLatentCache {
|
|
462
|
+
const out = this.makeEmptyBatch();
|
|
463
|
+
try { out.restoreRow(this.rowState(row)); return out; }
|
|
464
|
+
catch (error) { out.dispose(); throw error; }
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
filterRows(keep: readonly number[]): void {
|
|
468
|
+
if (this.batchSize === null) throw new Error("latent state is empty");
|
|
469
|
+
if (!keep.length) throw new Error("latent filterRows cannot keep zero rows");
|
|
470
|
+
const removable = Math.min(...keep.map(row => this.leftPad[row]!));
|
|
471
|
+
const width = this.offset - removable;
|
|
472
|
+
using indices = ops.fromInt32([...keep], [keep.length]);
|
|
473
|
+
const arrays: MlxArray[] = [];
|
|
474
|
+
try {
|
|
475
|
+
for (const array of this.#arrays()) {
|
|
476
|
+
using trimmed = array.slice([0, removable, 0], [this.batchSize, this.offset, array.shape[2]!]);
|
|
477
|
+
using selected = ops.takeAxis(trimmed, indices, 0);
|
|
478
|
+
arrays.push(ops.copyOf(selected));
|
|
479
|
+
}
|
|
480
|
+
} catch (error) { disposeResources(arrays); throw error; }
|
|
481
|
+
const shadow = !!this.#shadow;
|
|
482
|
+
disposeResources(this.#arrays());
|
|
483
|
+
if (this.#planes) this.#adopt(arrays, shadow);
|
|
484
|
+
this.batchSize = keep.length; this.offset = width;
|
|
485
|
+
this.rowOffsets = keep.map(row => this.rowOffsets[row]!);
|
|
486
|
+
this.leftPad = keep.map(row => this.leftPad[row]! - removable);
|
|
487
|
+
this.#converted = keep.map(row => this.#converted[row] ?? false);
|
|
488
|
+
this.#minimum = keep.map(row => this.#minimum[row] ?? 0);
|
|
489
|
+
if (this.#ends) this.#ends = keep.map(row => this.#ends![row]!);
|
|
490
|
+
this.#dropShadowIfConverted();
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
dispose(): void {
|
|
494
|
+
disposeResources(this.#arrays());
|
|
495
|
+
this.#planes = null; this.#shadow = null; this.#converted = []; this.#minimum = [];
|
|
496
|
+
this.offset = 0; this.batchSize = null; this.rowOffsets = []; this.leftPad = [];
|
|
497
|
+
this.#beforeRound = undefined; this.#ends = undefined;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/** Positions [0, to) along axis 1 of every row, as an owned copy. */
|
|
502
|
+
function rowSliceAll(array: MlxArray, to: number): MlxArray {
|
|
503
|
+
using view = array.slice([0, 0, 0], [array.shape[0]!, to, array.shape[2]!]);
|
|
504
|
+
return ops.copyOf(view);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// ---------------------------------------------------------------------------
|
|
508
|
+
// Paged latent state
|
|
509
|
+
|
|
510
|
+
/** One row's pages: per stored plane a pool [numBlocks, blockSize, w], the
|
|
511
|
+
* row's block table, its offset and the positions it holds as prefill padding. */
|
|
512
|
+
class LatentPages {
|
|
513
|
+
pools: MlxArray[] = [];
|
|
514
|
+
blockTable: number[] = [];
|
|
515
|
+
#free: number[] = [];
|
|
516
|
+
numBlocks = 0;
|
|
517
|
+
offset = 0;
|
|
518
|
+
constructor(readonly blockSize: number) {}
|
|
519
|
+
|
|
520
|
+
clone(): LatentPages {
|
|
521
|
+
const copy = new LatentPages(this.blockSize);
|
|
522
|
+
copy.pools = this.pools.map(pool => ops.contiguous(pool));
|
|
523
|
+
copy.blockTable = [...this.blockTable]; copy.#free = [...this.#free];
|
|
524
|
+
copy.numBlocks = this.numBlocks; copy.offset = this.offset;
|
|
525
|
+
return copy;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
#grow(blocks: number): void {
|
|
529
|
+
if (blocks <= this.numBlocks) return;
|
|
530
|
+
this.pools = this.pools.map(pool => {
|
|
531
|
+
using zeros = ops.zeros([blocks - this.numBlocks, ...pool.shape.slice(1)], pool.dtype);
|
|
532
|
+
try { return ops.concatAxis([pool, zeros], 0); } finally { pool.dispose(); }
|
|
533
|
+
});
|
|
534
|
+
for (let i = blocks - 1; i >= this.numBlocks; i--) this.#free.push(i);
|
|
535
|
+
this.numBlocks = blocks;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
/** Append encoded planes [1, L, w_i] (borrowed) at the offset. */
|
|
539
|
+
append(planes: readonly MlxArray[]): void {
|
|
540
|
+
const L = planes[0]!.shape[1]!;
|
|
541
|
+
if (!this.pools.length) {
|
|
542
|
+
this.pools = planes.map(plane => ops.zeros([1, this.blockSize, plane.shape[2]!], plane.dtype));
|
|
543
|
+
this.numBlocks = 1; this.#free = [0];
|
|
544
|
+
}
|
|
545
|
+
let written = 0;
|
|
546
|
+
while (written < L) {
|
|
547
|
+
const position = this.offset + written, block = Math.floor(position / this.blockSize), within = position % this.blockSize;
|
|
548
|
+
while (this.blockTable.length <= block) {
|
|
549
|
+
if (!this.#free.length) this.#grow(Math.max(this.numBlocks + 1, Math.ceil(this.numBlocks * 1.5)));
|
|
550
|
+
this.blockTable.push(this.#free.pop()!);
|
|
551
|
+
}
|
|
552
|
+
const count = Math.min(this.blockSize - within, L - written), physical = this.blockTable[block]!;
|
|
553
|
+
this.pools = this.pools.map((pool, index) => {
|
|
554
|
+
const plane = planes[index]!;
|
|
555
|
+
using piece = plane.slice([0, written, 0], [1, written + count, plane.shape[2]!]);
|
|
556
|
+
try { return ops.sliceUpdate(pool, piece, [physical, within, 0], [physical + 1, within + count, plane.shape[2]!]); }
|
|
557
|
+
finally { pool.dispose(); }
|
|
558
|
+
});
|
|
559
|
+
written += count;
|
|
560
|
+
}
|
|
561
|
+
this.offset += L;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
/** The stored planes in order, [1, offset, w_i] each, owned. */
|
|
565
|
+
gather(): MlxArray[] {
|
|
566
|
+
if (!this.offset) return this.pools.map(pool => ops.zeros([1, 0, pool.shape[2]!], pool.dtype));
|
|
567
|
+
using table = ops.fromInt32(this.blockTable, [this.blockTable.length]);
|
|
568
|
+
return this.pools.map(pool => {
|
|
569
|
+
using picked = ops.takeAxis(pool, table, 0);
|
|
570
|
+
using flat = ops.reshape(picked, [1, this.blockTable.length * this.blockSize, pool.shape[2]!]);
|
|
571
|
+
return ops.copyOf(flat.slice([0, 0, 0], [1, this.offset, pool.shape[2]!]));
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
/** Drop the last `n` positions; tail blocks return to the free list. */
|
|
576
|
+
trim(n: number): void {
|
|
577
|
+
this.offset = Math.max(0, this.offset - n);
|
|
578
|
+
const need = Math.ceil(this.offset / this.blockSize);
|
|
579
|
+
while (this.blockTable.length > need) this.#free.push(this.blockTable.pop()!);
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
/** Keep positions [from, to) (owned rewrite through the same encoding). */
|
|
583
|
+
keep(from: number, to: number): void {
|
|
584
|
+
const planes = this.gather();
|
|
585
|
+
try {
|
|
586
|
+
const kept = planes.map(plane => ops.copyOf(plane.slice([0, from, 0], [1, to, plane.shape[2]!])));
|
|
587
|
+
try { this.trim(this.offset); this.append(kept); } finally { disposeResources(kept); }
|
|
588
|
+
} finally { disposeResources(planes); }
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
/** Adopt saved pools (taking ownership) holding `offset` positions in `blockTable`'s blocks. */
|
|
592
|
+
restore(pools: MlxArray[], blockTable: readonly number[], numBlocks: number, offset: number): void {
|
|
593
|
+
this.dispose();
|
|
594
|
+
const used = new Set(blockTable);
|
|
595
|
+
this.pools = pools; this.blockTable = [...blockTable]; this.numBlocks = numBlocks; this.offset = offset;
|
|
596
|
+
this.#free = Array.from({ length: numBlocks }, (_, i) => numBlocks - 1 - i).filter(i => !used.has(i));
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
dispose(): void { disposeResources(this.pools); this.pools = []; this.blockTable = []; this.#free = []; this.numBlocks = 0; this.offset = 0; }
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
/**
|
|
603
|
+
* The latent attention state in pages of `blockSize` positions per row, stored
|
|
604
|
+
* as `format` names from the first position (null: f32, the checkpoint-native
|
|
605
|
+
* state, read as `MLACache` reads it). Each row owns its pages; a read gathers
|
|
606
|
+
* every row's stored positions, decodes them and joins the rows right-justified
|
|
607
|
+
* with zero padding, `MLACache`'s layout, so f32 pages read `MLACache`'s bits.
|
|
608
|
+
* The transaction trims each row back to its accepted length (its tail pages
|
|
609
|
+
* return to the row's free list); prefill padding is removed from each row's
|
|
610
|
+
* pages when the prefill finalizes.
|
|
611
|
+
*/
|
|
612
|
+
export class PagedLatentCache implements BatchableCache, LatentAttentionCache {
|
|
613
|
+
readonly kvLoraRank: number;
|
|
614
|
+
readonly ropeHeadDim: number;
|
|
615
|
+
readonly indexHeadDim: number | null;
|
|
616
|
+
readonly maxTokens: number;
|
|
617
|
+
readonly #encodings: (FamilyEncoding | null)[];
|
|
618
|
+
readonly #widths: number[];
|
|
619
|
+
#rows: LatentPages[] = [];
|
|
620
|
+
/** Per row, the leading positions it holds as left padding during a prefill. */
|
|
621
|
+
#leading: number[] = [];
|
|
622
|
+
#ends: number[] | undefined;
|
|
623
|
+
#beforeRound: number[] | undefined;
|
|
624
|
+
|
|
625
|
+
constructor(readonly geometry: LatentGeometry, readonly format: LatentFormat | null, readonly blockSize: number) {
|
|
626
|
+
positiveInteger(blockSize, "latent page size");
|
|
627
|
+
this.kvLoraRank = geometry.kvLoraRank;
|
|
628
|
+
this.ropeHeadDim = geometry.ropeHeadDim;
|
|
629
|
+
this.indexHeadDim = geometry.indexHeadDim ?? null;
|
|
630
|
+
this.maxTokens = geometry.maxTokens ?? Number.MAX_SAFE_INTEGER;
|
|
631
|
+
this.#widths = widthsOf(geometry);
|
|
632
|
+
this.#encodings = this.#widths.map(width => format ? familyEncoding(format, width) : null);
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
signature(): string { return `kv:latent-paged:${this.indexHeadDim ? "dsa:" : ""}${formatKey(this.format)}:${this.blockSize}`; }
|
|
636
|
+
get batchSize(): number | null { return this.#rows.length || null; }
|
|
637
|
+
/** Each row's stored positions, prefill padding excluded. */
|
|
638
|
+
get rowOffsets(): number[] { return this.#rows.map((row, index) => row.offset - (this.#leading[index] ?? 0)); }
|
|
639
|
+
get offset(): number { return Math.max(0, ...this.#rows.map(row => row.offset)); }
|
|
640
|
+
get leftPad(): number[] { const width = this.offset; return this.rowOffsets.map(offset => width - offset); }
|
|
641
|
+
/** The pages of each row (borrowed); the SSD form. */
|
|
642
|
+
get pages(): readonly LatentPages[] { return this.#rows; }
|
|
643
|
+
|
|
644
|
+
#encode(values: readonly MlxArray[]): MlxArray[] {
|
|
645
|
+
return values.flatMap((value, family) => this.#encodings[family]?.encode(value) ?? [ops.contiguous(value)]);
|
|
646
|
+
}
|
|
647
|
+
#decode(planes: readonly MlxArray[]): MlxArray[] {
|
|
648
|
+
const out: MlxArray[] = [];
|
|
649
|
+
let at = 0;
|
|
650
|
+
try {
|
|
651
|
+
for (const encoding of this.#encodings) {
|
|
652
|
+
const count = encoding?.planes ?? 1;
|
|
653
|
+
out.push(encoding ? encoding.decode(planes.slice(at, at + count)) : ops.contiguous(planes[at]!));
|
|
654
|
+
at += count;
|
|
655
|
+
}
|
|
656
|
+
return out;
|
|
657
|
+
} catch (error) { disposeResources(out); throw error; }
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
/** Every row's stored state decoded and joined right-justified: per family [B, T, W]. */
|
|
661
|
+
#read(families: number): MlxArray[] {
|
|
662
|
+
const width = this.offset;
|
|
663
|
+
const perRow: MlxArray[][] = [];
|
|
664
|
+
try {
|
|
665
|
+
for (const row of this.#rows) {
|
|
666
|
+
const planes = row.gather();
|
|
667
|
+
try { perRow.push(this.#decode(planes).map(values => { try { return padLeft(values, width - row.offset); } finally { values.dispose(); } })); }
|
|
668
|
+
finally { disposeResources(planes); }
|
|
669
|
+
}
|
|
670
|
+
return Array.from({ length: families }, (_, family) => ops.concatAxis(perRow.map(row => row[family]!), 0));
|
|
671
|
+
} finally { disposeResources(perRow.flat()); }
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
appendLatent(latent: MlxArray, rope: MlxArray, indexKeys: MlxArray | null): LatentAttentionState {
|
|
675
|
+
const [B, L] = appendShape(this.geometry, latent, rope, indexKeys, this.batchSize);
|
|
676
|
+
if (this.offset > this.maxTokens - L) throw new RangeError(`latent append would exceed maxTokens ${this.maxTokens}`);
|
|
677
|
+
if (!this.#rows.length) { this.#rows = Array.from({ length: B }, () => new LatentPages(this.blockSize)); this.#leading = new Array<number>(B).fill(0); }
|
|
678
|
+
const inputs = [latent, rope, ...(indexKeys ? [indexKeys] : [])];
|
|
679
|
+
for (const [index, row] of this.#rows.entries()) {
|
|
680
|
+
const values = inputs.map(input => rowSlice(input, index, 0, L));
|
|
681
|
+
try {
|
|
682
|
+
const planes = this.#encode(values);
|
|
683
|
+
try { row.append(planes); } finally { disposeResources(planes); }
|
|
684
|
+
} finally { disposeResources(values); }
|
|
685
|
+
}
|
|
686
|
+
const read = this.#read(inputs.length);
|
|
687
|
+
return { latent: read[0]!, rope: read[1]!, indexKeys: read[2] ?? null };
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
storedIndexKeys(): MlxArray | null {
|
|
691
|
+
if (!this.indexHeadDim || !this.offset) return null;
|
|
692
|
+
const all = this.#read(3);
|
|
693
|
+
disposeResources(all.slice(0, 2));
|
|
694
|
+
return all[2]!;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
state(): MlxArray[] { return this.#rows.flatMap(row => row.pools); }
|
|
698
|
+
bytesPerToken(): number {
|
|
699
|
+
const pools = this.#rows[0]?.pools ?? [];
|
|
700
|
+
return pools.length ? pools.reduce((bytes, pool) => bytes + pool.nbytes / (pool.shape[0]! * pool.shape[1]!), 0)
|
|
701
|
+
: this.#widths.reduce((bytes, width) => bytes + width * 4, 0);
|
|
702
|
+
}
|
|
703
|
+
projectedBytes(tokens: number): number {
|
|
704
|
+
return MLACache.projectedByteLength({ kvLoraRank: this.kvLoraRank, ropeHeadDim: this.ropeHeadDim,
|
|
705
|
+
...(this.indexHeadDim ? { dsa: { headDim: this.indexHeadDim } } : {}) }, 1, tokens);
|
|
706
|
+
}
|
|
707
|
+
isTrimmable(): boolean { return true; }
|
|
708
|
+
trim(n: number): void {
|
|
709
|
+
if (!Number.isSafeInteger(n) || n < 0 || n > Math.min(...this.rowOffsets, Infinity))
|
|
710
|
+
throw new RangeError(`cannot trim ${n} tokens from latent pages`);
|
|
711
|
+
for (const row of this.#rows) row.trim(n);
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
specRoundBegin(): void { this.#beforeRound = this.#rows.map(row => row.offset); }
|
|
715
|
+
specRoundCommit(): void { this.#beforeRound = undefined; }
|
|
716
|
+
specRoundRollback(keep: number | readonly number[]): void {
|
|
717
|
+
const before = this.#beforeRound!;
|
|
718
|
+
for (const [index, row] of this.#rows.entries()) row.trim(row.offset - before[index]! - (typeof keep === "number" ? keep : keep[index]!));
|
|
719
|
+
this.#beforeRound = undefined;
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
preparePrefill(padding: PrefillPadding): void {
|
|
723
|
+
if (!this.#rows.length) { this.#rows = padding.lengths.map(() => new LatentPages(this.blockSize)); this.#leading = padding.lengths.map(() => 0); }
|
|
724
|
+
if (padding.leftPadding) this.#leading = this.#leading.map((lead, row) => lead + padding.leftPadding![row]!);
|
|
725
|
+
this.#ends = padding.rightPadding?.some(pad => pad > 0)
|
|
726
|
+
? padding.lengths.map((length, row) => this.#rows[row]!.offset + length) : undefined;
|
|
727
|
+
}
|
|
728
|
+
finalizePrefill(): void {
|
|
729
|
+
for (const [index, row] of this.#rows.entries()) {
|
|
730
|
+
const lead = this.#leading[index] ?? 0, end = Math.min(row.offset, this.#ends?.[index] ?? row.offset);
|
|
731
|
+
if (lead || end < row.offset) row.keep(lead, end);
|
|
732
|
+
}
|
|
733
|
+
this.#leading = this.#rows.map(() => 0); this.#ends = undefined;
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
makeEmptyBatch(): PagedLatentCache { return new PagedLatentCache(this.geometry, this.format, this.blockSize); }
|
|
737
|
+
/** Adopt rows' pages (taking ownership): what extraction and the SSD form restore. */
|
|
738
|
+
adoptRows(rows: LatentPages[]): void { this.dispose(); this.#rows = rows; this.#leading = rows.map(() => 0); }
|
|
739
|
+
mergeRows(rows: readonly Cache[]): void {
|
|
740
|
+
if (this.#rows.length) throw new Error("latent mergeRows requires an empty destination cache");
|
|
741
|
+
const next: LatentPages[] = [];
|
|
742
|
+
try {
|
|
743
|
+
for (const source of rows) {
|
|
744
|
+
if (!(source instanceof PagedLatentCache) || source.signature() !== this.signature())
|
|
745
|
+
throw new Error(`latent pages cannot merge ${source.signature()} into ${this.signature()}`);
|
|
746
|
+
if (!source.#rows.length) next.push(new LatentPages(this.blockSize));
|
|
747
|
+
for (const row of source.#rows) next.push(row.clone());
|
|
748
|
+
}
|
|
749
|
+
} catch (error) { disposeResources(next); throw error; }
|
|
750
|
+
this.adoptRows(next);
|
|
751
|
+
}
|
|
752
|
+
extractRow(row: number): PagedLatentCache {
|
|
753
|
+
const out = this.makeEmptyBatch();
|
|
754
|
+
out.adoptRows([this.#rows[row]!.clone()]);
|
|
755
|
+
return out;
|
|
756
|
+
}
|
|
757
|
+
filterRows(keep: readonly number[]): void {
|
|
758
|
+
const previous = this.#rows, retained = new Set(keep);
|
|
759
|
+
this.#rows = keep.map(row => previous[row]!);
|
|
760
|
+
this.#leading = keep.map(row => this.#leading[row] ?? 0);
|
|
761
|
+
if (this.#ends) this.#ends = keep.map(row => this.#ends![row]!);
|
|
762
|
+
disposeResources(previous.filter((_, row) => !retained.has(row)));
|
|
763
|
+
}
|
|
764
|
+
dispose(): void {
|
|
765
|
+
disposeResources(this.#rows); this.#rows = []; this.#leading = [];
|
|
766
|
+
this.#ends = undefined; this.#beforeRound = undefined;
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
// ---------------------------------------------------------------------------
|
|
771
|
+
// Composition
|
|
772
|
+
|
|
773
|
+
/** The latent format a KV scheme names for the layer at cache index `layer`:
|
|
774
|
+
* every layer of a uniform affine scheme, the listed layers of a per-layer one,
|
|
775
|
+
* every layer under TurboQuant; none (f32) for bf16 and unlisted layers. */
|
|
776
|
+
export function latentFormatOf(kv: KvStorageScheme, layer: number): LatentFormat | null {
|
|
777
|
+
switch (kv.kind) {
|
|
778
|
+
case "bf16": return null;
|
|
779
|
+
case "affine": return { kind: "affine", bits: kv.bits, groupSize: kv.groupSize };
|
|
780
|
+
case "affine-layers": {
|
|
781
|
+
const entry = kv.layers.find(item => item.layer === layer);
|
|
782
|
+
return entry ? { kind: "affine", bits: entry.bits, groupSize: entry.groupSize } : null;
|
|
783
|
+
}
|
|
784
|
+
case "turbo": return { kind: "turbo", kBits: kv.kBits, vBits: kv.vBits };
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
/** The latent attention cache a composition names for one layer: f32
|
|
789
|
+
* (`MLACache`), f32-first encoded (`EncodedLatentCache`), or pages of either
|
|
790
|
+
* (`PagedLatentCache`). */
|
|
791
|
+
export function composedLatentCache(kv: KvStorageScheme, paged: { readonly blockSize: number } | null, layer: number,
|
|
792
|
+
geometry: LatentGeometry): LatentAttentionCache & BatchableCache {
|
|
793
|
+
const format = latentFormatOf(kv, layer);
|
|
794
|
+
if (paged) return new PagedLatentCache(geometry, format, paged.blockSize);
|
|
795
|
+
const mla: MLACacheGeometry = { kvLoraRank: geometry.kvLoraRank, ropeHeadDim: geometry.ropeHeadDim,
|
|
796
|
+
...(geometry.maxTokens !== undefined ? { maxTokens: geometry.maxTokens } : {}),
|
|
797
|
+
...(geometry.indexHeadDim ? { dsa: { headDim: geometry.indexHeadDim } } : {}) };
|
|
798
|
+
if (!format) return new MLACache(mla);
|
|
799
|
+
return new EncodedLatentCache(geometry, format, kv.kind === "bf16" ? 0 : kv.quantizedKvStart ?? 0);
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
// ---------------------------------------------------------------------------
|
|
803
|
+
// The snapshot and SSD forms
|
|
804
|
+
|
|
805
|
+
const ENCODED: LatentLegoKind = "latent-encoded", PAGED: LatentLegoKind = "latent-paged";
|
|
806
|
+
|
|
807
|
+
const geometryHeader = (g: LatentGeometry) => ({
|
|
808
|
+
kvLoraRank: g.kvLoraRank, ropeHeadDim: g.ropeHeadDim,
|
|
809
|
+
...(g.indexHeadDim ? { dsaHeadDim: g.indexHeadDim } : {}), ...(g.maxTokens !== undefined ? { maxTokens: g.maxTokens } : {}),
|
|
810
|
+
});
|
|
811
|
+
const geometryOf = (entry: CacheHeaderEntry): LatentGeometry => ({
|
|
812
|
+
kvLoraRank: entry.kvLoraRank!, ropeHeadDim: entry.ropeHeadDim!,
|
|
813
|
+
...(entry.dsaHeadDim ? { indexHeadDim: entry.dsaHeadDim } : {}), ...(entry.maxTokens !== undefined ? { maxTokens: entry.maxTokens } : {}),
|
|
814
|
+
});
|
|
815
|
+
const formatHeader = (format: LatentFormat | null) => !format ? {}
|
|
816
|
+
: format.kind === "affine" ? { bits: format.bits, groupSize: format.groupSize } : { kBits: format.kBits, vBits: format.vBits };
|
|
817
|
+
const formatOf = (entry: CacheHeaderEntry): LatentFormat | null =>
|
|
818
|
+
entry.kBits ? { kind: "turbo", kBits: entry.kBits, vBits: entry.vBits! }
|
|
819
|
+
: entry.bits ? { kind: "affine", bits: entry.bits, groupSize: entry.groupSize! } : null;
|
|
820
|
+
|
|
821
|
+
/** One serial encoded latent cache: its encoded planes, then its f32 shadow
|
|
822
|
+
* while it has not converted. */
|
|
823
|
+
const encodedCodec: CacheCodec = {
|
|
824
|
+
matches: cache => cache instanceof EncodedLatentCache,
|
|
825
|
+
snapshot(cache, context) {
|
|
826
|
+
const c = cache as EncodedLatentCache;
|
|
827
|
+
if (c.batchSize !== 1) throw new Error(`persistence takes serial state; extract each of the ${c.batchSize} rows first`);
|
|
828
|
+
const state = c.rowState(0);
|
|
829
|
+
for (const array of [...state.planes.flat(), ...(state.shadow ?? [])]) context.push(array, true);
|
|
830
|
+
return { kind: ENCODED, offset: state.offset, tensors: context.slots, ...geometryHeader(c.geometry), ...formatHeader(c.format),
|
|
831
|
+
quantizedKvStart: c.start, latentShadow: state.shadow !== null };
|
|
832
|
+
},
|
|
833
|
+
clone(cache) {
|
|
834
|
+
const c = cache as EncodedLatentCache;
|
|
835
|
+
if (c.batchSize !== 1) throw new Error(`persistence takes serial state; extract each of the ${c.batchSize} rows first`);
|
|
836
|
+
return c.extractRow(0);
|
|
837
|
+
},
|
|
838
|
+
load(entry, context) {
|
|
839
|
+
const cache = new EncodedLatentCache(geometryOf(entry), formatOf(entry)!, entry.quantizedKvStart ?? 0);
|
|
840
|
+
const arrays = entry.tensors.map(context.arr);
|
|
841
|
+
const families = widthsOf(cache.geometry).length, perFamily = (arrays.length - (entry.latentShadow ? families : 0)) / families;
|
|
842
|
+
const planes = Array.from({ length: families }, (_, f) => arrays.slice(f * perFamily, (f + 1) * perFamily));
|
|
843
|
+
cache.restoreRow({ planes, shadow: entry.latentShadow ? arrays.slice(families * perFamily) : null,
|
|
844
|
+
offset: entry.offset, minimumReusableOffset: entry.minimumReusableOffset ?? 0 });
|
|
845
|
+
return cache;
|
|
846
|
+
},
|
|
847
|
+
headerTrimmable: () => true,
|
|
848
|
+
};
|
|
849
|
+
|
|
850
|
+
/** One serial paged latent cache: its pools and block table. */
|
|
851
|
+
const pagedCodec: CacheCodec = {
|
|
852
|
+
matches: cache => cache instanceof PagedLatentCache,
|
|
853
|
+
snapshot(cache, context) {
|
|
854
|
+
const c = cache as PagedLatentCache;
|
|
855
|
+
if (c.batchSize !== 1) throw new Error(`persistence takes serial state; extract each of the ${c.batchSize} rows first`);
|
|
856
|
+
const pages = c.pages[0]!;
|
|
857
|
+
for (const pool of pages.pools) context.push(pool, false);
|
|
858
|
+
return { kind: PAGED, offset: pages.offset, tensors: context.slots, ...geometryHeader(c.geometry), ...formatHeader(c.format),
|
|
859
|
+
latentPages: { blockSize: c.blockSize, blockTable: [...pages.blockTable], numBlocks: pages.numBlocks } };
|
|
860
|
+
},
|
|
861
|
+
clone(cache) {
|
|
862
|
+
const c = cache as PagedLatentCache;
|
|
863
|
+
if (c.batchSize !== 1) throw new Error(`persistence takes serial state; extract each of the ${c.batchSize} rows first`);
|
|
864
|
+
return c.extractRow(0);
|
|
865
|
+
},
|
|
866
|
+
load(entry, context) {
|
|
867
|
+
const layout = entry.latentPages!;
|
|
868
|
+
const cache = new PagedLatentCache(geometryOf(entry), formatOf(entry), layout.blockSize);
|
|
869
|
+
const pages = new LatentPages(layout.blockSize);
|
|
870
|
+
pages.restore(entry.tensors.map(context.arr), layout.blockTable, layout.numBlocks, entry.offset);
|
|
871
|
+
cache.adoptRows([pages]);
|
|
872
|
+
return cache;
|
|
873
|
+
},
|
|
874
|
+
headerTrimmable: () => true,
|
|
875
|
+
};
|
|
876
|
+
|
|
877
|
+
/** The persistence codecs of every latent lego, by header kind. The default
|
|
878
|
+
* codecs (`legacyCacheCodecs`) serve every other storage, `MLACache` included. */
|
|
879
|
+
export const LATENT_LEGO_CODECS: Readonly<Record<LatentLegoKind, CacheCodec>> = Object.freeze({
|
|
880
|
+
[ENCODED]: encodedCodec, [PAGED]: pagedCodec,
|
|
881
|
+
});
|
|
882
|
+
|
|
883
|
+
/** The codec provider of a graph whose caches include latent legos: theirs, and
|
|
884
|
+
* the default codecs for everything else. */
|
|
885
|
+
export const latentLegoCodecs: CacheCodecProvider = Object.freeze({
|
|
886
|
+
id: `${legacyCacheCodecs.id}+latent-legos-v1`,
|
|
887
|
+
forCache(cache: Cache): CacheCodec {
|
|
888
|
+
return cache instanceof EncodedLatentCache ? encodedCodec : cache instanceof PagedLatentCache ? pagedCodec
|
|
889
|
+
: legacyCacheCodecs.forCache(cache);
|
|
890
|
+
},
|
|
891
|
+
forHeader(entry: CacheHeaderEntry): CacheCodec {
|
|
892
|
+
return entry.kind === ENCODED ? encodedCodec : entry.kind === PAGED ? pagedCodec : legacyCacheCodecs.forHeader(entry);
|
|
893
|
+
},
|
|
894
|
+
});
|
|
895
|
+
|