@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,797 @@
|
|
|
1
|
+
import {captureSpeculativeOptions, speculativePrefixNamespace} from "../generation/speculative/cache-identity";
|
|
2
|
+
import type { MlxTokenGraph } from "../models/graph";
|
|
3
|
+
import type { GenerateOptions } from "../generation/index";
|
|
4
|
+
import type { FillSession, Proposal } from "../generation/fill/session";
|
|
5
|
+
import type { DraftProvider, DraftRowGroup, DraftPrefillGroup, DraftSelection } from "../generation/speculative/source";
|
|
6
|
+
import type { DraftRowCheckpoint } from "../contracts/mlx/draft-checkpoint";
|
|
7
|
+
import { makeSampler, makeStepSampler, readStepExtras, type NumberStepSampler, type Sampler } from "../sampling/index";
|
|
8
|
+
import { draftSamplerOptions } from "../generation/speculative/draft-policy";
|
|
9
|
+
import { COMPOSITION_DEFAULTS } from "../contracts/portable/composition";
|
|
10
|
+
import { makeSubsetDraftSampler, type DraftVocabularyHead, type SubsetDraftSampler } from "../generation/speculative/draft-vocab";
|
|
11
|
+
import { MlxArray } from "@mlx-bun/mlx/array";
|
|
12
|
+
import * as ops from "@mlx-bun/mlx/ops";
|
|
13
|
+
import { clearCache } from "@mlx-bun/mlx/ffi";
|
|
14
|
+
import { applyStateChanges, cleanupFailure, disposeResources } from "../runtime/resources";
|
|
15
|
+
import { cloneKvCaches } from "../state/persistence";
|
|
16
|
+
import type { Cache } from "../contracts/mlx/cache";
|
|
17
|
+
import { targetCacheLayout } from "../state/layout";
|
|
18
|
+
import { MlxStateRows } from "../state/rows";
|
|
19
|
+
import { leaseCacheStates } from "../state/leases";
|
|
20
|
+
import { bindSpeculativeTargetModel, type MlxSpeculativeTargetBinding } from "../generation/speculative/bindings/binding";
|
|
21
|
+
import { bindLegacyDraftTarget } from "../generation/speculative/bindings/draft-target";
|
|
22
|
+
import { bindRowCacheRollback, bindRowStepRollback } from "../state/rollback";
|
|
23
|
+
import { disposeAttachments } from "../state/checkpoint";
|
|
24
|
+
import { type CheckpointAttachment } from "../contracts/mlx/checkpoint";
|
|
25
|
+
import { advanceSpeculativeOutputs } from "../generation/speculative/round";
|
|
26
|
+
import { AdaptiveDraftGate } from "../generation/speculative/adaptive-draft-gate";
|
|
27
|
+
import type { AdaptiveDraftGateSettings } from "../contracts/mlx/graph";
|
|
28
|
+
import { MlxPrefillRows, type MlxPrefillState } from "./prefill-rows";
|
|
29
|
+
import { prefillPhase } from "../contracts/portable/prefill";
|
|
30
|
+
import type { MlxForwardWork } from "../contracts/mlx/forward-work";
|
|
31
|
+
import type { MlxGroupMethodHost, MlxGroupMethodRequest, MlxGroupPreparation, MlxGroupedMethod, Row } from "./batch-types";
|
|
32
|
+
|
|
33
|
+
interface RequestState {
|
|
34
|
+
fill?: FillSession;
|
|
35
|
+
proposal?: { value: Proposal; emitted: number };
|
|
36
|
+
sampling: NumberStepSampler;
|
|
37
|
+
draftSampling: Sampler;
|
|
38
|
+
/** Coupled draw over a draft vocabulary; null when not reproducible there. */
|
|
39
|
+
draftSubset: SubsetDraftSampler | null;
|
|
40
|
+
retain?: () => void;
|
|
41
|
+
namespace: string;
|
|
42
|
+
prefixLength: number;
|
|
43
|
+
/** Generated inputs retained by committed target/draft rounds. The pending
|
|
44
|
+
* correction is excluded until a later round actually processes it. */
|
|
45
|
+
processed?: number[];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Binding owns graph/layout selection. The executor receives only the method
|
|
49
|
+
* key and lifecycle; sampling, checkpoints and numerical state remain ports.
|
|
50
|
+
* Undefined when the target cannot capture the hidden layers the provider taps. */
|
|
51
|
+
export function bindSpeculativeGroupRequests(model: MlxTokenGraph, provider: Pick<DraftProvider, "id" | "grouped">, depth: number,
|
|
52
|
+
adaptiveDepth = false, gate: AdaptiveDraftGateSettings = {}) {
|
|
53
|
+
if (adaptiveDepth && !model.verifyRoundCosts)
|
|
54
|
+
throw new Error("adaptive draft depth needs a graph that declares its measured verify costs (verifyRoundCosts)");
|
|
55
|
+
const binding = bindSpeculativeTargetModel(model);
|
|
56
|
+
// The target's ports, viewed only for a provider that asks about them.
|
|
57
|
+
let view: ReturnType<typeof bindLegacyDraftTarget> | undefined;
|
|
58
|
+
const target = () => view ??= bindLegacyDraftTarget(model, []);
|
|
59
|
+
// Resolved once for this target into a snapshot the binding owns: the checked
|
|
60
|
+
// list cannot change afterwards, and prefill and decode rows must tap exactly it.
|
|
61
|
+
const taps = Object.freeze([...(provider.grouped.targetTapLayers?.(target()) ?? [])]);
|
|
62
|
+
if (!binding.supportsTapLayers(taps)) return undefined;
|
|
63
|
+
// The selection's composition facts, resolved once for this target: argmax
|
|
64
|
+
// drafts (the composition's `greedyDraft`), else the vocabulary the provider
|
|
65
|
+
// declares for it.
|
|
66
|
+
const greedy = (model.composition ?? COMPOSITION_DEFAULTS).greedyDraft;
|
|
67
|
+
const listed = greedy ? null : provider.grouped.draftVocabulary?.(target()) ?? null;
|
|
68
|
+
return (input: GenerateOptions): MlxGroupMethodRequest => {
|
|
69
|
+
const options = captureSpeculativeOptions(input);
|
|
70
|
+
// The request's selection, resolved once here: the listed vocabulary only
|
|
71
|
+
// when its draft sampler reproduces there. Rows sharing a group share it, so
|
|
72
|
+
// the draft rows bind one selection operation when they open.
|
|
73
|
+
const selection: GroupSelection = greedy ? { kind: "greedy" }
|
|
74
|
+
: listed && makeSubsetDraftSampler(draftSamplerOptions(options, false)) ? { kind: "listed", ids: listed } : { kind: "sampled" };
|
|
75
|
+
return {
|
|
76
|
+
// The KV scheme is the composition's, shared by every request.
|
|
77
|
+
key: JSON.stringify(["speculative", provider.id, depth, adaptiveDepth, selection.kind]),
|
|
78
|
+
data: options,
|
|
79
|
+
open: host => new SpeculativeGroup(host, model, provider, binding, depth, taps, adaptiveDepth ? gate : null, selection),
|
|
80
|
+
};
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** A step without drafts whose samples are still unread: the GPU runs it while the step before it is delivered.
|
|
85
|
+
* Its input is kept (its window resolved, its taps committed to the drafter); `tokens` [B] are the samples the next
|
|
86
|
+
* step reads as its input, owned, and `rows` the rows it ran, in order. */
|
|
87
|
+
interface AheadStep {
|
|
88
|
+
readonly tokens: MlxArray;
|
|
89
|
+
readonly rows: readonly Row[];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** A probe drafted without a verify round: proposals [B, depth] from each row's input to a step without drafts,
|
|
93
|
+
* compared with the target's samples as the following steps deliver them. `seen` counts each row's samples compared
|
|
94
|
+
* so far; a row leaves it at its first mismatch or after `depth` matches, when the gate observes it. */
|
|
95
|
+
interface ShadowProbe {
|
|
96
|
+
tokens: MlxArray | null;
|
|
97
|
+
proposals: Map<Row, readonly number[]> | null;
|
|
98
|
+
readonly rows: readonly Row[];
|
|
99
|
+
readonly depth: number;
|
|
100
|
+
readonly seen: Map<Row, number>;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** How one delivered sample left its row: still decoding, or ended. */
|
|
104
|
+
type StepOutcome = { readonly kind: "continue" } | { readonly kind: "stop" | "length" } |
|
|
105
|
+
{ readonly kind: "cancelled"; readonly reason: unknown } | { readonly kind: "failed"; readonly error: unknown };
|
|
106
|
+
|
|
107
|
+
/** The draft selection one group's requests share, before the group binds its rows' draws. */
|
|
108
|
+
type GroupSelection = { readonly kind: "sampled" } | { readonly kind: "greedy" } |
|
|
109
|
+
{ readonly kind: "listed"; readonly ids: readonly number[] };
|
|
110
|
+
|
|
111
|
+
/** Rows must tap exactly the target layers resolved for their provider at binding time. */
|
|
112
|
+
function declaredTaps<Rows extends { readonly tapLayers: readonly number[]; dispose(): void }>(
|
|
113
|
+
provider: Pick<DraftProvider, "id">, declared: readonly number[], rows: Rows): Rows {
|
|
114
|
+
if (rows.tapLayers.length !== declared.length || rows.tapLayers.some((layer, index) => layer !== declared[index])) {
|
|
115
|
+
rows.dispose();
|
|
116
|
+
throw new Error(`draft provider ${provider.id} taps [${rows.tapLayers}] but declared [${declared}]`);
|
|
117
|
+
}
|
|
118
|
+
return rows;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
class SpeculativeGroup implements MlxGroupedMethod {
|
|
122
|
+
#target: MlxStateRows<ReturnType<typeof targetCacheLayout>> | null = null;
|
|
123
|
+
#draft: DraftRowGroup | null = null;
|
|
124
|
+
readonly #requests = new Map<Row, RequestState>();
|
|
125
|
+
#steps = 0;
|
|
126
|
+
/** The step without drafts in flight, when the last round was one. */
|
|
127
|
+
#ahead: AheadStep | null = null;
|
|
128
|
+
/** The probe the steps without drafts are comparing, if any. */
|
|
129
|
+
#shadow: ShadowProbe | null = null;
|
|
130
|
+
/** Adaptive draft depth: each round's depth comes from the decayed
|
|
131
|
+
* acceptance rate and the graph's measured verify costs. */
|
|
132
|
+
readonly #gate: AdaptiveDraftGate | null;
|
|
133
|
+
|
|
134
|
+
constructor(readonly host: MlxGroupMethodHost, readonly model: MlxTokenGraph,
|
|
135
|
+
readonly provider: Pick<DraftProvider, "id" | "grouped">, readonly binding: MlxSpeculativeTargetBinding, readonly depth: number,
|
|
136
|
+
readonly taps: readonly number[], gate: AdaptiveDraftGateSettings | null, readonly selection: GroupSelection) {
|
|
137
|
+
this.#gate = gate ? new AdaptiveDraftGate(model.verifyRoundCosts!, { ...gate, maxDraftTokens: depth }) : null;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
get runningTokens(): number {
|
|
141
|
+
const rows = this.host.rows;
|
|
142
|
+
if (!rows.length) return 0;
|
|
143
|
+
// Target verification consumes the pending token and the draft candidates.
|
|
144
|
+
const echo = Math.max(...rows.map(row => {
|
|
145
|
+
const proposal = this.#requests.get(row)?.proposal;
|
|
146
|
+
return proposal?.emitted ? proposal.value.ids.length - proposal.emitted : 0;
|
|
147
|
+
}));
|
|
148
|
+
return rows.length * (1 + (echo || Math.min(this.depth, Math.max(...rows.map(row => row.req.maxTokens - row.generated)))));
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
prepare(first: Row): MlxGroupPreparation {
|
|
152
|
+
const method = this;
|
|
153
|
+
const maintain = this.host.kvMaintenance;
|
|
154
|
+
interface Preparation extends MlxPrefillState {
|
|
155
|
+
request: RequestState;
|
|
156
|
+
end: number;
|
|
157
|
+
chunkSize: number;
|
|
158
|
+
pendingPrompt: boolean;
|
|
159
|
+
boundary: number;
|
|
160
|
+
transferred: boolean;
|
|
161
|
+
closed: boolean;
|
|
162
|
+
}
|
|
163
|
+
interface Ready {
|
|
164
|
+
row: Row;
|
|
165
|
+
request: RequestState;
|
|
166
|
+
caches: Cache[];
|
|
167
|
+
draft: DraftRowCheckpoint;
|
|
168
|
+
retain?: () => void;
|
|
169
|
+
transferred: boolean;
|
|
170
|
+
}
|
|
171
|
+
let prefix: DraftPrefillGroup | undefined;
|
|
172
|
+
let context: MlxArray | null = null;
|
|
173
|
+
const ready: Ready[] = [];
|
|
174
|
+
const closeReady = (state: Ready) => {
|
|
175
|
+
const caches = state.caches.splice(0), attachments = [state.draft.attachment];
|
|
176
|
+
const release = state.retain; state.retain = undefined;
|
|
177
|
+
disposeResources([...caches, { dispose: () => disposeAttachments(attachments) },
|
|
178
|
+
{ dispose: () => release?.() }, ...(!state.transferred ? [state.request.sampling] : [])]);
|
|
179
|
+
};
|
|
180
|
+
const capture = (state: Preparation, snapshot: () => Cache[], index: number) => {
|
|
181
|
+
let caches: Cache[] = [], attachments: CheckpointAttachment[] = [];
|
|
182
|
+
try {
|
|
183
|
+
caches = snapshot(); attachments.push(prefix!.capture(index).attachment);
|
|
184
|
+
method.host.cache.publish(state.row.req, state.request.namespace, state.row.req.promptIds.slice(0, state.pos),
|
|
185
|
+
{ caches, attachments });
|
|
186
|
+
caches = []; attachments = [];
|
|
187
|
+
} finally { disposeResources([...caches, { dispose: () => disposeAttachments(attachments) }]); }
|
|
188
|
+
};
|
|
189
|
+
const target = new MlxPrefillRows<Preparation>({
|
|
190
|
+
maintain, codecs: method.host.cache.codecs,
|
|
191
|
+
open(row) {
|
|
192
|
+
const options = row.req.method!.data as GenerateOptions;
|
|
193
|
+
row.spec = { drafted: 0, accepted: 0, targetCalls: 0, rejected: 0, rounds: 0,
|
|
194
|
+
acceptanceLengths: [], tokensPerForward: 0, forwardsSaved: 0, draftedByPos: [], acceptedByPos: [] };
|
|
195
|
+
// Draft policy: the request sampler (mlx-lm parity) or, opt-in, argmax.
|
|
196
|
+
// The verifier accepts a draft only when the TARGET's own sample equals
|
|
197
|
+
// it, so the output distribution never depends on how drafts are chosen;
|
|
198
|
+
// the argmax of the head's estimate maximizes the expected match.
|
|
199
|
+
const draftOptions = draftSamplerOptions(options, method.selection.kind === "greedy");
|
|
200
|
+
const sampler = makeSampler(draftOptions);
|
|
201
|
+
const request: RequestState = { draftSampling: sampler, draftSubset: makeSubsetDraftSampler(draftOptions),
|
|
202
|
+
namespace: "", prefixLength: 0,
|
|
203
|
+
fill: method.provider.grouped.supportsExternalTokens ? options.fill : undefined,
|
|
204
|
+
processed: method.host.cache.keeps(row.req) ? [] : undefined,
|
|
205
|
+
sampling: makeStepSampler(options, { tokenRepresentation: "number", grammarWait: "before-sample",
|
|
206
|
+
historyUpdate: "after-sample", initialHistory: row.req.promptIds, acceptGrammar: true,
|
|
207
|
+
eosTokenIds: row.req.eosTokenIds, captureSelectedLogprob: options.logprobs === true,
|
|
208
|
+
captureTopLogprobs: options.topLogprobs }) };
|
|
209
|
+
if (request.fill) row.fill = request.fill.stats;
|
|
210
|
+
let caches: Cache[] = [], retained: (() => void) | undefined;
|
|
211
|
+
let appended = false;
|
|
212
|
+
const previous = prefix?.rowCount ?? 0;
|
|
213
|
+
try {
|
|
214
|
+
if (!prefix) {
|
|
215
|
+
// The draft prefill binds its target view once per preparation, over
|
|
216
|
+
// fresh caches of the graph that bind and are released.
|
|
217
|
+
const probe = method.binding.makeCache();
|
|
218
|
+
try {
|
|
219
|
+
prefix = declaredTaps(method.provider, method.taps,
|
|
220
|
+
method.provider.grouped.openPrefill({ target: bindLegacyDraftTarget(method.model, probe), checkpoints: [] }));
|
|
221
|
+
} finally { disposeResources(probe); }
|
|
222
|
+
}
|
|
223
|
+
const prompt = row.req.promptIds;
|
|
224
|
+
const pendingPrompt = prefix.prefillMode !== "full" && method.binding.prefillTailSplit && prompt.length > 1;
|
|
225
|
+
request.prefixLength = prompt.length - Number(pendingPrompt);
|
|
226
|
+
request.namespace = speculativePrefixNamespace(prefix.namespace, row.cacheNamespace ?? "", options);
|
|
227
|
+
// The request's state from the cache service: a reused prefix with its
|
|
228
|
+
// draft state, else fresh state (paged when the composition pages).
|
|
229
|
+
const acquired = method.host.cache.acquire(row.req, request.namespace);
|
|
230
|
+
caches = acquired.caches; retained = acquired.retain;
|
|
231
|
+
if (acquired.reusedTokens) {
|
|
232
|
+
try {
|
|
233
|
+
prefix.append([{ processedTokens: acquired.reusedTokens, attachment: acquired.attachments![0]! }]);
|
|
234
|
+
appended = true;
|
|
235
|
+
// Target membership later releases backing leases after its own
|
|
236
|
+
// copies resolve. Settle companion copies under the same lease.
|
|
237
|
+
if (retained) prefix.materialize();
|
|
238
|
+
row.cachedTokens = acquired.reusedTokens;
|
|
239
|
+
} finally { disposeAttachments(acquired.attachments); }
|
|
240
|
+
} else { prefix.append([null]); appended = true; }
|
|
241
|
+
// The graph's composed caches, or a stored prefix composed again by
|
|
242
|
+
// the cache service: either converts inside its own appends.
|
|
243
|
+
return { row, request, solo: caches, retain: retained, pos: row.cachedTokens,
|
|
244
|
+
end: request.prefixLength, pendingPrompt, transferred: false, closed: false,
|
|
245
|
+
chunkSize: options.prefillChunkSize ?? row.req.prefillChunkSize ?? method.host.prefillChunkSize,
|
|
246
|
+
boundary: method.host.cache.keeps(row.req) ? Math.min(row.req.snapshotAt ?? prompt.length, prompt.length - 1) : -1 };
|
|
247
|
+
} catch (error) {
|
|
248
|
+
return cleanupFailure(error, () => disposeResources([
|
|
249
|
+
{ dispose: () => { if (appended) prefix!.filterRows(Array.from({ length: previous }, (_, i) => i)); } },
|
|
250
|
+
...caches, request.sampling, { dispose: () => retained?.() },
|
|
251
|
+
]));
|
|
252
|
+
}
|
|
253
|
+
},
|
|
254
|
+
plan(state) {
|
|
255
|
+
const limit = Math.min(state.end, state.boundary > state.pos ? state.boundary : Infinity);
|
|
256
|
+
const whole = state.pos + state.chunkSize <= limit;
|
|
257
|
+
const end = whole ? state.pos + state.chunkSize : limit;
|
|
258
|
+
return { start: state.pos, end, kind: end === state.end ? "final" : "drain",
|
|
259
|
+
snapshot: end === state.boundary && end > state.row.cachedTokens, batchYield: true, maintain: true,
|
|
260
|
+
phase: prefillPhase(state.pos, end, whole) };
|
|
261
|
+
},
|
|
262
|
+
async forward(ids, caches, _states, step, work) {
|
|
263
|
+
const taps = prefix!.tapLayers;
|
|
264
|
+
const output = await method.binding.prefill(ids, caches, step, taps.length ? taps : undefined, work);
|
|
265
|
+
context = output.ctxML;
|
|
266
|
+
return output.hidden;
|
|
267
|
+
},
|
|
268
|
+
afterForward(ids, _caches, _states, hidden) {
|
|
269
|
+
// Tapped providers consume their captured layers; assistant drafting
|
|
270
|
+
// consumes the true target hidden. Other providers ignore context.
|
|
271
|
+
const captured = context; context = null;
|
|
272
|
+
try {
|
|
273
|
+
const pending = prefix!.prefill(ids, captured ?? hidden);
|
|
274
|
+
if (pending) return pending.finally(() => captured?.dispose());
|
|
275
|
+
} catch (error) { captured?.dispose(); throw error; }
|
|
276
|
+
captured?.dispose();
|
|
277
|
+
},
|
|
278
|
+
project(hidden, _caches, completed, step) {
|
|
279
|
+
if (completed.every(state => state.pendingPrompt)) return null;
|
|
280
|
+
using last = hidden.slice([0, hidden.shape[1]! - 1, 0], [hidden.shape[0]!, hidden.shape[1]!, hidden.shape[2]!]);
|
|
281
|
+
return method.binding.projectLogits(last, step.sequences);
|
|
282
|
+
},
|
|
283
|
+
checkpoint: capture,
|
|
284
|
+
async complete(state, logits, index) {
|
|
285
|
+
const row = state.row;
|
|
286
|
+
row.spec!.targetCalls++;
|
|
287
|
+
if (state.pendingPrompt) { row.current = row.req.promptIds[state.end]!; row.generated = row.sampled = 0; }
|
|
288
|
+
else {
|
|
289
|
+
using scores = ops.reshape(logits!, [1, logits!.shape.at(-1)!]);
|
|
290
|
+
const sampled = await state.request.sampling.sample(scores, 0);
|
|
291
|
+
row.current = sampled.token; row.generated = row.sampled = 1;
|
|
292
|
+
if (row.req.eosTokenIds.includes(row.current)) {
|
|
293
|
+
if (method.host.cache.keeps(row.req)) capture(state, () => cloneKvCaches(state.solo, method.host.cache.codecs), index);
|
|
294
|
+
disposeResources(state.solo.splice(0)); state.retain?.(); state.retain = undefined;
|
|
295
|
+
method.host.finish(row, "stop"); return;
|
|
296
|
+
}
|
|
297
|
+
const more = await method.host.publish(row, row.current, readStepExtras(sampled.extras));
|
|
298
|
+
state.request.fill?.observe(row.current);
|
|
299
|
+
if (more === false || row.req.grammar?.isTerminated || row.generated >= row.req.maxTokens) {
|
|
300
|
+
if (method.host.cache.keeps(row.req) && !row.req.signal?.aborted) capture(state, () => cloneKvCaches(state.solo, method.host.cache.codecs), index);
|
|
301
|
+
disposeResources(state.solo.splice(0)); state.retain?.(); state.retain = undefined;
|
|
302
|
+
method.host.finish(row, more === false || row.req.grammar?.isTerminated ? "stop" : "length"); return;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
method.#proposeEcho(row, state.request);
|
|
306
|
+
ready.push({ row, request: state.request, caches: state.solo, retain: state.retain,
|
|
307
|
+
draft: prefix!.capture(index), transferred: false });
|
|
308
|
+
state.transferred = true;
|
|
309
|
+
},
|
|
310
|
+
reject(row, error) { row.reject(error); },
|
|
311
|
+
close(state) {
|
|
312
|
+
if (state.closed) return;
|
|
313
|
+
state.closed = true;
|
|
314
|
+
if (!state.transferred) state.request.sampling.dispose();
|
|
315
|
+
},
|
|
316
|
+
filterRows(keep) { prefix!.filterRows(keep); },
|
|
317
|
+
dispose() {
|
|
318
|
+
const held = [prefix, context].filter(value => value != null);
|
|
319
|
+
prefix = undefined; context = null; disposeResources(held);
|
|
320
|
+
},
|
|
321
|
+
});
|
|
322
|
+
const preparation: MlxGroupPreparation = {
|
|
323
|
+
get rows() { return [...target.rows, ...ready.map(state => state.row)]; },
|
|
324
|
+
get canAdmit() { return target.canAdmit; },
|
|
325
|
+
get tokenWeight() { return target.tokenWeight + ready.reduce((sum, state) => sum + state.row.promptTokens - state.row.cachedTokens, 0); },
|
|
326
|
+
admit: row => target.admit(row),
|
|
327
|
+
advance: async work => {
|
|
328
|
+
// Publication returns to scheduling before prepared state joins active
|
|
329
|
+
// decode. This preserves the first-output flush boundary at every B.
|
|
330
|
+
if (ready.length) {
|
|
331
|
+
while (ready.length) {
|
|
332
|
+
const state = ready[0]!;
|
|
333
|
+
try {
|
|
334
|
+
if (state.row.req.signal?.aborted) {
|
|
335
|
+
state.row.reject(state.row.req.signal.reason);
|
|
336
|
+
continue;
|
|
337
|
+
}
|
|
338
|
+
maintain?.prepareBatch?.(state.caches);
|
|
339
|
+
this.#target ??= new MlxStateRows(state.caches.map(targetCacheLayout));
|
|
340
|
+
this.#draft ??= declaredTaps(this.provider, this.taps, this.provider.grouped.open({ target: bindLegacyDraftTarget(this.model, this.#target.caches),
|
|
341
|
+
checkpoints: [], sampling: { sample: (lp, steps) => this.#sampleDraftRows(lp, steps), selection: this.#draftSelection() },
|
|
342
|
+
constraints: { propose: async (row, maxTokens) => this.host.rows[row]!.req.grammar?.proposeTokens(maxTokens) ?? [] } }));
|
|
343
|
+
applyStateChanges([() => this.#target!.prepareAppend(state.caches),
|
|
344
|
+
() => this.#draft!.prepareAppend([state.draft]), () => ({ commit: () => {
|
|
345
|
+
state.request.retain = state.retain; state.retain = undefined;
|
|
346
|
+
this.#requests.set(state.row, state.request); state.transferred = true;
|
|
347
|
+
this.host.join(state.row);
|
|
348
|
+
// New text: what the gate observed of the rows before says little about it.
|
|
349
|
+
this.#gate?.reset();
|
|
350
|
+
}, dispose() {} })]);
|
|
351
|
+
} finally { ready.shift(); closeReady(state); }
|
|
352
|
+
}
|
|
353
|
+
return target.rows.length === 0;
|
|
354
|
+
}
|
|
355
|
+
const done = await target.advance(work);
|
|
356
|
+
return done && ready.length === 0;
|
|
357
|
+
},
|
|
358
|
+
dispose() {
|
|
359
|
+
const held = ready.splice(0);
|
|
360
|
+
disposeResources([target, ...held.map(state => ({ dispose: () => closeReady(state) }))]);
|
|
361
|
+
},
|
|
362
|
+
};
|
|
363
|
+
try { target.admit(first); return preparation; }
|
|
364
|
+
catch (error) { return cleanupFailure(error, () => preparation.dispose()); }
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/** The selection this group's requests resolved when they were bound. Under
|
|
368
|
+
* `listed` every row's draft sampler reproduces on the subset (the request
|
|
369
|
+
* binding keyed its group on that), so each row draws its own coupled token. */
|
|
370
|
+
#draftSelection(): DraftSelection {
|
|
371
|
+
const selection = this.selection;
|
|
372
|
+
if (selection.kind !== "listed") return selection;
|
|
373
|
+
return { kind: "listed", ids: selection.ids, sample: (logits: MlxArray, head: DraftVocabularyHead, steps: readonly number[]): MlxArray => {
|
|
374
|
+
const rows = this.host.rows.map(row => this.#requests.get(row)!);
|
|
375
|
+
const tokens: MlxArray[] = [];
|
|
376
|
+
try {
|
|
377
|
+
for (let row = 0; row < rows.length; row++) {
|
|
378
|
+
using scores = logits.slice([row, 0], [row + 1, logits.shape[1]!]);
|
|
379
|
+
tokens.push(rows[row]!.draftSubset!(scores, head, steps[row]!));
|
|
380
|
+
}
|
|
381
|
+
return tokens.length === 1 ? tokens.pop()! : ops.concatAxis(tokens, 0);
|
|
382
|
+
} finally { disposeResources(tokens); }
|
|
383
|
+
} };
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
#sampleDraftRows(logprobs: MlxArray, steps: readonly number[]): MlxArray {
|
|
387
|
+
const tokens: MlxArray[] = [];
|
|
388
|
+
try {
|
|
389
|
+
for (let row = 0; row < this.host.rows.length; row++) {
|
|
390
|
+
using scores = logprobs.slice([row, 0], [row + 1, logprobs.shape[1]!]);
|
|
391
|
+
tokens.push(this.#requests.get(this.host.rows[row]!)!.draftSampling(scores, steps[row]!));
|
|
392
|
+
}
|
|
393
|
+
return ops.concatAxis(tokens, 0);
|
|
394
|
+
} finally { disposeResources(tokens); }
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
async advance(work?: MlxForwardWork): Promise<void> {
|
|
398
|
+
const active = [...this.host.rows];
|
|
399
|
+
const live = active.flatMap((row, index) => row.req.signal?.aborted ? [] : [index]);
|
|
400
|
+
if (live.length !== active.length) {
|
|
401
|
+
for (const row of active) if (row.req.signal?.aborted) row.reject(row.req.signal.reason);
|
|
402
|
+
this.host.filterRows(live);
|
|
403
|
+
}
|
|
404
|
+
// A row that joined since the last step has no sample in flight: deliver the ones in flight first.
|
|
405
|
+
if (this.#ahead && !sameRows(this.#ahead.rows, this.host.rows)) await this.#flushAhead();
|
|
406
|
+
let rows = [...this.host.rows];
|
|
407
|
+
if (!rows.length) return;
|
|
408
|
+
const cap = Math.min(this.depth, Math.max(...rows.map(row => row.req.maxTokens - row.generated)));
|
|
409
|
+
// A round without drafts verifies the pending token alone (one plain decode step) and still advances the
|
|
410
|
+
// drafter's context through its commit; rows that sample on the device pipeline such rounds as plain decode does.
|
|
411
|
+
let depth = this.#gate && cap > 0 ? this.#gate.next(cap, rows.length) : cap;
|
|
412
|
+
// A probe from a drafter that can propose without committing is compared with the samples instead of verified,
|
|
413
|
+
// drafted as deep as the gate's machine measured to pay.
|
|
414
|
+
const shadow = depth > 0 && this.#gate?.probing && this.#draft!.preview && !this.#shadow
|
|
415
|
+
? Math.min(cap, this.#gate.probeDepth) : 0;
|
|
416
|
+
if ((depth === 0 || shadow) && this.#pipelinesZero(rows)) return this.#zeroStep(rows, work, shadow);
|
|
417
|
+
this.#settleShadow();
|
|
418
|
+
if (this.#ahead) {
|
|
419
|
+
await this.#flushAhead();
|
|
420
|
+
rows = [...this.host.rows];
|
|
421
|
+
if (!rows.length) return;
|
|
422
|
+
depth = Math.min(depth, Math.max(...rows.map(row => row.req.maxTokens - row.generated)));
|
|
423
|
+
}
|
|
424
|
+
// The ordinary MTP round already supplies samples that can establish a
|
|
425
|
+
// copied prefix. Do not verify a wide span before any sample matches it.
|
|
426
|
+
const proposals = rows.map(row => {
|
|
427
|
+
const proposal = this.#requests.get(row)!.proposal;
|
|
428
|
+
return proposal?.emitted ? proposal.value.ids.slice(proposal.emitted) : [];
|
|
429
|
+
});
|
|
430
|
+
const echoDepth = Math.max(...proposals.map(ids => ids.length));
|
|
431
|
+
let externalTokens: MlxArray | undefined;
|
|
432
|
+
const rollback = bindRowCacheRollback(this.#target!.caches, rows.length);
|
|
433
|
+
const fills = echoDepth ? rows.flatMap((row, index) => {
|
|
434
|
+
const request = this.#requests.get(row)!;
|
|
435
|
+
return proposals[index]!.length ? [request.fill!] : [];
|
|
436
|
+
}) : [];
|
|
437
|
+
const halted = new Set<Row>();
|
|
438
|
+
try {
|
|
439
|
+
const completed = await advanceSpeculativeOutputs(rows.map(row => ({ pending: row.current,
|
|
440
|
+
step: row.sampled, remaining: row.req.maxTokens - row.generated, eosTokenIds: row.req.eosTokenIds,
|
|
441
|
+
sampling: this.#requests.get(row)!.sampling, grammarDone: () => row.req.grammar?.isTerminated === true,
|
|
442
|
+
output: { commit: async (ids, metadata) => {
|
|
443
|
+
row.generated++;
|
|
444
|
+
const more = await this.host.publish(row, ids[0]!, metadata?.[0]);
|
|
445
|
+
if (more === false) { halted.add(row); return false; }
|
|
446
|
+
} },
|
|
447
|
+
})), echoDepth || depth, echoDepth ? {
|
|
448
|
+
draft: () => proposals,
|
|
449
|
+
commit: (accepted, context) => this.#draft!.consume!(externalTokens!, context, accepted.map(n => n + 1)),
|
|
450
|
+
} : this.#draft!, {
|
|
451
|
+
materialize: () => {
|
|
452
|
+
const state = leaseCacheStates(this.#target!.caches);
|
|
453
|
+
try { ops.evalAll([...state.borrow()]); }
|
|
454
|
+
finally { state.close(); }
|
|
455
|
+
},
|
|
456
|
+
transaction: !echoDepth ? rollback : { ...rollback,
|
|
457
|
+
begin(depth) {
|
|
458
|
+
const start = performance.now(); rollback.begin(depth);
|
|
459
|
+
const elapsed = performance.now() - start;
|
|
460
|
+
for (const fill of fills) fill.noteVerifyEvent(elapsed);
|
|
461
|
+
},
|
|
462
|
+
resolve(accepted) {
|
|
463
|
+
const start = performance.now(); rollback.resolve(accepted);
|
|
464
|
+
const elapsed = performance.now() - start;
|
|
465
|
+
for (const fill of fills) fill.stats.checkpointMs += elapsed;
|
|
466
|
+
},
|
|
467
|
+
},
|
|
468
|
+
forward: async (ids, window) => {
|
|
469
|
+
if (echoDepth) externalTokens = ops.copyOf(ids);
|
|
470
|
+
const taps = this.#draft!.tapLayers;
|
|
471
|
+
const result = await this.binding.verify(ids, this.#target!.caches, window, taps.length ? taps : undefined, work);
|
|
472
|
+
const context = result.ctxML ?? result.hidden;
|
|
473
|
+
try { return { logits: this.binding.projectLogits(result.hidden, window.sequences * window.positions), context }; }
|
|
474
|
+
catch (error) { context.dispose(); throw error; }
|
|
475
|
+
finally { if (result.ctxML) result.hidden.dispose(); }
|
|
476
|
+
},
|
|
477
|
+
});
|
|
478
|
+
const keep: number[] = [];
|
|
479
|
+
for (let index = 0; index < rows.length; index++) {
|
|
480
|
+
const row = rows[index]!, output = completed.outputs[index]!, round = completed.rounds[index]!;
|
|
481
|
+
const request = this.#requests.get(row)!;
|
|
482
|
+
if (request.fill) {
|
|
483
|
+
for (const token of round.acceptance.emitted.slice(0, output.generated)) {
|
|
484
|
+
const proposal = request.proposal;
|
|
485
|
+
if (proposal && token === proposal.value.ids[proposal.emitted]) {
|
|
486
|
+
if (++proposal.emitted === proposal.value.ids.length) this.#settleEcho(request);
|
|
487
|
+
} else {
|
|
488
|
+
this.#settleEcho(request); request.fill.observe(token);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
if (output.kind === "continue") this.#proposeEcho(row, request);
|
|
492
|
+
else this.#settleEcho(request);
|
|
493
|
+
}
|
|
494
|
+
if (output.kind !== "failed" && output.kind !== "cancelled")
|
|
495
|
+
request.processed?.push(row.current, ...round.drafts.slice(0, output.accepted));
|
|
496
|
+
const stats = row.spec!;
|
|
497
|
+
stats.drafted += echoDepth ? 0 : round.drafts.length;
|
|
498
|
+
stats.accepted += echoDepth ? 0 : round.acceptance.accepted;
|
|
499
|
+
stats.rejected = stats.drafted - stats.accepted;
|
|
500
|
+
stats.targetCalls++;
|
|
501
|
+
if (!echoDepth) {
|
|
502
|
+
this.#gate?.observe(round.drafts.length, round.acceptance.accepted);
|
|
503
|
+
stats.rounds!++;
|
|
504
|
+
stats.acceptanceLengths!.push(round.acceptance.accepted);
|
|
505
|
+
for (let p = 0; p < round.drafts.length; p++) stats.draftedByPos![p] = (stats.draftedByPos![p] ?? 0) + 1;
|
|
506
|
+
for (let p = 0; p < round.acceptance.accepted; p++) stats.acceptedByPos![p] = (stats.acceptedByPos![p] ?? 0) + 1;
|
|
507
|
+
}
|
|
508
|
+
row.sampled += round.acceptance.emitted.length + Number(round.acceptance.sawEos);
|
|
509
|
+
// A consumer may stop inside a verified burst before its terminal EOS.
|
|
510
|
+
if (output.kind === "stop" && !halted.has(row) && round.acceptance.sawEos &&
|
|
511
|
+
output.generated === round.acceptance.emitted.length && row.generated < row.req.maxTokens)
|
|
512
|
+
row.generated++;
|
|
513
|
+
const forwards = stats.targetCalls - 1;
|
|
514
|
+
stats.tokensPerForward = row.generated / forwards;
|
|
515
|
+
stats.forwardsSaved = Math.max(0, row.generated - 1 - forwards);
|
|
516
|
+
if (completed.phaseMs) {
|
|
517
|
+
// Diagnostic phase attribution, summed per request. A shared round
|
|
518
|
+
// costs every row the same wall time; it is not divided by B.
|
|
519
|
+
const phase = stats.phaseMs ??= { draft: 0, verify: 0, sample: 0, commit: 0, rounds: 0 };
|
|
520
|
+
phase.draft += completed.phaseMs.draft; phase.verify += completed.phaseMs.verify;
|
|
521
|
+
phase.sample += completed.phaseMs.sample; phase.commit += completed.phaseMs.commit;
|
|
522
|
+
phase.rounds += completed.phaseMs.rounds;
|
|
523
|
+
for (const field of ["verifyOps", "draftOps"] as const) {
|
|
524
|
+
const counts = completed.phaseMs[field];
|
|
525
|
+
if (!counts) continue;
|
|
526
|
+
const total = phase[field] ??= {};
|
|
527
|
+
for (const [name, n] of Object.entries(counts)) total[name] = (total[name] ?? 0) + n;
|
|
528
|
+
}
|
|
529
|
+
if (completed.phaseMs.layers) {
|
|
530
|
+
const layers = phase.layers ??= {};
|
|
531
|
+
for (const [name, ms] of Object.entries(completed.phaseMs.layers)) layers[name] = (layers[name] ?? 0) + ms;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
if (output.kind === "continue") { row.current = output.pending; keep.push(index); }
|
|
535
|
+
else if (output.kind === "failed") row.reject(output.error);
|
|
536
|
+
else if (output.kind === "cancelled") row.reject(row.req.signal?.reason ?? new Error(output.reason));
|
|
537
|
+
else {
|
|
538
|
+
if (!row.req.signal?.aborted) this.#publishCheckpoint(row, index, request);
|
|
539
|
+
this.host.finish(row, output.kind);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
if (keep.length !== rows.length) this.host.filterRows(keep);
|
|
543
|
+
if (++this.#steps % 256 === 0) clearCache();
|
|
544
|
+
} finally { externalTokens?.dispose(); }
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
/** Whether rounds without drafts pipeline for these rows: each draws its sample on the device without reading it
|
|
548
|
+
* (no grammar or captured probabilities; processor history takes the draw unread) and none echoes proposals. */
|
|
549
|
+
#pipelinesZero(rows: readonly Row[]): boolean {
|
|
550
|
+
return rows.every(row => {
|
|
551
|
+
const request = this.#requests.get(row)!;
|
|
552
|
+
return !request.fill && request.sampling.unread !== undefined;
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
/** One sample per row of a step's logits [B, 1, V], left on the device [B]: the draws `sample` would make. */
|
|
557
|
+
#sampleStep(rows: readonly Row[], logits: MlxArray): MlxArray {
|
|
558
|
+
const samplers = rows.map(row => this.#requests.get(row)!.sampling);
|
|
559
|
+
const V = logits.shape[2]!, B = rows.length;
|
|
560
|
+
const independent = samplers[0]!.independent;
|
|
561
|
+
if (independent && samplers.every(sampling => sampling.independent === independent)) {
|
|
562
|
+
using flat = ops.reshape(logits, [B, V]);
|
|
563
|
+
return independent.sample(flat);
|
|
564
|
+
}
|
|
565
|
+
const tokens: MlxArray[] = [];
|
|
566
|
+
try {
|
|
567
|
+
for (const [index, sampling] of samplers.entries()) {
|
|
568
|
+
using slice = logits.slice([index, 0, 0], [index + 1, 1, V]);
|
|
569
|
+
using scores = ops.reshape(slice, [1, V]);
|
|
570
|
+
const step = rows[index]!.sampled;
|
|
571
|
+
tokens.push(sampling.unread!.sample(scores, step));
|
|
572
|
+
}
|
|
573
|
+
return tokens.length === 1 ? tokens.pop()! : ops.concatAxis(tokens, 0);
|
|
574
|
+
} finally { disposeResources(tokens); }
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
/** One round without drafts, pipelined as plain decode is: this step's window (one position per row, fed by the
|
|
578
|
+
* previous step's samples on the device) is built and submitted before those samples are read and delivered, so
|
|
579
|
+
* the GPU runs it meanwhile. Until delivered, its input may still end its row: rows that continue keep it and
|
|
580
|
+
* commit its taps to the drafter, rows that end drop it (`bindRowStepRollback`), so a finished row's state covers
|
|
581
|
+
* exactly the tokens it processed, as after an unpipelined round. A graph or state failure throws and discards
|
|
582
|
+
* the group. */
|
|
583
|
+
async #zeroStep(rows: readonly Row[], work: MlxForwardWork | undefined, shadow = 0): Promise<void> {
|
|
584
|
+
// A round for every row, which delivers the sample in flight; the forward runs unless every row has drawn its last.
|
|
585
|
+
const forward = !this.#ahead || rows.some(row => row.sampled < row.req.maxTokens);
|
|
586
|
+
for (const row of rows) {
|
|
587
|
+
const stats = row.spec!;
|
|
588
|
+
stats.rounds!++; stats.acceptanceLengths!.push(0);
|
|
589
|
+
if (forward) stats.targetCalls++;
|
|
590
|
+
stats.tokensPerForward = row.generated / (stats.targetCalls - 1);
|
|
591
|
+
stats.forwardsSaved = Math.max(0, row.generated - stats.targetCalls);
|
|
592
|
+
}
|
|
593
|
+
if (!forward) return this.#flushAhead();
|
|
594
|
+
const previous = this.#ahead;
|
|
595
|
+
this.#ahead = null;
|
|
596
|
+
const B = rows.length, caches = this.#target!.caches;
|
|
597
|
+
let context: MlxArray | null = null, tokens: MlxArray | null = null;
|
|
598
|
+
try {
|
|
599
|
+
const window = previous ? bindRowStepRollback(caches, B) : null;
|
|
600
|
+
{
|
|
601
|
+
using ids = previous ? ops.reshape(previous.tokens, [B, 1]) : ops.fromInt32(rows.map(row => row.current), [B, 1]);
|
|
602
|
+
window?.begin(1);
|
|
603
|
+
const taps = this.#draft!.tapLayers;
|
|
604
|
+
const result = await this.binding.decode(ids, caches, taps.length ? taps : undefined, work);
|
|
605
|
+
context = result.ctxML ?? result.hidden;
|
|
606
|
+
try {
|
|
607
|
+
using logits = this.binding.projectLogits(result.hidden, B);
|
|
608
|
+
tokens = this.#sampleStep(rows, logits);
|
|
609
|
+
} finally { if (result.ctxML) result.hidden.dispose(); }
|
|
610
|
+
}
|
|
611
|
+
ops.asyncEvalAll([tokens]);
|
|
612
|
+
const steps = rows.map(row => row.sampled++);
|
|
613
|
+
// The previous step's samples, delivered while the GPU runs this step: this step's inputs.
|
|
614
|
+
let inputs = rows.map(row => row.current), outcomes: StepOutcome[] = rows.map(() => ({ kind: "continue" }));
|
|
615
|
+
if (previous) {
|
|
616
|
+
inputs = previous.tokens.toIntTokens();
|
|
617
|
+
outcomes = [];
|
|
618
|
+
for (const [index, row] of rows.entries()) outcomes.push(await this.#deliverSample(row, inputs[index]!));
|
|
619
|
+
window!.resolve(outcomes.map(outcome => Number(outcome.kind === "continue")));
|
|
620
|
+
}
|
|
621
|
+
const keep = this.#retire(rows, outcomes);
|
|
622
|
+
if (keep.length !== B) this.host.filterRows(keep);
|
|
623
|
+
if (keep.length) {
|
|
624
|
+
if (keep.length !== B) {
|
|
625
|
+
using index = ops.fromInt32(keep, [keep.length]);
|
|
626
|
+
const keptContext = ops.takeAxis(context, index, 0), keptTokens = ops.takeAxis(tokens, index, 0);
|
|
627
|
+
context.dispose(); tokens.dispose();
|
|
628
|
+
context = keptContext; tokens = keptTokens;
|
|
629
|
+
}
|
|
630
|
+
// A probe drafts from the kept inputs before their taps join the drafter's context, as a round would.
|
|
631
|
+
if (shadow) this.#previewShadow(keep.map(index => rows[index]!), keep.map(index => inputs[index]!),
|
|
632
|
+
shadow, keep.map(index => steps[index]!));
|
|
633
|
+
// The drafter takes the kept inputs as a round's pending tokens, then their taps.
|
|
634
|
+
await this.#draft!.draft(keep.map(index => inputs[index]!), 0, keep.map(index => steps[index]!));
|
|
635
|
+
await this.#draft!.commit(keep.map(() => 0), context);
|
|
636
|
+
for (const index of keep) {
|
|
637
|
+
const row = rows[index]!;
|
|
638
|
+
this.#requests.get(row)!.processed?.push(inputs[index]!);
|
|
639
|
+
row.current = inputs[index]!;
|
|
640
|
+
}
|
|
641
|
+
this.#ahead = { tokens, rows: keep.map(index => rows[index]!) };
|
|
642
|
+
tokens = null;
|
|
643
|
+
}
|
|
644
|
+
if (++this.#steps % 256 === 0) clearCache();
|
|
645
|
+
} finally { disposeResources([previous?.tokens, context, tokens].filter(value => value != null)); }
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
/** Read and deliver the step in flight, leaving its rows' samples pending: the rounds that follow draft from them. */
|
|
649
|
+
async #flushAhead(): Promise<void> {
|
|
650
|
+
const ahead = this.#ahead;
|
|
651
|
+
if (!ahead) return;
|
|
652
|
+
this.#ahead = null;
|
|
653
|
+
let read: number[];
|
|
654
|
+
try { read = ahead.tokens.toIntTokens(); } finally { ahead.tokens.dispose(); }
|
|
655
|
+
// Rows that joined after the step follow its rows; they have nothing in flight.
|
|
656
|
+
const rows = [...this.host.rows], outcomes: StepOutcome[] = [];
|
|
657
|
+
for (const [index, row] of rows.entries())
|
|
658
|
+
outcomes.push(index < ahead.rows.length ? await this.#deliverSample(row, read[index]!) : { kind: "continue" });
|
|
659
|
+
for (const [index, row] of ahead.rows.entries()) if (outcomes[index]!.kind === "continue") row.current = read[index]!;
|
|
660
|
+
const keep = this.#retire(rows, outcomes);
|
|
661
|
+
if (keep.length !== rows.length) this.host.filterRows(keep);
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
/** Start a probe: each row's proposals from `pending`, left on the device and submitted behind the step in flight. */
|
|
665
|
+
#previewShadow(rows: readonly Row[], pending: readonly number[], depth: number, steps: readonly number[]): void {
|
|
666
|
+
const tokens = this.#draft!.preview!(pending, depth, steps);
|
|
667
|
+
if (!tokens) return;
|
|
668
|
+
ops.asyncEvalAll([tokens]);
|
|
669
|
+
this.#shadow = { tokens, proposals: null, rows, depth: tokens.shape[1]!, seen: new Map(rows.map(row => [row, 0])) };
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
/** Compare one delivered sample with its row's probe; the gate observes the row at its first mismatch or after
|
|
673
|
+
* every proposal matched. */
|
|
674
|
+
#compareShadow(row: Row, token: number): void {
|
|
675
|
+
const shadow = this.#shadow, seen = shadow?.seen.get(row);
|
|
676
|
+
if (!shadow || seen === undefined) return;
|
|
677
|
+
if (!shadow.proposals) {
|
|
678
|
+
const read = shadow.tokens!.toIntTokens();
|
|
679
|
+
shadow.tokens!.dispose(); shadow.tokens = null;
|
|
680
|
+
shadow.proposals = new Map(shadow.rows.map((probed, index) => [probed, read.slice(index * shadow.depth, (index + 1) * shadow.depth)]));
|
|
681
|
+
}
|
|
682
|
+
const matched = shadow.proposals.get(row)![seen] === token;
|
|
683
|
+
if (matched && seen + 1 < shadow.depth) { shadow.seen.set(row, seen + 1); return; }
|
|
684
|
+
this.#gate?.observe(shadow.depth, matched ? shadow.depth : seen);
|
|
685
|
+
shadow.seen.delete(row);
|
|
686
|
+
if (!shadow.seen.size) this.#settleShadow();
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
/** End the probe: rows still matching are observed over the samples they compared. */
|
|
690
|
+
#settleShadow(): void {
|
|
691
|
+
const shadow = this.#shadow;
|
|
692
|
+
if (!shadow) return;
|
|
693
|
+
this.#shadow = null;
|
|
694
|
+
shadow.tokens?.dispose();
|
|
695
|
+
for (const seen of shadow.seen.values()) if (seen > 0) this.#gate?.observe(seen, seen);
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
/** Deliver one verified sample of a step without drafts, as a round delivers its window: an EOS ends the row
|
|
699
|
+
* unpublished, a consumer may stop it, and its budget ends it. */
|
|
700
|
+
async #deliverSample(row: Row, token: number): Promise<StepOutcome> {
|
|
701
|
+
if (row.req.signal?.aborted) return { kind: "cancelled", reason: row.req.signal.reason };
|
|
702
|
+
this.#compareShadow(row, token);
|
|
703
|
+
if (row.req.eosTokenIds.includes(token)) {
|
|
704
|
+
if (row.generated < row.req.maxTokens) row.generated++;
|
|
705
|
+
return { kind: "stop" };
|
|
706
|
+
}
|
|
707
|
+
try {
|
|
708
|
+
row.generated++;
|
|
709
|
+
if (await this.host.publish(row, token) === false) return { kind: "stop" };
|
|
710
|
+
} catch (error) { return { kind: "failed", error }; }
|
|
711
|
+
return row.generated >= row.req.maxTokens ? { kind: "length" } : { kind: "continue" };
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
/** Retire the rows a step ended (a stopped or finished row publishes its state first) and return the indices
|
|
715
|
+
* of the rest. The caller filters the group to them. */
|
|
716
|
+
#retire(rows: readonly Row[], outcomes: readonly StepOutcome[]): number[] {
|
|
717
|
+
const keep: number[] = [];
|
|
718
|
+
for (const [index, row] of rows.entries()) {
|
|
719
|
+
const outcome = outcomes[index]!;
|
|
720
|
+
if (outcome.kind === "continue") { keep.push(index); continue; }
|
|
721
|
+
if (outcome.kind === "failed") row.reject(outcome.error);
|
|
722
|
+
else if (outcome.kind === "cancelled") row.reject(outcome.reason);
|
|
723
|
+
else {
|
|
724
|
+
if (!row.req.signal?.aborted) this.#publishCheckpoint(row, index, this.#requests.get(row)!);
|
|
725
|
+
this.host.finish(row, outcome.kind);
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
return keep;
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
#proposeEcho(row: Row, request: RequestState): void {
|
|
732
|
+
if (request.proposal || !request.fill) return;
|
|
733
|
+
const value = request.fill.propose(row.req.maxTokens - row.generated, "verify");
|
|
734
|
+
if (value) request.proposal = { value, emitted: 0 };
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
#settleEcho(request: RequestState): void {
|
|
738
|
+
if (!request.proposal) return;
|
|
739
|
+
request.fill!.commit(request.proposal.value, request.proposal.emitted);
|
|
740
|
+
request.proposal = undefined;
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
/** The method aligns token coverage and captures its state. The cache owns
|
|
744
|
+
* RAM retention and eventual SSD persistence; no storage work is awaited. */
|
|
745
|
+
#publishCheckpoint(row: Row, index: number, request: RequestState): void {
|
|
746
|
+
if (!this.host.cache.keeps(row.req) || !request.processed) return;
|
|
747
|
+
let target: Cache[] = [], attachments: CheckpointAttachment[] = [];
|
|
748
|
+
try {
|
|
749
|
+
target = this.#target!.extractRow(index);
|
|
750
|
+
attachments.push(this.#draft!.capture(index).attachment);
|
|
751
|
+
this.host.cache.publish(row.req, request.namespace,
|
|
752
|
+
[...row.req.promptIds.slice(0, request.prefixLength), ...request.processed], { caches: target, attachments });
|
|
753
|
+
target = []; attachments = [];
|
|
754
|
+
} finally { disposeResources([...target, { dispose: () => disposeAttachments(attachments) }]); }
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
filterRows(keep: readonly number[], _discard: boolean): void {
|
|
758
|
+
this.#target?.filterRows(keep); this.#draft?.filterRows(keep);
|
|
759
|
+
const ahead = this.#ahead;
|
|
760
|
+
if (ahead) {
|
|
761
|
+
// The step in flight ran a prefix of the rows: rows that joined after it follow.
|
|
762
|
+
const kept = keep.filter(index => index < ahead.rows.length);
|
|
763
|
+
if (!kept.length) { ahead.tokens.dispose(); this.#ahead = null; }
|
|
764
|
+
else if (kept.length !== ahead.rows.length) {
|
|
765
|
+
using index = ops.fromInt32(kept, [kept.length]);
|
|
766
|
+
const tokens = ops.takeAxis(ahead.tokens, index, 0);
|
|
767
|
+
ahead.tokens.dispose();
|
|
768
|
+
this.#ahead = { tokens, rows: kept.map(row => ahead.rows[row]!) };
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
const retained = new Set(keep.map(index => this.host.rows[index]!));
|
|
772
|
+
const releases: { dispose(): void }[] = [];
|
|
773
|
+
for (const [row, request] of this.#requests) if (!retained.has(row)) {
|
|
774
|
+
this.#settleEcho(request);
|
|
775
|
+
this.#requests.delete(row);
|
|
776
|
+
releases.push(request.sampling, { dispose: () => request.retain?.() });
|
|
777
|
+
}
|
|
778
|
+
disposeResources(releases);
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
dispose(): void {
|
|
782
|
+
this.#ahead?.tokens.dispose();
|
|
783
|
+
this.#ahead = null;
|
|
784
|
+
this.#shadow?.tokens?.dispose();
|
|
785
|
+
this.#shadow = null;
|
|
786
|
+
for (const request of this.#requests.values()) this.#settleEcho(request);
|
|
787
|
+
const resources = [...(this.#target ? [this.#target] : []), ...(this.#draft ? [this.#draft] : []),
|
|
788
|
+
...[...this.#requests.values()].flatMap(request => [request.sampling, { dispose: () => request.retain?.() }])];
|
|
789
|
+
this.#target = null; this.#draft = null; this.#requests.clear();
|
|
790
|
+
disposeResources(resources);
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
/** Whether a step in flight ran exactly the group's current rows. */
|
|
795
|
+
function sameRows(ran: readonly Row[], rows: readonly Row[]): boolean {
|
|
796
|
+
return ran.length === rows.length && ran.every((row, index) => row === rows[index]);
|
|
797
|
+
}
|