@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,123 @@
|
|
|
1
|
+
import { captureFullKvDonorRows } from "./full-kv-row-donor";
|
|
2
|
+
import { decodedKvDonorAttention } from "./decoded-kv-donor";
|
|
3
|
+
import { appendFullKvRows } from "./full-kv-row-append";
|
|
4
|
+
import type { MlxArray } from "@mlx-bun/mlx/array";
|
|
5
|
+
import { Dtype } from "@mlx-bun/mlx/ffi";
|
|
6
|
+
import * as ops from "@mlx-bun/mlx/ops";
|
|
7
|
+
import { unrotateValues } from "../kernels/turboquant/ops";
|
|
8
|
+
import { type AttentionRead, type Cache, type CommittedAttentionCache } from "../contracts/mlx/cache";
|
|
9
|
+
import { kvStorage, type RotatedValueAttentionState } from "./kv-storage";
|
|
10
|
+
import { TurboQuantKVCache } from "./turboquant-kv";
|
|
11
|
+
import { BatchedTurboQuantKVCache } from "./batched-turboquant-kv";
|
|
12
|
+
import { FullTransitioningKvRows } from "./full-transitioning-kv-rows";
|
|
13
|
+
import { FullPrefillRow } from "./full-prefill-row";
|
|
14
|
+
import { KVCache } from "./kv";
|
|
15
|
+
import { Bf16FirstTurboQuantKVCache, completeConversion, convertToTurboQuant, transitionDue } from "./bf16-first-kv";
|
|
16
|
+
import type { TurboQuantKernels } from "./turboquant-attention";
|
|
17
|
+
|
|
18
|
+
/** The TurboQuant transition of `Bf16FirstTurboQuantKVCache` over
|
|
19
|
+
* full-attention rows: a bf16 row (`KVCache`, or a padded `FullPrefillRow`
|
|
20
|
+
* over one) encodes its live window once it holds `start` positions. */
|
|
21
|
+
function settleTurboQuantRows(rows: Cache[], kBits: number, vBits: number, start: number, fusedDecode: boolean): void {
|
|
22
|
+
for (let index = 0; index < rows.length; index++) {
|
|
23
|
+
const row = rows[index]!;
|
|
24
|
+
if (row instanceof FullPrefillRow) {
|
|
25
|
+
const source = row.turboConversion;
|
|
26
|
+
if (!source || !transitionDue(source.offset, start)) continue;
|
|
27
|
+
const offset = source.offset;
|
|
28
|
+
rows[index] = completeConversion(source.toTurboQuantized(kBits, vBits, fusedDecode), offset, start);
|
|
29
|
+
} else if (row instanceof KVCache && transitionDue(row.offset, start)) {
|
|
30
|
+
rows[index] = convertToTurboQuant(row, kBits, vBits, start, fusedDecode);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** The rows of the TurboQuant bf16-first lego (`Bf16FirstTurboQuantKVCache`)
|
|
36
|
+
* for continuous batching: TurboQuant tensor access and value-domain handling
|
|
37
|
+
* over the shared row-transition lifecycle (when a row converts is described on
|
|
38
|
+
* `TransitioningKvRows`). Model projections and attention remain batched.
|
|
39
|
+
* `fusedDecode` is resolved by whoever builds the layout. `turboQuantKernels` is
|
|
40
|
+
* the read of the graph whose caches these rows hold (dense for Qwen 3.5,
|
|
41
|
+
* deferred for Gemma 4 and MiniCPM5); the rows this layout makes and the
|
|
42
|
+
* layout it packs them into carry it, and a converted row attends with its
|
|
43
|
+
* source's.
|
|
44
|
+
*
|
|
45
|
+
* The reads are the graph's call today: this layout's mask before the append
|
|
46
|
+
* (no sliding window), then the kernel set's fetch and attention. The dense set
|
|
47
|
+
* fetches every row decoded (`updateAndFetch`) and runs the fused SDPA, Qwen
|
|
48
|
+
* 3.5's call; the deferred set fetches converted rows rotated
|
|
49
|
+
* (`updateAndFetchDeferredV`), runs the fused SDPA and applies the value
|
|
50
|
+
* transform that fetch captured, Gemma 4's and MiniCPM5's. */
|
|
51
|
+
export class DelayedTurboQuantKVCache extends FullTransitioningKvRows<BatchedTurboQuantKVCache>
|
|
52
|
+
implements RotatedValueAttentionState, CommittedAttentionCache {
|
|
53
|
+
#rotated: boolean[] = [];
|
|
54
|
+
constructor(readonly kBits: number, readonly vBits: number, readonly start: number,
|
|
55
|
+
readonly fusedDecode: boolean, readonly turboQuantKernels: TurboQuantKernels, row?: Cache,
|
|
56
|
+
/** How the lego these rows came from composes the rows this layout hands
|
|
57
|
+
* back to serial use (`ComposedStorage.over`). */
|
|
58
|
+
readonly compose?: (storage: Cache) => Cache) {
|
|
59
|
+
super({ signature: `kv:delayed-turboquant:${kBits}:${vBits}:${start}`, conversionOffset: start,
|
|
60
|
+
settle: rows => settleTurboQuantRows(rows, kBits, vBits, start, fusedDecode),
|
|
61
|
+
converted: row => row instanceof TurboQuantKVCache,
|
|
62
|
+
makeLayout: () => new BatchedTurboQuantKVCache(kBits, vBits, turboQuantKernels, fusedDecode),
|
|
63
|
+
lone: storage => new Bf16FirstTurboQuantKVCache(kBits, vBits, start, fusedDecode, turboQuantKernels, undefined, storage),
|
|
64
|
+
...(compose ? { compose } : {}) }, row);
|
|
65
|
+
}
|
|
66
|
+
/** Rows made for an empty layout carry the graph's read into their conversion. */
|
|
67
|
+
protected override plainRow(): KVCache { return new KVCache(undefined, this.turboQuantKernels); }
|
|
68
|
+
captureDonorRows(): import("../contracts/mlx/cache").KvDonorRows {
|
|
69
|
+
return this.packed?.captureDonorRows() ?? captureFullKvDonorRows(this.rows, this.leftPad, this.offset);
|
|
70
|
+
}
|
|
71
|
+
captureDonorAttention(): import("../contracts/mlx/cache").KvDonorAttention {
|
|
72
|
+
return decodedKvDonorAttention(this.captureDonorRows());
|
|
73
|
+
}
|
|
74
|
+
get rotatedValueAttention(): RotatedValueAttentionState { return this; }
|
|
75
|
+
|
|
76
|
+
appendDecode(k: MlxArray, v: MlxArray): AttentionRead { return this.#read(k, v); }
|
|
77
|
+
appendWindow(k: MlxArray, v: MlxArray): AttentionRead { return this.#read(k, v); }
|
|
78
|
+
/** The committed read is the window read: a committed span reads this layout
|
|
79
|
+
* through the kernel set's fetch and the fused SDPA under the mask the layout
|
|
80
|
+
* builds, as a window does. */
|
|
81
|
+
appendCommitted(k: MlxArray, v: MlxArray): AttentionRead { return this.#read(k, v); }
|
|
82
|
+
/** The read: the mask from this layout before the append (no sliding
|
|
83
|
+
* window), then the kernel set's fetch and attention. */
|
|
84
|
+
#read(k: MlxArray, v: MlxArray): AttentionRead {
|
|
85
|
+
const mask = this.makeMask(k.shape[2]!, null);
|
|
86
|
+
return this.turboQuantKernels.read({
|
|
87
|
+
decoded: () => this.updateAndFetch(k, v),
|
|
88
|
+
rotated: () => {
|
|
89
|
+
const [keys, values] = this.updateAndFetchDeferredV(k, v);
|
|
90
|
+
return { keys, values, restore: this.captureValueTransform() };
|
|
91
|
+
},
|
|
92
|
+
}, { mask, release: () => mask.arr?.dispose() });
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
#append(k: MlxArray, v: MlxArray, deferred: boolean): [MlxArray, MlxArray] {
|
|
96
|
+
this.advance();
|
|
97
|
+
if (this.packed) {
|
|
98
|
+
this.#rotated = Array.from({ length: this.batchSize! }, () => true);
|
|
99
|
+
return deferred ? this.packed.updateAndFetchDeferredV(k, v) : this.packed.updateAndFetch(k, v);
|
|
100
|
+
}
|
|
101
|
+
this.#rotated = this.rows.map(row => !!kvStorage(row).rotatedValueAttention);
|
|
102
|
+
const result = appendFullKvRows(this.rows, this.leftPad, this.offset + k.shape[2]!, k, v, deferred);
|
|
103
|
+
try { this.syncPositions(true); } catch (error) { result[0].dispose(); result[1].dispose(); throw error; }
|
|
104
|
+
return result;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
updateAndFetch(k: MlxArray, v: MlxArray): [MlxArray, MlxArray] { return this.#append(k, v, false); }
|
|
108
|
+
updateAndFetchDeferredV(k: MlxArray, v: MlxArray): [MlxArray, MlxArray] { return this.#append(k, v, true); }
|
|
109
|
+
captureValueTransform(): (output: MlxArray) => MlxArray {
|
|
110
|
+
const rotated = [...this.#rotated];
|
|
111
|
+
if (rotated.every(value => value)) return unrotateValues;
|
|
112
|
+
if (rotated.every(value => !value)) return output => ops.contiguous(output);
|
|
113
|
+
return output => {
|
|
114
|
+
using restored = unrotateValues(output);
|
|
115
|
+
using flags = ops.fromInt32(rotated.map(value => Number(value)), [rotated.length, 1, 1, 1]);
|
|
116
|
+
using mask = flags.astype(Dtype.bool);
|
|
117
|
+
return ops.where(mask, restored, output);
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
makeEmptyBatch(): DelayedTurboQuantKVCache {
|
|
121
|
+
return new DelayedTurboQuantKVCache(this.kBits, this.vBits, this.start, this.fusedDecode, this.turboQuantKernels, undefined, this.compose);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { cloneAttachments } from "./checkpoint";
|
|
2
|
+
import { cleanupFailure, disposeResources } from "../runtime/resources";
|
|
3
|
+
import type { Cache } from "../contracts/mlx/cache";
|
|
4
|
+
import type { PromptCacheEntry } from "./prefix-cache";
|
|
5
|
+
import type { SpillItem } from "./persistence-types";
|
|
6
|
+
import type { SpillQueue } from "./persistence";
|
|
7
|
+
|
|
8
|
+
export interface DurabilityPromptCache {
|
|
9
|
+
findExact(tokens: number[], ns?: string): PromptCacheEntry | null;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface DurabilitySnapshotStats {
|
|
13
|
+
pendingSnapshots: number;
|
|
14
|
+
pendingSpills: number;
|
|
15
|
+
pendingSpillBytes: number;
|
|
16
|
+
droppedSpills: number;
|
|
17
|
+
failedSpills: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface DurabilityFlushResult extends DurabilitySnapshotStats {
|
|
21
|
+
durable: boolean;
|
|
22
|
+
flushedSnapshots: number;
|
|
23
|
+
missingSnapshots: number;
|
|
24
|
+
elapsedMs: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface DirtySnapshot {
|
|
28
|
+
key: string;
|
|
29
|
+
tokens: number[];
|
|
30
|
+
ns: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
type StoreOutcome = "stored" | "missing" | "failed";
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Turns debounced prompt-cache puts into a durability boundary.
|
|
37
|
+
*
|
|
38
|
+
* A dirty record stays present until SpillQueue reports that its atomic store
|
|
39
|
+
* completed. Queue drops and write failures therefore remain retryable during
|
|
40
|
+
* an explicit flush or graceful shutdown.
|
|
41
|
+
*/
|
|
42
|
+
export class SsdDurabilityCoordinator {
|
|
43
|
+
readonly #dirty = new Map<string, DirtySnapshot>();
|
|
44
|
+
readonly #timers = new Map<string, ReturnType<typeof setTimeout>>();
|
|
45
|
+
readonly #attempts = new Map<string, Promise<StoreOutcome>>();
|
|
46
|
+
#flush: Promise<DurabilityFlushResult> | null = null;
|
|
47
|
+
|
|
48
|
+
constructor(
|
|
49
|
+
readonly promptCache: DurabilityPromptCache,
|
|
50
|
+
readonly spillQueue: SpillQueue,
|
|
51
|
+
readonly cloneCaches: (caches: Cache[]) => Cache[],
|
|
52
|
+
readonly isAlreadyDurable: (tokens: number[], ns: string) => boolean = () => false,
|
|
53
|
+
readonly debounceMs = 0,
|
|
54
|
+
readonly retryMs = 5_000,
|
|
55
|
+
readonly onStored: () => void = () => {},
|
|
56
|
+
) {}
|
|
57
|
+
|
|
58
|
+
schedule(tokens: number[], ns = "", replace = true): void {
|
|
59
|
+
// Exact tensor objects use an empty history and still need persistence.
|
|
60
|
+
if (tokens.length === 0 && !this.promptCache.findExact(tokens, ns)?.attachments?.length) return;
|
|
61
|
+
// PromptCache can hold unrelated entries with the same namespace and
|
|
62
|
+
// length. Include the tokens so one conversation cannot cancel another
|
|
63
|
+
// conversation's pending durability record.
|
|
64
|
+
const key = `${ns.length}:${ns}:${tokens.join(",")}`;
|
|
65
|
+
if (!replace && this.#dirty.has(key)) return;
|
|
66
|
+
const rec: DirtySnapshot = { key, tokens: [...tokens], ns };
|
|
67
|
+
this.#dirty.set(key, rec);
|
|
68
|
+
this.#arm(key, this.debounceMs);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
get stats(): DurabilitySnapshotStats {
|
|
72
|
+
return {
|
|
73
|
+
pendingSnapshots: this.#dirty.size,
|
|
74
|
+
pendingSpills: this.spillQueue.pendingCount,
|
|
75
|
+
pendingSpillBytes: this.spillQueue.pendingBytes,
|
|
76
|
+
droppedSpills: this.spillQueue.droppedCount,
|
|
77
|
+
failedSpills: this.spillQueue.failedCount,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Force every dirty RAM snapshot through the queue and wait for storage. */
|
|
82
|
+
flush(): Promise<DurabilityFlushResult> {
|
|
83
|
+
if (this.#flush) return this.#flush;
|
|
84
|
+
const started = performance.now();
|
|
85
|
+
this.#flush = this.#flushInner(started).finally(() => { this.#flush = null; });
|
|
86
|
+
return this.#flush;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
#arm(key: string, delayMs: number): void {
|
|
90
|
+
const old = this.#timers.get(key);
|
|
91
|
+
if (old) clearTimeout(old);
|
|
92
|
+
const timer = setTimeout(() => {
|
|
93
|
+
this.#timers.delete(key);
|
|
94
|
+
void this.#attempt(key, false);
|
|
95
|
+
}, delayMs);
|
|
96
|
+
timer.unref?.();
|
|
97
|
+
this.#timers.set(key, timer);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async #attempt(key: string, force: boolean): Promise<StoreOutcome> {
|
|
101
|
+
const running = this.#attempts.get(key);
|
|
102
|
+
if (running) {
|
|
103
|
+
const outcome = await running;
|
|
104
|
+
if (!force || !this.#dirty.has(key)) return outcome;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const rec = this.#dirty.get(key);
|
|
108
|
+
if (!rec) return "stored";
|
|
109
|
+
|
|
110
|
+
const task = this.#store(rec);
|
|
111
|
+
this.#attempts.set(key, task);
|
|
112
|
+
try {
|
|
113
|
+
const outcome = await task;
|
|
114
|
+
if (outcome === "stored" && this.#dirty.get(key) === rec)
|
|
115
|
+
this.#dirty.delete(key);
|
|
116
|
+
if (outcome !== "stored" && !force && this.#dirty.get(key) === rec)
|
|
117
|
+
this.#arm(key, this.retryMs);
|
|
118
|
+
return outcome;
|
|
119
|
+
} finally {
|
|
120
|
+
if (this.#attempts.get(key) === task) this.#attempts.delete(key);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async #store(rec: DirtySnapshot): Promise<StoreOutcome> {
|
|
125
|
+
let snap: SpillItem | null = null;
|
|
126
|
+
try {
|
|
127
|
+
snap = (() => {
|
|
128
|
+
if (this.isAlreadyDurable(rec.tokens, rec.ns)) return null;
|
|
129
|
+
const entry = this.promptCache.findExact(rec.tokens, rec.ns);
|
|
130
|
+
if (!entry) return null;
|
|
131
|
+
const caches = this.cloneCaches(entry.caches);
|
|
132
|
+
try {
|
|
133
|
+
return { tokens: [...entry.tokens], caches, ns: rec.ns,
|
|
134
|
+
attachments: cloneAttachments(entry.attachments) };
|
|
135
|
+
} catch (error) {
|
|
136
|
+
return cleanupFailure(error, () => disposeResources(caches));
|
|
137
|
+
}
|
|
138
|
+
})();
|
|
139
|
+
} catch {
|
|
140
|
+
return "failed";
|
|
141
|
+
}
|
|
142
|
+
if (!snap)
|
|
143
|
+
return this.isAlreadyDurable(rec.tokens, rec.ns) ? "stored" : "missing";
|
|
144
|
+
const stored = await this.spillQueue.enqueue(snap);
|
|
145
|
+
// The queue releases its views before this continuation runs. Residency
|
|
146
|
+
// policy now sees memory without the completed writer's ownership.
|
|
147
|
+
if (stored) this.onStored();
|
|
148
|
+
return stored ? "stored" : "failed";
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async #flushInner(started: number): Promise<DurabilityFlushResult> {
|
|
152
|
+
for (const timer of this.#timers.values()) clearTimeout(timer);
|
|
153
|
+
this.#timers.clear();
|
|
154
|
+
|
|
155
|
+
let flushedSnapshots = 0;
|
|
156
|
+
const missing = new Map<string, DirtySnapshot>();
|
|
157
|
+
const droppedBefore = this.spillQueue.droppedCount;
|
|
158
|
+
const failedBefore = this.spillQueue.failedCount;
|
|
159
|
+
|
|
160
|
+
if (this.#attempts.size > 0) {
|
|
161
|
+
const results = await Promise.allSettled([...this.#attempts.values()]);
|
|
162
|
+
flushedSnapshots += results.filter(result => result.status === "fulfilled" && result.value === "stored").length;
|
|
163
|
+
}
|
|
164
|
+
await this.spillQueue.drain();
|
|
165
|
+
// A background failure may have rearmed its retry while flush waited.
|
|
166
|
+
// Forced attempts below own retries now; no timer may outlive this flush.
|
|
167
|
+
for (const timer of this.#timers.values()) clearTimeout(timer);
|
|
168
|
+
this.#timers.clear();
|
|
169
|
+
|
|
170
|
+
// Flush dirty entries serially without a second queue of snapshots.
|
|
171
|
+
// Attempt each record version once. Missing state stays dirty, so a
|
|
172
|
+
// second flush cannot report success without a real durable snapshot.
|
|
173
|
+
const attempted = new Set<DirtySnapshot>();
|
|
174
|
+
while (this.#dirty.size > 0) {
|
|
175
|
+
const records = [...this.#dirty.entries()].filter(([, rec]) => !attempted.has(rec));
|
|
176
|
+
if (records.length === 0) break;
|
|
177
|
+
for (const [key, rec] of records) {
|
|
178
|
+
if (this.#dirty.get(key) !== rec) continue;
|
|
179
|
+
attempted.add(rec);
|
|
180
|
+
const outcome = await this.#attempt(key, true);
|
|
181
|
+
await this.spillQueue.drain();
|
|
182
|
+
if (outcome === "stored") {
|
|
183
|
+
flushedSnapshots++;
|
|
184
|
+
} else if (outcome === "missing") {
|
|
185
|
+
missing.set(key, rec);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// A later trimmable snapshot may now cover a superseded RAM ancestor.
|
|
191
|
+
// Reconcile against committed SSD coverage after all writes settle;
|
|
192
|
+
// never clear a missing prefix merely because another write succeeded.
|
|
193
|
+
for (const [key, rec] of missing) {
|
|
194
|
+
if (!this.isAlreadyDurable(rec.tokens, rec.ns)) continue;
|
|
195
|
+
if (this.#dirty.get(key) === rec) this.#dirty.delete(key);
|
|
196
|
+
missing.delete(key);
|
|
197
|
+
flushedSnapshots++;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const stats = this.stats;
|
|
201
|
+
return {
|
|
202
|
+
...stats,
|
|
203
|
+
durable:
|
|
204
|
+
stats.pendingSnapshots === 0 &&
|
|
205
|
+
stats.pendingSpills === 0 &&
|
|
206
|
+
stats.droppedSpills === droppedBefore &&
|
|
207
|
+
stats.failedSpills === failedBefore &&
|
|
208
|
+
missing.size === 0,
|
|
209
|
+
flushedSnapshots,
|
|
210
|
+
missingSnapshots: missing.size,
|
|
211
|
+
elapsedMs: performance.now() - started,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { ObjectCache } from "../contracts/portable/object-cache";
|
|
2
|
+
import { cloneAttachments, disposeAttachments } from "./checkpoint";
|
|
3
|
+
import { type CheckpointAttachment } from "../contracts/mlx/checkpoint";
|
|
4
|
+
import type { MlxArray } from "@mlx-bun/mlx/array";
|
|
5
|
+
|
|
6
|
+
/** Encoder-owned tensor schema over the cache's exact-object interface. */
|
|
7
|
+
export class EncoderCache {
|
|
8
|
+
constructor(private readonly objects: ObjectCache<CheckpointAttachment[]>,
|
|
9
|
+
readonly identity: string) {}
|
|
10
|
+
|
|
11
|
+
async take(key: string): Promise<MlxArray | null> {
|
|
12
|
+
const lease = await this.objects.take(`encoder-v1:${this.identity}:${key}`);
|
|
13
|
+
if (!lease) return null;
|
|
14
|
+
try {
|
|
15
|
+
const tensor = lease.value[0]!.tensors[0]!;
|
|
16
|
+
return tensor.slice(tensor.shape.map(() => 0), [...tensor.shape]);
|
|
17
|
+
} finally { lease.dispose(); }
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Borrow the result; storage retains its own immutable native view. */
|
|
21
|
+
put(key: string, features: MlxArray): void {
|
|
22
|
+
const owned = cloneAttachments([{ schema: "encoder-features-v1", metadata: {}, tensors: [features] }]);
|
|
23
|
+
try { this.objects.put(`encoder-v1:${this.identity}:${key}`, owned); }
|
|
24
|
+
catch (error) { disposeAttachments(owned); throw error; }
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { MlxArray } from "@mlx-bun/mlx/array";
|
|
2
|
+
import * as ops from "@mlx-bun/mlx/ops";
|
|
3
|
+
import type { Cache } from "../contracts/mlx/cache";
|
|
4
|
+
import { fullRowPadding } from "./full-prefill-row";
|
|
5
|
+
import { kvStorage } from "./kv-storage";
|
|
6
|
+
import { disposeResources } from "../runtime/resources";
|
|
7
|
+
|
|
8
|
+
/** Assemble full-attention tensors at the model's B while storage retains
|
|
9
|
+
* per-row formats and positions. Returned arrays own their captured state. */
|
|
10
|
+
export function appendFullKvRows(rows: readonly Cache[], leftPad: readonly number[], width: number,
|
|
11
|
+
k: MlxArray, v: MlxArray, deferredValues = false): [MlxArray, MlxArray] {
|
|
12
|
+
const parts: [MlxArray, MlxArray][] = [], padded: MlxArray[] = [];
|
|
13
|
+
try {
|
|
14
|
+
for (const [index, row] of rows.map(kvStorage).entries()) {
|
|
15
|
+
using kr = k.slice([index, 0, 0, 0], [index + 1, k.shape[1]!, k.shape[2]!, k.shape[3]!]);
|
|
16
|
+
using vr = v.slice([index, 0, 0, 0], [index + 1, v.shape[1]!, v.shape[2]!, v.shape[3]!]);
|
|
17
|
+
parts.push(deferredValues && row.rotatedValueAttention
|
|
18
|
+
? row.rotatedValueAttention.updateAndFetchDeferredV(kr, vr) : row.updateAndFetch(kr, vr));
|
|
19
|
+
}
|
|
20
|
+
const combine = (field: 0 | 1) => {
|
|
21
|
+
const views = parts.map((part, row) => {
|
|
22
|
+
const a = part[field], before = leftPad[row]! - fullRowPadding(rows[row]!), after = width - before - a.shape[2]!;
|
|
23
|
+
if (!before && !after) return a;
|
|
24
|
+
using prefix = ops.zeros([1, a.shape[1]!, before, a.shape[3]!], a.dtype);
|
|
25
|
+
using suffix = ops.zeros([1, a.shape[1]!, after, a.shape[3]!], a.dtype);
|
|
26
|
+
const result = ops.concatAxis([prefix, a, suffix], 2); padded.push(result); return result;
|
|
27
|
+
});
|
|
28
|
+
return views.length === 1 ? ops.contiguous(views[0]!) : ops.concatAxis(views, 0);
|
|
29
|
+
};
|
|
30
|
+
const keys = combine(0);
|
|
31
|
+
try { return [keys, combine(1)]; } catch (error) { keys.dispose(); throw error; }
|
|
32
|
+
} finally { disposeResources([...parts.flat(), ...padded]); }
|
|
33
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { captureKvDonorAttention, combineKvDonorAttention } from "./kv-attention-view";
|
|
2
|
+
import type { MlxArray } from "@mlx-bun/mlx/array";
|
|
3
|
+
import * as ops from "@mlx-bun/mlx/ops";
|
|
4
|
+
import type { Cache, KvDonorRows } from "../contracts/mlx/cache";
|
|
5
|
+
import { fullRowInner, fullRowPadding } from "./full-prefill-row";
|
|
6
|
+
import { disposeResources } from "../runtime/resources";
|
|
7
|
+
|
|
8
|
+
/** Read full-attention rows in their existing physical columns. Valid token
|
|
9
|
+
* coverage excludes prefill padding; no append, conversion or compaction. */
|
|
10
|
+
export function captureFullKvDonorRows(rows: readonly Cache[], leftPad: readonly number[], width: number): KvDonorRows {
|
|
11
|
+
const parts: KvDonorRows[] = [], padded: MlxArray[] = [];
|
|
12
|
+
try {
|
|
13
|
+
for (const row of rows) parts.push(fullRowInner(row).captureDonorRows!());
|
|
14
|
+
const combine = (field: "keys" | "values") => {
|
|
15
|
+
const views = parts.map((part, index) => {
|
|
16
|
+
const a = part[field], before = leftPad[index]! - fullRowPadding(rows[index]!), after = width - before - a.shape[2]!;
|
|
17
|
+
if (!before && !after) return a;
|
|
18
|
+
using prefix = ops.zeros([1, a.shape[1]!, before, a.shape[3]!], a.dtype);
|
|
19
|
+
using suffix = ops.zeros([1, a.shape[1]!, after, a.shape[3]!], a.dtype);
|
|
20
|
+
const result = ops.concatAxis([prefix, a, suffix], 2); padded.push(result); return result;
|
|
21
|
+
});
|
|
22
|
+
return views.length === 1 ? ops.contiguous(views[0]!) : ops.concatAxis(views, 0);
|
|
23
|
+
};
|
|
24
|
+
const offsets = rows.map(row => row.offset), starts = [...leftPad];
|
|
25
|
+
const keys = combine("keys");
|
|
26
|
+
try { return { keys, values: combine("values"), offsets, starts, ends: offsets.map((offset, row) => starts[row]! + offset) }; }
|
|
27
|
+
catch (error) { keys.dispose(); throw error; }
|
|
28
|
+
} finally { disposeResources([...parts.flatMap(part => [part.keys, part.values]), ...padded]); }
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
/** Capture full rows in global columns while each encoded view retains its
|
|
33
|
+
* own physical width and numerical attention operation. */
|
|
34
|
+
export function captureFullKvDonorAttention(rows: readonly Cache[], leftPad: readonly number[], width: number): import("../contracts/mlx/cache").KvDonorAttention {
|
|
35
|
+
const captured: import("../contracts/mlx/cache").KvDonorAttention[] = [];
|
|
36
|
+
try {
|
|
37
|
+
for (const [index, row] of rows.entries()) {
|
|
38
|
+
const view = captureKvDonorAttention(fullRowInner(row));
|
|
39
|
+
const pad = leftPad[index]! - fullRowPadding(row);
|
|
40
|
+
captured.push({ width, dtype: view.dtype, offsets: [row.offset],
|
|
41
|
+
starts: [leftPad[index]!], ends: [leftPad[index]! + row.offset],
|
|
42
|
+
attend(q, scale, mask) {
|
|
43
|
+
if (!mask.arr) return view.attend(q, scale, mask);
|
|
44
|
+
const from = mask.arr.shape.map(() => 0), to = [...mask.arr.shape];
|
|
45
|
+
from[from.length - 1] = pad; to[to.length - 1] = pad + view.width;
|
|
46
|
+
using arr = mask.arr.slice(from, to);
|
|
47
|
+
return view.attend(q, scale, { mode: mask.mode, arr });
|
|
48
|
+
},
|
|
49
|
+
dispose: () => view.dispose(),
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
return combineKvDonorAttention(captured);
|
|
53
|
+
} catch (error) { disposeResources(captured); throw error; }
|
|
54
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { MlxArray } from "@mlx-bun/mlx/array";
|
|
2
|
+
import * as ops from "@mlx-bun/mlx/ops";
|
|
3
|
+
import type { PrefillPadding } from "../contracts/mlx/cache";
|
|
4
|
+
import { rowRollIndices } from "./batched-row-storage";
|
|
5
|
+
import { disposeResources } from "../runtime/resources";
|
|
6
|
+
|
|
7
|
+
interface Positions { rowOffsets: number[]; leftPad: number[] }
|
|
8
|
+
|
|
9
|
+
/** mlx-lm BatchKVCache.prepare/finalize (MIT), over arbitrary stored planes.
|
|
10
|
+
* Padding changes token positions; the representation's codec is uninvolved. */
|
|
11
|
+
export class FullPrefillPadding {
|
|
12
|
+
constructor(readonly tokenAxis = 2) {}
|
|
13
|
+
#ends?: number[];
|
|
14
|
+
get endOffsets(): readonly number[] | undefined { return this.#ends; }
|
|
15
|
+
restoreEnds(ends: readonly number[] | undefined): void { this.#ends = ends ? [...ends] : undefined; }
|
|
16
|
+
validOffset(offset: number, row: number): number { return Math.min(offset, this.#ends?.[row] ?? offset); }
|
|
17
|
+
|
|
18
|
+
prepare(positions: Positions, padding: PrefillPadding): void {
|
|
19
|
+
if (!positions.rowOffsets.length) {
|
|
20
|
+
positions.rowOffsets = padding.lengths.map(() => 0);
|
|
21
|
+
positions.leftPad = [...positions.rowOffsets];
|
|
22
|
+
}
|
|
23
|
+
if (padding.leftPadding) {
|
|
24
|
+
positions.leftPad = positions.leftPad.map((pad, row) => pad + padding.leftPadding![row]!);
|
|
25
|
+
positions.rowOffsets = positions.rowOffsets.map((offset, row) => offset - padding.leftPadding![row]!);
|
|
26
|
+
}
|
|
27
|
+
// Like rotating storage, retain absolute valid ends. A cohort can finish
|
|
28
|
+
// before its planned width when a longer request leaves during prefill.
|
|
29
|
+
this.#ends = padding.rightPadding?.some(pad => pad > 0)
|
|
30
|
+
? padding.lengths.map((length, row) => positions.rowOffsets[row]! + length) : undefined;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Borrow planes; return owned replacements only when padding needs moving.
|
|
34
|
+
* Stage every plane before committing positions, including on FFI failure. */
|
|
35
|
+
finalize(planes: readonly MlxArray[], positions: Positions): MlxArray[] | undefined {
|
|
36
|
+
if (!this.#ends) return;
|
|
37
|
+
const padding = this.#ends.map((end, row) => Math.max(0, positions.rowOffsets[row]! - end));
|
|
38
|
+
if (!padding.some(pad => pad > 0)) { this.#ends = undefined; return; }
|
|
39
|
+
const next: MlxArray[] = [];
|
|
40
|
+
try {
|
|
41
|
+
if (planes.length) {
|
|
42
|
+
const width = planes[0]!.shape[this.tokenAxis]!;
|
|
43
|
+
using indices = rowRollIndices(width, padding);
|
|
44
|
+
using compressed = this.tokenAxis === 1 ? ops.reshape(indices, [padding.length, width, 1]) : null;
|
|
45
|
+
for (const plane of planes) next.push(ops.takeAlongAxis(plane, compressed ?? indices, this.tokenAxis));
|
|
46
|
+
}
|
|
47
|
+
} catch (error) { disposeResources(next); throw error; }
|
|
48
|
+
positions.rowOffsets = positions.rowOffsets.map((offset, row) => offset - padding[row]!);
|
|
49
|
+
positions.leftPad = positions.leftPad.map((pad, row) => pad + padding[row]!);
|
|
50
|
+
this.#ends = undefined;
|
|
51
|
+
return next;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
filter(keep: readonly number[]): void { if (this.#ends) this.#ends = keep.map(row => this.#ends![row]!); }
|
|
55
|
+
clear(): void { this.#ends = undefined; }
|
|
56
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import type { MlxArray } from "@mlx-bun/mlx/array";
|
|
2
|
+
import { materializeCopy } from "@mlx-bun/mlx/materialize";
|
|
3
|
+
import { withResource, disposeResources } from "../runtime/resources";
|
|
4
|
+
import { leaseCacheState } from "./leases";
|
|
5
|
+
import { FullPrefillPadding } from "./full-prefill-padding";
|
|
6
|
+
import { KVCache, type AffineKernelSet } from "./kv";
|
|
7
|
+
import { QuantizedKVCache } from "./quantized-kv";
|
|
8
|
+
import { TurboQuantKVCache } from "./turboquant-kv";
|
|
9
|
+
import { type Cache, type Mask, type PrefillPadding } from "../contracts/mlx/cache";
|
|
10
|
+
import { kvStorage } from "./kv-storage";
|
|
11
|
+
|
|
12
|
+
type FullCache = KVCache | QuantizedKVCache | TurboQuantKVCache;
|
|
13
|
+
|
|
14
|
+
/** A physical full-attention row with separate valid-token coverage. Codecs
|
|
15
|
+
* consume the same physical tensors; padding never advances their threshold. */
|
|
16
|
+
export class FullPrefillRow implements Cache {
|
|
17
|
+
readonly padding = new FullPrefillPadding();
|
|
18
|
+
leftPadding = 0;
|
|
19
|
+
constructor(public inner: FullCache) {}
|
|
20
|
+
get minimumReusableOffset(): number { return (this.inner as Cache).minimumReusableOffset ?? 0; }
|
|
21
|
+
set minimumReusableOffset(value: number) { (this.inner as Cache).minimumReusableOffset = value; }
|
|
22
|
+
get positionOffset(): number { return this.inner.offset - this.leftPadding; }
|
|
23
|
+
get physicalLength(): number { return this.inner.offset; }
|
|
24
|
+
get offset(): number { return Math.max(0, this.padding.validOffset(this.positionOffset, 0)); }
|
|
25
|
+
get stateNeedsDispose(): boolean { return (this.inner as Cache).stateNeedsDispose ?? false; }
|
|
26
|
+
get affineConversion(): FullPrefillRow | undefined { return this.inner instanceof KVCache ? this : undefined; }
|
|
27
|
+
get turboConversion(): FullPrefillRow | undefined { return this.inner instanceof KVCache ? this : undefined; }
|
|
28
|
+
get quantizedAttention() { return kvStorage(this.inner).quantizedAttention; }
|
|
29
|
+
get rotatedValueAttention() { return kvStorage(this.inner).rotatedValueAttention; }
|
|
30
|
+
signature(): string { return this.inner.signature(); }
|
|
31
|
+
state(): MlxArray[] { return this.inner.state(); }
|
|
32
|
+
makeMask(tokens: number, window: number | null): Mask { return this.inner.makeMask(tokens, window); }
|
|
33
|
+
isTrimmable(): boolean { return true; }
|
|
34
|
+
trim(count: number): void { this.inner.trim(count); }
|
|
35
|
+
updateAndFetch(k: MlxArray, v: MlxArray): [MlxArray, MlxArray] { return this.inner.updateAndFetch(k, v); }
|
|
36
|
+
bytesPerToken(): number { return (this.inner as Cache).bytesPerToken?.() ?? 0; }
|
|
37
|
+
preparePrefill(padding: PrefillPadding): void {
|
|
38
|
+
const positions = { rowOffsets: [this.positionOffset], leftPad: [this.leftPadding] };
|
|
39
|
+
this.padding.prepare(positions, padding);
|
|
40
|
+
this.leftPadding = positions.leftPad[0]!;
|
|
41
|
+
}
|
|
42
|
+
finalizePrefill(): void {
|
|
43
|
+
const positions = { rowOffsets: [this.positionOffset], leftPad: [this.leftPadding] };
|
|
44
|
+
const next = withResource(leaseCacheState(this.inner), state => this.padding.finalize(state, positions));
|
|
45
|
+
if (next) this.replace(next, this.inner.offset);
|
|
46
|
+
this.leftPadding = positions.leftPad[0]!;
|
|
47
|
+
}
|
|
48
|
+
removeLeftPadding(count: number): void {
|
|
49
|
+
if (!count) return;
|
|
50
|
+
const next: MlxArray[] = [];
|
|
51
|
+
try {
|
|
52
|
+
withResource(leaseCacheState(this.inner), state => {
|
|
53
|
+
for (const plane of state) next.push(plane.slice([0, 0, count, 0], [...plane.shape]));
|
|
54
|
+
});
|
|
55
|
+
this.replace(next, this.inner.offset - count); next.length = 0;
|
|
56
|
+
this.leftPadding -= count;
|
|
57
|
+
} finally { disposeResources(next); }
|
|
58
|
+
}
|
|
59
|
+
private restored(planes: MlxArray[], offset: number): FullCache {
|
|
60
|
+
const source = this.inner;
|
|
61
|
+
if (source instanceof KVCache) {
|
|
62
|
+
const result = new KVCache(undefined, source.turboQuantKernels, source.windowRead);
|
|
63
|
+
if (planes.length) result.restoreState(planes[0]!, planes[1]!, offset);
|
|
64
|
+
return result;
|
|
65
|
+
}
|
|
66
|
+
if (source instanceof QuantizedKVCache) {
|
|
67
|
+
const result = new QuantizedKVCache(source.groupSize, source.bits, source.kernels);
|
|
68
|
+
if (planes.length) result.restoreState({ packed: planes[0]!, scales: planes[1]!, biases: planes[2]! },
|
|
69
|
+
{ packed: planes[3]!, scales: planes[4]!, biases: planes[5]! }, offset);
|
|
70
|
+
return result;
|
|
71
|
+
}
|
|
72
|
+
const result = new TurboQuantKVCache(source.kBits, source.vBits, source.turboQuantKernels, source.fusedDecode);
|
|
73
|
+
if (planes.length) result.restoreState({ kIdx: planes[0]!, kScales: planes[1]!, kZeros: planes[2]!,
|
|
74
|
+
vPacked: planes[3]!, vScales: planes[4]! }, offset, source.headDim!);
|
|
75
|
+
return result;
|
|
76
|
+
}
|
|
77
|
+
private replace(planes: MlxArray[], offset: number): void {
|
|
78
|
+
const next = this.restored(planes, offset), previous = this.inner;
|
|
79
|
+
(next as Cache).minimumReusableOffset = this.minimumReusableOffset;
|
|
80
|
+
this.inner = next; previous.dispose();
|
|
81
|
+
}
|
|
82
|
+
extract(): Cache {
|
|
83
|
+
const count = this.offset, planes: MlxArray[] = [];
|
|
84
|
+
try {
|
|
85
|
+
if (count) withResource(leaseCacheState(this.inner), state => {
|
|
86
|
+
for (const plane of state) {
|
|
87
|
+
using view = plane.slice([0, 0, this.leftPadding, 0], [1, plane.shape[1]!, this.leftPadding + count, plane.shape[3]!]);
|
|
88
|
+
planes.push(materializeCopy(view));
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
const result = this.restored(planes, count); planes.length = 0;
|
|
92
|
+
(result as Cache).minimumReusableOffset = this.minimumReusableOffset;
|
|
93
|
+
return result;
|
|
94
|
+
} finally { disposeResources(planes); }
|
|
95
|
+
}
|
|
96
|
+
/** The plain inner storage's conversion (`KVCache.toQuantized`): the
|
|
97
|
+
* converted row attends with the set `kernels` builds. */
|
|
98
|
+
toQuantized(groupSize: number, bits: number, kernels: AffineKernelSet): FullPrefillRow {
|
|
99
|
+
this.inner = (this.inner as KVCache).toQuantized(groupSize, bits, kernels);
|
|
100
|
+
return this;
|
|
101
|
+
}
|
|
102
|
+
toTurboQuantized(kBits: number, vBits: number, fusedDecode?: boolean): FullPrefillRow {
|
|
103
|
+
const source = this.inner as KVCache;
|
|
104
|
+
this.inner = TurboQuantKVCache.fromKVCache(source, kBits, vBits, source.turboQuantKernels, fusedDecode);
|
|
105
|
+
return this;
|
|
106
|
+
}
|
|
107
|
+
dispose(): void { this.inner.dispose(); this.padding.clear(); }
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function fullRowInner(row: Cache): Cache { return row instanceof FullPrefillRow ? row.inner : row; }
|
|
111
|
+
export function fullRowPadding(row: Cache): number { return row instanceof FullPrefillRow ? row.leftPadding : 0; }
|
|
112
|
+
export function fullRowPhysicalLength(row: Cache): number { return row instanceof FullPrefillRow ? row.physicalLength : row.offset; }
|