@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,663 @@
|
|
|
1
|
+
// The quantized KV legos a composition's `makeCache` builds: bf16 storage
|
|
2
|
+
// through the first append (or the first `start` tokens), quantized storage
|
|
3
|
+
// after. Today's served path and mlx-lm's generate loop both run the first
|
|
4
|
+
// forward against unquantized keys and values and convert afterwards, even with
|
|
5
|
+
// `quantized_kv_start` 0; these caches keep that order inside their own
|
|
6
|
+
// appends. A cache quantized from token zero (`QuantizedKVCache` built empty)
|
|
7
|
+
// is a different composition with different first-chunk numbers.
|
|
8
|
+
import type { MlxArray } from "@mlx-bun/mlx/array";
|
|
9
|
+
import { clearCache, Dtype } from "@mlx-bun/mlx/ffi";
|
|
10
|
+
import * as ops from "@mlx-bun/mlx/ops";
|
|
11
|
+
import type {
|
|
12
|
+
AttentionCache, AttentionRead, BidirectionalAttentionCache, BlockAttentionCache, Cache, CommittedAttentionCache, CompiledDecodeCache, ComposedStorage,
|
|
13
|
+
EncoderAttentionCache,
|
|
14
|
+
DecodeSlot, DecodeStepInputs, DecodeStepPlan, Mask,
|
|
15
|
+
} from "../contracts/mlx/cache";
|
|
16
|
+
import type { KvStorageScheme } from "../contracts/portable/composition";
|
|
17
|
+
import { composedAffineKernels, type AffineKernels, type AffineKernelSet } from "./affine-attention";
|
|
18
|
+
import { AttentionMasks, decodeByPosition, sdpaRead, type DenseRead } from "./attention-read";
|
|
19
|
+
import { KVCache } from "./kv";
|
|
20
|
+
import { KV4_DECODE_BLOCK } from "../kernels/attention/kvq4-decode";
|
|
21
|
+
import { kv4Head256Kernels } from "./kv4-head256-cache";
|
|
22
|
+
import { QuantizedKVCache } from "./quantized-kv";
|
|
23
|
+
import { RotatingKVCache } from "./rotating-kv";
|
|
24
|
+
import { RotatingQuantizedKVCache } from "./rotating-quantized-kv";
|
|
25
|
+
import { TurboQuantKVCache } from "./turboquant-kv";
|
|
26
|
+
import type { TurboQuantKernels } from "./turboquant-attention";
|
|
27
|
+
import { turboQuantFusedDecode } from "./turboquant-codec";
|
|
28
|
+
import { isQuantizedKvCache, isRotatingQuantizedCache, isTurboQuantKvCache } from "./capabilities";
|
|
29
|
+
|
|
30
|
+
/** Whether bf16 storage holding `offset` positions converts under a
|
|
31
|
+
* conversion start of `start` tokens: once it holds at least `start`
|
|
32
|
+
* positions, and never while empty, so the first append is bf16 even when the
|
|
33
|
+
* start is 0. */
|
|
34
|
+
export function transitionDue(offset: number, start: number): boolean {
|
|
35
|
+
return offset >= start && offset !== 0;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Finish one conversion: a nonzero start records the converted `offset`
|
|
39
|
+
* (read before the source was consumed) as the earliest reusable prefix, then
|
|
40
|
+
* the new storage is materialized and the allocator pool released before
|
|
41
|
+
* anything else converts, which bounds the live bf16 source plus quantized
|
|
42
|
+
* copy to one layer. Returns `converted`. */
|
|
43
|
+
export function completeConversion<C extends Cache>(converted: C, offset: number, start: number): C {
|
|
44
|
+
if (start > 0) converted.minimumReusableOffset = offset;
|
|
45
|
+
const state = converted.state();
|
|
46
|
+
try { ops.evalAll(state); }
|
|
47
|
+
finally { if (converted.stateNeedsDispose) for (const array of state) array.dispose(); }
|
|
48
|
+
clearCache();
|
|
49
|
+
return converted;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** The affine conversion of one bf16 entry: the source's own `toQuantized`
|
|
53
|
+
* (which consumes the source) with `kernelSet`, the set the converted storage
|
|
54
|
+
* attends with, then the shared bookkeeping. */
|
|
55
|
+
export function convertToAffine<C extends Cache>(
|
|
56
|
+
source: { readonly offset: number; toQuantized(groupSize: number, bits: number, kernels: AffineKernelSet): C },
|
|
57
|
+
groupSize: number, bits: number, start: number, kernelSet: AffineKernelSet,
|
|
58
|
+
): C {
|
|
59
|
+
const offset = source.offset;
|
|
60
|
+
return completeConversion(source.toQuantized(groupSize, bits, kernelSet), offset, start);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** The TurboQuant conversion of one bf16 entry: encode the source's live
|
|
64
|
+
* window (consuming the source), then the shared bookkeeping. The converted
|
|
65
|
+
* cache attends with the source's TurboQuant read (`turboQuantKernels`);
|
|
66
|
+
* `fusedDecode` is its decode policy. */
|
|
67
|
+
export function convertToTurboQuant(source: KVCache, kBits: number, vBits: number, start: number,
|
|
68
|
+
fusedDecode: boolean): TurboQuantKVCache {
|
|
69
|
+
const offset = source.offset;
|
|
70
|
+
return completeConversion(TurboQuantKVCache.fromKVCache(source, kBits, vBits, source.turboQuantKernels, fusedDecode), offset, start);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Affine storage `source` holds, re-homed into a cache of the same storage
|
|
74
|
+
* class that attends with `kernels` and `masks`: the arrays move (no copy) and
|
|
75
|
+
* `source` is left empty. A restored, cloned or extracted entry enters a lego
|
|
76
|
+
* with the lego's own kernels. */
|
|
77
|
+
function rehomeAffine(source: QuantizedKVCache, kernels: AffineKernels, masks: AttentionMasks): QuantizedKVCache {
|
|
78
|
+
const cache = new QuantizedKVCache(source.groupSize, source.bits, kernels, masks);
|
|
79
|
+
if (source.keys && source.values) cache.restoreState(source.keys, source.values, source.offset);
|
|
80
|
+
else cache.offset = source.offset;
|
|
81
|
+
cache.minimumReusableOffset = source.minimumReusableOffset;
|
|
82
|
+
source.keys = source.values = null;
|
|
83
|
+
source.dispose();
|
|
84
|
+
return cache;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function rehomeRotatingAffine(source: RotatingQuantizedKVCache, kernels: AffineKernels, masks: AttentionMasks): RotatingQuantizedKVCache {
|
|
88
|
+
const cache = new RotatingQuantizedKVCache(source.maxSize, source.groupSize, source.bits, kernels, masks);
|
|
89
|
+
if (source.keys && source.values) cache.restoreState(source.keys, source.values, source.offset, source.ringIdx);
|
|
90
|
+
else { cache.offset = source.offset; cache.ringIdx = source.ringIdx; }
|
|
91
|
+
cache.minimumReusableOffset = source.minimumReusableOffset;
|
|
92
|
+
source.keys = source.values = null;
|
|
93
|
+
source.dispose();
|
|
94
|
+
return cache;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** The deprecated plain fetch of a lego in bf16: the storage's fetch, then the
|
|
98
|
+
* transition its append may owe. The caller owns the fetched keys and values,
|
|
99
|
+
* which outlive the conversion that consumes the bf16 storage. */
|
|
100
|
+
function fetchThenSettle(fetched: [MlxArray, MlxArray], settle: () => void): [MlxArray, MlxArray] {
|
|
101
|
+
try { settle(); }
|
|
102
|
+
catch (error) { fetched[0].dispose(); fetched[1].dispose(); throw error; }
|
|
103
|
+
return fetched;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** The bidirectional and encoder reads are defined for a sequence's first
|
|
107
|
+
* window only, which a lego always holds in bf16. */
|
|
108
|
+
function firstWindowOnly(read: string): never {
|
|
109
|
+
throw new Error(`${read} reads a sequence's first window (offset 0); this cache already holds positions`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Affine-quantized keys and values for one full-attention layer, bf16 first:
|
|
114
|
+
* the lego `--kv-quant N` (and a `kv_config.json` layer) composes.
|
|
115
|
+
*
|
|
116
|
+
* Numbers: today's served path and mlx-lm's (`quantized_kv_start`). Storage is
|
|
117
|
+
* a `KVCache` until an append leaves it holding at least `start` positions (the
|
|
118
|
+
* first append when `start` is 0). That append is read bf16 and then, before it
|
|
119
|
+
* returns, the storage converts (`KVCache.toQuantized`, the reuse floor at a
|
|
120
|
+
* nonzero start, materialized at once). Every later append quantizes its
|
|
121
|
+
* positions and is read by the quantized storage's own reads, with
|
|
122
|
+
* `kernelSet`'s kernels. Stored values below the offset therefore equal the
|
|
123
|
+
* convert-after path's bit for bit, and so does every read. A
|
|
124
|
+
* `QuantizedKVCache` built empty is the other composition: it quantizes the
|
|
125
|
+
* first append too, so its first chunk's output differs
|
|
126
|
+
* (`tests/state/bf16-first-kv.test.ts` records by how much), and it has no
|
|
127
|
+
* mlx-lm oracle.
|
|
128
|
+
*
|
|
129
|
+
* The transition keys only on this cache's stored length and runs only inside
|
|
130
|
+
* its appends (or when it adopts storage that already holds `start`
|
|
131
|
+
* positions), so the caller's append boundaries are part of the numbers: a
|
|
132
|
+
* caller that splits one planned prefill chunk into two appends moves the
|
|
133
|
+
* conversion to the split. The rows layout the scheduler batches
|
|
134
|
+
* (`DelayedQuantizedKVCache`) converts with this same code but only at each
|
|
135
|
+
* row's planned chunk boundary (`TransitioningKvRows`). The transition is
|
|
136
|
+
* irreversible: trimming below the start afterwards keeps the quantized
|
|
137
|
+
* storage, so a verify window that crosses the start converts even if its tail
|
|
138
|
+
* is later rejected.
|
|
139
|
+
*
|
|
140
|
+
* The row layouts, clones and saved files take the storage, never this cache
|
|
141
|
+
* (`composed`): its signature is its own (`kv:bf16-first:` and the storage's),
|
|
142
|
+
* and its storage is what the convert-after path saves.
|
|
143
|
+
*/
|
|
144
|
+
export class Bf16FirstQuantizedKVCache implements CommittedAttentionCache, BidirectionalAttentionCache, EncoderAttentionCache, BlockAttentionCache, CompiledDecodeCache {
|
|
145
|
+
#plain: KVCache | null;
|
|
146
|
+
#quantized: QuantizedKVCache | null = null;
|
|
147
|
+
|
|
148
|
+
/** `storage`: bf16 or affine storage of this layer to adopt (restored,
|
|
149
|
+
* cloned or extracted elsewhere); affine storage is re-homed to this lego's
|
|
150
|
+
* kernels, bf16 storage that already holds `start` positions converts now. */
|
|
151
|
+
constructor(
|
|
152
|
+
readonly groupSize: number,
|
|
153
|
+
readonly bits: number,
|
|
154
|
+
/** Positions held in bf16 before the conversion; 0 converts after the first append. */
|
|
155
|
+
readonly start: number,
|
|
156
|
+
/** The affine kernel set the converted storage attends with, named by the
|
|
157
|
+
* composition (`composedAffineKernels`, or the KV4 set). */
|
|
158
|
+
readonly kernelSet: AffineKernelSet,
|
|
159
|
+
/** The per-forward masks of the model this cache belongs to. */
|
|
160
|
+
readonly masks = new AttentionMasks(),
|
|
161
|
+
storage?: Cache,
|
|
162
|
+
) {
|
|
163
|
+
if (!storage) { this.#plain = new KVCache(masks); return; }
|
|
164
|
+
if (isQuantizedKvCache(storage)) {
|
|
165
|
+
const source = storage as QuantizedKVCache;
|
|
166
|
+
this.#plain = null;
|
|
167
|
+
this.#quantized = rehomeAffine(source, this.#kernelsFor(source), masks);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
this.#plain = storage as KVCache;
|
|
171
|
+
this.#convertIfDue();
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
#kernelsFor(source: QuantizedKVCache): AffineKernels {
|
|
175
|
+
return this.kernelSet(this.bits, this.groupSize, source.keys?.scales.dtype ?? Dtype.bfloat16);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
get #storage(): KVCache | QuantizedKVCache { return this.#quantized ?? this.#plain!; }
|
|
179
|
+
get offset(): number { return this.#storage.offset; }
|
|
180
|
+
get minimumReusableOffset(): number | undefined { return (this.#storage as Cache).minimumReusableOffset; }
|
|
181
|
+
set minimumReusableOffset(value: number | undefined) { (this.#storage as Cache).minimumReusableOffset = value; }
|
|
182
|
+
/** The donor reads of the storage held now: a drafter borrowing this layer's
|
|
183
|
+
* keys and values reads them as the storage hands them out. */
|
|
184
|
+
get captureDonorRows() { const storage = this.#storage as Cache; return storage.captureDonorRows?.bind(storage); }
|
|
185
|
+
get captureDonorAttention() { const storage = this.#storage as Cache; return storage.captureDonorAttention?.bind(storage); }
|
|
186
|
+
|
|
187
|
+
/** The storage the row layouts, clones and saved files take (see the class). */
|
|
188
|
+
readonly composed: ComposedStorage = {
|
|
189
|
+
storage: () => this.#storage,
|
|
190
|
+
over: (storage: Cache) => new Bf16FirstQuantizedKVCache(this.groupSize, this.bits, this.start, this.kernelSet, this.masks, storage),
|
|
191
|
+
release: () => { const storage = this.#storage; this.#plain = new KVCache(this.masks); this.#quantized = null; return storage; },
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
appendDecode(k: MlxArray, v: MlxArray): AttentionRead {
|
|
195
|
+
if (this.#quantized) return this.#quantized.appendDecode(k, v);
|
|
196
|
+
return this.#settle(this.#plain!.appendDecode(k, v));
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
appendWindow(k: MlxArray, v: MlxArray): AttentionRead {
|
|
200
|
+
if (this.#quantized) return this.#quantized.appendWindow(k, v);
|
|
201
|
+
return this.#settle(this.#plain!.appendWindow(k, v));
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
appendCommitted(k: MlxArray, v: MlxArray): AttentionRead {
|
|
205
|
+
if (this.#quantized) return this.#quantized.appendCommitted(k, v);
|
|
206
|
+
return this.#settle(this.#plain!.appendCommitted(k, v));
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
appendBidirectional(k: MlxArray, v: MlxArray, bidirectional: MlxArray): AttentionRead {
|
|
210
|
+
if (this.#quantized) return firstWindowOnly("appendBidirectional");
|
|
211
|
+
return this.#settle(this.#plain!.appendBidirectional(k, v, bidirectional));
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** The encoder window, a first window: bf16 storage's read, then the
|
|
215
|
+
* transition its append owes. */
|
|
216
|
+
appendEncoderWindow(k: MlxArray, v: MlxArray, bidirectional: MlxArray): AttentionRead {
|
|
217
|
+
if (this.#quantized) return firstWindowOnly("appendEncoderWindow");
|
|
218
|
+
return this.#settle(this.#plain!.appendEncoderWindow(k, v, bidirectional));
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/** The block read of the storage held now (it appends nothing, so nothing
|
|
222
|
+
* converts). */
|
|
223
|
+
readBlock(k: MlxArray, v: MlxArray): AttentionRead { return this.#storage.readBlock(k, v); }
|
|
224
|
+
|
|
225
|
+
/** Positions an append may add before the transition: up to the start. */
|
|
226
|
+
maxAppendTokens(): number {
|
|
227
|
+
return this.#plain && this.#plain.offset < this.start ? this.start - this.#plain.offset : Infinity;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// The compiled decode step drives the storage held now; a committed step
|
|
231
|
+
// owes the transition an append owes.
|
|
232
|
+
prepareDecodeStep(): DecodeStepPlan { return this.#storage.prepareDecodeStep(); }
|
|
233
|
+
decodePhase(): DecodeStepPlan["fetch"] { return this.#storage.decodePhase(); }
|
|
234
|
+
decodeSlot(plan: DecodeStepPlan): DecodeSlot { return this.#storage.decodeSlot(plan); }
|
|
235
|
+
decodeInputs(plan: DecodeStepPlan, step: DecodeStepInputs): MlxArray[] { return this.#storage.decodeInputs(plan, step); }
|
|
236
|
+
commitDecodeStep(slot: DecodeSlot, outputs: MlxArray[]): MlxArray[] {
|
|
237
|
+
const evalWith = this.#storage.commitDecodeStep(slot, outputs);
|
|
238
|
+
if (!this.#plain || !transitionDue(this.#plain.offset, this.start)) return evalWith;
|
|
239
|
+
// The conversion consumes the arrays the step wrote and evaluates the
|
|
240
|
+
// converted storage before it returns: nothing is left to evaluate here.
|
|
241
|
+
this.#convertIfDue();
|
|
242
|
+
return [];
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/** After a bf16 append: convert once the stored length reaches the start.
|
|
246
|
+
* The read already holds the bf16 keys and values it attends. */
|
|
247
|
+
#settle(read: AttentionRead): AttentionRead {
|
|
248
|
+
try { this.#convertIfDue(); }
|
|
249
|
+
catch (error) { read.dispose(); throw error; }
|
|
250
|
+
return read;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
#convertIfDue(): void {
|
|
254
|
+
const plain = this.#plain!;
|
|
255
|
+
if (!transitionDue(plain.offset, this.start)) return;
|
|
256
|
+
this.#quantized = convertToAffine(plain, this.groupSize, this.bits, this.start, this.kernelSet);
|
|
257
|
+
this.#plain = null;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
signature(): string { return `kv:bf16-first:${this.#storage.signature()}`; }
|
|
261
|
+
state(): MlxArray[] { return this.#storage.state(); }
|
|
262
|
+
bytesPerToken(): number { return this.#storage.bytesPerToken(); }
|
|
263
|
+
isTrimmable(): boolean { return this.#storage.isTrimmable(); }
|
|
264
|
+
trim(n: number): void { this.#storage.trim(n); }
|
|
265
|
+
/** @deprecated The reads build their own masks. */
|
|
266
|
+
makeMask(N: number, windowSize: number | null): Mask { return this.#storage.makeMask(N, windowSize); }
|
|
267
|
+
/** @deprecated Read through `appendDecode` or `appendWindow`. The dense
|
|
268
|
+
* fetch of the storage (the softcapped read's): bf16 storage fetches and then
|
|
269
|
+
* converts as an append does; affine storage fetches its window dequantized. */
|
|
270
|
+
updateAndFetch(k: MlxArray, v: MlxArray): [MlxArray, MlxArray] {
|
|
271
|
+
if (this.#quantized) return this.#quantized.updateAndFetch(k, v);
|
|
272
|
+
return fetchThenSettle(this.#plain!.updateAndFetch(k, v), () => this.#convertIfDue());
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/** Releases the storage and returns to the constructed state: empty bf16. */
|
|
276
|
+
dispose(): void {
|
|
277
|
+
this.#quantized?.dispose();
|
|
278
|
+
this.#plain?.dispose();
|
|
279
|
+
this.#quantized = null;
|
|
280
|
+
this.#plain = new KVCache(this.masks);
|
|
281
|
+
this.masks.clear();
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Affine-quantized keys and values for one sliding-window layer, bf16 first:
|
|
287
|
+
* the rotating counterpart of {@link Bf16FirstQuantizedKVCache}, with the same
|
|
288
|
+
* numbers, transition and obligations. Storage is a `RotatingKVCache` of
|
|
289
|
+
* `maxSize` until an append leaves it holding at least `start` positions, then
|
|
290
|
+
* converts (`RotatingKVCache.toQuantized`: the ring as laid out, its write
|
|
291
|
+
* index carried) into a `RotatingQuantizedKVCache`, whose own reads serve every
|
|
292
|
+
* later append. Every written ring position equals the convert-after path's
|
|
293
|
+
* bit for bit.
|
|
294
|
+
*/
|
|
295
|
+
export class Bf16FirstRotatingQuantizedKVCache implements CommittedAttentionCache, BidirectionalAttentionCache, EncoderAttentionCache, BlockAttentionCache, CompiledDecodeCache {
|
|
296
|
+
#plain: RotatingKVCache | null;
|
|
297
|
+
#quantized: RotatingQuantizedKVCache | null = null;
|
|
298
|
+
|
|
299
|
+
constructor(
|
|
300
|
+
/** The sliding window: the ring's capacity and the window every read applies. */
|
|
301
|
+
readonly maxSize: number,
|
|
302
|
+
readonly groupSize: number,
|
|
303
|
+
readonly bits: number,
|
|
304
|
+
/** Positions held in bf16 before the conversion; 0 converts after the first append. */
|
|
305
|
+
readonly start: number,
|
|
306
|
+
/** The affine kernel set the converted storage attends with, named by the
|
|
307
|
+
* composition (`composedAffineKernels`). */
|
|
308
|
+
readonly kernelSet: AffineKernelSet,
|
|
309
|
+
/** The per-forward masks of the model this cache belongs to. */
|
|
310
|
+
readonly masks = new AttentionMasks(),
|
|
311
|
+
storage?: Cache,
|
|
312
|
+
) {
|
|
313
|
+
if (!storage) { this.#plain = new RotatingKVCache(maxSize, masks); return; }
|
|
314
|
+
if (isRotatingQuantizedCache(storage)) {
|
|
315
|
+
const source = storage as RotatingQuantizedKVCache;
|
|
316
|
+
this.#plain = null;
|
|
317
|
+
this.#quantized = rehomeRotatingAffine(source,
|
|
318
|
+
this.kernelSet(this.bits, this.groupSize, source.keys?.scales.dtype ?? Dtype.bfloat16), masks);
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
this.#plain = storage as RotatingKVCache;
|
|
322
|
+
this.#convertIfDue();
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
get #storage(): RotatingKVCache | RotatingQuantizedKVCache { return this.#quantized ?? this.#plain!; }
|
|
326
|
+
get offset(): number { return this.#storage.offset; }
|
|
327
|
+
get minimumReusableOffset(): number | undefined { return (this.#storage as Cache).minimumReusableOffset; }
|
|
328
|
+
set minimumReusableOffset(value: number | undefined) { (this.#storage as Cache).minimumReusableOffset = value; }
|
|
329
|
+
/** The donor reads of the storage held now: a drafter borrowing this layer's
|
|
330
|
+
* keys and values reads them as the storage hands them out. */
|
|
331
|
+
get captureDonorRows() { const storage = this.#storage as Cache; return storage.captureDonorRows?.bind(storage); }
|
|
332
|
+
get captureDonorAttention() { const storage = this.#storage as Cache; return storage.captureDonorAttention?.bind(storage); }
|
|
333
|
+
|
|
334
|
+
/** The storage the row layouts, clones and saved files take. */
|
|
335
|
+
readonly composed: ComposedStorage = {
|
|
336
|
+
storage: () => this.#storage,
|
|
337
|
+
over: (storage: Cache) => new Bf16FirstRotatingQuantizedKVCache(this.maxSize, this.groupSize, this.bits, this.start,
|
|
338
|
+
this.kernelSet, this.masks, storage),
|
|
339
|
+
release: () => { const storage = this.#storage; this.#plain = new RotatingKVCache(this.maxSize, this.masks); this.#quantized = null; return storage; },
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
appendDecode(k: MlxArray, v: MlxArray): AttentionRead {
|
|
343
|
+
if (this.#quantized) return this.#quantized.appendDecode(k, v);
|
|
344
|
+
return this.#settle(this.#plain!.appendDecode(k, v));
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
appendWindow(k: MlxArray, v: MlxArray): AttentionRead {
|
|
348
|
+
if (this.#quantized) return this.#quantized.appendWindow(k, v);
|
|
349
|
+
return this.#settle(this.#plain!.appendWindow(k, v));
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/** A committed span, one decode per position through this lego
|
|
353
|
+
* (`decodeByPosition`), so the transition falls where `L` decodes put it. */
|
|
354
|
+
appendCommitted(k: MlxArray, v: MlxArray): AttentionRead {
|
|
355
|
+
return decodeByPosition(this, k, v);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
appendBidirectional(k: MlxArray, v: MlxArray, bidirectional: MlxArray): AttentionRead {
|
|
359
|
+
if (this.#quantized) return firstWindowOnly("appendBidirectional");
|
|
360
|
+
return this.#settle(this.#plain!.appendBidirectional(k, v, bidirectional));
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/** The encoder window, a first window: bf16 storage's read, then the
|
|
364
|
+
* transition its append owes. */
|
|
365
|
+
appendEncoderWindow(k: MlxArray, v: MlxArray, bidirectional: MlxArray): AttentionRead {
|
|
366
|
+
if (this.#quantized) return firstWindowOnly("appendEncoderWindow");
|
|
367
|
+
return this.#settle(this.#plain!.appendEncoderWindow(k, v, bidirectional));
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/** The block read of the storage held now (it appends nothing, so nothing
|
|
371
|
+
* converts). */
|
|
372
|
+
readBlock(k: MlxArray, v: MlxArray): AttentionRead { return this.#storage.readBlock(k, v); }
|
|
373
|
+
|
|
374
|
+
maxAppendTokens(): number {
|
|
375
|
+
return this.#plain && this.#plain.offset < this.start ? this.start - this.#plain.offset : Infinity;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// The compiled decode step drives the ring held now; a committed step owes
|
|
379
|
+
// the transition an append owes.
|
|
380
|
+
prepareDecodeStep(): DecodeStepPlan { return this.#storage.prepareDecodeStep(); }
|
|
381
|
+
decodePhase(): DecodeStepPlan["fetch"] { return this.#storage.decodePhase(); }
|
|
382
|
+
decodeSlot(plan: DecodeStepPlan): DecodeSlot { return this.#storage.decodeSlot(plan); }
|
|
383
|
+
decodeInputs(plan: DecodeStepPlan, step: DecodeStepInputs): MlxArray[] { return this.#storage.decodeInputs(plan, step); }
|
|
384
|
+
commitDecodeStep(slot: DecodeSlot, outputs: MlxArray[]): MlxArray[] {
|
|
385
|
+
const evalWith = this.#storage.commitDecodeStep(slot, outputs);
|
|
386
|
+
if (!this.#plain || !transitionDue(this.#plain.offset, this.start)) return evalWith;
|
|
387
|
+
// The conversion consumes the arrays the step wrote and evaluates the
|
|
388
|
+
// converted storage before it returns: nothing is left to evaluate here.
|
|
389
|
+
this.#convertIfDue();
|
|
390
|
+
return [];
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
#settle(read: AttentionRead): AttentionRead {
|
|
394
|
+
try { this.#convertIfDue(); }
|
|
395
|
+
catch (error) { read.dispose(); throw error; }
|
|
396
|
+
return read;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
#convertIfDue(): void {
|
|
400
|
+
const plain = this.#plain!;
|
|
401
|
+
if (!transitionDue(plain.offset, this.start)) return;
|
|
402
|
+
this.#quantized = convertToAffine(plain, this.groupSize, this.bits, this.start, this.kernelSet);
|
|
403
|
+
this.#plain = null;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
signature(): string { return `kv:bf16-first:${this.#storage.signature()}`; }
|
|
407
|
+
state(): MlxArray[] { return this.#storage.state(); }
|
|
408
|
+
bytesPerToken(): number { return this.#storage.bytesPerToken(); }
|
|
409
|
+
isTrimmable(): boolean { return this.#storage.isTrimmable(); }
|
|
410
|
+
trim(n: number, bypass?: boolean): void { this.#storage.trim(n, bypass); }
|
|
411
|
+
/** @deprecated The reads build their own masks. */
|
|
412
|
+
makeMask(N: number, windowSize: number | null): Mask { return this.#storage.makeMask(N, windowSize); }
|
|
413
|
+
/** @deprecated Read through `appendDecode` or `appendWindow`. The dense
|
|
414
|
+
* fetch of the storage (the softcapped read's): a bf16 ring fetches and then
|
|
415
|
+
* converts as an append does; an affine ring fetches its ring dequantized. */
|
|
416
|
+
updateAndFetch(k: MlxArray, v: MlxArray): [MlxArray, MlxArray] {
|
|
417
|
+
if (this.#quantized) return this.#quantized.updateAndFetch(k, v);
|
|
418
|
+
return fetchThenSettle(this.#plain!.updateAndFetch(k, v), () => this.#convertIfDue());
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/** Releases the storage and returns to the constructed state: empty bf16. */
|
|
422
|
+
dispose(): void {
|
|
423
|
+
this.#quantized?.dispose();
|
|
424
|
+
this.#plain?.dispose();
|
|
425
|
+
this.#quantized = null;
|
|
426
|
+
this.#plain = new RotatingKVCache(this.maxSize, this.masks);
|
|
427
|
+
this.masks.clear();
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* TurboQuant-encoded keys and values for one full-attention layer, bf16 first:
|
|
433
|
+
* the lego `--kv-quant turbo` composes (sliding-window layers keep a plain
|
|
434
|
+
* `RotatingKVCache`).
|
|
435
|
+
*
|
|
436
|
+
* Numbers: today's served path. Storage is a `KVCache` until an append leaves
|
|
437
|
+
* it holding at least `start` positions (the first append when `start` is 0).
|
|
438
|
+
* That append is read bf16 and then, before it returns, the storage converts
|
|
439
|
+
* (`TurboQuantKVCache.fromKVCache` over the live window, the reuse floor at a
|
|
440
|
+
* nonzero start, materialized at once). Every later append encodes its
|
|
441
|
+
* positions and is read through the codec. Stored values below the offset
|
|
442
|
+
* equal the convert-after path's bit for bit. The transition has the
|
|
443
|
+
* obligations described on {@link Bf16FirstQuantizedKVCache}.
|
|
444
|
+
*
|
|
445
|
+
* Reads: before the transition, `KVCache`'s reads; after it, the encoded
|
|
446
|
+
* storage's reads, which attend with `turboQuantKernels` (no mask for decode,
|
|
447
|
+
* the causal mask for a window): decoded values and the stock fused SDPA for
|
|
448
|
+
* Qwen 3.5's graph, or the deferred-V fetch, the stock fused SDPA and the
|
|
449
|
+
* inverse rotation of the output for Gemma 4's and MiniCPM5's. `fusedDecode` is
|
|
450
|
+
* resolved by whoever builds the cache; nothing here reads the runtime
|
|
451
|
+
* configuration.
|
|
452
|
+
*/
|
|
453
|
+
export class Bf16FirstTurboQuantKVCache implements CommittedAttentionCache, BidirectionalAttentionCache, EncoderAttentionCache, BlockAttentionCache, CompiledDecodeCache {
|
|
454
|
+
#plain: KVCache | null;
|
|
455
|
+
#encoded: TurboQuantKVCache | null = null;
|
|
456
|
+
|
|
457
|
+
constructor(
|
|
458
|
+
readonly kBits: number,
|
|
459
|
+
readonly vBits: number,
|
|
460
|
+
/** Positions held in bf16 before the conversion; 0 converts after the first append. */
|
|
461
|
+
readonly start: number,
|
|
462
|
+
/** The codec's decode policy for the converted storage. */
|
|
463
|
+
readonly fusedDecode: boolean,
|
|
464
|
+
/** How the converted storage attends, named by the graph the cache serves
|
|
465
|
+
* (`denseTurboQuantKernels` for Qwen 3.5, `deferredTurboQuantKernels` for
|
|
466
|
+
* Gemma 4 and MiniCPM5). */
|
|
467
|
+
readonly turboQuantKernels: TurboQuantKernels,
|
|
468
|
+
/** The per-forward masks of the model this cache belongs to. */
|
|
469
|
+
readonly masks = new AttentionMasks(),
|
|
470
|
+
storage?: Cache,
|
|
471
|
+
) {
|
|
472
|
+
if (!storage) { this.#plain = new KVCache(masks, turboQuantKernels); return; }
|
|
473
|
+
if (isTurboQuantKvCache(storage)) { this.#plain = null; this.#encoded = storage as TurboQuantKVCache; return; }
|
|
474
|
+
this.#plain = storage as KVCache;
|
|
475
|
+
this.#convertIfDue();
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
get #storage(): KVCache | TurboQuantKVCache { return this.#encoded ?? this.#plain!; }
|
|
479
|
+
get offset(): number { return this.#storage.offset; }
|
|
480
|
+
get minimumReusableOffset(): number | undefined { return (this.#storage as Cache).minimumReusableOffset; }
|
|
481
|
+
set minimumReusableOffset(value: number | undefined) { (this.#storage as Cache).minimumReusableOffset = value; }
|
|
482
|
+
/** The donor reads of the storage held now: a drafter borrowing this layer's
|
|
483
|
+
* keys and values reads them as the storage hands them out. */
|
|
484
|
+
get captureDonorRows() { const storage = this.#storage as Cache; return storage.captureDonorRows?.bind(storage); }
|
|
485
|
+
get captureDonorAttention() { const storage = this.#storage as Cache; return storage.captureDonorAttention?.bind(storage); }
|
|
486
|
+
/** `state()` returns fresh views once the storage is encoded. */
|
|
487
|
+
get stateNeedsDispose(): boolean { return this.#encoded !== null; }
|
|
488
|
+
|
|
489
|
+
/** The storage the row layouts, clones and saved files take. */
|
|
490
|
+
readonly composed: ComposedStorage = {
|
|
491
|
+
storage: () => this.#storage,
|
|
492
|
+
over: (storage: Cache) => new Bf16FirstTurboQuantKVCache(this.kBits, this.vBits, this.start, this.fusedDecode,
|
|
493
|
+
this.turboQuantKernels, this.masks, storage),
|
|
494
|
+
release: () => { const storage = this.#storage; this.#plain = new KVCache(this.masks, this.turboQuantKernels); this.#encoded = null; return storage; },
|
|
495
|
+
};
|
|
496
|
+
|
|
497
|
+
appendDecode(k: MlxArray, v: MlxArray): AttentionRead {
|
|
498
|
+
if (this.#encoded) return this.#encoded.appendDecode(k, v);
|
|
499
|
+
return this.#settle(this.#plain!.appendDecode(k, v));
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
appendWindow(k: MlxArray, v: MlxArray): AttentionRead {
|
|
503
|
+
if (this.#encoded) return this.#encoded.appendWindow(k, v);
|
|
504
|
+
return this.#settle(this.#plain!.appendWindow(k, v));
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
appendCommitted(k: MlxArray, v: MlxArray): AttentionRead {
|
|
508
|
+
if (this.#encoded) return this.#encoded.appendCommitted(k, v);
|
|
509
|
+
return this.#settle(this.#plain!.appendCommitted(k, v));
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
appendBidirectional(k: MlxArray, v: MlxArray, bidirectional: MlxArray): AttentionRead {
|
|
513
|
+
if (this.#encoded) return firstWindowOnly("appendBidirectional");
|
|
514
|
+
return this.#settle(this.#plain!.appendBidirectional(k, v, bidirectional));
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/** The encoder window, a first window: bf16 storage's read, then the
|
|
518
|
+
* transition its append owes. */
|
|
519
|
+
appendEncoderWindow(k: MlxArray, v: MlxArray, bidirectional: MlxArray): AttentionRead {
|
|
520
|
+
if (this.#encoded) return firstWindowOnly("appendEncoderWindow");
|
|
521
|
+
return this.#settle(this.#plain!.appendEncoderWindow(k, v, bidirectional));
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
/** The block read of the storage held now (it appends nothing, so nothing
|
|
525
|
+
* converts). */
|
|
526
|
+
readBlock(k: MlxArray, v: MlxArray): AttentionRead { return this.#storage.readBlock(k, v); }
|
|
527
|
+
|
|
528
|
+
maxAppendTokens(): number {
|
|
529
|
+
return this.#plain && this.#plain.offset < this.start ? this.start - this.#plain.offset : Infinity;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// The compiled decode step drives the storage held now; a committed step
|
|
533
|
+
// owes the transition an append owes.
|
|
534
|
+
prepareDecodeStep(): DecodeStepPlan { return this.#storage.prepareDecodeStep(); }
|
|
535
|
+
decodePhase(): DecodeStepPlan["fetch"] { return this.#storage.decodePhase(); }
|
|
536
|
+
decodeSlot(plan: DecodeStepPlan): DecodeSlot { return this.#storage.decodeSlot(plan); }
|
|
537
|
+
decodeInputs(plan: DecodeStepPlan, step: DecodeStepInputs): MlxArray[] { return this.#storage.decodeInputs(plan, step); }
|
|
538
|
+
commitDecodeStep(slot: DecodeSlot, outputs: MlxArray[]): MlxArray[] {
|
|
539
|
+
const evalWith = this.#storage.commitDecodeStep(slot, outputs);
|
|
540
|
+
if (!this.#plain || !transitionDue(this.#plain.offset, this.start)) return evalWith;
|
|
541
|
+
// The conversion consumes the arrays the step wrote and evaluates the
|
|
542
|
+
// converted storage before it returns: nothing is left to evaluate here.
|
|
543
|
+
this.#convertIfDue();
|
|
544
|
+
return [];
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
#settle(read: AttentionRead): AttentionRead {
|
|
548
|
+
try { this.#convertIfDue(); }
|
|
549
|
+
catch (error) { read.dispose(); throw error; }
|
|
550
|
+
return read;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
#convertIfDue(): void {
|
|
554
|
+
const plain = this.#plain!;
|
|
555
|
+
if (!transitionDue(plain.offset, this.start)) return;
|
|
556
|
+
this.#encoded = convertToTurboQuant(plain, this.kBits, this.vBits, this.start, this.fusedDecode);
|
|
557
|
+
this.#plain = null;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
signature(): string { return `kv:bf16-first:${this.#storage.signature()}`; }
|
|
561
|
+
state(): MlxArray[] { return this.#storage.state(); }
|
|
562
|
+
bytesPerToken(): number { return this.#storage.bytesPerToken(); }
|
|
563
|
+
isTrimmable(): boolean { return this.#storage.isTrimmable(); }
|
|
564
|
+
trim(n: number): void { this.#storage.trim(n); }
|
|
565
|
+
/** @deprecated The reads build their own masks. */
|
|
566
|
+
makeMask(N: number, windowSize: number | null): Mask { return this.#storage.makeMask(N, windowSize); }
|
|
567
|
+
/** @deprecated Read through `appendDecode` or `appendWindow`. The plain
|
|
568
|
+
* fetch of the storage (the softcapped read's): bf16 storage fetches and then
|
|
569
|
+
* encodes as an append does; encoded storage decodes its window. */
|
|
570
|
+
updateAndFetch(k: MlxArray, v: MlxArray): [MlxArray, MlxArray] {
|
|
571
|
+
if (this.#encoded) return this.#encoded.updateAndFetch(k, v);
|
|
572
|
+
return fetchThenSettle(this.#plain!.updateAndFetch(k, v), () => this.#convertIfDue());
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
/** Releases the storage and returns to the constructed state: empty bf16. */
|
|
576
|
+
dispose(): void {
|
|
577
|
+
this.#encoded?.dispose();
|
|
578
|
+
this.#plain?.dispose();
|
|
579
|
+
this.#encoded = null;
|
|
580
|
+
this.#plain = new KVCache(this.masks, this.turboQuantKernels);
|
|
581
|
+
this.masks.clear();
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
/** The KV4 kernel set over the composition's affine set `plain`: the 4-bit
|
|
586
|
+
* group-64 head-dim-256 reads (`kv4Head256Kernels`, the decode kernel at
|
|
587
|
+
* `decodeBlock` tokens per threadgroup) for one sequence, and `plain`'s kernels
|
|
588
|
+
* for a row layout holding more. */
|
|
589
|
+
export function kv4Head256KernelSet(plain: AffineKernelSet, decodeBlock: number): AffineKernelSet {
|
|
590
|
+
return (bits, groupSize, dtype) => kv4Head256Kernels(plain(bits, groupSize, dtype), decodeBlock);
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
/**
|
|
594
|
+
* The 4-bit group-64 full-attention lego of the Qwen3.8-27B Trellis M4 Pro and
|
|
595
|
+
* M1 Max compositions, bf16 first: {@link Bf16FirstQuantizedKVCache} whose
|
|
596
|
+
* quantized storage reads with `Kv4Head256Cache`'s kernels, the decode kernel at
|
|
597
|
+
* the composition's `decodeBlock`. The first append is bf16, as
|
|
598
|
+
* the old graph's conversion after the first forward kept it; every later
|
|
599
|
+
* decode reads with the fused one-row KV4 kernel and every window with the
|
|
600
|
+
* folded-GQA kernel, the cores the old graph injected for one sequence. Its
|
|
601
|
+
* storage carries the KV4 set into the row layouts it joins, which read with
|
|
602
|
+
* the KV4 kernels while they hold one unpadded row and with `plain`'s affine
|
|
603
|
+
* kernels when they hold more (`AffineKernels.multiRow`), as the old graph read
|
|
604
|
+
* several sequences through the generic path.
|
|
605
|
+
*/
|
|
606
|
+
export class Bf16FirstKv4Head256Cache extends Bf16FirstQuantizedKVCache {
|
|
607
|
+
/** `plain`: the composition's affine kernel set (`composedAffineKernels`); `decodeBlock`: the decode kernel's
|
|
608
|
+
* tokens per threadgroup its device's table names (the M4 Pro's by default). */
|
|
609
|
+
constructor(start: number, readonly plain: AffineKernelSet, masks = new AttentionMasks(),
|
|
610
|
+
readonly decodeBlock = KV4_DECODE_BLOCK, storage?: Cache) {
|
|
611
|
+
super(64, 4, start, kv4Head256KernelSet(plain, decodeBlock), masks, storage);
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
/** One-time notice that TurboQuant leaves sliding-window layers bf16. */
|
|
616
|
+
let warnedTurboRotating = false;
|
|
617
|
+
|
|
618
|
+
/** The cache a composition's KV scheme names for one full-attention layer
|
|
619
|
+
* (cache index `layer`) of a graph whose TurboQuant read is
|
|
620
|
+
* `turboQuantKernels` and whose bf16 window read is `windowRead` (the stock
|
|
621
|
+
* fused SDPA unless it names another): plain bf16 for `bf16` and for a layer a
|
|
622
|
+
* per-layer scheme does not list, the bf16-first affine lego for affine schemes
|
|
623
|
+
* (the scheme's read kernels, `composedAffineKernels`), the bf16-first
|
|
624
|
+
* TurboQuant lego for `turbo`. Every cache of one `makeCache` call shares
|
|
625
|
+
* `masks`. */
|
|
626
|
+
export function composedFullAttentionCache(kv: KvStorageScheme, layer: number, masks: AttentionMasks,
|
|
627
|
+
turboQuantKernels: TurboQuantKernels, windowRead: DenseRead = sdpaRead): AttentionCache {
|
|
628
|
+
const start = kv.kind === "bf16" ? 0 : kv.quantizedKvStart ?? 0;
|
|
629
|
+
switch (kv.kind) {
|
|
630
|
+
case "bf16": return new KVCache(masks, turboQuantKernels, windowRead);
|
|
631
|
+
case "affine": return new Bf16FirstQuantizedKVCache(kv.groupSize, kv.bits, start, composedAffineKernels(kv.kernels), masks);
|
|
632
|
+
case "affine-layers": {
|
|
633
|
+
const entry = kv.layers.find(item => item.layer === layer);
|
|
634
|
+
return entry ? new Bf16FirstQuantizedKVCache(entry.groupSize, entry.bits, start, composedAffineKernels(kv.kernels), masks)
|
|
635
|
+
: new KVCache(masks, turboQuantKernels, windowRead);
|
|
636
|
+
}
|
|
637
|
+
case "turbo": return new Bf16FirstTurboQuantKVCache(kv.kBits, kv.vBits, start, turboQuantFusedDecode(), turboQuantKernels, masks);
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
/** The cache a composition's KV scheme names for one sliding-window layer of
|
|
642
|
+
* `maxSize` (cache index `layer`): plain for `bf16`, for a layer a per-layer
|
|
643
|
+
* scheme does not list and for `turbo` (TurboQuant encodes full-attention
|
|
644
|
+
* layers only), the bf16-first rotating affine lego otherwise. */
|
|
645
|
+
export function composedSlidingAttentionCache(kv: KvStorageScheme, layer: number, maxSize: number,
|
|
646
|
+
masks: AttentionMasks): AttentionCache {
|
|
647
|
+
const start = kv.kind === "bf16" ? 0 : kv.quantizedKvStart ?? 0;
|
|
648
|
+
switch (kv.kind) {
|
|
649
|
+
case "bf16": return new RotatingKVCache(maxSize, masks);
|
|
650
|
+
case "affine": return new Bf16FirstRotatingQuantizedKVCache(maxSize, kv.groupSize, kv.bits, start, composedAffineKernels(kv.kernels), masks);
|
|
651
|
+
case "affine-layers": {
|
|
652
|
+
const entry = kv.layers.find(item => item.layer === layer);
|
|
653
|
+
return entry ? new Bf16FirstRotatingQuantizedKVCache(maxSize, entry.groupSize, entry.bits, start, composedAffineKernels(kv.kernels), masks)
|
|
654
|
+
: new RotatingKVCache(maxSize, masks);
|
|
655
|
+
}
|
|
656
|
+
case "turbo":
|
|
657
|
+
if (!warnedTurboRotating) {
|
|
658
|
+
warnedTurboRotating = true;
|
|
659
|
+
console.warn("[turbo-quant] sliding-window (RotatingKVCache) layers stay bf16 in v1 (full-attention only).");
|
|
660
|
+
}
|
|
661
|
+
return new RotatingKVCache(maxSize, masks);
|
|
662
|
+
}
|
|
663
|
+
}
|