@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,39 @@
|
|
|
1
|
+
import type { MlxArray } from "@mlx-bun/mlx/array";
|
|
2
|
+
import * as ops from "@mlx-bun/mlx/ops";
|
|
3
|
+
import type { Cache } from "../contracts/mlx/cache";
|
|
4
|
+
import { cleanupFailure, disposeResources } from "../runtime/resources";
|
|
5
|
+
import type { MlxPhaseDispatch } from "./phase-dispatch";
|
|
6
|
+
|
|
7
|
+
/** Append an already accepted grammar span in one graph call: known tokens
|
|
8
|
+
* (`appendKnown`), a single position through `decode`. This operation
|
|
9
|
+
* intentionally does not use the fill append policy: splitting the span changes
|
|
10
|
+
* the historical grammar path's arithmetic. Inputs/state are borrowed. Record
|
|
11
|
+
* committed IDs before projection so failures cannot hide written state.
|
|
12
|
+
* The sampler must retain its result before returning: tensor cleanup can fail
|
|
13
|
+
* after sampling. All temporary tensors belong to this operation. */
|
|
14
|
+
export async function appendGrammarSpan(
|
|
15
|
+
phases: MlxPhaseDispatch, state: Cache[], ids: readonly number[],
|
|
16
|
+
committed: (ids: readonly number[]) => void, sampleLast?: (logits: MlxArray) => void,
|
|
17
|
+
): Promise<void> {
|
|
18
|
+
let input: MlxArray | null = null, hidden: MlxArray | null = null;
|
|
19
|
+
let last: MlxArray | null = null, logits: MlxArray | null = null;
|
|
20
|
+
const close = () => {
|
|
21
|
+
const owned = [input, hidden, last, logits];
|
|
22
|
+
input = hidden = last = logits = null;
|
|
23
|
+
disposeResources(owned.filter((array): array is MlxArray => array !== null));
|
|
24
|
+
};
|
|
25
|
+
try {
|
|
26
|
+
input = ops.fromInt32([...ids], [1, ids.length]);
|
|
27
|
+
hidden = await (ids.length === 1 ? phases.decode(1)(input, state, null) : phases.known(ids.length)(input, state));
|
|
28
|
+
const consumed = input; input = null; consumed.dispose();
|
|
29
|
+
committed(ids);
|
|
30
|
+
if (!sampleLast) return;
|
|
31
|
+
const [, length, width] = hidden.shape as [number, number, number];
|
|
32
|
+
last = hidden.slice([0, length - 1, 0], [1, length, width]);
|
|
33
|
+
const full = hidden; hidden = null; full.dispose();
|
|
34
|
+
logits = phases.logits(1)(last);
|
|
35
|
+
const selected = last; last = null; selected.dispose();
|
|
36
|
+
sampleLast(logits);
|
|
37
|
+
} catch (error) { return cleanupFailure(error, close); }
|
|
38
|
+
finally { close(); }
|
|
39
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export * from "./types";
|
|
2
|
+
export * from "./result";
|
|
3
|
+
export * from "./generate";
|
|
4
|
+
export * from "./autoregressive";
|
|
5
|
+
export * from "./denoising";
|
|
6
|
+
export * from "./diffusion";
|
|
7
|
+
export { maybePageKv } from "../state/request-policy";
|
|
8
|
+
export { evalCacheState } from "./prefill";
|
|
9
|
+
export type { TokenLogprobs } from "../contracts/portable/generation";
|
|
10
|
+
export { acquireModelWiredLimit, wiredWorkingSetBytes, modelNeedsWiredLimit, withModelWiredLimit, withModelUsageFlush } from "./scopes";
|
|
11
|
+
|
|
12
|
+
export { generationCheckpointKey } from "./checkpoint-identity";
|
|
13
|
+
|
|
14
|
+
export { MlxPhaseDispatch, knownOperationFor } from "./phase-dispatch";
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
// The scheduler's side of the graph contract: it names the phase it is in and
|
|
2
|
+
// the rows it is loading, and this resolves the operation the graph built for
|
|
3
|
+
// them. Bound once per graph; nothing here inspects a tensor.
|
|
4
|
+
import { type MlxCapturingForward, type MlxHeadForward, type MlxLayerCapture, type MlxPromptRange, type MlxPhaseForward, type MlxTokenPhases, type MlxKnownOperations } from "../contracts/mlx/graph";
|
|
5
|
+
import type { PrefillPhase } from "../contracts/portable/prefill";
|
|
6
|
+
import type { MlxArray } from "@mlx-bun/mlx/array";
|
|
7
|
+
import type { MlxForwardStep } from "../contracts/mlx/forward-work";
|
|
8
|
+
import { rowOperationFor } from "../runtime/row-operations";
|
|
9
|
+
|
|
10
|
+
/** Index the declared whole-span operation without changing its geometry. */
|
|
11
|
+
export function knownOperationFor(operations: MlxKnownOperations, rows: number): MlxPhaseForward {
|
|
12
|
+
try { return rowOperationFor(operations, rows); }
|
|
13
|
+
catch { throw new Error(`graph has no known-span operation for ${rows} rows`); }
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** One prompt step the scheduler runs: the phase its planner named and its
|
|
17
|
+
* rows, `sequences` sequences of `tokens` positions each. */
|
|
18
|
+
export interface MlxPromptStep {
|
|
19
|
+
readonly phase: PrefillPhase;
|
|
20
|
+
readonly sequences: number;
|
|
21
|
+
readonly tokens: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** A verify window: `sequences` sequences of `positions` (depth + 1) each. */
|
|
25
|
+
export interface MlxVerifyWindow {
|
|
26
|
+
readonly sequences: number;
|
|
27
|
+
readonly positions: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** A prompt step as the mixed iteration's hook takes it: one position per
|
|
31
|
+
* sequence is a decode group, any other piece a window. */
|
|
32
|
+
export function promptWorkStep(step: MlxPromptStep, capture: MlxLayerCapture | null,
|
|
33
|
+
own: () => Promise<MlxArray>): MlxForwardStep {
|
|
34
|
+
const rows = step.sequences * step.tokens;
|
|
35
|
+
return { phase: step.phase === "decode" ? "decode" : "window", rows, capture, own };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** A verify window as the mixed iteration's hook takes it. */
|
|
39
|
+
export function verifyWorkStep(window: MlxVerifyWindow, capture: MlxLayerCapture | null,
|
|
40
|
+
own: () => Promise<MlxArray>): MlxForwardStep {
|
|
41
|
+
const rows = window.sequences * window.positions;
|
|
42
|
+
return { phase: "verify", rows, capture, own };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** The phase operations of one graph, indexed by what the scheduler decides.
|
|
46
|
+
*
|
|
47
|
+
* The Neural Engine is a unit the scheduler owns: read once, here, which
|
|
48
|
+
* Neural Engine operations the composition built and the row range each takes.
|
|
49
|
+
* A prompt piece of one sequence, a whole chunk or a tail, whose positions one
|
|
50
|
+
* range holds runs on that operation; every other piece runs on the GPU. One
|
|
51
|
+
* scheduler drives one dispatch at a time, so the unit serves one piece at a
|
|
52
|
+
* time. */
|
|
53
|
+
export class MlxPhaseDispatch {
|
|
54
|
+
readonly #phases: Partial<MlxTokenPhases> | null;
|
|
55
|
+
|
|
56
|
+
/** `graph`: the phases `tokenPhasesOf` resolved, or null for a graph whose
|
|
57
|
+
* method runs none (denoising), which then refuses every token phase. */
|
|
58
|
+
constructor(graph: Partial<MlxTokenPhases> | null) {
|
|
59
|
+
this.#phases = graph;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
get #graph(): Partial<MlxTokenPhases> {
|
|
63
|
+
if (!this.#phases) throw new Error("this graph's method runs no token phases");
|
|
64
|
+
return this.#phases;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Whether the composition built a Neural Engine operation. */
|
|
68
|
+
get neuralEngine(): boolean {
|
|
69
|
+
const graph = this.#phases;
|
|
70
|
+
return [graph?.prompt, graph?.promptChunk, graph?.multiSequencePrompt, graph?.multiSequencePromptChunk]
|
|
71
|
+
.some(ranges => ranges?.some(range => range.unit === "ane"));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** One prompt step of `sequences` sequences of `tokens` positions each, in
|
|
75
|
+
* the `phase` its planner named. */
|
|
76
|
+
prompt(phase: PrefillPhase, sequences: number, tokens: number): MlxCapturingForward {
|
|
77
|
+
if (phase === "decode") return this.decode(sequences);
|
|
78
|
+
const positions = sequences > 1 ? sequences * tokens : tokens;
|
|
79
|
+
const ranges = sequences > 1
|
|
80
|
+
? phase === "chunk" ? this.#graph.multiSequencePromptChunk : this.#graph.multiSequencePrompt
|
|
81
|
+
: phase === "chunk" ? this.#graph.promptChunk : this.#graph.prompt;
|
|
82
|
+
if (!ranges) throw new Error(`graph has no ${sequences > 1 ? "multi-sequence " : ""}${phase} operation table`);
|
|
83
|
+
const range = ranges.find(range => positions >= range.from && positions <= range.to);
|
|
84
|
+
if (!range) throw new Error(`graph has no prompt operation for ${tokens} positions`);
|
|
85
|
+
return range.operation;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** One new token for each of `sequences` sequences. */
|
|
89
|
+
decode(sequences: number): MlxCapturingForward {
|
|
90
|
+
const operations = this.#graph.decode;
|
|
91
|
+
if (!operations) throw new Error("graph has no decode operation table");
|
|
92
|
+
return rowOperationFor(operations, sequences);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** A verify window of `positions` (depth + 1) per sequence. */
|
|
96
|
+
verify(sequences: number, positions: number): MlxCapturingForward {
|
|
97
|
+
const operations = this.#graph.verify;
|
|
98
|
+
if (!operations) throw new Error("graph has no verify operation table");
|
|
99
|
+
return rowOperationFor(operations, sequences * positions);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** `tokens` known tokens appended to one sequence. */
|
|
103
|
+
known(tokens: number): MlxPhaseForward {
|
|
104
|
+
const operations = this.#graph.appendKnown;
|
|
105
|
+
if (!operations) throw new Error("graph has no known-span operation table");
|
|
106
|
+
return knownOperationFor(operations, tokens);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** The head over a hidden of `rows` rows. */
|
|
110
|
+
logits(rows: number): MlxHeadForward {
|
|
111
|
+
const operations = this.#graph.logits;
|
|
112
|
+
if (!operations) throw new Error("graph has no logits operation table");
|
|
113
|
+
return rowOperationFor(operations, rows);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { PrefillStep } from "../contracts/portable/prefill";
|
|
2
|
+
import type { MlxArray } from "@mlx-bun/mlx/array";
|
|
3
|
+
import type { Cache } from "../contracts/mlx/cache";
|
|
4
|
+
import * as ops from "@mlx-bun/mlx/ops";
|
|
5
|
+
import { clearCache } from "@mlx-bun/mlx/ffi";
|
|
6
|
+
import { leaseCacheStates } from "../state/leases";
|
|
7
|
+
import { withResource } from "../runtime/resources";
|
|
8
|
+
import type { MlxPhaseDispatch } from "./phase-dispatch";
|
|
9
|
+
|
|
10
|
+
/** State views with explicit ownership are released even on deferred errors.
|
|
11
|
+
* Owned views precede borrowed state, preserving the serial eval ordering. */
|
|
12
|
+
export function evalCacheState(cache: Cache[]): void {
|
|
13
|
+
withResource(leaseCacheStates(cache), (state) => { if (state.length) ops.evalAll([...state]); });
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Forward one prompt step of one sequence through the operation its planned
|
|
17
|
+
* phase names. Drains evaluate state (the composed caches converted inside
|
|
18
|
+
* their own appends), then clear the allocator cache. Finals return owned
|
|
19
|
+
* hidden state without forcing eval. */
|
|
20
|
+
export async function executeMlxPrefillStep(
|
|
21
|
+
phases: MlxPhaseDispatch, cache: Cache[], prompt: readonly number[], step: PrefillStep,
|
|
22
|
+
): Promise<MlxArray | null> {
|
|
23
|
+
const chunk = prompt.slice(step.start, step.end);
|
|
24
|
+
const ids = ops.fromInt32(chunk, [1, chunk.length]);
|
|
25
|
+
let hidden: MlxArray;
|
|
26
|
+
try { hidden = await phases.prompt(step.phase, 1, chunk.length)(ids, cache, null); }
|
|
27
|
+
finally { ids.dispose(); }
|
|
28
|
+
if (step.kind === "final") return hidden;
|
|
29
|
+
hidden.dispose();
|
|
30
|
+
evalCacheState(cache);
|
|
31
|
+
clearCache();
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// Streaming generation — port of mlx-lm's generate_step:
|
|
2
|
+
// - prefill in chunks; cache state evaluated per chunk (bounded transient
|
|
3
|
+
// memory), logits never computed for non-final prefill positions
|
|
4
|
+
// - decode pipelining via mx.async_eval: step n+1's graph is built and
|
|
5
|
+
// dispatched before step n's token is read back, so the GPU never idles
|
|
6
|
+
// on the JS round-trip
|
|
7
|
+
// - opt-in early token-zero yield reduces latency before that pipeline starts
|
|
8
|
+
// - sampling stays on-device; only the chosen token id crosses to JS
|
|
9
|
+
|
|
10
|
+
import { runtimeConfig,withRuntimeConfig } from "../runtime/config";
|
|
11
|
+
import { GenerateStats,GeneratedToken } from "./types";
|
|
12
|
+
|
|
13
|
+
export class Generation implements AsyncIterable<GeneratedToken> {
|
|
14
|
+
stats: GenerateStats | null = null;
|
|
15
|
+
readonly #next: () => Promise<IteratorResult<GeneratedToken, GenerateStats>>;
|
|
16
|
+
readonly #return: () => Promise<IteratorResult<GeneratedToken, GenerateStats>>;
|
|
17
|
+
|
|
18
|
+
constructor(iter: AsyncGenerator<GeneratedToken, GenerateStats>, runtime = runtimeConfig()) {
|
|
19
|
+
// Async generators resume in the caller's context on every next/return.
|
|
20
|
+
// Bind both operations so consumer awaits and early close cannot change
|
|
21
|
+
// the runtime snapshot seen by kernels or their cleanup.
|
|
22
|
+
const next = iter.next.bind(iter);
|
|
23
|
+
const close = () => iter.return(undefined as unknown as GenerateStats);
|
|
24
|
+
this.#next = () => withRuntimeConfig(runtime, next);
|
|
25
|
+
this.#return = () => withRuntimeConfig(runtime, close);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async *[Symbol.asyncIterator](): AsyncGenerator<GeneratedToken> {
|
|
29
|
+
try {
|
|
30
|
+
while (true) {
|
|
31
|
+
const r = await this.#next();
|
|
32
|
+
if (r.done) {
|
|
33
|
+
this.stats = r.value;
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
yield r.value;
|
|
37
|
+
}
|
|
38
|
+
} finally {
|
|
39
|
+
// Consumer broke early (e.g. a decoded-text stop sequence fired):
|
|
40
|
+
// drive the inner generator's shutdown so its finallys run (array
|
|
41
|
+
// disposal, wired/adapter scopes) and capture the stats its
|
|
42
|
+
// early-return path still reports.
|
|
43
|
+
if (this.stats === null) {
|
|
44
|
+
const r = await this.#return();
|
|
45
|
+
if (r.done && r.value) this.stats = r.value;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
// Streaming generation — port of mlx-lm's generate_step:
|
|
2
|
+
// - prefill in chunks; cache state evaluated per chunk (bounded transient
|
|
3
|
+
// memory), logits never computed for non-final prefill positions
|
|
4
|
+
// - decode pipelining via mx.async_eval: step n+1's graph is built and
|
|
5
|
+
// dispatched before step n's token is read back, so the GPU never idles
|
|
6
|
+
// on the JS round-trip
|
|
7
|
+
// - opt-in early token-zero yield reduces latency before that pipeline starts
|
|
8
|
+
// - sampling stays on-device; only the chosen token id crosses to JS
|
|
9
|
+
|
|
10
|
+
import { gpuStream } from "@mlx-bun/mlx/array";
|
|
11
|
+
import {
|
|
12
|
+
maxRecommendedWorkingSetSize,
|
|
13
|
+
setWiredLimit,
|
|
14
|
+
synchronize
|
|
15
|
+
} from "@mlx-bun/mlx/ffi";
|
|
16
|
+
import { GenerateStats,GeneratedToken } from "./types";
|
|
17
|
+
import {
|
|
18
|
+
type MlxModelMemory
|
|
19
|
+
} from "./bindings/autoregressive";
|
|
20
|
+
import { runtimeValue } from "../runtime/config";
|
|
21
|
+
|
|
22
|
+
// Scoped wired limit, raised only for near-ceiling models. mlx-lm's
|
|
23
|
+
// wired_limit context wires unconditionally per generation; we deviate
|
|
24
|
+
// with a measured justification:
|
|
25
|
+
// - 26B-A4B (16.4 GB = 92% of the 17.8 GiB working set) NEEDS wiring —
|
|
26
|
+
// 8.6 tok/s without, 32.3 with (Metal evicts weight buffers per token).
|
|
27
|
+
// - 12B/e4b (≤47%) hit reference parity WITHOUT wiring, and wiring in a
|
|
28
|
+
// multi-model process (the test suite) pins memory the OTHER resident
|
|
29
|
+
// models need — async GPU exec OOM, which is uncatchable (the mlx
|
|
30
|
+
// completion-handler throw terminates the process).
|
|
31
|
+
// Scope semantics match the reference: set → generate → synchronize →
|
|
32
|
+
// restore; nothing stays pinned between generations. Re-entrant: only
|
|
33
|
+
// the outermost wiring scope touches the limit.
|
|
34
|
+
// macOS 26.6 reports a 26.8e9 B (24.96 GiB, 0.78 × RAM) recommended set on
|
|
35
|
+
// a 32 GB M1 Max (2026-09-08; the earlier "24 GB machine" attribution was
|
|
36
|
+
// wrong — 24.96 GiB exceeds a 24 GB box's RAM). At 0.75 × that the old
|
|
37
|
+
// fraction stopped wiring the 13-16 GiB Qwen/GLM models even though they
|
|
38
|
+
// page heavily without an explicit wired limit.
|
|
39
|
+
// Keep smaller 8-9 GiB models unwired while covering the large-model class.
|
|
40
|
+
// A graph whose composition measured a gain below the line declares
|
|
41
|
+
// `wiredWeights` and is wired whatever its share.
|
|
42
|
+
const WIRE_THRESHOLD = 0.5;
|
|
43
|
+
let wiredScopeDepth = 0;
|
|
44
|
+
let wiredOldLimit = 0;
|
|
45
|
+
|
|
46
|
+
type WiredModelMemory = MlxModelMemory;
|
|
47
|
+
|
|
48
|
+
/** Bytes whose MLX graph must stay resident while a model executes. */
|
|
49
|
+
export function wiredWorkingSetBytes(model: WiredModelMemory): number {
|
|
50
|
+
const planned = model.expertRuntime?.plan.plannedBytes;
|
|
51
|
+
return typeof planned === "number" && Number.isFinite(planned) && planned > 0
|
|
52
|
+
? Math.max(model.weightsBytes, planned)
|
|
53
|
+
: model.weightsBytes;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function modelNeedsWiredLimit(
|
|
57
|
+
model: WiredModelMemory,
|
|
58
|
+
recommendedBytes = maxRecommendedWorkingSetSize(),
|
|
59
|
+
force = runtimeValue("MLX_BUN_FORCE_WIRE") === "1",
|
|
60
|
+
): boolean {
|
|
61
|
+
return force || model.wiredWeights === true ||
|
|
62
|
+
wiredWorkingSetBytes(model) > WIRE_THRESHOLD * recommendedBytes;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function enterWiredScope(): void {
|
|
66
|
+
if (wiredScopeDepth++ === 0)
|
|
67
|
+
wiredOldLimit = setWiredLimit(maxRecommendedWorkingSetSize());
|
|
68
|
+
}
|
|
69
|
+
function exitWiredScope(): void {
|
|
70
|
+
if (--wiredScopeDepth === 0) {
|
|
71
|
+
synchronize(gpuStream);
|
|
72
|
+
setWiredLimit(wiredOldLimit);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Hold the process wired limit while one model owns GPU execution. */
|
|
77
|
+
export function acquireModelWiredLimit(model: WiredModelMemory): () => void {
|
|
78
|
+
if (!modelNeedsWiredLimit(model)) return () => {};
|
|
79
|
+
enterWiredScope();
|
|
80
|
+
let released = false;
|
|
81
|
+
return () => {
|
|
82
|
+
if (released) return;
|
|
83
|
+
released = true;
|
|
84
|
+
exitWiredScope();
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Hold the model's active-adapter list for exactly this generation. */
|
|
89
|
+
export async function* adapterScoped(
|
|
90
|
+
model: { loraState: { active: string[] } },
|
|
91
|
+
adapters: string[],
|
|
92
|
+
inner: AsyncGenerator<GeneratedToken, GenerateStats>,
|
|
93
|
+
): AsyncGenerator<GeneratedToken, GenerateStats> {
|
|
94
|
+
const previous = model.loraState.active;
|
|
95
|
+
model.loraState.active = adapters;
|
|
96
|
+
try {
|
|
97
|
+
return yield* inner;
|
|
98
|
+
} finally {
|
|
99
|
+
model.loraState.active = previous;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Wrap the generator so the wired limit is held exactly while it runs
|
|
104
|
+
* (incl. early break/return/throw — finally fires on .return()). */
|
|
105
|
+
export async function* wiredScoped(
|
|
106
|
+
inner: AsyncGenerator<GeneratedToken, GenerateStats>,
|
|
107
|
+
): AsyncGenerator<GeneratedToken, GenerateStats> {
|
|
108
|
+
enterWiredScope();
|
|
109
|
+
try {
|
|
110
|
+
return yield* inner;
|
|
111
|
+
} finally {
|
|
112
|
+
exitWiredScope();
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Publish the streamed model's route ledger on every generator exit path. */
|
|
117
|
+
export async function* usageScoped(
|
|
118
|
+
model: WiredModelMemory,
|
|
119
|
+
inner: AsyncGenerator<GeneratedToken, GenerateStats>,
|
|
120
|
+
): AsyncGenerator<GeneratedToken, GenerateStats> {
|
|
121
|
+
try {
|
|
122
|
+
return yield* inner;
|
|
123
|
+
} finally {
|
|
124
|
+
if (model.expertRuntime?.finishUsage)
|
|
125
|
+
await model.expertRuntime.finishUsage();
|
|
126
|
+
else
|
|
127
|
+
model.expertRuntime?.flushUsage?.();
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** Apply generate()'s scoped wiring policy to non-generator execution paths. */
|
|
132
|
+
export async function withModelWiredLimit<T>(
|
|
133
|
+
model: WiredModelMemory,
|
|
134
|
+
run: () => Promise<T>,
|
|
135
|
+
): Promise<T> {
|
|
136
|
+
const release = acquireModelWiredLimit(model);
|
|
137
|
+
try {
|
|
138
|
+
return await run();
|
|
139
|
+
} finally {
|
|
140
|
+
release();
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** Apply generate()'s usage safe-point to direct/non-generator paths. */
|
|
145
|
+
export async function withModelUsageFlush<T>(
|
|
146
|
+
model: WiredModelMemory,
|
|
147
|
+
run: () => Promise<T>,
|
|
148
|
+
): Promise<T> {
|
|
149
|
+
try {
|
|
150
|
+
return await run();
|
|
151
|
+
} finally {
|
|
152
|
+
if (model.expertRuntime?.finishUsage)
|
|
153
|
+
await model.expertRuntime.finishUsage();
|
|
154
|
+
else
|
|
155
|
+
model.expertRuntime?.flushUsage?.();
|
|
156
|
+
}
|
|
157
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/** One request's exact-token speculative acceptance walk. The backend owns
|
|
2
|
+
* logits and sampling; this state owns only the accepted prefix and stop facts.
|
|
3
|
+
* A group advances each live walk independently, regardless of batch size. */
|
|
4
|
+
export class DraftAcceptance {
|
|
5
|
+
position = 0;
|
|
6
|
+
accepted = 0;
|
|
7
|
+
correction: number | null = null;
|
|
8
|
+
readonly emitted: number[] = [];
|
|
9
|
+
sawEos = false;
|
|
10
|
+
grammarDone = false;
|
|
11
|
+
done = false;
|
|
12
|
+
|
|
13
|
+
constructor(
|
|
14
|
+
readonly drafts: readonly number[],
|
|
15
|
+
readonly remaining: number,
|
|
16
|
+
readonly eosTokenIds: readonly number[],
|
|
17
|
+
) {}
|
|
18
|
+
|
|
19
|
+
/** Supply one target sample after its sampler has updated request history
|
|
20
|
+
* and grammar. Once done, this request needs no further target samples. */
|
|
21
|
+
accept(token: number, grammarDone = false): void {
|
|
22
|
+
const matches = this.position < this.drafts.length && token === this.drafts[this.position];
|
|
23
|
+
this.position++;
|
|
24
|
+
if (matches) {
|
|
25
|
+
this.accepted++;
|
|
26
|
+
if (this.eosTokenIds.includes(token)) {
|
|
27
|
+
this.sawEos = this.done = true;
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
this.emitted.push(token);
|
|
31
|
+
this.grammarDone = grammarDone;
|
|
32
|
+
this.done = grammarDone || this.emitted.length >= this.remaining;
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
this.correction = token;
|
|
36
|
+
this.sawEos = this.eosTokenIds.includes(token);
|
|
37
|
+
if (!this.sawEos) this.emitted.push(token);
|
|
38
|
+
this.grammarDone = grammarDone;
|
|
39
|
+
this.done = true;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/** Cost-aware speculative draft scheduler.
|
|
2
|
+
*
|
|
3
|
+
* Adapted from mlx-optiq 0.5.8's AcceptanceGate (MIT). The scheduler models
|
|
4
|
+
* the expected emitted tokens for a proposal depth and divides that by the
|
|
5
|
+
* measured cost of its round, by the rows its forward carries: a group of B
|
|
6
|
+
* sequences drafting k each verifies B(k + 1) rows, and its round without
|
|
7
|
+
* drafts is a decode step of B rows. It uses acceptance rather than
|
|
8
|
+
* observed wall-clock throughput as the online signal, so unrelated machine
|
|
9
|
+
* load does not make it oscillate.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { AdaptiveDraftGateSettings } from "../../contracts/mlx/graph";
|
|
13
|
+
|
|
14
|
+
export type VerifyCostModel = ReadonlyMap<number, number>;
|
|
15
|
+
|
|
16
|
+
/** The cost of a forward of `positions` rows: the measured one, interpolated between measured rows, and past the
|
|
17
|
+
* widest measured forward the widest one's cost per row (a forward's cost per row does not rise with its rows, so
|
|
18
|
+
* an unmeasured wider forward is charged at most that). */
|
|
19
|
+
function interpolatedCost(costs: VerifyCostModel, positions: number): number {
|
|
20
|
+
const keys = [...costs.keys()].sort((a, b) => a - b);
|
|
21
|
+
if (keys.length < 2 || !costs.has(1))
|
|
22
|
+
throw new Error("adaptive draft costs need position 1 and at least one larger position");
|
|
23
|
+
const exact = costs.get(positions);
|
|
24
|
+
if (exact !== undefined) return exact;
|
|
25
|
+
if (positions > keys.at(-1)!) return costs.get(keys.at(-1)!)! * positions / keys.at(-1)!;
|
|
26
|
+
const lo = keys.filter((key) => key < positions).at(-1)!;
|
|
27
|
+
const hi = keys.find((key) => key > positions)!;
|
|
28
|
+
return costs.get(lo)! +
|
|
29
|
+
((costs.get(hi)! - costs.get(lo)!) / (hi - lo)) * (positions - lo);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** The defaults of each setting a graph may declare for its machine (`AdaptiveDraftGateSettings`):
|
|
33
|
+
* - `priorAccepted`, `priorRejected`: every position's counts before any round is observed, 4.5 and 0.5 (an
|
|
34
|
+
* acceptance of 0.9, so a new gate drafts deeply and backs off as rejections arrive);
|
|
35
|
+
* - `decay` 0.85, and `idleDecay` 1: a round without drafts leaves the counts as they are;
|
|
36
|
+
* - `margin` 0.03;
|
|
37
|
+
* - `probeInterval`: rounds without drafts before `next` drafts one token to observe acceptance again (8); each probe
|
|
38
|
+
* after which the gate still drafts nothing doubles the interval, up to `maxProbeInterval` (32);
|
|
39
|
+
* - `probeDepth`: the depth a drafter that proposes without committing drafts a probe at (the round's budget). */
|
|
40
|
+
export interface AdaptiveDraftGateOptions extends AdaptiveDraftGateSettings {
|
|
41
|
+
readonly maxDraftTokens: number;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Acceptance is modeled per draft position: the probability that the draft at position i is accepted given that
|
|
45
|
+
* every draft before it was. Each position's decayed counts are shrunk toward the position before it (weight 1;
|
|
46
|
+
* position 1 toward the prior), so a position seldom reached follows the ones that are, and a drafter whose later
|
|
47
|
+
* positions fall off (a recurrent MTP head trained for two steps) and one that stays flat (a block drafter) are
|
|
48
|
+
* both modeled. A round decays the positions it drafted; with `idleDecay` a round without drafts decays them all,
|
|
49
|
+
* so what the gate observed ages with the text it decodes and not only with the rounds it drafts. */
|
|
50
|
+
export class AdaptiveDraftGate {
|
|
51
|
+
/** Decayed accepted and rejected drafts at each position (index 0 is position 1). */
|
|
52
|
+
readonly #accepted: number[];
|
|
53
|
+
readonly #rejected: number[];
|
|
54
|
+
readonly #prior: number;
|
|
55
|
+
readonly #priorWeight: number;
|
|
56
|
+
readonly #decay: number;
|
|
57
|
+
readonly #idleDecay: number;
|
|
58
|
+
readonly #margin: number;
|
|
59
|
+
readonly #maxDraftTokens: number;
|
|
60
|
+
readonly #probeInterval: number;
|
|
61
|
+
readonly #maxProbeInterval: number;
|
|
62
|
+
/** The depth of a probe drafted without a verify round. */
|
|
63
|
+
readonly probeDepth: number;
|
|
64
|
+
/** Rounds in a row `next` has run without drafts, and the rounds before its next probe. */
|
|
65
|
+
#idle = 0;
|
|
66
|
+
#interval: number;
|
|
67
|
+
#probing = false;
|
|
68
|
+
|
|
69
|
+
constructor(readonly costs: VerifyCostModel, options: AdaptiveDraftGateOptions) {
|
|
70
|
+
this.#maxDraftTokens = options.maxDraftTokens;
|
|
71
|
+
const accepted = options.priorAccepted ?? 4.5, rejected = options.priorRejected ?? 0.5;
|
|
72
|
+
this.#prior = accepted / (accepted + rejected);
|
|
73
|
+
this.#priorWeight = accepted + rejected;
|
|
74
|
+
this.#accepted = Array<number>(options.maxDraftTokens).fill(accepted);
|
|
75
|
+
this.#rejected = Array<number>(options.maxDraftTokens).fill(rejected);
|
|
76
|
+
this.#decay = options.decay ?? 0.85;
|
|
77
|
+
this.#idleDecay = options.idleDecay ?? 1;
|
|
78
|
+
this.#margin = options.margin ?? 0.03;
|
|
79
|
+
this.#probeInterval = this.#interval = options.probeInterval ?? 8;
|
|
80
|
+
this.#maxProbeInterval = options.maxProbeInterval ?? 32;
|
|
81
|
+
this.probeDepth = options.probeDepth ?? Infinity;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Forget what the gate observed: back to the prior, probing at the first interval. For new text (a request
|
|
85
|
+
* joining), whose acceptance the rounds before say little about. */
|
|
86
|
+
reset(): void {
|
|
87
|
+
this.#accepted.fill(this.#prior * this.#priorWeight);
|
|
88
|
+
this.#rejected.fill((1 - this.#prior) * this.#priorWeight);
|
|
89
|
+
this.#idle = 0;
|
|
90
|
+
this.#interval = this.#probeInterval;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** The probability that the draft at `position` (1-based) is accepted, given that the drafts before it were. */
|
|
94
|
+
acceptance(position: number): number {
|
|
95
|
+
let shrunk = this.#prior;
|
|
96
|
+
for (let i = 0; i < position; i++)
|
|
97
|
+
shrunk = (this.#accepted[i]! + shrunk) / (this.#accepted[i]! + this.#rejected[i]! + 1);
|
|
98
|
+
return shrunk;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** One round's outcome: `drafted` proposals, the first `accepted` of them accepted. Positions 1..accepted count an
|
|
102
|
+
* acceptance and the first rejected position a rejection, after the counts decay: every position's when every
|
|
103
|
+
* draft was accepted (the text may have turned predictable, so the positions past the round follow the ones it
|
|
104
|
+
* accepted), only the drafted positions' otherwise (a rejected probe says nothing about deeper drafts). A round
|
|
105
|
+
* without drafts observes nothing. */
|
|
106
|
+
observe(drafted: number, accepted: number): void {
|
|
107
|
+
const decayed = drafted > 0 && accepted === drafted ? this.#maxDraftTokens : Math.min(drafted, this.#maxDraftTokens);
|
|
108
|
+
for (let i = 0; i < decayed; i++) {
|
|
109
|
+
this.#accepted[i] = this.#accepted[i]! * this.#decay + Number(i < Math.min(accepted, drafted));
|
|
110
|
+
this.#rejected[i] = this.#rejected[i]! * this.#decay + Number(i === accepted && accepted < drafted);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Expected emitted tokens per sequence of a round drafting `k` for each of `rows` sequences, per unit of cost,
|
|
115
|
+
* where a round without drafts over those sequences costs 1 (a plain decode step emits 1 per 1). */
|
|
116
|
+
expectedRate(k: number, rows = 1): number {
|
|
117
|
+
// survive: the probability that every draft up to a position is accepted; the round emits one token more
|
|
118
|
+
// than it accepts, so tokens = 1 + the sum of survive over positions 1..k.
|
|
119
|
+
let survive = 1, tokens = 1;
|
|
120
|
+
for (let position = 1; position <= k; position++) {
|
|
121
|
+
survive *= this.acceptance(position);
|
|
122
|
+
tokens += survive;
|
|
123
|
+
}
|
|
124
|
+
return tokens * interpolatedCost(this.costs, rows) / interpolatedCost(this.costs, rows * (k + 1));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** The next round's depth: `choose`'s, except that after a run of rounds without drafts it drafts one token,
|
|
128
|
+
* the cheapest round that observes acceptance, so the estimate keeps following the text (a round without drafts
|
|
129
|
+
* observes nothing). The run starts at `probeInterval` rounds and doubles after each probe that leaves the gate
|
|
130
|
+
* drafting nothing, up to `maxProbeInterval`: a probe costs more than the plain step it replaces, so text that
|
|
131
|
+
* stays hard to draft pays for fewer of them. `rows`: the sequences the round drafts for. */
|
|
132
|
+
next(budget: number, rows = 1): number {
|
|
133
|
+
this.#probing = false;
|
|
134
|
+
const depth = this.choose(budget, rows);
|
|
135
|
+
if (depth > 0 || budget < 1) { this.#idle = 0; this.#interval = this.#probeInterval; return depth; }
|
|
136
|
+
if (this.#idleDecay < 1) for (let i = 0; i < this.#maxDraftTokens; i++) {
|
|
137
|
+
this.#accepted[i] = this.#accepted[i]! * this.#idleDecay;
|
|
138
|
+
this.#rejected[i] = this.#rejected[i]! * this.#idleDecay;
|
|
139
|
+
}
|
|
140
|
+
if (++this.#idle <= this.#interval) return 0;
|
|
141
|
+
this.#idle = 0;
|
|
142
|
+
this.#interval = Math.min(2 * this.#interval, this.#maxProbeInterval);
|
|
143
|
+
this.#probing = true;
|
|
144
|
+
return 1;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** Whether the depth `next` last returned is a probe: drafts the estimate does not pay for, drafted only to observe
|
|
148
|
+
* acceptance. A drafter that can propose without committing may observe them without a verify round. */
|
|
149
|
+
get probing(): boolean { return this.#probing; }
|
|
150
|
+
|
|
151
|
+
/** The depth whose expected rate for a group of `rows` sequences beats a round without drafts by the margin, the
|
|
152
|
+
* best such, up to `budget`; none when no depth does. */
|
|
153
|
+
choose(budget: number, rows = 1): number {
|
|
154
|
+
const limit = Math.min(this.#maxDraftTokens, budget);
|
|
155
|
+
let bestDepth = 0;
|
|
156
|
+
let bestRate = 1 + this.#margin;
|
|
157
|
+
for (let depth = 1; depth <= limit; depth++) {
|
|
158
|
+
const rate = this.expectedRate(depth, rows);
|
|
159
|
+
if (rate > bestRate) {
|
|
160
|
+
bestDepth = depth;
|
|
161
|
+
bestRate = rate;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return bestDepth;
|
|
165
|
+
}
|
|
166
|
+
}
|