@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,430 @@
|
|
|
1
|
+
// Batched sliding-window (rotating) KV cache for `--batch N` serving — the
|
|
2
|
+
// hard half of dynamic-B that BatchedDecodeMaskCache (full-attention) doesn't
|
|
3
|
+
// cover. Faithful port of mlx-lm's BatchRotatingKVCache (models/cache.py):
|
|
4
|
+
// a shared ring buffer whose rotation state (_idx / _offset / rotated) is
|
|
5
|
+
// SCALAR across the batch (all rows advance one token per step, so the write
|
|
6
|
+
// column is the same for every row), with PER-ROW `offset` (absolute position,
|
|
7
|
+
// drives RoPE) and `leftPad` (padding columns in the buffer, masked out).
|
|
8
|
+
//
|
|
9
|
+
// Includes merge, one-token writes, block prefill, padding finalization, masks
|
|
10
|
+
// and row extraction. Prefill preparation is a storage capability; the serving
|
|
11
|
+
// scheduler's use of whole prompt cohorts is a separate concern.
|
|
12
|
+
// Positions (offset/leftPad/_idx/_offset/
|
|
13
|
+
// rotated) are tracked on the HOST (small deterministic ints) so make_mask is
|
|
14
|
+
// built in JS like buildBatchedDecodeMask; only K/V live on device.
|
|
15
|
+
//
|
|
16
|
+
// The genuinely tricky bit is make_mask once the ring wraps: the buffer is no
|
|
17
|
+
// longer in temporal order, so the causal+window+padding mask is built in
|
|
18
|
+
// temporal coordinates then ROLLED to the ring's physical layout — see
|
|
19
|
+
// buildBatchedRotatingMask (a column-by-column port of mlx-lm make_mask,
|
|
20
|
+
// including the `roll(shift=idx+1)`). Gated model-free against mlx-lm
|
|
21
|
+
// (`02d723a:tests/unit/batched-rotating.test.ts`) + end-to-end vs a long-context Gemma oracle.
|
|
22
|
+
|
|
23
|
+
import { MlxArray } from "@mlx-bun/mlx/array";
|
|
24
|
+
import * as ops from "@mlx-bun/mlx/ops";
|
|
25
|
+
import { RotatingKVCache } from "./rotating-kv";
|
|
26
|
+
import { type AttentionCache, type AttentionRead, type BatchableCache, type Cache, type Mask, type PaddedPrefillCache, type PrefillPadding } from "../contracts/mlx/cache";
|
|
27
|
+
import { AttentionMasks, sdpaRead, withLease, type MaskLease } from "./attention-read";
|
|
28
|
+
import { isRotatingPlainCache } from "./capabilities";
|
|
29
|
+
import { BatchedRotatingState, type RotatingPositionSnapshot } from "./batched-rotating-state";
|
|
30
|
+
import {
|
|
31
|
+
appendRotatingStorage,
|
|
32
|
+
mergeStorageRows,
|
|
33
|
+
plainRowStorage,
|
|
34
|
+
temporalStorageView,
|
|
35
|
+
rowRollIndices,
|
|
36
|
+
} from "./batched-row-storage";
|
|
37
|
+
|
|
38
|
+
const STEP = 256;
|
|
39
|
+
|
|
40
|
+
/** Port of BatchRotatingKVCache.make_mask for the DECODE case. Returns a
|
|
41
|
+
* [B,1,N,S] bool mask (S == min(maxSize-1, offsetScalar) + N) for the step
|
|
42
|
+
* ABOUT to be written: causal AND sliding-window AND per-row left-padding,
|
|
43
|
+
* rolled to the ring's physical layout once wrapped.
|
|
44
|
+
*
|
|
45
|
+
* Inputs are the PRE-write scalar ring state (`idx`=_idx, `offsetScalar`=_offset,
|
|
46
|
+
* `rotated`) and per-row `leftPad`. The leftPad adjustments here are LOCAL to
|
|
47
|
+
* the mask (mlx-lm decrements left_padding both in make_mask AND in update; the
|
|
48
|
+
* update's decrement is the persistent one, applied separately by the cache). */
|
|
49
|
+
export function buildBatchedRotatingMask(
|
|
50
|
+
B: number, N: number, leftPad: number[],
|
|
51
|
+
maxSize: number, window: number, idx: number, offsetScalar: number, rotated: boolean, block = N > 1,
|
|
52
|
+
): MlxArray {
|
|
53
|
+
const off = Math.min(maxSize - 1, offsetScalar);
|
|
54
|
+
const S = off + N;
|
|
55
|
+
|
|
56
|
+
// Local (non-persisted) leftPad for the mask: trim + rotation shrink it. A
|
|
57
|
+
// block write first puts the ring in temporal order, so its history is the
|
|
58
|
+
// ring's active length (the whole ring once rotated), not the write head.
|
|
59
|
+
const history = block && rotated ? maxSize : idx;
|
|
60
|
+
const trimSize = history - maxSize + (block ? 1 : 0);
|
|
61
|
+
const isRot = !block && (rotated || idx >= maxSize);
|
|
62
|
+
const lp = leftPad.map((x) => x - (trimSize > 0 ? trimSize : 0) - (isRot ? 1 : 0));
|
|
63
|
+
|
|
64
|
+
// roll(shift): physical column = (temporal column + shift) mod S.
|
|
65
|
+
const shift = isRot ? (idx >= maxSize ? 0 : idx) + 1 : 0;
|
|
66
|
+
|
|
67
|
+
const data = new Int32Array(B * N * S);
|
|
68
|
+
for (let b = 0; b < B; b++) {
|
|
69
|
+
const pad = lp[b]!;
|
|
70
|
+
for (let i = 0; i < N; i++) {
|
|
71
|
+
const lind = off + i; // query temporal position
|
|
72
|
+
for (let j = 0; j < S; j++) {
|
|
73
|
+
// temporal key position j: causal, within window, past this row's pad.
|
|
74
|
+
const allow = lind >= j && lind < j + window && j >= pad;
|
|
75
|
+
if (!allow) continue;
|
|
76
|
+
const col = ((j + shift) % S + S) % S;
|
|
77
|
+
data[(b * N + i) * S + col] = 1;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
const intArr = MlxArray.fromInt32(data, [B, 1, N, S]);
|
|
82
|
+
const zero = MlxArray.fromInt32(new Int32Array([0]), []);
|
|
83
|
+
const mask = ops.less(zero, intArr); // 0 < x → bool
|
|
84
|
+
zero.dispose();
|
|
85
|
+
intArr.dispose();
|
|
86
|
+
return mask;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Port of mlx-lm BatchRotatingKVCache; padded singleton chunks use its
|
|
90
|
+
* concat operation with the matching block mask until finalization.
|
|
91
|
+
*
|
|
92
|
+
* Reads: the sliding window is `maxSize`. Decode, which runs with no prefill
|
|
93
|
+
* padding pending (finalize first), writes one position in place at the ring
|
|
94
|
+
* head; a window writes by concatenation, which also serves padded prefill
|
|
95
|
+
* chunks of any length. Each attends with the stock fused SDPA under mlx-lm's
|
|
96
|
+
* batched rotating mask (`buildBatchedRotatingMask`) for its write, built once
|
|
97
|
+
* per forward through `masks`. The deprecated `updateAndFetch` picks the write
|
|
98
|
+
* from the length instead, so a one-row window without pending padding wrote in
|
|
99
|
+
* place there and concatenates here: the same bits until the ring wraps, a
|
|
100
|
+
* different key order after. */
|
|
101
|
+
export class BatchedRotatingCache implements AttentionCache, BatchableCache, PaddedPrefillCache {
|
|
102
|
+
keys: MlxArray | null = null;
|
|
103
|
+
values: MlxArray | null = null;
|
|
104
|
+
#rows: BatchedRotatingState;
|
|
105
|
+
#ropeArr: MlxArray | null = null;
|
|
106
|
+
#ropeForOffset = -1;
|
|
107
|
+
readonly maxSize: number;
|
|
108
|
+
|
|
109
|
+
constructor(
|
|
110
|
+
maxSize: number,
|
|
111
|
+
leftPad: number[],
|
|
112
|
+
/** The per-forward masks of the model this layout serves. */
|
|
113
|
+
readonly masks = new AttentionMasks(),
|
|
114
|
+
) {
|
|
115
|
+
this.maxSize = maxSize;
|
|
116
|
+
this.#rows = new BatchedRotatingState(maxSize, leftPad);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
appendDecode(k: MlxArray, v: MlxArray): AttentionRead {
|
|
120
|
+
return withLease(this.#mask(1, false), mask => {
|
|
121
|
+
const [keys, values] = this.#updateInPlace(k, v);
|
|
122
|
+
return sdpaRead(keys, values, mask);
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
appendWindow(k: MlxArray, v: MlxArray): AttentionRead {
|
|
127
|
+
return withLease(this.#mask(k.shape[2]!, true), mask => {
|
|
128
|
+
const [keys, values] = this.#updateConcat(k, v);
|
|
129
|
+
return sdpaRead(keys, values, mask);
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** The mask of an `N`-position write before it: `block` for concatenation,
|
|
134
|
+
* not for an in-place write. */
|
|
135
|
+
#mask(N: number, block: boolean): MaskLease {
|
|
136
|
+
const rows = this.#rows;
|
|
137
|
+
const key = `${N}|${block}|${rows.leftPad.join(",")}|${rows.ringIndex}|${rows.totalOffset}|${rows.rotated}`;
|
|
138
|
+
return this.masks.lease(`batched-rotating:${this.maxSize}`, key, () => ({
|
|
139
|
+
mode: "array",
|
|
140
|
+
arr: buildBatchedRotatingMask(rows.batchSize, N, rows.leftPad, this.maxSize, this.maxSize,
|
|
141
|
+
rows.ringIndex, rows.totalOffset, rows.rotated, block),
|
|
142
|
+
}));
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** Same signature as the serial RotatingKVCache — the scheduler's merge
|
|
146
|
+
* guards recognize a batched ring as "rotating-plain + RowBatchCache"
|
|
147
|
+
* (packages/inference/src/execution/batch-group.ts #mergeJoiner prevRot branch); without this the next
|
|
148
|
+
* join sees "unknown", skips the prevRot rows, and replaces the batch's
|
|
149
|
+
* sliding-layer KV with a one-row cache (B≥3 collapse). Guard order
|
|
150
|
+
* everywhere is isRowBatchCache FIRST, so the shared string never
|
|
151
|
+
* misroutes a batched cache into a serial-only path. */
|
|
152
|
+
signature(): string { return "kv:rotating-plain"; }
|
|
153
|
+
|
|
154
|
+
get positionSnapshot(): RotatingPositionSnapshot { return this.#rows.snapshot(); }
|
|
155
|
+
validOffset(row: number): number { return this.#rows.validOffset(row); }
|
|
156
|
+
preparePrefill(padding: PrefillPadding, rightPaddedBatch?: boolean): void { this.#rows.preparePrefill(padding, rightPaddedBatch); this.releaseRopeArr(); }
|
|
157
|
+
finalizePrefill(): void {
|
|
158
|
+
const shifts = this.#rows.prefillRoll();
|
|
159
|
+
if (shifts?.some(shift => shift !== 0) && this.keys && this.values) {
|
|
160
|
+
using indices = rowRollIndices(this.keys.shape[2]!, shifts);
|
|
161
|
+
const keys = plainRowStorage.rollRows(this.keys, indices);
|
|
162
|
+
let values: MlxArray;
|
|
163
|
+
try { values = plainRowStorage.rollRows(this.values, indices); }
|
|
164
|
+
catch (error) { keys.dispose(); throw error; }
|
|
165
|
+
this.keys.dispose(); this.values.dispose(); this.keys = keys; this.values = values;
|
|
166
|
+
}
|
|
167
|
+
this.#rows.finalizePrefill(); this.releaseRopeArr();
|
|
168
|
+
}
|
|
169
|
+
/** Adopt owned tensors without changing their physical ring columns. */
|
|
170
|
+
static adoptPhysical(keys: MlxArray | null, values: MlxArray | null, position: RotatingPositionSnapshot,
|
|
171
|
+
masks = new AttentionMasks()): BatchedRotatingCache {
|
|
172
|
+
const cache = new BatchedRotatingCache(position.maxSize, [...position.leftPad], masks);
|
|
173
|
+
cache.#rows.restore(position); cache.keys = keys; cache.values = values;
|
|
174
|
+
|
|
175
|
+
return cache;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
get offsetArr(): number[] { return this.#rows.offsets; }
|
|
179
|
+
get rowOffsets(): readonly number[] { return this.#rows.offsets; }
|
|
180
|
+
get leftPad(): number[] { return this.#rows.leftPad; }
|
|
181
|
+
get batchSize(): number { return this.#rows.batchSize; }
|
|
182
|
+
makeEmptyBatch(): BatchedRotatingCache { return new BatchedRotatingCache(this.maxSize, [], this.masks); }
|
|
183
|
+
/** Bytes one row holds for `tokens` more positions, capped at the window. */
|
|
184
|
+
projectedBytes(tokens: number): number {
|
|
185
|
+
if (!this.keys || !this.values || !this.batchSize) return 0;
|
|
186
|
+
return (this.keys.nbytes + this.values.nbytes) / (this.batchSize * this.keys.shape[2]!) * Math.min(tokens, this.maxSize);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** Current batch size — tracks filter() (which shrinks the per-row arrays). */
|
|
190
|
+
get #B(): number {
|
|
191
|
+
return this.#rows.batchSize;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** mlx-lm uses `cache.offset` (the per-row array) for the scalar interface
|
|
195
|
+
* too; we expose the scalar total as `offset` and the per-row positions via
|
|
196
|
+
* `ropeOffsetArr` (the model's per-row RoPE path). */
|
|
197
|
+
get offset(): number {
|
|
198
|
+
return this.#rows.totalOffset;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
get ropeOffsetArr(): MlxArray {
|
|
202
|
+
if (this.#ropeArr && this.#ropeForOffset === this.offset) return this.#ropeArr;
|
|
203
|
+
this.#ropeArr?.dispose();
|
|
204
|
+
this.#ropeArr = MlxArray.fromInt32(Int32Array.from(this.offsetArr), [this.#B]);
|
|
205
|
+
this.#ropeForOffset = this.offset;
|
|
206
|
+
return this.#ropeArr;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
makeMask(N: number, windowSize: number | null): Mask {
|
|
210
|
+
const window = windowSize ?? this.maxSize;
|
|
211
|
+
return {
|
|
212
|
+
mode: "array",
|
|
213
|
+
arr: buildBatchedRotatingMask(
|
|
214
|
+
this.#B, N, this.leftPad, this.maxSize, window,
|
|
215
|
+
this.#rows.ringIndex, this.offset, this.#rows.rotated, N > 1 || this.#rows.hasPendingPadding,
|
|
216
|
+
),
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
updateAndFetch(k: MlxArray, v: MlxArray): [MlxArray, MlxArray] {
|
|
221
|
+
if (k.shape[2] !== 1 || this.#rows.hasPendingPadding) return this.#updateConcat(k, v);
|
|
222
|
+
return this.#updateInPlace(k, v);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/** N=1 decode update — port of _update_in_place. */
|
|
226
|
+
#updateInPlace(k: MlxArray, v: MlxArray): [MlxArray, MlxArray] {
|
|
227
|
+
const [B, H, S, D] = k.shape as [number, number, number, number];
|
|
228
|
+
const vD = v.shape[3]!;
|
|
229
|
+
const prev = this.offset;
|
|
230
|
+
|
|
231
|
+
// Grow the buffer (in STEP chunks) until it reaches maxSize.
|
|
232
|
+
if (!this.keys || (prev >= this.keys.shape[2]! && this.keys.shape[2]! < this.maxSize)) {
|
|
233
|
+
const newSize = Math.min(STEP, this.maxSize - prev);
|
|
234
|
+
const newK = ops.zeros([B, H, newSize, D], k.dtype);
|
|
235
|
+
const newV = ops.zeros([B, H, newSize, vD], v.dtype);
|
|
236
|
+
if (this.keys && this.values) {
|
|
237
|
+
const ck = ops.concatAxis([this.keys, newK], 2);
|
|
238
|
+
const cv = ops.concatAxis([this.values, newV], 2);
|
|
239
|
+
for (const a of [this.keys, this.values, newK, newV]) a.dispose();
|
|
240
|
+
this.keys = ck;
|
|
241
|
+
this.values = cv;
|
|
242
|
+
} else {
|
|
243
|
+
this.keys = newK;
|
|
244
|
+
this.values = newV;
|
|
245
|
+
}
|
|
246
|
+
this.#rows.markGrown(prev);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Trim any overshoot past maxSize (decrements left padding persistently).
|
|
250
|
+
const trimSize = this.keys!.shape[2]! - this.maxSize;
|
|
251
|
+
if (trimSize > 0) {
|
|
252
|
+
const tk = this.keys!.slice([0, 0, trimSize, 0], this.keys!.shape as number[]);
|
|
253
|
+
const tv = this.values!.slice([0, 0, trimSize, 0], this.values!.shape as number[]);
|
|
254
|
+
this.keys!.dispose();
|
|
255
|
+
this.values!.dispose();
|
|
256
|
+
this.keys = tk;
|
|
257
|
+
this.values = tv;
|
|
258
|
+
this.#rows.trimOvershoot(trimSize);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Rotate when the write head reaches the end of the ring.
|
|
262
|
+
const writeIndex = this.#rows.beginWrite(S);
|
|
263
|
+
|
|
264
|
+
// Write the new K/V at the ring head.
|
|
265
|
+
const [, , SK, DK] = this.keys!.shape as [number, number, number, number];
|
|
266
|
+
const k2 = ops.sliceUpdate(this.keys!, k, [0, 0, writeIndex, 0], [B, H, writeIndex + S, DK]);
|
|
267
|
+
const v2 = ops.sliceUpdate(this.values!, v, [0, 0, writeIndex, 0], [B, H, writeIndex + S, vD]);
|
|
268
|
+
this.keys!.dispose();
|
|
269
|
+
this.values!.dispose();
|
|
270
|
+
this.keys = k2;
|
|
271
|
+
this.values = v2;
|
|
272
|
+
this.#rows.commitWrite(S);
|
|
273
|
+
|
|
274
|
+
// Return the populated prefix (ring not yet full) or the whole buffer.
|
|
275
|
+
if (this.offset < this.maxSize) {
|
|
276
|
+
return [
|
|
277
|
+
this.keys.slice([0, 0, 0, 0], [B, H, this.offset, DK]),
|
|
278
|
+
this.values.slice([0, 0, 0, 0], [B, H, this.offset, vD]),
|
|
279
|
+
];
|
|
280
|
+
}
|
|
281
|
+
return [
|
|
282
|
+
this.keys.slice([0, 0, 0, 0], [B, H, SK, DK]),
|
|
283
|
+
this.values.slice([0, 0, 0, 0], [B, H, SK, vD]),
|
|
284
|
+
];
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
#updateConcat(k: MlxArray, v: MlxArray): [MlxArray, MlxArray] {
|
|
288
|
+
const keys = appendRotatingStorage(plainRowStorage, this.keys, k, this.#rows);
|
|
289
|
+
let values: MlxArray;
|
|
290
|
+
try { values = appendRotatingStorage(plainRowStorage, this.values, v, this.#rows); }
|
|
291
|
+
catch (error) { keys.dispose(); throw error; }
|
|
292
|
+
const history = this.keys ? this.#rows.activeLength : 0;
|
|
293
|
+
this.keys?.dispose(); this.values?.dispose();
|
|
294
|
+
this.keys = keys; this.values = values;
|
|
295
|
+
this.#rows.commitConcat(k.shape[2]!, history);
|
|
296
|
+
return [
|
|
297
|
+
plainRowStorage.slice(keys, 0, this.#B, 0, this.#rows.ringIndex),
|
|
298
|
+
plainRowStorage.slice(values, 0, this.#B, 0, this.#rows.ringIndex),
|
|
299
|
+
];
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/** Free the per-step RoPE array without disposing KV (wrapper-rebuild path). */
|
|
303
|
+
releaseRopeArr(): void {
|
|
304
|
+
this.#ropeArr?.dispose();
|
|
305
|
+
this.#ropeArr = null;
|
|
306
|
+
this.#ropeForOffset = -1;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/** Ring contents in temporal order, cut to the valid length (extract). */
|
|
310
|
+
temporalView(): [MlxArray, MlxArray] {
|
|
311
|
+
if (!this.keys || !this.values) throw new Error("cache is empty");
|
|
312
|
+
return [
|
|
313
|
+
temporalStorageView(plainRowStorage, this.keys, this.#rows, {
|
|
314
|
+
from: Math.max(0, this.#rows.activeLength - this.maxSize), to: this.#rows.activeLength,
|
|
315
|
+
}),
|
|
316
|
+
temporalStorageView(plainRowStorage, this.values, this.#rows, {
|
|
317
|
+
from: Math.max(0, this.#rows.activeLength - this.maxSize), to: this.#rows.activeLength,
|
|
318
|
+
}),
|
|
319
|
+
];
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/** mlx-lm `BatchRotatingKVCache.extract` (models/cache.py:1417): row `i`
|
|
323
|
+
* as a fresh SERIAL RotatingKVCache — de-rolled to temporal order (the
|
|
324
|
+
* oracle's roll(-_idx) when rotated), left padding stripped
|
|
325
|
+
* (`max(0, left_padding[idx])` — post-wrap pads go negative), OWNED
|
|
326
|
+
* contiguous copies. `offset` = the row's absolute position (may exceed
|
|
327
|
+
* the buffer: wrapped rings have evicted tokens); ring idx = the new
|
|
328
|
+
* buffer length — temporal order ⇔ write head at the end, exactly the
|
|
329
|
+
* oracle's `cache._idx = cache.keys.shape[2]`. Bit-exact vs a solo run:
|
|
330
|
+
* merge/decode/filter keep each row's ring bytes identical to the serial
|
|
331
|
+
* cache's (`02d723a:tests/unit/batched-rotating.test.ts`) and this is a pure
|
|
332
|
+
* slice+copy. */
|
|
333
|
+
extractRow(i: number, limit?: number): RotatingKVCache {
|
|
334
|
+
if (!this.keys || !this.values) return new RotatingKVCache(this.maxSize);
|
|
335
|
+
const pad = Math.max(0, this.leftPad[i]!, limit === undefined ? 0 : this.#rows.activeLength - limit);
|
|
336
|
+
const c = new RotatingKVCache(this.maxSize);
|
|
337
|
+
const k = temporalStorageView(plainRowStorage, this.keys, this.#rows, {
|
|
338
|
+
row: i, from: pad, to: this.#rows.activeLength, copy: true,
|
|
339
|
+
});
|
|
340
|
+
const v = temporalStorageView(plainRowStorage, this.values, this.#rows, {
|
|
341
|
+
row: i, from: pad, to: this.#rows.activeLength, copy: true,
|
|
342
|
+
});
|
|
343
|
+
c.restoreState(k, v, this.offsetArr[i]!, k.shape[2]!);
|
|
344
|
+
return c;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/** Keep only `keep` rows along the batch axis (eviction). */
|
|
348
|
+
filter(keep: number[]): void {
|
|
349
|
+
if (this.keys && this.values) {
|
|
350
|
+
const k = plainRowStorage.takeRows(this.keys, keep);
|
|
351
|
+
const v = plainRowStorage.takeRows(this.values, keep);
|
|
352
|
+
this.keys.dispose();
|
|
353
|
+
this.values.dispose();
|
|
354
|
+
this.keys = k;
|
|
355
|
+
this.values = v;
|
|
356
|
+
}
|
|
357
|
+
this.#rows.filter(keep);
|
|
358
|
+
this.releaseRopeArr();
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
filterRows(keep: readonly number[]): void { this.filter([...keep]); }
|
|
362
|
+
|
|
363
|
+
state(): MlxArray[] {
|
|
364
|
+
return this.keys && this.values ? [this.keys, this.values] : [];
|
|
365
|
+
}
|
|
366
|
+
isTrimmable(): boolean {
|
|
367
|
+
return this.#rows.trimmable;
|
|
368
|
+
}
|
|
369
|
+
trim(n: number): void {
|
|
370
|
+
this.#rows.trim(n);
|
|
371
|
+
}
|
|
372
|
+
dispose(): void {
|
|
373
|
+
this.keys?.dispose();
|
|
374
|
+
this.values?.dispose();
|
|
375
|
+
this.keys = this.values = null;
|
|
376
|
+
this.#ropeArr?.dispose();
|
|
377
|
+
this.#ropeArr = null;
|
|
378
|
+
this.masks.clear();
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/** Join rows into this empty ring: sources are running rings, whose rows are
|
|
382
|
+
* cut out in temporal order, or serial RotatingKVCaches (an adopted lone row
|
|
383
|
+
* or a joiner), in row order. Sources are borrowed. */
|
|
384
|
+
mergeRows(rows: readonly Cache[]): void {
|
|
385
|
+
if (this.#rows.batchSize) throw new Error("BatchedRotatingCache.mergeRows needs an empty batch");
|
|
386
|
+
const parts: { keys: MlxArray; values: MlxArray }[] = [], offsets: number[] = [], held: MlxArray[] = [];
|
|
387
|
+
const own = (array: MlxArray) => { held.push(array); return array; };
|
|
388
|
+
try {
|
|
389
|
+
for (const source of rows) {
|
|
390
|
+
if (source instanceof BatchedRotatingCache) {
|
|
391
|
+
const [k0, v0] = source.temporalView(); own(k0); own(v0);
|
|
392
|
+
const [, H, valid, D] = k0.shape as [number, number, number, number];
|
|
393
|
+
const vD = v0.shape[3]!;
|
|
394
|
+
for (let b = 0; b < source.batchSize; b++) {
|
|
395
|
+
const pad = Math.max(0, source.leftPad[b]!);
|
|
396
|
+
parts.push({ keys: own(k0.slice([b, 0, pad, 0], [b + 1, H, valid, D])),
|
|
397
|
+
values: own(v0.slice([b, 0, pad, 0], [b + 1, H, valid, vD])) });
|
|
398
|
+
offsets.push(source.offsetArr[b]!);
|
|
399
|
+
}
|
|
400
|
+
} else if (isRotatingPlainCache(source)) {
|
|
401
|
+
const [keys, values] = (source as RotatingKVCache).temporalView();
|
|
402
|
+
parts.push({ keys: own(keys), values: own(values) }); offsets.push(source.offset);
|
|
403
|
+
} else throw new Error(`sliding-window batch cannot merge ${source.signature()}`);
|
|
404
|
+
}
|
|
405
|
+
const merged = BatchedRotatingCache.merge(parts, offsets, this.maxSize);
|
|
406
|
+
this.#rows = merged.#rows; this.keys = merged.keys; this.values = merged.values;
|
|
407
|
+
merged.keys = merged.values = null;
|
|
408
|
+
} finally { for (const array of held) array.dispose(); }
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/** Assemble a batch from per-row temporal KV slices (port of merge). Each row
|
|
412
|
+
* is its solo cache's temporalView ([1,H,Li,D], Li ≤ maxSize, temporal
|
|
413
|
+
* order) and `offsets[i]` its absolute position. Left-pads to the longest
|
|
414
|
+
* row; the result is in temporal order (rotated=false, idx=offset=width). */
|
|
415
|
+
static merge(
|
|
416
|
+
rows: { keys: MlxArray; values: MlxArray }[], offsets: number[], maxSize: number,
|
|
417
|
+
masks = new AttentionMasks(),
|
|
418
|
+
): BatchedRotatingCache {
|
|
419
|
+
const lens = rows.map((r) => r.keys.shape[2]!);
|
|
420
|
+
const width = Math.max(...lens, 0);
|
|
421
|
+
const leftPad = lens.map((l) => width - l);
|
|
422
|
+
const cache = new BatchedRotatingCache(maxSize, leftPad, masks);
|
|
423
|
+
cache.#rows.restoreMerged(width, offsets);
|
|
424
|
+
if (width === 0) return cache;
|
|
425
|
+
|
|
426
|
+
cache.keys = mergeStorageRows(plainRowStorage, rows.map((row) => row.keys), leftPad);
|
|
427
|
+
cache.values = mergeStorageRows(plainRowStorage, rows.map((row) => row.values), leftPad);
|
|
428
|
+
return cache;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { MlxArray } from "@mlx-bun/mlx/array";
|
|
2
|
+
import * as ops from "@mlx-bun/mlx/ops";
|
|
3
|
+
import { BatchedRotatingState } from "./batched-rotating-state";
|
|
4
|
+
import { Dtype } from "@mlx-bun/mlx/ffi";
|
|
5
|
+
|
|
6
|
+
export interface RowStorage<T> {
|
|
7
|
+
shape(value: T): readonly [number, number, number, number];
|
|
8
|
+
slice(value: T, batchFrom: number, batchTo: number, tokenFrom: number, tokenTo: number): T;
|
|
9
|
+
concatTokens(values: readonly T[]): T;
|
|
10
|
+
concatRows(values: readonly T[]): T;
|
|
11
|
+
padLeft(value: T, tokens: number): T;
|
|
12
|
+
takeRows(value: T, keep: readonly number[]): T;
|
|
13
|
+
copy(value: T): T;
|
|
14
|
+
rollRows(value: T, indices: MlxArray): T;
|
|
15
|
+
dispose(value: T): void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const plainRowStorage: RowStorage<MlxArray> = {
|
|
19
|
+
shape: (value) => value.shape as [number, number, number, number],
|
|
20
|
+
slice(value, batchFrom, batchTo, tokenFrom, tokenTo) {
|
|
21
|
+
const [, H, , D] = value.shape as [number, number, number, number];
|
|
22
|
+
return value.slice([batchFrom, 0, tokenFrom, 0], [batchTo, H, tokenTo, D]);
|
|
23
|
+
},
|
|
24
|
+
concatTokens: (values) => ops.concatAxis([...values], 2),
|
|
25
|
+
concatRows: (values) => ops.concatAxis([...values], 0),
|
|
26
|
+
padLeft(value, tokens) {
|
|
27
|
+
if (tokens === 0) return value.slice([0, 0, 0, 0], value.shape as number[]);
|
|
28
|
+
const [B, H, , D] = value.shape as [number, number, number, number];
|
|
29
|
+
const zeros = ops.zeros([B, H, tokens, D], value.dtype);
|
|
30
|
+
const out = ops.concatAxis([zeros, value], 2);
|
|
31
|
+
zeros.dispose();
|
|
32
|
+
return out;
|
|
33
|
+
},
|
|
34
|
+
takeRows(value, keep) {
|
|
35
|
+
const indices = MlxArray.fromInt32(Int32Array.from(keep), [keep.length]);
|
|
36
|
+
const out = ops.takeAxis(value, indices, 0);
|
|
37
|
+
indices.dispose();
|
|
38
|
+
return out;
|
|
39
|
+
},
|
|
40
|
+
copy: (value) => ops.copyOf(value),
|
|
41
|
+
rollRows: (value, indices) => ops.takeAlongAxis(value, indices, 2),
|
|
42
|
+
dispose: (value) => value.dispose(),
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const quantizedRowStorage: RowStorage<ops.QuantizedTensor> = {
|
|
46
|
+
shape: (value) => value.packed.shape as [number, number, number, number],
|
|
47
|
+
slice(value, batchFrom, batchTo, tokenFrom, tokenTo) {
|
|
48
|
+
const cut = (array: MlxArray): MlxArray => {
|
|
49
|
+
const [, H, , D] = array.shape as [number, number, number, number];
|
|
50
|
+
return array.slice([batchFrom, 0, tokenFrom, 0], [batchTo, H, tokenTo, D]);
|
|
51
|
+
};
|
|
52
|
+
return { packed: cut(value.packed), scales: cut(value.scales), biases: cut(value.biases) };
|
|
53
|
+
},
|
|
54
|
+
concatTokens(values) {
|
|
55
|
+
return {
|
|
56
|
+
packed: ops.concatAxis(values.map((value) => value.packed), 2),
|
|
57
|
+
scales: ops.concatAxis(values.map((value) => value.scales), 2),
|
|
58
|
+
biases: ops.concatAxis(values.map((value) => value.biases), 2),
|
|
59
|
+
};
|
|
60
|
+
},
|
|
61
|
+
concatRows(values) {
|
|
62
|
+
return {
|
|
63
|
+
packed: ops.concatAxis(values.map((value) => value.packed), 0),
|
|
64
|
+
scales: ops.concatAxis(values.map((value) => value.scales), 0),
|
|
65
|
+
biases: ops.concatAxis(values.map((value) => value.biases), 0),
|
|
66
|
+
};
|
|
67
|
+
},
|
|
68
|
+
padLeft(value, tokens) {
|
|
69
|
+
const pad = (array: MlxArray): MlxArray => plainRowStorage.padLeft(array, tokens);
|
|
70
|
+
return { packed: pad(value.packed), scales: pad(value.scales), biases: pad(value.biases) };
|
|
71
|
+
},
|
|
72
|
+
takeRows(value, keep) {
|
|
73
|
+
const take = (array: MlxArray): MlxArray => plainRowStorage.takeRows(array, keep);
|
|
74
|
+
return { packed: take(value.packed), scales: take(value.scales), biases: take(value.biases) };
|
|
75
|
+
},
|
|
76
|
+
copy(value) {
|
|
77
|
+
return {
|
|
78
|
+
packed: ops.copyOf(value.packed),
|
|
79
|
+
scales: ops.copyOf(value.scales),
|
|
80
|
+
biases: ops.copyOf(value.biases),
|
|
81
|
+
};
|
|
82
|
+
},
|
|
83
|
+
rollRows(value, indices) {
|
|
84
|
+
const packed = ops.takeAlongAxis(value.packed, indices, 2);
|
|
85
|
+
let scales: MlxArray | undefined;
|
|
86
|
+
try {
|
|
87
|
+
scales = ops.takeAlongAxis(value.scales, indices, 2);
|
|
88
|
+
return { packed, scales, biases: ops.takeAlongAxis(value.biases, indices, 2) };
|
|
89
|
+
} catch (error) { packed.dispose(); scales?.dispose(); throw error; }
|
|
90
|
+
},
|
|
91
|
+
dispose(value) {
|
|
92
|
+
value.packed.dispose();
|
|
93
|
+
value.scales.dispose();
|
|
94
|
+
value.biases.dispose();
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
/** Per-row cyclic shift, shared by plain and encoded storage planes.
|
|
99
|
+
* This is mlx-lm's dynamic_roll indexing; only positions cross the FFI. */
|
|
100
|
+
export function rowRollIndices(length: number, shifts: readonly number[]): MlxArray {
|
|
101
|
+
using columns = ops.arange(0, length, 1, Dtype.int32);
|
|
102
|
+
using positions = ops.reshape(columns, [1, 1, length, 1]);
|
|
103
|
+
using starts = ops.fromInt32(shifts.map(shift => ((-shift % length) + length) % length), [shifts.length, 1, 1, 1]);
|
|
104
|
+
using raw = ops.add(positions, starts);
|
|
105
|
+
using width = ops.fromInt32([length], []);
|
|
106
|
+
using wrapped = ops.sub(raw, width);
|
|
107
|
+
using over = ops.greaterEqual(raw, width);
|
|
108
|
+
return ops.where(over, wrapped, raw);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function mergeStorageRows<T>(
|
|
112
|
+
storage: RowStorage<T>,
|
|
113
|
+
rows: readonly T[],
|
|
114
|
+
leftPad: readonly number[],
|
|
115
|
+
): T {
|
|
116
|
+
const padded = rows.map((row, index) => storage.padLeft(row, leftPad[index]!));
|
|
117
|
+
try {
|
|
118
|
+
return storage.concatRows(padded);
|
|
119
|
+
} finally {
|
|
120
|
+
for (const row of padded) storage.dispose(row);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** De-roll storage into temporal order, then cut one row or the whole batch. */
|
|
125
|
+
export function temporalStorageView<T>(
|
|
126
|
+
storage: RowStorage<T>,
|
|
127
|
+
value: T,
|
|
128
|
+
state: BatchedRotatingState,
|
|
129
|
+
options: { row?: number; from?: number; to?: number; copy?: boolean } = {},
|
|
130
|
+
): T {
|
|
131
|
+
const [batch, , tokens] = storage.shape(value);
|
|
132
|
+
const batchFrom = options.row ?? 0;
|
|
133
|
+
const batchTo = options.row === undefined ? batch : options.row + 1;
|
|
134
|
+
const parts = state.temporalRanges(tokens).map(([from, to]) =>
|
|
135
|
+
storage.slice(value, batchFrom, batchTo, from, to)
|
|
136
|
+
);
|
|
137
|
+
const ordered = parts.length === 1 ? parts[0]! : storage.concatTokens(parts);
|
|
138
|
+
if (parts.length > 1) for (const part of parts) storage.dispose(part);
|
|
139
|
+
const cut = storage.slice(
|
|
140
|
+
ordered,
|
|
141
|
+
0,
|
|
142
|
+
batchTo - batchFrom,
|
|
143
|
+
options.from ?? 0,
|
|
144
|
+
options.to ?? state.validLength,
|
|
145
|
+
);
|
|
146
|
+
storage.dispose(ordered);
|
|
147
|
+
if (!options.copy) return cut;
|
|
148
|
+
const owned = storage.copy(cut);
|
|
149
|
+
storage.dispose(cut);
|
|
150
|
+
return owned;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** Borrow storage, return an owned chronological block without decoding the
|
|
154
|
+
* representation. Port of mlx-lm's BatchRotatingKVCache._update_concat
|
|
155
|
+
* (MIT): discard spare capacity, retain the full window for the first query. */
|
|
156
|
+
export function appendRotatingStorage<T>(
|
|
157
|
+
storage: RowStorage<T>, previous: T | null, incoming: T, state: BatchedRotatingState,
|
|
158
|
+
): T {
|
|
159
|
+
if (previous === null) return storage.slice(incoming, 0, storage.shape(incoming)[0], 0, storage.shape(incoming)[2]);
|
|
160
|
+
const active = state.activeLength;
|
|
161
|
+
const shifts = state.prefillRoll();
|
|
162
|
+
if (shifts?.some(shift => shift !== 0)) {
|
|
163
|
+
const chronological = temporalStorageView(storage, previous, state, { to: active });
|
|
164
|
+
let rolled: T | undefined, history: T | undefined;
|
|
165
|
+
try {
|
|
166
|
+
using indices = rowRollIndices(active, shifts);
|
|
167
|
+
rolled = storage.rollRows(chronological, indices);
|
|
168
|
+
history = storage.slice(rolled, 0, state.batchSize, Math.max(0, active - state.maxSize + 1), active);
|
|
169
|
+
return storage.concatTokens([history, incoming]);
|
|
170
|
+
} finally {
|
|
171
|
+
storage.dispose(chronological); if (rolled) storage.dispose(rolled); if (history) storage.dispose(history);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
const history = temporalStorageView(storage, previous, state, {
|
|
175
|
+
from: Math.max(0, active - state.maxSize + 1), to: active,
|
|
176
|
+
});
|
|
177
|
+
try { return storage.concatTokens([history, incoming]); }
|
|
178
|
+
finally { storage.dispose(history); }
|
|
179
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { SsmPrefillPadding } from "./ssm-prefill-padding";
|
|
2
|
+
import type { PaddedPrefillCache, PrefillPadding } from "../contracts/mlx/cache";
|
|
3
|
+
import * as ops from "@mlx-bun/mlx/ops";
|
|
4
|
+
import type { MlxArray } from "@mlx-bun/mlx/array";
|
|
5
|
+
import type { BatchableCache, Cache } from "../contracts/mlx/cache";
|
|
6
|
+
import { SSMCache } from "./ssm";
|
|
7
|
+
|
|
8
|
+
/** Recurrent storage behind the same row-layout port as attention KV.
|
|
9
|
+
* Inputs to merge are borrowed; request membership is owned by the caller. */
|
|
10
|
+
export class BatchedSSMCache extends SSMCache implements BatchableCache, PaddedPrefillCache {
|
|
11
|
+
constructor() { super(); this.offsets = []; }
|
|
12
|
+
preparePrefill(padding: PrefillPadding): void {
|
|
13
|
+
if (!this.offsets!.length) this.offsets = padding.lengths.map(() => 0);
|
|
14
|
+
const padded = padding.leftPadding?.some(n => n > 0) || padding.rightPadding?.some(n => n > 0);
|
|
15
|
+
this.prefillPadding = padded ? new SsmPrefillPadding(padding) : null;
|
|
16
|
+
}
|
|
17
|
+
finalizePrefill(): void { this.prefillPadding = null; }
|
|
18
|
+
get rowOffsets(): readonly number[] { return this.offsets!; }
|
|
19
|
+
get leftPad(): readonly number[] { return this.rowOffsets.map(() => 0); }
|
|
20
|
+
makeEmptyBatch(): BatchedSSMCache { return new BatchedSSMCache(); }
|
|
21
|
+
/** Recurrent state cannot be trimmed: only a row whose own advance count is the key. */
|
|
22
|
+
canPublishRow(row: number, tokens: number): boolean { return !!this.conv && this.rowOffset(row) === tokens; }
|
|
23
|
+
projectedBytes(_tokens: number): number {
|
|
24
|
+
return this.batchSize ? this.state().reduce((bytes, array) => bytes + array.nbytes, 0) / this.batchSize : 0;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
mergeRows(rows: readonly Cache[]): void {
|
|
28
|
+
if (!rows.length) { this.dispose(); return; }
|
|
29
|
+
const sources = rows as readonly SSMCache[];
|
|
30
|
+
const offsets = sources.flatMap(source => source.offsets ?? [source.offset]);
|
|
31
|
+
const merge = (field: "conv" | "recurrent"): MlxArray | null => {
|
|
32
|
+
const prototype = sources.find(source => source[field])?.[field];
|
|
33
|
+
if (!prototype) return null;
|
|
34
|
+
if (sources.length === 1) return ops.copyOf(prototype);
|
|
35
|
+
const empty: MlxArray[] = [];
|
|
36
|
+
try {
|
|
37
|
+
return ops.concatAxis(sources.map(source => {
|
|
38
|
+
if (source[field]) return source[field]!;
|
|
39
|
+
const zeros = ops.zeros([source.offsets?.length ?? 1, ...prototype.shape.slice(1)], prototype.dtype);
|
|
40
|
+
empty.push(zeros); return zeros;
|
|
41
|
+
}), 0);
|
|
42
|
+
} finally { for (const array of empty) array.dispose(); }
|
|
43
|
+
};
|
|
44
|
+
const conv = merge("conv");
|
|
45
|
+
let recurrent;
|
|
46
|
+
try { recurrent = merge("recurrent"); }
|
|
47
|
+
catch (error) { conv?.dispose(); throw error; }
|
|
48
|
+
this.dispose();
|
|
49
|
+
this.conv = conv; this.recurrent = recurrent;
|
|
50
|
+
this.offsets = offsets; this.offset = Math.max(0, ...offsets);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
override dispose(): void { super.dispose(); this.offset = 0; this.offsets = []; }
|
|
54
|
+
}
|