@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,36 @@
|
|
|
1
|
+
import type { CacheCodec } from "../persistence-types";
|
|
2
|
+
import type { KVCache } from "../kv";
|
|
3
|
+
import { turboQuantFusedDecode } from "../turboquant-codec";
|
|
4
|
+
import { BlockPool, PagedKVCache } from "./cache";
|
|
5
|
+
|
|
6
|
+
/** Page layout owns its persistence codec, independently of SSD transport.
|
|
7
|
+
* Affine pages record their bit width and group size, TurboQuant pages their
|
|
8
|
+
* key and value bit widths; a restored TurboQuant page reads with the read the
|
|
9
|
+
* restoring graph composed into its cache (`LoadContext.prototype`). */
|
|
10
|
+
export const pagedCacheCodec: CacheCodec = {
|
|
11
|
+
matches: cache => cache instanceof PagedKVCache,
|
|
12
|
+
snapshot(cache, context) {
|
|
13
|
+
const c = cache as PagedKVCache, p = c.pool!;
|
|
14
|
+
for (const array of p.arrays()) context.push(array, false);
|
|
15
|
+
return { kind: "paged", offset: c.offset, tensors: context.slots,
|
|
16
|
+
...(p.quantization ? p.quantization : {}),
|
|
17
|
+
...(p.turbo ? { kBits: p.turbo.kBits, vBits: p.turbo.vBits } : {}),
|
|
18
|
+
paged: { capacityTokens: c.capacityTokens, blockSize: c.blockSize, blockTable: [...c.blockTable],
|
|
19
|
+
numBlocks: p.numBlocks, headDim: p.headDim, vHeadDim: p.vHeadDim, dtype: p.dtype, direct: c.direct } };
|
|
20
|
+
},
|
|
21
|
+
clone: cache => (cache as PagedKVCache).clone(),
|
|
22
|
+
load(entry, context) {
|
|
23
|
+
const layout = entry.paged!;
|
|
24
|
+
const quantization = entry.bits ? { bits: entry.bits, groupSize: entry.groupSize! } : undefined;
|
|
25
|
+
const turbo = entry.kBits ? { kBits: entry.kBits, vBits: entry.vBits!, fusedDecode: turboQuantFusedDecode(),
|
|
26
|
+
kernels: (context.prototype as PagedKVCache).turbo?.kernels ?? (context.prototype as KVCache).turboQuantKernels } : undefined;
|
|
27
|
+
const cache = new PagedKVCache(layout.capacityTokens, layout.blockSize, layout.direct, quantization, undefined, turbo);
|
|
28
|
+
const pool = new BlockPool({ ...layout, numKvHeads: entry.tensors[0]!.shape[1]!, quantization,
|
|
29
|
+
...(turbo ? { turbo: { kBits: turbo.kBits, vBits: turbo.vBits, fusedDecode: turbo.fusedDecode } } : {}) });
|
|
30
|
+
try { pool.restoreArrays(entry.tensors.map(context.arr), layout.blockTable); }
|
|
31
|
+
catch (error) { pool.dispose(); throw error; }
|
|
32
|
+
cache.pool = pool; cache.offset = entry.offset; cache.blockTable = [...layout.blockTable];
|
|
33
|
+
return cache;
|
|
34
|
+
},
|
|
35
|
+
headerTrimmable: () => true,
|
|
36
|
+
};
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import type { AttentionRead, Cache, CommittedAttentionCache, KvAttentionView, KvDonorRows, Mask, SpeculativeRowLayout } from "../../contracts/mlx/cache";
|
|
2
|
+
import { buildBatchedDecodeMask, mergeKVRows } from "../batched-mask";
|
|
3
|
+
import * as ops from "@mlx-bun/mlx/ops";
|
|
4
|
+
import { MlxArray } from "@mlx-bun/mlx/array";
|
|
5
|
+
import { disposeResources } from "../../runtime/resources";
|
|
6
|
+
import { PagedKVCache, poolBlocksFor, type PagedQuantization, type PagedTurboQuant } from "./cache";
|
|
7
|
+
|
|
8
|
+
/** Block storage owns row membership; attention consumes the same gathered
|
|
9
|
+
* K/V representation as the existing paged cache. No scheduler policy here.
|
|
10
|
+
*
|
|
11
|
+
* Reads: fixed at construction by the pages' representation, as the graphs
|
|
12
|
+
* read these rows before the cache owned the read. Gathered bf16 pages (not
|
|
13
|
+
* `direct`, no `quantization`): each row appends to its own blocks, the rows'
|
|
14
|
+
* gathered keys and values join left-padded along the batch, and one fused
|
|
15
|
+
* SDPA attends under the padding mask of the append (`buildBatchedDecodeMask`),
|
|
16
|
+
* for decode and windows alike. Reading each row alone over its own length
|
|
17
|
+
* reduces in another order than this read over the padded length and differs
|
|
18
|
+
* in the last bits (up to 4e-3 on decode at head dim 512). Direct, affine or
|
|
19
|
+
* TurboQuant pages: each row reads with its own `PagedKVCache` read for the
|
|
20
|
+
* phase and the outputs join along the batch. A committed span is the window read, as on
|
|
21
|
+
* the serial pages.
|
|
22
|
+
*
|
|
23
|
+
* The same layout serves continuous decoding, prefill cohorts and
|
|
24
|
+
* verification: every row owns its blocks and its offset, so the speculative
|
|
25
|
+
* transaction keeps each row's accepted prefix by trimming that row's blocks
|
|
26
|
+
* back to it: the rejected tail's blocks return to the row's pool, and the next
|
|
27
|
+
* appends take the same blocks again. The donor rows are each row's gathered positions,
|
|
28
|
+
* joined left-padded as the joined read joins them. */
|
|
29
|
+
export class PagedKvRows implements CommittedAttentionCache, SpeculativeRowLayout {
|
|
30
|
+
#rows: PagedKVCache[] = [];
|
|
31
|
+
#rope?: MlxArray;
|
|
32
|
+
#ropeOffset = -1;
|
|
33
|
+
#beforeRound?: number[];
|
|
34
|
+
readonly #decode: (keys: MlxArray, values: MlxArray) => AttentionRead;
|
|
35
|
+
readonly #window: (keys: MlxArray, values: MlxArray) => AttentionRead;
|
|
36
|
+
|
|
37
|
+
constructor(readonly capacityTokens: number, readonly blockSize: number, readonly direct = false,
|
|
38
|
+
readonly quantization?: PagedQuantization, readonly turbo?: PagedTurboQuant) {
|
|
39
|
+
const alone = direct || quantization !== undefined || turbo !== undefined;
|
|
40
|
+
this.#decode = alone ? (keys, values) => this.#rowReads(keys, values, (row, k, v) => row.appendDecode(k, v))
|
|
41
|
+
: (keys, values) => this.#joinedRead(keys, values);
|
|
42
|
+
this.#window = alone ? (keys, values) => this.#rowReads(keys, values, (row, k, v) => row.appendWindow(k, v))
|
|
43
|
+
: (keys, values) => this.#joinedRead(keys, values);
|
|
44
|
+
}
|
|
45
|
+
signature(): string { return "kv:paged-rows"; }
|
|
46
|
+
get batchSize(): number { return this.#rows.length; }
|
|
47
|
+
get offset(): number { return Math.max(0, ...this.#rows.map(row => row.offset)); }
|
|
48
|
+
get rowOffsets(): number[] { return this.#rows.map(row => row.offset); }
|
|
49
|
+
get leftPad(): number[] { const width = this.offset; return this.#rows.map(row => width - row.offset); }
|
|
50
|
+
get ropeOffsetArr(): MlxArray | undefined {
|
|
51
|
+
if (this.#rows.length < 2) return undefined;
|
|
52
|
+
if (!this.#rope || this.#ropeOffset !== this.offset) {
|
|
53
|
+
this.releaseRopeArr();
|
|
54
|
+
this.#rope = MlxArray.fromInt32(Int32Array.from(this.rowOffsets), [this.batchSize]);
|
|
55
|
+
this.#ropeOffset = this.offset;
|
|
56
|
+
}
|
|
57
|
+
return this.#rope;
|
|
58
|
+
}
|
|
59
|
+
releaseRopeArr(): void { this.#rope?.dispose(); this.#rope = undefined; this.#ropeOffset = -1; }
|
|
60
|
+
makeEmptyBatch(): PagedKvRows { return new PagedKvRows(this.capacityTokens, this.blockSize, this.direct, this.quantization, this.turbo); }
|
|
61
|
+
|
|
62
|
+
mergeRows(rows: readonly Cache[]): void {
|
|
63
|
+
const next: PagedKVCache[] = [];
|
|
64
|
+
try {
|
|
65
|
+
for (const source of rows) {
|
|
66
|
+
if (source instanceof PagedKvRows) {
|
|
67
|
+
for (const row of source.#rows) next.push(row.clone());
|
|
68
|
+
} else if (source instanceof PagedKVCache) next.push(source.clone());
|
|
69
|
+
else throw new Error(`Paged rows cannot merge ${source.signature()}`);
|
|
70
|
+
}
|
|
71
|
+
} catch (error) { disposeResources(next); throw error; }
|
|
72
|
+
const previous = this.#rows; this.#rows = next;
|
|
73
|
+
this.releaseRopeArr(); disposeResources(previous);
|
|
74
|
+
}
|
|
75
|
+
extractRow(row: number): PagedKVCache { return this.#rows[row]!.clone(); }
|
|
76
|
+
filterRows(keep: readonly number[]): void {
|
|
77
|
+
const previous = this.#rows;
|
|
78
|
+
this.#rows = keep.map(row => previous[row]!);
|
|
79
|
+
const retained = new Set(keep);
|
|
80
|
+
this.releaseRopeArr();
|
|
81
|
+
disposeResources(previous.filter((_, row) => !retained.has(row)));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
appendDecode(keys: MlxArray, values: MlxArray): AttentionRead {
|
|
85
|
+
return this.#decode(keys, values);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
appendWindow(keys: MlxArray, values: MlxArray): AttentionRead {
|
|
89
|
+
return this.#window(keys, values);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** The window read, as on the serial pages. */
|
|
93
|
+
appendCommitted(keys: MlxArray, values: MlxArray): AttentionRead {
|
|
94
|
+
return this.#window(keys, values);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
specRoundBegin(): void { this.#beforeRound = this.rowOffsets; }
|
|
98
|
+
specRoundCommit(): void { this.#beforeRound = undefined; }
|
|
99
|
+
specRoundRollback(keep: number | readonly number[]): void {
|
|
100
|
+
const before = this.#beforeRound!;
|
|
101
|
+
for (const [row, storage] of this.#rows.entries())
|
|
102
|
+
storage.trim(storage.offset - before[row]! - (typeof keep === "number" ? keep : keep[row]!));
|
|
103
|
+
this.#beforeRound = undefined;
|
|
104
|
+
this.releaseRopeArr();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** Each row's gathered positions, joined left-padded to the longest row. */
|
|
108
|
+
captureDonorRows(): KvDonorRows {
|
|
109
|
+
const gathered: MlxArray[] = [];
|
|
110
|
+
try {
|
|
111
|
+
const rows = this.#rows.map(row => {
|
|
112
|
+
const { keys, values } = row.captureDonorRows();
|
|
113
|
+
gathered.push(keys, values);
|
|
114
|
+
return { keys, values };
|
|
115
|
+
});
|
|
116
|
+
const { keys, values, leftPad, width } = mergeKVRows(rows);
|
|
117
|
+
return { keys, values, offsets: this.rowOffsets, starts: leftPad, ends: leftPad.map(() => width) };
|
|
118
|
+
} finally { disposeResources(gathered); }
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** The graphs' read of gathered bf16 rows before the cache owned it: the
|
|
122
|
+
* padding mask of an `N`-position append built before it, each row's
|
|
123
|
+
* gathered keys and values joined left-padded along the batch, one fused
|
|
124
|
+
* SDPA. */
|
|
125
|
+
#joinedRead(keys: MlxArray, values: MlxArray): AttentionRead {
|
|
126
|
+
const N = keys.shape[2]!;
|
|
127
|
+
const mask = buildBatchedDecodeMask(this.batchSize, N, this.offset + N, this.leftPad, null);
|
|
128
|
+
let joined: [MlxArray, MlxArray];
|
|
129
|
+
try { joined = this.#joinRows(keys, values); } catch (error) { mask.dispose(); throw error; }
|
|
130
|
+
const [k, v] = joined;
|
|
131
|
+
return {
|
|
132
|
+
attend: (q, scale) => ops.sdpa(q, k, v, scale, "array", mask),
|
|
133
|
+
dispose() { k.dispose(); v.dispose(); mask.dispose(); },
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** Append each row's slice to its blocks, then join the rows' gathered keys
|
|
138
|
+
* and values left-padded to the longest row; the caller owns both. */
|
|
139
|
+
#joinRows(keys: MlxArray, values: MlxArray): [MlxArray, MlxArray] {
|
|
140
|
+
const fetched: Array<{ keys: MlxArray; values: MlxArray }> = [];
|
|
141
|
+
try {
|
|
142
|
+
for (const [row, storage] of this.#rows.entries()) {
|
|
143
|
+
using k = keys.slice([row, 0, 0, 0], [row + 1, ...keys.shape.slice(1)]);
|
|
144
|
+
using v = values.slice([row, 0, 0, 0], [row + 1, ...values.shape.slice(1)]);
|
|
145
|
+
const [nextK, nextV] = storage.appendAndGather(k, v);
|
|
146
|
+
fetched.push({ keys: nextK, values: nextV });
|
|
147
|
+
}
|
|
148
|
+
const result = mergeKVRows(fetched);
|
|
149
|
+
return [result.keys, result.values];
|
|
150
|
+
} finally { disposeResources(fetched.flatMap(row => [row.keys, row.values])); }
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
#rowReads(keys: MlxArray, values: MlxArray,
|
|
154
|
+
read: (row: PagedKVCache, k: MlxArray, v: MlxArray) => AttentionRead): AttentionRead {
|
|
155
|
+
const reads: AttentionRead[] = [];
|
|
156
|
+
try {
|
|
157
|
+
for (const [row, storage] of this.#rows.entries()) {
|
|
158
|
+
using k = keys.slice([row, 0, 0, 0], [row + 1, ...keys.shape.slice(1)]);
|
|
159
|
+
using v = values.slice([row, 0, 0, 0], [row + 1, ...values.shape.slice(1)]);
|
|
160
|
+
reads.push(read(storage, k, v));
|
|
161
|
+
}
|
|
162
|
+
} catch (error) { disposeResources(reads); throw error; }
|
|
163
|
+
return {
|
|
164
|
+
attend(q, scale) {
|
|
165
|
+
const outputs: MlxArray[] = [];
|
|
166
|
+
try {
|
|
167
|
+
for (const [row, rowRead] of reads.entries()) {
|
|
168
|
+
using query = q.slice([row, 0, 0, 0], [row + 1, ...q.shape.slice(1)]);
|
|
169
|
+
outputs.push(rowRead.attend(query, scale));
|
|
170
|
+
}
|
|
171
|
+
return ops.concatAxis(outputs, 0);
|
|
172
|
+
} finally { disposeResources(outputs); }
|
|
173
|
+
},
|
|
174
|
+
dispose() { disposeResources(reads); },
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
get attentionState(): this | undefined { return this.direct || this.quantization ? this : undefined; }
|
|
179
|
+
appendAndFetch(keys: MlxArray, values: MlxArray): KvAttentionView {
|
|
180
|
+
const views: KvAttentionView[] = [];
|
|
181
|
+
try {
|
|
182
|
+
for (const [row, storage] of this.#rows.entries()) {
|
|
183
|
+
using k = keys.slice([row, 0, 0, 0], [row + 1, ...keys.shape.slice(1)]);
|
|
184
|
+
using v = values.slice([row, 0, 0, 0], [row + 1, ...values.shape.slice(1)]);
|
|
185
|
+
views.push(storage.appendAndFetch(k, v));
|
|
186
|
+
}
|
|
187
|
+
} catch (error) { disposeResources(views); throw error; }
|
|
188
|
+
return {
|
|
189
|
+
attend(q, scale, mask) {
|
|
190
|
+
const outputs: MlxArray[] = [];
|
|
191
|
+
try {
|
|
192
|
+
for (const [row, view] of views.entries()) {
|
|
193
|
+
using query = q.slice([row, 0, 0, 0], [row + 1, ...q.shape.slice(1)]);
|
|
194
|
+
outputs.push(view.attend(query, scale, mask));
|
|
195
|
+
}
|
|
196
|
+
return ops.concatAxis(outputs, 0);
|
|
197
|
+
} finally { disposeResources(outputs); }
|
|
198
|
+
},
|
|
199
|
+
dispose() { disposeResources(views); },
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
updateAndFetch(keys: MlxArray, values: MlxArray): [MlxArray, MlxArray] {
|
|
204
|
+
if (this.#rows.length === 1) return this.#rows[0]!.updateAndFetch(keys, values);
|
|
205
|
+
return this.#joinRows(keys, values);
|
|
206
|
+
}
|
|
207
|
+
makeMask(tokens: number, window: number | null): Mask {
|
|
208
|
+
if (this.attentionState && window === null) return { mode: tokens === 1 ? "" : "causal", arr: null };
|
|
209
|
+
if (this.#rows.length === 1) return this.#rows[0]!.makeMask(tokens, window);
|
|
210
|
+
return { mode: "array", arr: buildBatchedDecodeMask(this.batchSize, tokens,
|
|
211
|
+
this.offset + tokens, this.leftPad, window) };
|
|
212
|
+
}
|
|
213
|
+
state(): MlxArray[] { return this.#rows.flatMap(row => row.state()); }
|
|
214
|
+
isTrimmable(): boolean { return true; }
|
|
215
|
+
trim(tokens: number): void { for (const row of this.#rows) row.trim(tokens); this.releaseRopeArr(); }
|
|
216
|
+
projectedBytes(tokens: number): number {
|
|
217
|
+
const pool = this.#rows.find(row => row.pool)?.pool;
|
|
218
|
+
if (!pool) return 0;
|
|
219
|
+
const bytesPerBlock = pool.arrays().reduce((bytes, array) => bytes + array.nbytes, 0) / pool.numBlocks;
|
|
220
|
+
return poolBlocksFor(tokens, this.blockSize) * bytesPerBlock;
|
|
221
|
+
}
|
|
222
|
+
dispose(): void { const rows = this.#rows; this.#rows = []; this.#beforeRound = undefined; this.releaseRopeArr(); disposeResources(rows); }
|
|
223
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { MlxArray } from "@mlx-bun/mlx/array";
|
|
2
|
+
import type { Dtype } from "@mlx-bun/mlx/ffi";
|
|
3
|
+
import * as ops from "@mlx-bun/mlx/ops";
|
|
4
|
+
import { type Cache } from "../contracts/mlx/cache";
|
|
5
|
+
import { type CheckpointAttachment } from "../contracts/mlx/checkpoint";
|
|
6
|
+
|
|
7
|
+
export type CacheKind =
|
|
8
|
+
| "kv"
|
|
9
|
+
| "paged"
|
|
10
|
+
| "rotating"
|
|
11
|
+
| "qkv"
|
|
12
|
+
| "rotating-qkv"
|
|
13
|
+
| "ssm"
|
|
14
|
+
| "turboquant"
|
|
15
|
+
| "mla"
|
|
16
|
+
| "mla-dsa"
|
|
17
|
+
| "mtp-mla";
|
|
18
|
+
|
|
19
|
+
/** Header kinds of the latent attention legos (`state/latent-legos.ts`), which
|
|
20
|
+
* their own codecs (`LATENT_LEGO_CODECS`) save and restore. */
|
|
21
|
+
export type LatentLegoKind = "latent-encoded" | "latent-paged";
|
|
22
|
+
|
|
23
|
+
export interface TensorSlot {
|
|
24
|
+
blocks?: Array<{ hash: string; bytes: number }>;
|
|
25
|
+
off: number;
|
|
26
|
+
bytes: number;
|
|
27
|
+
shape: number[];
|
|
28
|
+
dtype: number;
|
|
29
|
+
hash: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface CacheHeaderEntry {
|
|
33
|
+
paged?: { capacityTokens: number; blockSize: number; blockTable: number[]; numBlocks: number;
|
|
34
|
+
headDim: number; vHeadDim: number; dtype: Dtype; direct: boolean };
|
|
35
|
+
kind: CacheKind | LatentLegoKind;
|
|
36
|
+
offset: number;
|
|
37
|
+
minimumReusableOffset?: number;
|
|
38
|
+
/** rotating variants: ring write index */
|
|
39
|
+
idx?: number;
|
|
40
|
+
/** rotating variants: window size */
|
|
41
|
+
maxSize?: number;
|
|
42
|
+
/** quantized variants (qkv/rotating-qkv: mlx affine scheme) */
|
|
43
|
+
groupSize?: number;
|
|
44
|
+
bits?: number;
|
|
45
|
+
/** turboquant: per-side bit widths (reuses the `bits` field's slot class
|
|
46
|
+
* but needs both — asymmetric key/value bit widths, unlike qkv's single
|
|
47
|
+
* `bits`). `groupSize` is unused for turboquant (fixed at 32, the
|
|
48
|
+
* BLOCK_SIZE constant — not a configurable field like qkv's). */
|
|
49
|
+
kBits?: number;
|
|
50
|
+
vBits?: number;
|
|
51
|
+
/** turboquant: head_dim, needed to unpack kIdx/vPacked on restore
|
|
52
|
+
* (packed byte width alone doesn't recover the original element count). */
|
|
53
|
+
headDim?: number;
|
|
54
|
+
/** GLM checkpoint-native compressed-cache geometry. */
|
|
55
|
+
kvLoraRank?: number;
|
|
56
|
+
ropeHeadDim?: number;
|
|
57
|
+
dsaHeadDim?: number;
|
|
58
|
+
maxTokens?: number;
|
|
59
|
+
/** latent-encoded: the conversion start, and whether the saved row still
|
|
60
|
+
* reads f32 (its f32 values follow its encoded planes). */
|
|
61
|
+
quantizedKvStart?: number;
|
|
62
|
+
latentShadow?: boolean;
|
|
63
|
+
/** latent-paged: the row's page layout. */
|
|
64
|
+
latentPages?: { blockSize: number; blockTable: number[]; numBlocks: number };
|
|
65
|
+
/** kv/rotating: [k, v] · qkv/rotating-qkv: [kPacked, kScales, kBiases,
|
|
66
|
+
* vPacked, vScales, vBiases] · ssm: [conv, recurrent] · turboquant:
|
|
67
|
+
* [kIdx, kScales, kZeros, vPacked, vScales] */
|
|
68
|
+
tensors: TensorSlot[];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface KvSaveMeta {
|
|
72
|
+
attachments?: CheckpointAttachment[];
|
|
73
|
+
modelId?: string;
|
|
74
|
+
/** Caller-supplied store identity, including the model and cache scheme. */
|
|
75
|
+
configFingerprint?: string;
|
|
76
|
+
/** Adapter namespace (PromptCache ns — adapters joined with "+"). */
|
|
77
|
+
ns?: string;
|
|
78
|
+
/** sha256 of tokenizer.json: identical ids must mean identical text. */
|
|
79
|
+
tokenizerHash?: string;
|
|
80
|
+
/** In-flight generation state. The serialized caches cover `tokens`;
|
|
81
|
+
* pendingToken was sampled from that state but has not been emitted. */
|
|
82
|
+
generationCheckpoint?: {
|
|
83
|
+
key: string;
|
|
84
|
+
cacheNs: string;
|
|
85
|
+
originalPromptTokens: number;
|
|
86
|
+
generatedTokens: number;
|
|
87
|
+
pendingToken: number;
|
|
88
|
+
/** Actual sampler seed, including a server-generated default. Required to
|
|
89
|
+
* continue the random stream exactly after a restart. */
|
|
90
|
+
seed: number;
|
|
91
|
+
/** True when `seed` was part of the client request rather than generated
|
|
92
|
+
* by the server. */
|
|
93
|
+
seedWasExplicit: boolean;
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface KvFileHeader extends Omit<KvSaveMeta, "attachments"> {
|
|
98
|
+
attachments?: Array<Omit<CheckpointAttachment, "tensors"> & { tensors: TensorSlot[] }>;
|
|
99
|
+
/** Absent in earlier v3 files, which used mlx-cache-v3. */
|
|
100
|
+
codecProvider?: string;
|
|
101
|
+
formatVersion: 3 | 4 | 5;
|
|
102
|
+
createdAt: number;
|
|
103
|
+
tokens: number[];
|
|
104
|
+
caches: CacheHeaderEntry[];
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export interface SnapshotContext {
|
|
108
|
+
slots: TensorSlot[];
|
|
109
|
+
push(a: MlxArray, disposeAfter: boolean): void;
|
|
110
|
+
liveSlice(a: MlxArray, upTo: number): MlxArray;
|
|
111
|
+
liveMlaSlice(a: MlxArray, upTo: number): MlxArray;
|
|
112
|
+
pushTriple(t: ops.QuantizedTensor, upTo: number | null): void;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export interface CloneContext {
|
|
116
|
+
view(a: MlxArray): MlxArray;
|
|
117
|
+
liveView(a: MlxArray, upTo: number): MlxArray;
|
|
118
|
+
mlaView(a: MlxArray, upTo: number): MlxArray;
|
|
119
|
+
tripleView(t: ops.QuantizedTensor, upTo: number | null): ops.QuantizedTensor;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export interface LoadContext {
|
|
123
|
+
path: string;
|
|
124
|
+
/** The restoring graph's own empty cache for this entry's layer (from its
|
|
125
|
+
* `makeCache()`), borrowed for the load: what the graph composed into its
|
|
126
|
+
* caches (the TurboQuant read a `KVCache` carries) reaches the restored one. */
|
|
127
|
+
prototype: Cache;
|
|
128
|
+
arr(slot: TensorSlot): MlxArray;
|
|
129
|
+
grownArr(slot: TensorSlot, offset: number): MlxArray;
|
|
130
|
+
triple(slots: TensorSlot[], at: number): ops.QuantizedTensor;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export interface CacheCodec {
|
|
134
|
+
matches(cache: Cache): boolean;
|
|
135
|
+
snapshot(cache: Cache, context: SnapshotContext): CacheHeaderEntry;
|
|
136
|
+
clone(cache: Cache, context: CloneContext): Cache;
|
|
137
|
+
load(entry: CacheHeaderEntry, context: LoadContext): Cache;
|
|
138
|
+
headerTrimmable(entry: CacheHeaderEntry): boolean;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** Codecs belong to the loaded backend binding. The persisted ID is checked
|
|
142
|
+
* before allocating state; codecs are engine code, never loaded from a file. */
|
|
143
|
+
export interface CacheCodecProvider {
|
|
144
|
+
readonly id: string;
|
|
145
|
+
forCache(cache: Cache): CacheCodec;
|
|
146
|
+
forHeader(entry: CacheHeaderEntry): CacheCodec;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** Queued immutable snapshot; the queue releases its shared views on settle. */
|
|
150
|
+
export interface SpillItem {
|
|
151
|
+
attachments?: CheckpointAttachment[];
|
|
152
|
+
tokens: number[];
|
|
153
|
+
caches: Cache[];
|
|
154
|
+
ns: string;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export interface LoadedKvCache {
|
|
158
|
+
attachments?: CheckpointAttachment[];
|
|
159
|
+
tokens: number[];
|
|
160
|
+
header: KvFileHeader;
|
|
161
|
+
caches: Cache[];
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export interface KvLoadExpect {
|
|
165
|
+
/** Reject on metadata mismatch (pass what the server is running). */
|
|
166
|
+
modelId?: string;
|
|
167
|
+
configFingerprint?: string;
|
|
168
|
+
tokenizerHash?: string;
|
|
169
|
+
ns?: string;
|
|
170
|
+
/** Verify every tensor hash before copying it in (off by default,
|
|
171
|
+
* `--ssd-cache-verify` — the hash pass roughly doubles restore reads). */
|
|
172
|
+
verify?: boolean;
|
|
173
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { Worker } from "node:worker_threads";
|
|
2
|
+
import workerSource from "./persistence.worker.js" with { type: "text" };
|
|
3
|
+
import type { KvFileHeader } from "./persistence-types";
|
|
4
|
+
|
|
5
|
+
/** Immutable, evaluated host-visible storage. The owner retains the native
|
|
6
|
+
* buffers until write() settles. No MLX handles cross the worker boundary. */
|
|
7
|
+
export interface StoredTensorView {
|
|
8
|
+
pointer: number;
|
|
9
|
+
shape: number[];
|
|
10
|
+
strides: number[];
|
|
11
|
+
itemSize: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface KvWriteRequest {
|
|
15
|
+
layout?: "whole" | "blocks";
|
|
16
|
+
blockBytes?: number;
|
|
17
|
+
segmented?: boolean;
|
|
18
|
+
path: string;
|
|
19
|
+
header: KvFileHeader;
|
|
20
|
+
tensors: StoredTensorView[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Hashing, layout packing and atomic disk writes run on a dedicated CPU
|
|
24
|
+
* worker. Only metadata and pointers are posted; tensor bytes are not copied
|
|
25
|
+
* through structured clone. The RAM cache remains an independent owner. */
|
|
26
|
+
export class KvWriter {
|
|
27
|
+
#worker: Worker | null = null;
|
|
28
|
+
#next = 0;
|
|
29
|
+
#pending = new Map<number, { resolve(): void; reject(error: Error): void }>();
|
|
30
|
+
|
|
31
|
+
lastMetrics: { elapsedMs: number; writtenBytes?: number; reusedBytes?: number; copiedBytes?: number; scratchPeak?: number } | null = null;
|
|
32
|
+
|
|
33
|
+
write(request: KvWriteRequest): Promise<void> { return this.#send(request); }
|
|
34
|
+
|
|
35
|
+
read(request: { path: string; header: KvFileHeader & { dataStart: number }; pointers: number[]; verify?: boolean }): Promise<void> {
|
|
36
|
+
return this.#send({ ...request, operation: "read" });
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
collect(root: string): Promise<void> { return this.#send({ root, operation: "collect" }); }
|
|
40
|
+
|
|
41
|
+
#send(request: object): Promise<void> {
|
|
42
|
+
const worker = this.#worker ?? this.#start();
|
|
43
|
+
worker.ref();
|
|
44
|
+
const id = ++this.#next;
|
|
45
|
+
return new Promise<void>((resolve, reject) => {
|
|
46
|
+
this.#pending.set(id, { resolve, reject });
|
|
47
|
+
try { worker.postMessage({ id, ...request }); }
|
|
48
|
+
catch (error) {
|
|
49
|
+
this.#pending.delete(id);
|
|
50
|
+
if (!this.#pending.size) worker.unref();
|
|
51
|
+
reject(error);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
#start(): Worker {
|
|
57
|
+
// Embed the worker in the single-file executable as well as source runs.
|
|
58
|
+
const worker = new Worker(workerSource, { eval: true });
|
|
59
|
+
worker.on("message", (message: { id: number; error?: string; metrics?: KvWriter["lastMetrics"] }) => {
|
|
60
|
+
const result = this.#pending.get(message.id);
|
|
61
|
+
if (!result) return;
|
|
62
|
+
this.#pending.delete(message.id);
|
|
63
|
+
if (message.error) result.reject(new Error(message.error));
|
|
64
|
+
else { this.lastMetrics = message.metrics ?? null; result.resolve(); }
|
|
65
|
+
if (!this.#pending.size) worker.unref();
|
|
66
|
+
});
|
|
67
|
+
// Job failures are acknowledged by the worker. A worker-level failure
|
|
68
|
+
// settles only at exit, after it can no longer read borrowed buffers.
|
|
69
|
+
worker.on("error", () => {});
|
|
70
|
+
worker.on("exit", () => {
|
|
71
|
+
for (const job of this.#pending.values()) job.reject(new Error("KV persistence worker exited"));
|
|
72
|
+
this.#pending.clear();
|
|
73
|
+
this.#worker = null;
|
|
74
|
+
});
|
|
75
|
+
this.#worker = worker;
|
|
76
|
+
return worker;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export const kvWriter = new KvWriter();
|
|
81
|
+
|
|
82
|
+
/** Reads have their own queue: a restore never waits behind persistence. */
|
|
83
|
+
export const kvReader = new KvWriter();
|