@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,300 @@
|
|
|
1
|
+
import { MlxArray } from "@mlx-bun/mlx/array";
|
|
2
|
+
import { Dtype } from "@mlx-bun/mlx/ffi";
|
|
3
|
+
import * as ops from "@mlx-bun/mlx/ops";
|
|
4
|
+
import { createCausalMask } from "../kernels/attention/masks";
|
|
5
|
+
import { flashAttention, flashSupported, getTrainingAttn } from "../kernels/attention/flash";
|
|
6
|
+
import { gatedDeltaUpdate } from "../kernels/delta/gated";
|
|
7
|
+
import type { AttentionRead, Cache, GatedDeltaCache, GatedDeltaParameters, Mask, TrainingAttentionCache } from "../contracts/mlx/cache";
|
|
8
|
+
import { ropeBlocks } from "../layers/prefix-layout";
|
|
9
|
+
import { AttentionMasks, causalLease, unmaskedLease, withLease, type MaskLease } from "./attention-read";
|
|
10
|
+
import type { PrefixLayout } from "../contracts/mlx/trainable";
|
|
11
|
+
import { isRecurrentCache } from "./capabilities";
|
|
12
|
+
|
|
13
|
+
/** The kernel a training cache's reads attend with, fixed when the cache is
|
|
14
|
+
* built. `sdpa` is the stock fused SDPA, the kernel mlx-lm's tuner
|
|
15
|
+
* differentiates. `flash` is the opt-in O(L)-memory kernel
|
|
16
|
+
* (`MLX_BUN_TRAIN_ATTN=flash`): it serves a causal or masked read of a query
|
|
17
|
+
* it supports (`flashSupported`: head size, at least 32 positions) as causal
|
|
18
|
+
* attention within the cache's sliding window, and the SDPA serves the rest,
|
|
19
|
+
* the rule the training forwards applied per call before the caches owned
|
|
20
|
+
* their reads. */
|
|
21
|
+
export type TrainingAttention = "sdpa" | "flash";
|
|
22
|
+
|
|
23
|
+
/** The training attention the trainer selected for its run (`setTrainingAttn`,
|
|
24
|
+
* set around a run from `MLX_BUN_TRAIN_ATTN` once the graph's training
|
|
25
|
+
* declaration allows it). Read where a forward's training caches are built,
|
|
26
|
+
* never inside a read. */
|
|
27
|
+
export function selectedTrainingAttention(): TrainingAttention {
|
|
28
|
+
return getTrainingAttn() === "flash" ? "flash" : "sdpa";
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** A training cache's read: pass-through views of `k` and `v` (borrowed, as
|
|
32
|
+
* every append's are) under `mask`, which the read owns. */
|
|
33
|
+
function trainingRead(k: MlxArray, v: MlxArray, mask: MaskLease, attention: TrainingAttention, window: number | null): AttentionRead {
|
|
34
|
+
return withLease(mask, held => {
|
|
35
|
+
const keys = k.slice([0, 0, 0, 0], k.shape);
|
|
36
|
+
let values: MlxArray;
|
|
37
|
+
try { values = v.slice([0, 0, 0, 0], v.shape); } catch (error) { keys.dispose(); throw error; }
|
|
38
|
+
const { mode, arr } = held.mask;
|
|
39
|
+
const flash = attention === "flash" && (mode === "causal" || mode === "array");
|
|
40
|
+
return {
|
|
41
|
+
attend: (q, scale) => flash && flashSupported(q)
|
|
42
|
+
? flashAttention(q, keys, values, scale, true, window ?? 0)
|
|
43
|
+
: ops.sdpa(q, keys, values, scale, mode, arr),
|
|
44
|
+
dispose() { keys.dispose(); values.dispose(); held.release(); },
|
|
45
|
+
};
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** The padded-batch training mask over `L` positions: [B, 1, L, L] bool, true
|
|
50
|
+
* where query i of row b may attend key j:
|
|
51
|
+
* (j <= i) AND (j < lengths[b]) [AND (i < j + window) under a sliding window]
|
|
52
|
+
* createCausalMask(L, 0, window) broadcast over the rows and masked by each
|
|
53
|
+
* row's key validity. Caller owns the result. */
|
|
54
|
+
export function paddedTrainingMask(B: number, L: number, lengths: readonly number[], window: number | null): MlxArray {
|
|
55
|
+
const causal2d = createCausalMask(L, 0, window);
|
|
56
|
+
const causal4d = ops.reshape(causal2d, [1, 1, L, L]);
|
|
57
|
+
causal2d.dispose();
|
|
58
|
+
const keyValidData = new Int32Array(B * L);
|
|
59
|
+
for (let b = 0; b < B; b++) {
|
|
60
|
+
const length = lengths[b]!;
|
|
61
|
+
for (let j = 0; j < L; j++) keyValidData[b * L + j] = j < length ? 1 : 0;
|
|
62
|
+
}
|
|
63
|
+
const keyValidI = MlxArray.fromInt32(keyValidData, [B, 1, 1, L]);
|
|
64
|
+
const zero = MlxArray.fromInt32(new Int32Array([0]), []);
|
|
65
|
+
const keyValid = ops.less(zero, keyValidI);
|
|
66
|
+
zero.dispose();
|
|
67
|
+
keyValidI.dispose();
|
|
68
|
+
const allow = ops.logicalAnd(causal4d, keyValid);
|
|
69
|
+
causal4d.dispose();
|
|
70
|
+
keyValid.dispose();
|
|
71
|
+
return allow;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Stateless cache for the training and scoring forward. Training is always a
|
|
75
|
+
* single offset-0 full-sequence pass, so nothing is stored and every append
|
|
76
|
+
* reads exactly the keys and values it is handed; the offset stays 0. The
|
|
77
|
+
* statelessness is REQUIRED: gradient checkpointing re-runs a layer's closure
|
|
78
|
+
* during the backward recompute, and a layer that shares another layer's keys
|
|
79
|
+
* and values reads them through that layer's cache again, so an append must
|
|
80
|
+
* leave the cache as it found it.
|
|
81
|
+
*
|
|
82
|
+
* Reads: pass-through views of `k` and `v`, attended with `attention`.
|
|
83
|
+
* Without `rowLengths`: no mask for decode, the fused causal mask for a
|
|
84
|
+
* window, and the offset-0 sliding-window mask once a window is longer than
|
|
85
|
+
* `window`. With `rowLengths` (a right-padded batch whose rows hold that many
|
|
86
|
+
* real positions), both reads take the padded-batch mask
|
|
87
|
+
* (`paddedTrainingMask`) for the cache's window, so no real query sees a
|
|
88
|
+
* padded key. A layer with a sliding window gets a cache composed with it; the
|
|
89
|
+
* caches of one forward share `masks`, so its layers build each mask once. */
|
|
90
|
+
export class TrainingCache implements TrainingAttentionCache {
|
|
91
|
+
constructor(
|
|
92
|
+
/** The layer's sliding window; null for full attention. */
|
|
93
|
+
readonly window: number | null = null,
|
|
94
|
+
/** The per-forward masks of the model this cache belongs to. */
|
|
95
|
+
readonly masks = new AttentionMasks(),
|
|
96
|
+
/** The kernel this cache's reads attend with. */
|
|
97
|
+
readonly attention: TrainingAttention = "sdpa",
|
|
98
|
+
/** Real positions per row of a right-padded batch; null for unpadded rows. */
|
|
99
|
+
readonly rowLengths: readonly number[] | null = null,
|
|
100
|
+
) {}
|
|
101
|
+
/** Training-only adapter; never admitted, merged, or persisted. */
|
|
102
|
+
signature(): string { return "train:training"; }
|
|
103
|
+
offset = 0;
|
|
104
|
+
/** Uniform positions from the pinned offset. */
|
|
105
|
+
ropePositions(x: MlxArray, rope: (block: MlxArray, offset: number) => MlxArray): MlxArray { return rope(x, this.offset); }
|
|
106
|
+
appendDecode(k: MlxArray, v: MlxArray): AttentionRead {
|
|
107
|
+
return trainingRead(k, v, this.rowLengths ? this.#padded(k.shape[2]!) : unmaskedLease, this.attention, this.window);
|
|
108
|
+
}
|
|
109
|
+
appendWindow(k: MlxArray, v: MlxArray): AttentionRead {
|
|
110
|
+
const L = k.shape[2]!, window = this.window;
|
|
111
|
+
const mask = this.rowLengths ? this.#padded(L)
|
|
112
|
+
: window === null || L <= window ? causalLease
|
|
113
|
+
: this.masks.lease(`training-window:${window}`, `${L}`,
|
|
114
|
+
() => ({ mode: "array", arr: createCausalMask(L, 0, window) }));
|
|
115
|
+
return trainingRead(k, v, mask, this.attention, window);
|
|
116
|
+
}
|
|
117
|
+
#padded(L: number): MaskLease {
|
|
118
|
+
const lengths = this.rowLengths!;
|
|
119
|
+
return this.masks.lease(`training-padded:${this.window}`, `${L}|${lengths.join(",")}`,
|
|
120
|
+
() => ({ mode: "array", arr: paddedTrainingMask(lengths.length, L, lengths, this.window) }));
|
|
121
|
+
}
|
|
122
|
+
/** The dense fetch and mask the softcapped read (`state/softcap-attention.ts`)
|
|
123
|
+
* attends through: pass-through views and the mask the window read takes. */
|
|
124
|
+
updateAndFetch(k: MlxArray, v: MlxArray): [MlxArray, MlxArray] {
|
|
125
|
+
return [k.slice([0, 0, 0, 0], k.shape), v.slice([0, 0, 0, 0], v.shape)];
|
|
126
|
+
}
|
|
127
|
+
makeMask(N: number, windowSize: number | null): Mask {
|
|
128
|
+
if (this.rowLengths) return { mode: "array", arr: paddedTrainingMask(this.rowLengths.length, N, this.rowLengths, this.window) };
|
|
129
|
+
if (N === 1) return { mode: "", arr: null };
|
|
130
|
+
if (windowSize === null || N <= windowSize) return { mode: "causal", arr: null };
|
|
131
|
+
return { mode: "array", arr: createCausalMask(N, 0, windowSize) };
|
|
132
|
+
}
|
|
133
|
+
state(): MlxArray[] {
|
|
134
|
+
return [];
|
|
135
|
+
}
|
|
136
|
+
isTrimmable(): boolean {
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
139
|
+
trim(_n: number): void {
|
|
140
|
+
/* offset is pinned at 0 */
|
|
141
|
+
}
|
|
142
|
+
dispose(): void {
|
|
143
|
+
// Owns no arrays; drops the memo's holds on its masks.
|
|
144
|
+
this.masks.clear();
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** Stateless DeltaNet stand-in (same re-runnability contract as
|
|
149
|
+
* TrainingCache): state WRITES are discarded immediately, so a gradient-
|
|
150
|
+
* checkpoint recompute re-enters with the identical null-state conditions;
|
|
151
|
+
* reads always yield null (zeros inside the kernel) — a full-sequence
|
|
152
|
+
* forward from t=0, exactly the training/perplexity semantics. Before this
|
|
153
|
+
* existed, qwen3_5 routed its DeltaNet layers into plain TrainingCache and
|
|
154
|
+
* threw on the missing conv/recurrent/advance surface (the recorded
|
|
155
|
+
* "mlx-bun perplexity cannot score qwen3_5" gap).
|
|
156
|
+
*
|
|
157
|
+
* Its `recurDecode` and `recurWindow` run the recurrence with a backward
|
|
158
|
+
* attached (`gatedDeltaUpdate`'s `differentiable`), since the inference
|
|
159
|
+
* kernel has no gradient; the DeltaNet block hands them its projections as it
|
|
160
|
+
* does to any recurrent cache. */
|
|
161
|
+
export class TrainingSSMCache implements GatedDeltaCache {
|
|
162
|
+
/** Training-only adapter; never admitted, merged, or persisted. */
|
|
163
|
+
signature(): string { return "train:training-ssm"; }
|
|
164
|
+
offset = 0;
|
|
165
|
+
specRound: null = null;
|
|
166
|
+
recurDecode(qkv: MlxArray, a: MlxArray, b: MlxArray, layer: GatedDeltaParameters): MlxArray {
|
|
167
|
+
return this.#recur(qkv, a, b, layer);
|
|
168
|
+
}
|
|
169
|
+
recurWindow(qkv: MlxArray, a: MlxArray, b: MlxArray, layer: GatedDeltaParameters): MlxArray {
|
|
170
|
+
return this.#recur(qkv, a, b, layer);
|
|
171
|
+
}
|
|
172
|
+
/** The training recurrence: a zero conv state, the
|
|
173
|
+
* causal convolution, the block's glue, and the differentiable recurrence
|
|
174
|
+
* from a zero recurrent state. Nothing is stored, so the new recurrent
|
|
175
|
+
* state is disposed at once and no conv tail is kept. */
|
|
176
|
+
#recur(qkv: MlxArray, a: MlxArray, b: MlxArray, layer: GatedDeltaParameters): MlxArray {
|
|
177
|
+
const [B, , D] = qkv.shape as [number, number, number];
|
|
178
|
+
const convState = ops.zeros([B, layer.convWeight.shape[1]! - 1, D], qkv.dtype);
|
|
179
|
+
const input = ops.concatAxis([convState, qkv], 1);
|
|
180
|
+
convState.dispose();
|
|
181
|
+
qkv.dispose();
|
|
182
|
+
const conv = ops.conv1d(input, layer.convWeight, 1, 0, 1, D);
|
|
183
|
+
input.dispose();
|
|
184
|
+
const { q, k, v } = layer.heads(conv);
|
|
185
|
+
conv.dispose();
|
|
186
|
+
const [out, state] = gatedDeltaUpdate(q, k, v, a, b, layer.aLog, layer.dtBias, null, null, true);
|
|
187
|
+
for (const array of [q, k, v, a, b, state]) array.dispose();
|
|
188
|
+
return out;
|
|
189
|
+
}
|
|
190
|
+
advance(_n: number): void { /* offset pinned at 0 */ }
|
|
191
|
+
rowOffset(_i: number): number { return 0; }
|
|
192
|
+
state(): MlxArray[] { return []; }
|
|
193
|
+
isTrimmable(): boolean { return true; }
|
|
194
|
+
trim(_n: number): void { /* stateless */ }
|
|
195
|
+
dispose(): void { /* owns no arrays */ }
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** Block-sparse attention mask [T,T] (bool, true = attend) for the prefix-shared
|
|
199
|
+
* concat [prompt(P); chosen(Rc); rejected(Rr)]: physical causal AND NOT
|
|
200
|
+
* (rejected row -> chosen col). Chosen rows already can't see rejected cols
|
|
201
|
+
* (rejected come later), so the only extra cut is rejected->chosen. With a
|
|
202
|
+
* sliding `window`, one more AND on LOGICAL positions (`logpos[row] -
|
|
203
|
+
* logpos[col] < window`, `logpos[i] = i` for prompt+chosen and `i - Rc` for the
|
|
204
|
+
* rejected block, reset to P): a rejected token at physical P+Rc+k has logical
|
|
205
|
+
* position P+k, so its window to the prompt tail differs from its physical
|
|
206
|
+
* distance by Rc. Caller owns the result. */
|
|
207
|
+
export function blockSparsePrefixMask(layout: PrefixLayout, window: number | null): MlxArray {
|
|
208
|
+
const { P, Rc, Rr } = layout;
|
|
209
|
+
const T = P + Rc + Rr;
|
|
210
|
+
const causal = createCausalMask(T, 0, null); // [T,T] physical causal (a window is applied separately, on logical positions)
|
|
211
|
+
const idxFlat = ops.arange(0, T, 1, Dtype.int32);
|
|
212
|
+
const row = ops.reshape(idxFlat, [T, 1]);
|
|
213
|
+
const col = ops.reshape(idxFlat, [1, T]);
|
|
214
|
+
const pp = ops.fromInt32([P], []);
|
|
215
|
+
const pRc = ops.fromInt32([P + Rc], []);
|
|
216
|
+
// notForbid = NOT(rejRow AND chosenCol) = (i < P+Rc) OR (j < P) OR (j >= P+Rc)
|
|
217
|
+
const notRejRow = ops.less(row, pRc);
|
|
218
|
+
const colLtP = ops.less(col, pp);
|
|
219
|
+
const colGePRc = ops.greaterEqual(col, pRc);
|
|
220
|
+
const notChosenCol = ops.logicalOr(colLtP, colGePRc);
|
|
221
|
+
const notForbid = ops.logicalOr(notRejRow, notChosenCol);
|
|
222
|
+
let allow = ops.logicalAnd(causal, notForbid); // [T,T] bool
|
|
223
|
+
|
|
224
|
+
if (window !== null) {
|
|
225
|
+
const logposArr = new Int32Array(T);
|
|
226
|
+
for (let i = 0; i < T; i++) logposArr[i] = i < P + Rc ? i : i - Rc;
|
|
227
|
+
const logpos = MlxArray.fromInt32(logposArr, [T]);
|
|
228
|
+
const lrow = ops.reshape(logpos, [T, 1]);
|
|
229
|
+
const lcol = ops.reshape(logpos, [1, T]);
|
|
230
|
+
const dist = ops.sub(lrow, lcol); // logpos[row] - logpos[col] (>=0 wherever causal allows)
|
|
231
|
+
const w = ops.fromInt32([window], []);
|
|
232
|
+
const slidingOK = ops.less(dist, w); // attend only within `window` logical positions
|
|
233
|
+
const next = ops.logicalAnd(allow, slidingOK);
|
|
234
|
+
allow.dispose();
|
|
235
|
+
allow = next;
|
|
236
|
+
for (const a of [logpos, lrow, lcol, dist, w, slidingOK]) a.dispose();
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
for (const a of [causal, idxFlat, row, col, pp, pRc, notRejRow, colLtP, colGePRc, notChosenCol, notForbid]) a.dispose();
|
|
240
|
+
return allow;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/** Stateless pass-through cache (offset 0, like TrainingCache) for the
|
|
244
|
+
* prefix-shared forward over the concat [prompt(P); chosen(Rc); rejected(Rr)]:
|
|
245
|
+
* its window read takes exactly the concat's T positions under the
|
|
246
|
+
* block-sparse mask (`blockSparsePrefixMask`) for the cache's sliding window,
|
|
247
|
+
* attended with `attention`. The caches of one pass share `masks`, so each
|
|
248
|
+
* window's mask is built once per pass, across its layers, segments and
|
|
249
|
+
* backward recomputes. A decode read is not defined over a concat; it reads as
|
|
250
|
+
* the window does and refuses anything but the whole concat. */
|
|
251
|
+
export class PrefixSharedCache implements TrainingAttentionCache {
|
|
252
|
+
/** Training-only adapter; never admitted, merged, or persisted. */
|
|
253
|
+
signature(): string { return "train:prefix-shared"; }
|
|
254
|
+
offset = 0;
|
|
255
|
+
constructor(
|
|
256
|
+
private readonly layout: PrefixLayout,
|
|
257
|
+
/** The layer's sliding window, on logical positions; null for full attention. */
|
|
258
|
+
readonly window: number | null = null,
|
|
259
|
+
/** The per-pass masks of the model this cache belongs to. */
|
|
260
|
+
readonly masks = new AttentionMasks(),
|
|
261
|
+
/** The kernel this cache's reads attend with. */
|
|
262
|
+
readonly attention: TrainingAttention = "sdpa",
|
|
263
|
+
) {}
|
|
264
|
+
/** The concat's block positions: the prompt from 0, each response restarted at P. */
|
|
265
|
+
ropePositions(x: MlxArray, rope: (block: MlxArray, offset: number) => MlxArray): MlxArray {
|
|
266
|
+
return ropeBlocks(x, this.layout, rope);
|
|
267
|
+
}
|
|
268
|
+
appendDecode(k: MlxArray, v: MlxArray): AttentionRead { return this.appendWindow(k, v); }
|
|
269
|
+
appendWindow(k: MlxArray, v: MlxArray): AttentionRead {
|
|
270
|
+
const { P, Rc, Rr } = this.layout, N = k.shape[2]!;
|
|
271
|
+
if (N !== P + Rc + Rr) throw new Error(`PrefixSharedCache: N=${N} != P+Rc+Rr=${P + Rc + Rr}`);
|
|
272
|
+
const mask = this.masks.lease(`prefix-shared:${this.window}`, `${P},${Rc},${Rr}`,
|
|
273
|
+
() => ({ mode: "array", arr: blockSparsePrefixMask(this.layout, this.window) }));
|
|
274
|
+
return trainingRead(k, v, mask, this.attention, this.window);
|
|
275
|
+
}
|
|
276
|
+
state(): MlxArray[] { return []; }
|
|
277
|
+
isTrimmable(): boolean { return true; }
|
|
278
|
+
trim(_n: number): void { /* offset pinned at 0 */ }
|
|
279
|
+
dispose(): void { this.masks.clear(); }
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/** Prefix-shared caches for one pass, one per entry of `windows` (each layer's
|
|
283
|
+
* sliding window, or null), sharing one set of masks and attending with
|
|
284
|
+
* `attention`. */
|
|
285
|
+
export function prefixSharedCaches(layout: PrefixLayout, windows: readonly (number | null)[],
|
|
286
|
+
attention: TrainingAttention = "sdpa"): Cache[] {
|
|
287
|
+
const masks = new AttentionMasks();
|
|
288
|
+
return windows.map(window => new PrefixSharedCache(layout, window, masks, attention));
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/** The stateless stand-in a training or scoring forward runs in place of one
|
|
292
|
+
* of the graph's fresh caches: recurrent state takes `TrainingSSMCache` (fixed
|
|
293
|
+
* size and causal, so right padding never reaches a real position and needs no
|
|
294
|
+
* mask); an attention cache takes `attention(window)`, the caller's stand-in
|
|
295
|
+
* over that cache's sliding window (its ring size; null for full attention). */
|
|
296
|
+
export function trainingStandIn(cache: Cache, attention: (window: number | null) => Cache): Cache {
|
|
297
|
+
if (isRecurrentCache(cache)) return new TrainingSSMCache();
|
|
298
|
+
const maxSize = (cache as { maxSize?: number }).maxSize;
|
|
299
|
+
return attention(typeof maxSize === "number" ? maxSize : null);
|
|
300
|
+
}
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import type { MlxArray } from "@mlx-bun/mlx/array";
|
|
2
|
+
import * as ops from "@mlx-bun/mlx/ops";
|
|
3
|
+
import type { BatchableCache, Cache, CompiledDecodeCache, DecodeSlot, DecodeStepInputs, DecodeStepPlan, Mask } from "../contracts/mlx/cache";
|
|
4
|
+
import { KvTensorRows } from "./kv-tensor-rows";
|
|
5
|
+
import type { KvStorage } from "./kv-storage";
|
|
6
|
+
import { leaseCacheStates } from "./leases";
|
|
7
|
+
import { minimumReusableOffset } from "./views";
|
|
8
|
+
import { withResource, disposeResources } from "../runtime/resources";
|
|
9
|
+
import { cloneKvCaches } from "./persistence";
|
|
10
|
+
|
|
11
|
+
/** Encoded layouts retain physical row alignment across precision changes. */
|
|
12
|
+
export interface TransitionedKvLayout extends BatchableCache, KvStorage {
|
|
13
|
+
alignRows?(leftPad: readonly number[]): void;
|
|
14
|
+
}
|
|
15
|
+
export interface SpeculativeTransitionedKvLayout extends TransitionedKvLayout {
|
|
16
|
+
specRoundBegin(): void;
|
|
17
|
+
specRoundCommit(): void;
|
|
18
|
+
specRoundRollback(keep: number | readonly number[]): void;
|
|
19
|
+
}
|
|
20
|
+
/** One precision transition over a row layout, built with the layout's scheme:
|
|
21
|
+
* the same conversion the single-sequence legos make (`state/bf16-first-kv`). */
|
|
22
|
+
export interface KvRowTransition<Layout extends TransitionedKvLayout> {
|
|
23
|
+
readonly signature: string;
|
|
24
|
+
readonly conversionOffset?: number;
|
|
25
|
+
/** Converts, in place, every row whose stored length has reached the
|
|
26
|
+
* conversion offset. Converted rows, and rows below the offset, stay. */
|
|
27
|
+
settle(rows: Cache[]): void;
|
|
28
|
+
converted(row: Cache): boolean;
|
|
29
|
+
makeLayout(): Layout;
|
|
30
|
+
packRows?(layout: Layout, rows: readonly Cache[]): void;
|
|
31
|
+
prepareRows?(rows: Cache[]): Cache[];
|
|
32
|
+
extractRow?(row: Cache): Cache;
|
|
33
|
+
rollbackRow?(row: Cache, before: number, keep: number, preserveColumns: boolean): void;
|
|
34
|
+
/** How the lego these rows came from composes storage for serial use
|
|
35
|
+
* (`ComposedStorage.over`): a row this layout hands back to serial use is
|
|
36
|
+
* that lego again. Absent for rows that came as storage. */
|
|
37
|
+
compose?(storage: Cache): Cache;
|
|
38
|
+
/** The lego of this transition over one row's storage (taking ownership):
|
|
39
|
+
* what a compiled decode step drives this layout's lone row through. */
|
|
40
|
+
lone(storage: Cache): Cache & CompiledDecodeCache;
|
|
41
|
+
/** `row` handed to serial use as it is (ownership moves), or null when the
|
|
42
|
+
* row is a wrapper whose storage must be extracted. Absent: every row is
|
|
43
|
+
* extracted. */
|
|
44
|
+
adopt?(row: Cache): Cache | null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
export interface TransitioningKvPositions {
|
|
49
|
+
readonly rowOffsets: readonly number[];
|
|
50
|
+
readonly leftPad: readonly number[];
|
|
51
|
+
readonly offset: number;
|
|
52
|
+
readonly batchSize: number | null;
|
|
53
|
+
readonly ropeOffsetArr: MlxArray | undefined;
|
|
54
|
+
sync(rows: readonly Cache[], preserve: boolean): void;
|
|
55
|
+
filterRows(keep: readonly number[]): void;
|
|
56
|
+
makeMask(tokens: number, window: number | null): Mask;
|
|
57
|
+
dispose(): void;
|
|
58
|
+
}
|
|
59
|
+
export class FullKvPositions extends KvTensorRows implements TransitioningKvPositions {
|
|
60
|
+
sync(rows: readonly Cache[], preserve: boolean): void {
|
|
61
|
+
this.mergeRows(preserve
|
|
62
|
+
? [{ planes: [], rowOffsets: rows.map(row => row.offset), leftPad: [...this.leftPad] }]
|
|
63
|
+
: rows.map(row => ({ planes: [], rowOffsets: [row.offset], leftPad: [0] })));
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Owns membership, committed positions and precision transitions. Codec and
|
|
68
|
+
* attention adapters own tensor access; scheduling and persistence use the
|
|
69
|
+
* existing batch and checkpoint contracts. Row conversion never examines an
|
|
70
|
+
* uncommitted speculative suffix.
|
|
71
|
+
*
|
|
72
|
+
* Rows convert before an append outside prefill, and during prefill only at
|
|
73
|
+
* each row's committed chunk boundary (`prefillMaintenance.commitPrefill`), so
|
|
74
|
+
* a sibling that splits a row's planned chunk across forwards does not move
|
|
75
|
+
* that row's precision boundary. This is why the transition runs here rather
|
|
76
|
+
* than at the end of each append, as the single-sequence legos run it.
|
|
77
|
+
*
|
|
78
|
+
* Compiled decode: a layout holding one row (the lone row a batch adopted)
|
|
79
|
+
* drives it through a compiled decode step as the lego of its transition
|
|
80
|
+
* (`KvRowTransition.lone`): the first step that prepares this layout moves the
|
|
81
|
+
* row's storage into that lego, which converts at the same position the rows
|
|
82
|
+
* convert (the append that reaches the start is read before the conversion), and
|
|
83
|
+
* every compiled step after reaches the row there. Its offsets, state and
|
|
84
|
+
* signature read through the lego; any other operation (an ordinary append, a
|
|
85
|
+
* merge, an extraction, a speculative round) first moves the storage back into
|
|
86
|
+
* the rows. A one-row layout reads each decode position with the same bits as
|
|
87
|
+
* its lego (`tests/state/compiled-decode-protocol.test.ts`). */
|
|
88
|
+
export abstract class TransitioningKvRows<Layout extends TransitionedKvLayout> implements BatchableCache, CompiledDecodeCache {
|
|
89
|
+
readonly #positions: TransitioningKvPositions;
|
|
90
|
+
#rows: Cache[] = [];
|
|
91
|
+
#packed?: Layout;
|
|
92
|
+
/** The lone row as its lego, while a compiled decode step drives it. */
|
|
93
|
+
#lone: (Cache & CompiledDecodeCache) | null = null;
|
|
94
|
+
#prefilling = false;
|
|
95
|
+
|
|
96
|
+
readonly stateNeedsDispose = true;
|
|
97
|
+
constructor(readonly transition: KvRowTransition<Layout>, row?: Cache, positions: TransitioningKvPositions = new FullKvPositions()) {
|
|
98
|
+
this.#positions = positions;
|
|
99
|
+
if (row) { this.rows = transition.prepareRows?.([row]) ?? [row]; this.syncPositions(); }
|
|
100
|
+
}
|
|
101
|
+
// Every access to the rows, the packed layout or the positions finds the row
|
|
102
|
+
// home first (see the class).
|
|
103
|
+
protected get rows(): Cache[] { this.#home(); return this.#rows; }
|
|
104
|
+
protected set rows(rows: Cache[]) { this.#home(); this.#rows = rows; }
|
|
105
|
+
protected get packed(): Layout | undefined { this.#home(); return this.#packed; }
|
|
106
|
+
protected set packed(packed: Layout | undefined) { this.#home(); this.#packed = packed; }
|
|
107
|
+
protected get positions(): TransitioningKvPositions { this.#home(); return this.#positions; }
|
|
108
|
+
/** The lone row's storage, moved back from its lego into the rows. */
|
|
109
|
+
#home(): void {
|
|
110
|
+
const lone = this.#lone;
|
|
111
|
+
if (!lone) return;
|
|
112
|
+
this.#lone = null;
|
|
113
|
+
const storage = lone.composed ? lone.composed.release() : lone;
|
|
114
|
+
if (storage !== lone) lone.dispose();
|
|
115
|
+
this.#rows = this.transition.prepareRows?.([storage]) ?? [storage];
|
|
116
|
+
this.#positions.sync(this.#rows, false);
|
|
117
|
+
}
|
|
118
|
+
/** The lone row as its lego, moving its storage there on the first call. */
|
|
119
|
+
#loneLego(): Cache & CompiledDecodeCache {
|
|
120
|
+
if (this.#lone) return this.#lone;
|
|
121
|
+
if (this.batchSize !== 1) throw new Error(`compiled decode drives a layout of one row; this one holds ${this.batchSize}`);
|
|
122
|
+
const packed = this.#packed, row = this.#rows[0]!;
|
|
123
|
+
const adopted = packed ? null : this.transition.adopt?.(row) ?? null;
|
|
124
|
+
const storage = adopted ?? this.extractStorageRow(0);
|
|
125
|
+
let lego: Cache & CompiledDecodeCache;
|
|
126
|
+
try { lego = this.transition.lone(storage); }
|
|
127
|
+
catch (error) { if (!adopted) storage.dispose(); throw error; }
|
|
128
|
+
this.#rows = []; this.#packed = undefined;
|
|
129
|
+
if (packed) packed.dispose();
|
|
130
|
+
else if (!adopted) row.dispose();
|
|
131
|
+
this.#lone = lego;
|
|
132
|
+
return lego;
|
|
133
|
+
}
|
|
134
|
+
/** The fetch of the coming compiled step: the lego's while it holds the row,
|
|
135
|
+
* else the rows' (`decodePhaseOfRows`), without moving the row. */
|
|
136
|
+
decodePhase(): DecodeStepPlan["fetch"] { return this.#lone ? this.#lone.decodePhase() : this.decodePhaseOfRows(); }
|
|
137
|
+
/** The fetch a lone row's lego would choose, from this layout's positions. */
|
|
138
|
+
protected decodePhaseOfRows(): DecodeStepPlan["fetch"] { return "concat"; }
|
|
139
|
+
prepareDecodeStep(): DecodeStepPlan { return this.#loneLego().prepareDecodeStep(); }
|
|
140
|
+
decodeSlot(plan: DecodeStepPlan): DecodeSlot { return this.#loneLego().decodeSlot(plan); }
|
|
141
|
+
decodeInputs(plan: DecodeStepPlan, step: DecodeStepInputs): MlxArray[] { return this.#loneLego().decodeInputs(plan, step); }
|
|
142
|
+
commitDecodeStep(slot: DecodeSlot, outputs: MlxArray[]): MlxArray[] { return this.#loneLego().commitDecodeStep(slot, outputs); }
|
|
143
|
+
signature(): string { return this.transition.signature; }
|
|
144
|
+
get prefillMaintenance() { return this; }
|
|
145
|
+
beginPrefill(): void { this.#prefilling = true; }
|
|
146
|
+
commitPrefill(indices: readonly number[]): void {
|
|
147
|
+
if (this.packed) return;
|
|
148
|
+
for (const index of indices) {
|
|
149
|
+
const row = [this.rows[index]!];
|
|
150
|
+
try { this.transition.settle(row); }
|
|
151
|
+
finally { this.rows[index] = row[0]!; }
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
endPrefill(): void { this.#prefilling = false; }
|
|
155
|
+
get minimumReusableOffset(): number {
|
|
156
|
+
if (this.#lone) return this.#lone.minimumReusableOffset ?? 0;
|
|
157
|
+
return this.packed?.minimumReusableOffset ?? minimumReusableOffset(this.rows);
|
|
158
|
+
}
|
|
159
|
+
get rowOffsets(): readonly number[] { return this.#lone ? [this.#lone.offset] : this.packed?.rowOffsets ?? this.positions.rowOffsets; }
|
|
160
|
+
get leftPad(): readonly number[] { return this.#lone ? [0] : this.packed?.leftPad ?? this.positions.leftPad; }
|
|
161
|
+
get offset(): number { return this.#lone ? this.#lone.offset : this.packed?.offset ?? this.positions.offset; }
|
|
162
|
+
maxAppendTokens(): number {
|
|
163
|
+
if (this.packed) return this.packed.maxAppendTokens?.() ?? Infinity;
|
|
164
|
+
const end = this.transition.conversionOffset;
|
|
165
|
+
if (end === undefined) return Infinity;
|
|
166
|
+
return Math.min(Infinity, ...this.rowOffsets.map((offset, row) =>
|
|
167
|
+
offset < end && !this.transition.converted(this.rows[row]!) ? end - offset : Infinity));
|
|
168
|
+
}
|
|
169
|
+
get batchSize(): number | null { return this.#lone ? 1 : this.packed?.batchSize ?? this.positions.batchSize; }
|
|
170
|
+
get ropeOffsetArr(): MlxArray | undefined {
|
|
171
|
+
if (this.#lone) return this.#lone.ropeOffsetArr;
|
|
172
|
+
return this.packed ? this.packed.ropeOffsetArr : this.positions.ropeOffsetArr;
|
|
173
|
+
}
|
|
174
|
+
bytesPerToken(): number { return this.packed?.bytesPerToken?.() ?? Math.max(0, ...this.rows.map(row => row.bytesPerToken?.() ?? 0)); }
|
|
175
|
+
projectedBytes(tokens: number): number { return this.bytesPerToken() * tokens; }
|
|
176
|
+
makeMask(tokens: number, window: number | null): Mask { return (this.packed ?? this.positions).makeMask(tokens, window); }
|
|
177
|
+
isTrimmable(): boolean { return this.packed?.isTrimmable() ?? this.rows.every(row => row.isTrimmable()); }
|
|
178
|
+
trim(count: number): void {
|
|
179
|
+
if (this.packed) this.packed.trim(count);
|
|
180
|
+
else { for (const row of this.rows) row.trim(count); this.syncPositions(true); }
|
|
181
|
+
}
|
|
182
|
+
protected syncPositions(preserve = false): void { this.positions.sync(this.rows, preserve); }
|
|
183
|
+
protected advance(): void {
|
|
184
|
+
if (this.packed) return;
|
|
185
|
+
if (!this.#prefilling) this.transition.settle(this.rows);
|
|
186
|
+
if (this.rows.length && this.rows.every(row => this.transition.converted(row))) {
|
|
187
|
+
const packed = this.transition.makeLayout();
|
|
188
|
+
try {
|
|
189
|
+
if (this.transition.packRows) this.transition.packRows(packed, this.rows);
|
|
190
|
+
else packed.mergeRows(this.rows);
|
|
191
|
+
packed.alignRows?.(this.positions.leftPad);
|
|
192
|
+
}
|
|
193
|
+
catch (error) { packed.dispose(); throw error; }
|
|
194
|
+
const previous = this.rows; this.rows = []; this.packed = packed;
|
|
195
|
+
this.positions.dispose(); disposeResources(previous);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
abstract updateAndFetch(k: MlxArray, v: MlxArray): [MlxArray, MlxArray];
|
|
199
|
+
abstract makeEmptyBatch(): TransitioningKvRows<Layout>;
|
|
200
|
+
mergeRows(rows: readonly Cache[]): void {
|
|
201
|
+
const owned: Cache[] = [];
|
|
202
|
+
try {
|
|
203
|
+
for (const row of rows) {
|
|
204
|
+
if (row instanceof TransitioningKvRows) {
|
|
205
|
+
for (let index = 0; index < row.batchSize!; index++) owned.push(row.extractStorageRow(index));
|
|
206
|
+
} else owned.push(...cloneKvCaches([row.composed ? row.composed.storage() : row]));
|
|
207
|
+
}
|
|
208
|
+
} catch (error) { disposeResources(owned); throw error; }
|
|
209
|
+
let prepared: Cache[];
|
|
210
|
+
try { prepared = this.transition.prepareRows?.(owned) ?? owned; }
|
|
211
|
+
catch (error) { disposeResources(owned); throw error; }
|
|
212
|
+
this.dispose(); this.rows = prepared; this.syncPositions();
|
|
213
|
+
}
|
|
214
|
+
filterRows(keep: readonly number[]): void {
|
|
215
|
+
if (this.packed) { this.packed.filterRows(keep); return; }
|
|
216
|
+
const previous = this.rows; this.rows = keep.map(index => previous[index]!);
|
|
217
|
+
disposeResources(previous.filter((_, index) => !keep.includes(index))); this.positions.filterRows(keep);
|
|
218
|
+
}
|
|
219
|
+
/** A row's storage, owned by the caller: what another row layout merges. */
|
|
220
|
+
extractStorageRow(row: number): Cache {
|
|
221
|
+
return this.packed?.extractRow(row) ?? (this.transition.extractRow?.(this.rows[row]!) ?? cloneKvCaches([this.rows[row]!])[0]!);
|
|
222
|
+
}
|
|
223
|
+
/** A row handed back to serial use: its storage, composed again by the lego
|
|
224
|
+
* the row came from (`KvRowTransition.compose`). */
|
|
225
|
+
extractRow(row: number): Cache {
|
|
226
|
+
const storage = this.extractStorageRow(row);
|
|
227
|
+
return this.transition.compose ? this.transition.compose(storage) : storage;
|
|
228
|
+
}
|
|
229
|
+
state(): MlxArray[] {
|
|
230
|
+
return withResource(leaseCacheStates(this.#lone ? [this.#lone] : this.packed ? [this.packed] : this.rows), arrays => {
|
|
231
|
+
const views: MlxArray[] = [];
|
|
232
|
+
try { for (const array of arrays) views.push(ops.contiguous(array)); return views; }
|
|
233
|
+
catch (error) { disposeResources(views); throw error; }
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
dispose(): void {
|
|
237
|
+
this.#prefilling = false;
|
|
238
|
+
const lone = this.#lone; this.#lone = null; lone?.dispose();
|
|
239
|
+
const previous = this.#rows; this.#rows = [];
|
|
240
|
+
this.#packed?.dispose(); this.#packed = undefined; this.#positions.dispose(); disposeResources(previous);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/** Rollback is a method capability, separate from ordinary cache membership. */
|
|
245
|
+
export abstract class SpeculativeTransitioningKvRows<Layout extends SpeculativeTransitionedKvLayout> extends TransitioningKvRows<Layout> {
|
|
246
|
+
#beforeRound?: number[];
|
|
247
|
+
specRoundBegin(): void {
|
|
248
|
+
this.advance(); this.#beforeRound = [...this.rowOffsets]; this.packed?.specRoundBegin();
|
|
249
|
+
}
|
|
250
|
+
specRoundCommit(): void {
|
|
251
|
+
this.packed?.specRoundCommit(); this.#beforeRound = undefined; this.advance();
|
|
252
|
+
}
|
|
253
|
+
specRoundRollback(keep: number | readonly number[]): void {
|
|
254
|
+
if (this.packed) this.packed.specRoundRollback(keep);
|
|
255
|
+
else {
|
|
256
|
+
for (const [index, row] of this.rows.entries()) {
|
|
257
|
+
const count = typeof keep === "number" ? keep : keep[index]!;
|
|
258
|
+
const before = this.#beforeRound![index]!;
|
|
259
|
+
if (this.transition.rollbackRow) this.transition.rollbackRow(row, before, count, this.rows.length > 1);
|
|
260
|
+
else row.trim(row.offset - before - count);
|
|
261
|
+
}
|
|
262
|
+
this.syncPositions(true);
|
|
263
|
+
}
|
|
264
|
+
this.#beforeRound = undefined; this.advance();
|
|
265
|
+
}
|
|
266
|
+
override dispose(): void { this.#beforeRound = undefined; super.dispose(); }
|
|
267
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import type { MlxArray } from "@mlx-bun/mlx/array";
|
|
2
|
+
import * as ops from "@mlx-bun/mlx/ops";
|
|
3
|
+
import type { AttentionRead } from "../contracts/mlx/cache";
|
|
4
|
+
import * as tq from "../kernels/turboquant/ops";
|
|
5
|
+
import { sdpaRead, withLease, type MaskLease } from "./attention-read";
|
|
6
|
+
|
|
7
|
+
/** One append's window decoded with its values still in the codec's rotated
|
|
8
|
+
* domain, and the transform that returns an attention output over those
|
|
9
|
+
* values to the values' own domain. The caller owns `keys` and `values`. */
|
|
10
|
+
export interface RotatedWindow {
|
|
11
|
+
readonly keys: MlxArray;
|
|
12
|
+
readonly values: MlxArray;
|
|
13
|
+
/** Borrows an attention output [B, Hq, L, Dv] and returns an owned one. */
|
|
14
|
+
readonly restore: (output: MlxArray) => MlxArray;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** The two ways TurboQuant storage can append one call's keys and values and
|
|
18
|
+
* decode its window. A read calls exactly one of them, once. */
|
|
19
|
+
export interface TurboQuantFetches {
|
|
20
|
+
/** Append, then decode the window with keys and values in their own domain
|
|
21
|
+
* (`updateAndFetch`). The caller owns both. */
|
|
22
|
+
decoded(): [MlxArray, MlxArray];
|
|
23
|
+
/** Append, then decode the window with values left rotated
|
|
24
|
+
* (`updateAndFetchDeferredV`). */
|
|
25
|
+
rotated(): RotatedWindow;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** How a TurboQuant cache attends, fixed when the cache is built, like the
|
|
29
|
+
* affine kernel sets. The cache leases the mask for the phase and offers its
|
|
30
|
+
* fetches; the set decides which fetch runs and what attends over it. Both sets
|
|
31
|
+
* below are a graph's served numbers today, so the graph that composes the
|
|
32
|
+
* cache names its set: `denseTurboQuantKernels` for Qwen 3.5's,
|
|
33
|
+
* `deferredTurboQuantKernels` for Gemma 4's and MiniCPM5's. */
|
|
34
|
+
export interface TurboQuantKernels {
|
|
35
|
+
/** The fetch `read` calls: the window decoded to the values' own domain, or
|
|
36
|
+
* with values left rotated. A compiled decode step decodes the stored prefix
|
|
37
|
+
* in this domain outside its recorded graph (`TurboQuantKVCache`). */
|
|
38
|
+
readonly fetch: "decoded" | "rotated";
|
|
39
|
+
/** Appends through `fetches` and returns the read under `mask`. Takes
|
|
40
|
+
* ownership of `mask` (released if the fetch throws) and of what the fetch
|
|
41
|
+
* returns. */
|
|
42
|
+
read(fetches: TurboQuantFetches, mask: MaskLease): AttentionRead;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** The read over fetched keys and values still in the codec's rotated domain:
|
|
46
|
+
* the stock fused SDPA under the leased mask, then `restore` on its output
|
|
47
|
+
* (the inverse value rotation, f32 through the transform, unless the storage
|
|
48
|
+
* says otherwise). Attention is linear in V, so the caller receives the output
|
|
49
|
+
* in the values' own domain. Takes ownership of `keys`, `values` and `mask`. */
|
|
50
|
+
function rotatedValueRead(keys: MlxArray, values: MlxArray, mask: MaskLease,
|
|
51
|
+
restore: (output: MlxArray) => MlxArray = tq.unrotateValues): AttentionRead {
|
|
52
|
+
return {
|
|
53
|
+
attend(q, scale) {
|
|
54
|
+
const rotated = ops.sdpa(q, keys, values, scale, mask.mask.mode, mask.mask.arr);
|
|
55
|
+
try { return restore(rotated); } finally { rotated.dispose(); }
|
|
56
|
+
},
|
|
57
|
+
dispose() { keys.dispose(); values.dispose(); mask.release(); },
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Decode keys and values back to their own domain, then the stock fused SDPA
|
|
62
|
+
* under the mask. The Qwen graphs (Qwen3, Qwen3-MoE, Qwen 3.5 and the written
|
|
63
|
+
* Trellis graphs) and the universal graph compose this set; it computes their
|
|
64
|
+
* served numbers. */
|
|
65
|
+
export const denseTurboQuantKernels: TurboQuantKernels = Object.freeze({
|
|
66
|
+
fetch: "decoded" as const,
|
|
67
|
+
read: (fetches: TurboQuantFetches, mask: MaskLease) => withLease(mask, held => {
|
|
68
|
+
const [keys, values] = fetches.decoded();
|
|
69
|
+
return sdpaRead(keys, values, held);
|
|
70
|
+
}),
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
/** Leave values rotated, run the stock fused SDPA under the mask on them, then
|
|
74
|
+
* undo the rotation on the output: attention is linear in V, so one inverse
|
|
75
|
+
* rotation per query row replaces one per cached token. The Gemma 4 graphs
|
|
76
|
+
* (generic and written), DiffusionGemma and MiniCPM5 compose this set; it computes their served
|
|
77
|
+
* numbers. */
|
|
78
|
+
export const deferredTurboQuantKernels: TurboQuantKernels = Object.freeze({
|
|
79
|
+
fetch: "rotated" as const,
|
|
80
|
+
read: (fetches: TurboQuantFetches, mask: MaskLease) => withLease(mask, held => {
|
|
81
|
+
const { keys, values, restore } = fetches.rotated();
|
|
82
|
+
return rotatedValueRead(keys, values, held, restore);
|
|
83
|
+
}),
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
/** The set of plain storage whose graph composes no TurboQuant read: every
|
|
87
|
+
* graph but Qwen 3.5's, Gemma 4's and MiniCPM5's reads converted TurboQuant
|
|
88
|
+
* storage through the deprecated fetches, which take no set. A TurboQuant cache
|
|
89
|
+
* converted from such storage serves those fetches; its named reads throw. */
|
|
90
|
+
export const uncomposedTurboQuantKernels: TurboQuantKernels = Object.freeze({
|
|
91
|
+
fetch: "decoded" as const,
|
|
92
|
+
read(_fetches: TurboQuantFetches, mask: MaskLease): AttentionRead {
|
|
93
|
+
mask.release();
|
|
94
|
+
throw new Error("this TurboQuant cache was converted from storage whose graph composed no TurboQuant read: " +
|
|
95
|
+
"the graph's makeCache passes denseTurboQuantKernels or deferredTurboQuantKernels (state/turboquant-attention.ts) to its KVCache");
|
|
96
|
+
},
|
|
97
|
+
});
|