@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,840 @@
|
|
|
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 { MlxArray,gpuStream } from "@mlx-bun/mlx/array";
|
|
11
|
+
import {
|
|
12
|
+
Dtype,
|
|
13
|
+
activeMemory,
|
|
14
|
+
clearCache,
|
|
15
|
+
peakMemory,
|
|
16
|
+
synchronize
|
|
17
|
+
} from "@mlx-bun/mlx/ffi";
|
|
18
|
+
import * as ops from "@mlx-bun/mlx/ops";
|
|
19
|
+
import { appendFileSync } from "node:fs";
|
|
20
|
+
import { type Cache } from "../contracts/mlx/cache";
|
|
21
|
+
import { type MlxKnownSpans } from "../contracts/mlx/graph";
|
|
22
|
+
import { nextPrefillStep } from "../contracts/portable/prefill";
|
|
23
|
+
import {
|
|
24
|
+
assertMlxAutoregressiveBinding,
|
|
25
|
+
type MlxAutoregressiveBinding,
|
|
26
|
+
type MlxDecodeStep
|
|
27
|
+
} from "./bindings/autoregressive";
|
|
28
|
+
import { runtimeConfig,type RuntimeConfig } from "../runtime/config";
|
|
29
|
+
import { appendFillHidden } from "./fill-append";
|
|
30
|
+
import { appendGrammarSpan } from "./grammar-step";
|
|
31
|
+
import { adapterScoped,modelNeedsWiredLimit,usageScoped,wiredScoped } from "./scopes";
|
|
32
|
+
import { evalCacheState,executeMlxPrefillStep } from "./prefill";
|
|
33
|
+
import {
|
|
34
|
+
disposeStepExtras,
|
|
35
|
+
makeStepSampler,
|
|
36
|
+
readStepExtras,
|
|
37
|
+
stepExtrasArrays,
|
|
38
|
+
type StepExtras
|
|
39
|
+
} from "../sampling/index";
|
|
40
|
+
import { BF16_KV, COMPOSITION_DEFAULTS, type KvStorageScheme } from "../contracts/portable/composition";
|
|
41
|
+
import { maybePageKv } from "../state/request-policy";
|
|
42
|
+
import { rollbackVerifyBlock, rollsBackVerifyBlock } from "../state/rollback";
|
|
43
|
+
import {
|
|
44
|
+
fillTraceEnabled,
|
|
45
|
+
fillTracePath,
|
|
46
|
+
resolveFillMode,
|
|
47
|
+
type FillTraceRecord,
|
|
48
|
+
type Proposal
|
|
49
|
+
} from "./fill/session";
|
|
50
|
+
import { Generation } from "./result";
|
|
51
|
+
import { GenerateDiagnostics,GenerateOptions,GenerateStats,GeneratedToken } from "./types";
|
|
52
|
+
|
|
53
|
+
/** Jump-forward decoding for this request under a composition whose
|
|
54
|
+
* `grammarJump` is `composed`: grammar requests that return no logprobs. */
|
|
55
|
+
export function shouldUseGrammarJump(
|
|
56
|
+
options: Pick<GenerateOptions, "grammar" | "logprobs" | "topLogprobs">,
|
|
57
|
+
composed: boolean = COMPOSITION_DEFAULTS.grammarJump,
|
|
58
|
+
): boolean {
|
|
59
|
+
return options.grammar !== undefined &&
|
|
60
|
+
composed &&
|
|
61
|
+
!options.logprobs &&
|
|
62
|
+
!(options.topLogprobs && options.topLogprobs > 0);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Composition gate for token fast-forwarding, enforced inside the engine
|
|
66
|
+
* The serving planner owns request-level eligibility, including explicit
|
|
67
|
+
* seeds in serial execution and mounted draft models. Shared strict fill
|
|
68
|
+
* has its own grouped method. The composition's KV scheme is no gate: every
|
|
69
|
+
* cache lego takes known tokens, one position at a time or through the
|
|
70
|
+
* graph's committed spans.
|
|
71
|
+
* - grammar: forced-token production is the grammar's job; two producers in
|
|
72
|
+
* one iteration is forbidden (asserted in the loop).
|
|
73
|
+
* - logprobs/top_logprobs: injected tokens are never sampled, so they have
|
|
74
|
+
* no distribution row (same rule as shouldUseGrammarJump).
|
|
75
|
+
* - promptEmbeddings: vision/audio prompts (and mRoPE) — untested shape. */
|
|
76
|
+
export function shouldUseFill(
|
|
77
|
+
options: Pick<GenerateOptions, "fill" | "grammar" | "logprobs" | "topLogprobs" | "promptEmbeddings">,
|
|
78
|
+
runtime: RuntimeConfig = runtimeConfig(),
|
|
79
|
+
): boolean {
|
|
80
|
+
if (!options.fill) return false;
|
|
81
|
+
if (resolveFillMode(runtime.value("MLX_BUN_FILL") ?? "off") === "off") return false;
|
|
82
|
+
return options.grammar === undefined &&
|
|
83
|
+
!options.logprobs &&
|
|
84
|
+
!(options.topLogprobs && options.topLogprobs > 0) &&
|
|
85
|
+
options.promptEmbeddings === undefined;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** The rewind surface a verify-policy fill needs. `Cache` declares the
|
|
89
|
+
* spec-round trio as optional members, but the cache list is a UNION with
|
|
90
|
+
* GLM's MLACache (which declares none of them), so the union has no such
|
|
91
|
+
* property to read. One narrowing helper keeps the call sites honest — every
|
|
92
|
+
* use is still optional-chained. */
|
|
93
|
+
type RewindableCache = {
|
|
94
|
+
isTrimmable(): boolean;
|
|
95
|
+
trim(n: number, bypass?: boolean): void;
|
|
96
|
+
specRoundBegin?(): void;
|
|
97
|
+
specRoundCommit?(): void;
|
|
98
|
+
specRoundRollback?(keep: number): void;
|
|
99
|
+
};
|
|
100
|
+
const rewindable = (c: unknown): RewindableCache => c as RewindableCache;
|
|
101
|
+
|
|
102
|
+
/** Execute a supplied backend binding through the same AR loop and resource
|
|
103
|
+
* scopes as generate(). Callers serialize shared model/adapter access. Replacing
|
|
104
|
+
* this binding replaces graph, cache construction, media, and optional decode
|
|
105
|
+
* together; it never borrows another model's compiled execution implicitly. */
|
|
106
|
+
export function generateAutoregressive(
|
|
107
|
+
binding: MlxAutoregressiveBinding,
|
|
108
|
+
promptTokens: number[],
|
|
109
|
+
options: GenerateOptions = {},
|
|
110
|
+
diagnostics: GenerateDiagnostics = {},
|
|
111
|
+
): Generation {
|
|
112
|
+
const runtime = binding.runtime ?? runtimeConfig();
|
|
113
|
+
let inner = generateInner(binding, promptTokens, options, diagnostics, runtime);
|
|
114
|
+
if (options.adapters?.length && binding.adapters) {
|
|
115
|
+
inner = adapterScoped({ loraState: binding.adapters }, options.adapters, inner);
|
|
116
|
+
}
|
|
117
|
+
if (binding.memory.expertRuntime?.finishUsage || binding.memory.expertRuntime?.flushUsage)
|
|
118
|
+
inner = usageScoped(binding.memory, inner);
|
|
119
|
+
return new Generation(modelNeedsWiredLimit(binding.memory, undefined,
|
|
120
|
+
runtime.value("MLX_BUN_FORCE_WIRE") === "1") ? wiredScoped(inner) : inner, runtime);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async function* generateInner(
|
|
124
|
+
binding: MlxAutoregressiveBinding,
|
|
125
|
+
promptTokens: number[],
|
|
126
|
+
options: GenerateOptions,
|
|
127
|
+
diagnostics: GenerateDiagnostics,
|
|
128
|
+
runtime: RuntimeConfig,
|
|
129
|
+
): AsyncGenerator<GeneratedToken, GenerateStats> {
|
|
130
|
+
if (options.signal?.aborted) {
|
|
131
|
+
options.grammar?.dispose();
|
|
132
|
+
options.signal.throwIfAborted();
|
|
133
|
+
}
|
|
134
|
+
const {
|
|
135
|
+
maxTokens = 512,
|
|
136
|
+
eosTokenIds = binding.eosTokenIds,
|
|
137
|
+
prefillChunkSize = binding.prefillPolicy?.chunkSize(promptTokens.length) ?? 2048,
|
|
138
|
+
} = options;
|
|
139
|
+
|
|
140
|
+
try {
|
|
141
|
+
assertMlxAutoregressiveBinding(binding);
|
|
142
|
+
if (options.promptEmbeddings && !binding.prefillEmbeddings)
|
|
143
|
+
throw new Error("AR binding does not support prompt embeddings");
|
|
144
|
+
if (options.adapters?.length && !binding.adapters)
|
|
145
|
+
throw new Error("AR binding does not support adapters");
|
|
146
|
+
} catch (error) {
|
|
147
|
+
options.grammar?.dispose();
|
|
148
|
+
throw error;
|
|
149
|
+
}
|
|
150
|
+
const phases = binding.phases;
|
|
151
|
+
|
|
152
|
+
// logprobs capture (mlx_lm.server semantics — see GenerateOptions.logprobs).
|
|
153
|
+
// Everything below is gated: when neither flag is set, no extra ops, evals,
|
|
154
|
+
// or readbacks happen on the hot path.
|
|
155
|
+
const captureSelLp = options.logprobs === true;
|
|
156
|
+
const captureTopK =
|
|
157
|
+
options.topLogprobs && options.topLogprobs > 0 ? options.topLogprobs : 0;
|
|
158
|
+
const stepSampler = makeStepSampler(options, {
|
|
159
|
+
tokenRepresentation: "device",
|
|
160
|
+
grammarWait: "external",
|
|
161
|
+
historyUpdate: "manual",
|
|
162
|
+
captureSelectedLogprob: captureSelLp,
|
|
163
|
+
captureTopLogprobs: captureTopK,
|
|
164
|
+
});
|
|
165
|
+
const needsTokenHistory = stepSampler.needsHistory;
|
|
166
|
+
|
|
167
|
+
const closePrefill = diagnostics.trace?.begin("prefill.total", {
|
|
168
|
+
mechanism: diagnostics.mechanism ?? "serial",
|
|
169
|
+
promptTokens: promptTokens.length,
|
|
170
|
+
cachedTokens: options.cache?.[0]?.offset ?? 0,
|
|
171
|
+
});
|
|
172
|
+
const closeBatchSetup = diagnostics.trace?.begin("prefill.batch_setup", {
|
|
173
|
+
mechanism: diagnostics.mechanism ?? "serial",
|
|
174
|
+
});
|
|
175
|
+
const ownsCache = !options.cache;
|
|
176
|
+
const resuming = options.initialPendingToken !== undefined;
|
|
177
|
+
let cache: Cache[] = options.cache ?? [];
|
|
178
|
+
// The composition's KV scheme, which every request stores.
|
|
179
|
+
const kv = binding.kv ?? BF16_KV;
|
|
180
|
+
let appender: MlxKnownSpans | null = null;
|
|
181
|
+
try {
|
|
182
|
+
if (ownsCache) cache = binding.makeCache();
|
|
183
|
+
if (!cache.length) throw new Error("AR binding returned an empty cache");
|
|
184
|
+
const cached = cache[0]!.offset;
|
|
185
|
+
if ((!resuming && cached >= promptTokens.length) ||
|
|
186
|
+
(resuming && cached !== promptTokens.length))
|
|
187
|
+
throw new Error(
|
|
188
|
+
resuming
|
|
189
|
+
? `resumed cache (${cached} tokens) must exactly cover the resume prefix (${promptTokens.length})`
|
|
190
|
+
: `pre-warmed cache (${cached} tokens) must be a strict prefix of the prompt (${promptTokens.length})`,
|
|
191
|
+
);
|
|
192
|
+
// Replace fresh full-attention caches before any forward. The deepest
|
|
193
|
+
// write is prompt + maxTokens - 1, so capacity covers every decode step.
|
|
194
|
+
maybePageKv(cache, kv, binding.pagedKv ?? null, promptTokens.length + maxTokens);
|
|
195
|
+
// Committed known-token spans, where the graph declares them and the run
|
|
196
|
+
// mounts no adapter; otherwise known tokens append one position at a time.
|
|
197
|
+
appender = options.fill && binding.knownSpans && !options.adapters?.length
|
|
198
|
+
? binding.knownSpans : null;
|
|
199
|
+
} catch (error) {
|
|
200
|
+
if (ownsCache) for (const state of cache) state.dispose();
|
|
201
|
+
stepSampler.dispose();
|
|
202
|
+
options.grammar?.dispose();
|
|
203
|
+
closeBatchSetup?.();
|
|
204
|
+
closePrefill?.();
|
|
205
|
+
throw error;
|
|
206
|
+
}
|
|
207
|
+
const cachedTokens = cache[0]!.offset;
|
|
208
|
+
// Token fast-forwarding gate (see shouldUseFill). Sliding-window layers take
|
|
209
|
+
// part: a multi-token append is one concat write, and a rejected verify tail
|
|
210
|
+
// leaves the ring by the bypass trim that write allows (see rewind below).
|
|
211
|
+
const fillOn = shouldUseFill(options, runtime);
|
|
212
|
+
const fillTrace = fillOn && fillTraceEnabled();
|
|
213
|
+
const fillTraceFile = fillOn ? fillTracePath() : null;
|
|
214
|
+
// Verify-policy proposals (echo tier) need the rejected tail rewound. That
|
|
215
|
+
// is the SAME contract the spec lane's rounds use: trimmable caches drop the
|
|
216
|
+
// tail; recurrent caches (SSMCache — gated-DeltaNet state, untrimmable)
|
|
217
|
+
// restore a pre-round snapshot and bit-exactly replay the accepted prefix.
|
|
218
|
+
// A model whose caches can do neither still gets assert-policy fills; verify
|
|
219
|
+
// proposals are dropped and counted (stats.verifyUnsupported).
|
|
220
|
+
const verifyCapable = fillOn && rollsBackVerifyBlock(kv, cache);
|
|
221
|
+
closeBatchSetup?.();
|
|
222
|
+
|
|
223
|
+
// logits [1,1,V] → sampled token array [1] (+ optional logprob capture,
|
|
224
|
+
// all on-device)
|
|
225
|
+
const sampleStep = (
|
|
226
|
+
logits3d: MlxArray,
|
|
227
|
+
step: number,
|
|
228
|
+
): { tok: MlxArray; extras: StepExtras | null } => {
|
|
229
|
+
const result = stepSampler.sample(logits3d, step);
|
|
230
|
+
return { tok: result.token, extras: result.extras };
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
const pushHistory = (tok: MlxArray): void => stepSampler.commitDevice(tok);
|
|
234
|
+
|
|
235
|
+
// Decode-loop state lives at function scope so the finally can still
|
|
236
|
+
// report stats and dispose in-flight arrays when the consumer
|
|
237
|
+
// terminates the generator early (break on a stop sequence — the
|
|
238
|
+
// forced .return() resumes at the yield and runs the finally).
|
|
239
|
+
let prefillMs = 0;
|
|
240
|
+
let tDecode = 0;
|
|
241
|
+
let decodeMs = 0;
|
|
242
|
+
let generated = options.initialGeneratedTokens ?? 0;
|
|
243
|
+
let finishReason: GenerateStats["finishReason"];
|
|
244
|
+
const forwarded: number[] = [];
|
|
245
|
+
let pending: MlxArray | null = null;
|
|
246
|
+
let nextPending: MlxArray | null = null;
|
|
247
|
+
let pendingExtras: StepExtras | null = null;
|
|
248
|
+
let nextExtras: StepExtras | null = null;
|
|
249
|
+
let decoder: MlxDecodeStep | null = null;
|
|
250
|
+
let finished = false;
|
|
251
|
+
let threw = false;
|
|
252
|
+
let executionError: unknown;
|
|
253
|
+
let earlyUnforwardedToken: number | null = null;
|
|
254
|
+
let closeTokenZero: (() => void) | undefined;
|
|
255
|
+
const makeStats = (): GenerateStats => ({
|
|
256
|
+
promptTokens: options.originalPromptTokens ?? promptTokens.length,
|
|
257
|
+
cachedTokens: Math.min(cachedTokens, options.originalPromptTokens ?? promptTokens.length),
|
|
258
|
+
generatedTokens: generated,
|
|
259
|
+
...(finishReason ? { finishReason } : {}),
|
|
260
|
+
prefillMs,
|
|
261
|
+
decodeMs,
|
|
262
|
+
prefillTps: prefillMs > 0
|
|
263
|
+
? ((promptTokens.length - cachedTokens) / prefillMs) * 1000
|
|
264
|
+
: 0,
|
|
265
|
+
decodeTps: (generated / decodeMs) * 1000,
|
|
266
|
+
cacheTokens: [...promptTokens, ...forwarded],
|
|
267
|
+
...(fillOn ? { fill: options.fill!.stats } : {}),
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
/** The one invariant that makes fill safe: the caches hold exactly
|
|
271
|
+
* prompt + everything `forwarded` records, so a fill append must forward
|
|
272
|
+
* ONLY the injected ids — the normal step already consumed `cur` and wrote
|
|
273
|
+
* its KV. Forwarding [token, ...ids] would duplicate a position and
|
|
274
|
+
* silently corrupt both the KV and PromptCache.put's key. Checked under
|
|
275
|
+
* MLX_BUN_FILL_TRACE=1 on both sides of the append. */
|
|
276
|
+
const assertFillAlignment = (where: string): void => {
|
|
277
|
+
const offset = cache[0]!.offset;
|
|
278
|
+
const want = promptTokens.length + forwarded.length;
|
|
279
|
+
if (offset !== want)
|
|
280
|
+
throw new Error(
|
|
281
|
+
`[fill] cache misalignment ${where}: cache offset ${offset} != ` +
|
|
282
|
+
`prompt ${promptTokens.length} + forwarded ${forwarded.length}`,
|
|
283
|
+
);
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
/** THE apply primitive for token fast-forwarding — ONE chunked forward
|
|
287
|
+
* carries a proposal into the KV (and the recurrent state), and BOTH
|
|
288
|
+
* policies ride it (packages/inference/src/generation/fill/proposal.ts):
|
|
289
|
+
*
|
|
290
|
+
* - assert: the span is determined (a template scaffold). Append and move
|
|
291
|
+
* on. No readback, no checkpoint, no rewind. The in-flight sample for
|
|
292
|
+
* the position after `token` is dropped UNEXAMINED — a discarded
|
|
293
|
+
* pipeline dispatch, not a rejected draft.
|
|
294
|
+
* - verify: the span is likely (a copy from earlier in the session).
|
|
295
|
+
* Position 0 is checked BEFORE the forward against the in-flight sample
|
|
296
|
+
* (free, and a mismatch costs nothing — nothing has been written).
|
|
297
|
+
* Positions 1..m-1 are checked against the argmax already sitting in
|
|
298
|
+
* THIS forward's logits, so verification adds no pass over the weights.
|
|
299
|
+
* The rejected tail is rewound through the same cache contract the spec
|
|
300
|
+
* lane's rounds use, and decode resumes at the first disagreement.
|
|
301
|
+
*
|
|
302
|
+
* Returns the ids the engine must now emit (empty = no fill happened).
|
|
303
|
+
* Mutates nextPending/nextExtras/forwarded, exactly like the normal step. */
|
|
304
|
+
/** One trace record: the proposal next to the model's own token at every
|
|
305
|
+
* span position (`actual[0]` = in-flight sample, `actual[j]` = argmax after
|
|
306
|
+
* ids[j-1]). Appended as JSONL to MLX_BUN_FILL_TRACE=<file>. */
|
|
307
|
+
const traceProposal = (
|
|
308
|
+
proposal: Proposal, generatedNow: number, accepted: number, actual: number[],
|
|
309
|
+
): void => {
|
|
310
|
+
const ids = proposal.ids;
|
|
311
|
+
let firstMismatch = -1;
|
|
312
|
+
for (let j = 0; j < ids.length && j < actual.length; j++)
|
|
313
|
+
if (actual[j] !== ids[j]) { firstMismatch = j; break; }
|
|
314
|
+
const dec = options.fill!.decode;
|
|
315
|
+
const rec: FillTraceRecord = {
|
|
316
|
+
ts: new Date().toISOString(), origin: proposal.origin, policy: proposal.policy,
|
|
317
|
+
generated: generatedNow, proposedLen: ids.length, accepted, firstMismatch,
|
|
318
|
+
proposed: ids, actual,
|
|
319
|
+
...(dec ? { proposedText: dec(ids), actualText: dec(actual) } : {}),
|
|
320
|
+
};
|
|
321
|
+
try { appendFileSync(fillTraceFile!, JSON.stringify(rec) + "\n"); } catch { /* trace is best-effort */ }
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
const applyProposal = async (
|
|
325
|
+
proposal: Proposal, generatedNow: number,
|
|
326
|
+
): Promise<number[]> => {
|
|
327
|
+
const fill = options.fill!;
|
|
328
|
+
const ids = proposal.ids;
|
|
329
|
+
const verify = proposal.policy === "verify";
|
|
330
|
+
if (verify && !verifyCapable) {
|
|
331
|
+
fill.noteVerifyUnsupported();
|
|
332
|
+
fill.commit(proposal, 0);
|
|
333
|
+
return [];
|
|
334
|
+
}
|
|
335
|
+
// The in-flight sample IS the model's own choice for position 0 (read for
|
|
336
|
+
// the trace under both policies; the served assert path never checks it).
|
|
337
|
+
const inFlight = verify || fillTraceFile ? ops.itemUint32(nextPending!) : -1;
|
|
338
|
+
if (verify) {
|
|
339
|
+
if (inFlight !== ids[0]) {
|
|
340
|
+
if (fillTraceFile) traceProposal(proposal, generatedNow, 0, [inFlight]);
|
|
341
|
+
fill.commit(proposal, 0);
|
|
342
|
+
return []; // nextPending survives; this becomes an ordinary step
|
|
343
|
+
}
|
|
344
|
+
} else {
|
|
345
|
+
fill.noteWastedSample();
|
|
346
|
+
}
|
|
347
|
+
if (fillTrace) assertFillAlignment("at fill entry");
|
|
348
|
+
nextPending!.dispose();
|
|
349
|
+
nextPending = null;
|
|
350
|
+
disposeStepExtras(nextExtras);
|
|
351
|
+
nextExtras = null;
|
|
352
|
+
|
|
353
|
+
let checkpointMs = 0;
|
|
354
|
+
let roundOpen = false;
|
|
355
|
+
if (verify) {
|
|
356
|
+
const t0 = performance.now();
|
|
357
|
+
for (const c of cache) rewindable(c).specRoundBegin?.();
|
|
358
|
+
roundOpen = true;
|
|
359
|
+
checkpointMs += performance.now() - t0;
|
|
360
|
+
}
|
|
361
|
+
let accepted = ids.length;
|
|
362
|
+
let hf: MlxArray | null = null;
|
|
363
|
+
let logitsAll: MlxArray | null = null;
|
|
364
|
+
try {
|
|
365
|
+
// A committed span stops at any composed cache's pending transition,
|
|
366
|
+
// which that cache then runs inside its append.
|
|
367
|
+
const chunkSize = (state: readonly Cache[]) => {
|
|
368
|
+
const maxChunk = Math.min(appender?.maxChunkSize(state) ?? 1,
|
|
369
|
+
...state.map(c => c.maxAppendTokens?.() ?? Number.POSITIVE_INFINITY));
|
|
370
|
+
return fill.appendChunkSize > 0 ? Math.min(fill.appendChunkSize, maxChunk) : maxChunk;
|
|
371
|
+
};
|
|
372
|
+
// A verified proposal is one verify window (one position decodes); an
|
|
373
|
+
// asserted span appends known tokens, through the committed read under
|
|
374
|
+
// the graph's known spans, one decoded position at a time otherwise.
|
|
375
|
+
const forward = (part: MlxArray, state: Cache[], positions: number) => positions === 1 && (verify || !appender)
|
|
376
|
+
? phases.decode(1)(part, state, null)
|
|
377
|
+
: verify ? phases.verify(1, positions)(part, state, null) : phases.known(positions)(part, state);
|
|
378
|
+
hf = await appendFillHidden(forward, cache, ids, verify ? () => ids.length : chunkSize);
|
|
379
|
+
const [, Lf, Hf] = hf.shape as [number, number, number];
|
|
380
|
+
let pred: number[] | null = null;
|
|
381
|
+
if (verify || fillTraceFile) {
|
|
382
|
+
// Free logits: this forward already computed every span position's
|
|
383
|
+
// hidden state. argmax at j is the model's continuation after ids[j],
|
|
384
|
+
// so it verifies ids[j+1]. (Under assert this readback is trace-only.)
|
|
385
|
+
logitsAll = phases.logits(ids.length)(hf);
|
|
386
|
+
const argmax = ops.argmaxAxis(logitsAll, -1);
|
|
387
|
+
pred = argmax.toIntTokens();
|
|
388
|
+
argmax.dispose();
|
|
389
|
+
}
|
|
390
|
+
if (verify && pred) {
|
|
391
|
+
for (let j = 0; j + 1 < ids.length; j++) {
|
|
392
|
+
if (pred[j] !== ids[j + 1]) { accepted = j + 1; break; }
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
if (fillTraceFile && pred)
|
|
396
|
+
traceProposal(proposal, generatedNow, accepted, [inFlight, ...pred.slice(0, ids.length - 1)]);
|
|
397
|
+
const t1 = performance.now();
|
|
398
|
+
if (accepted < ids.length) {
|
|
399
|
+
// The state drops the rejected tail (trim, a ring's rollback trim, or
|
|
400
|
+
// a recurrence's restore and replay).
|
|
401
|
+
rollbackVerifyBlock(cache, accepted, ids.length);
|
|
402
|
+
roundOpen = false;
|
|
403
|
+
} else if (roundOpen) {
|
|
404
|
+
for (const c of cache) rewindable(c).specRoundCommit?.();
|
|
405
|
+
roundOpen = false;
|
|
406
|
+
}
|
|
407
|
+
if (verify) checkpointMs += performance.now() - t1;
|
|
408
|
+
forwarded.push(...ids.slice(0, accepted));
|
|
409
|
+
if (fillTrace) assertFillAlignment("after fill append");
|
|
410
|
+
// The sampler's token history gets the ACCEPTED ids only, before the
|
|
411
|
+
// resume sample — so logits processors (repetition/presence/frequency)
|
|
412
|
+
// see exactly the sequence that was emitted.
|
|
413
|
+
stepSampler.commitNumbers(ids.slice(0, accepted));
|
|
414
|
+
// Resume from the LAST ACCEPTED position's logits: the bonus token on a
|
|
415
|
+
// full accept, the correction on a partial one. Same sampler and step
|
|
416
|
+
// index as an ordinary step — the correction's KV is written by the next
|
|
417
|
+
// iteration's forward, like any sampled token.
|
|
418
|
+
const willGen = generatedNow + accepted;
|
|
419
|
+
if (willGen < maxTokens) {
|
|
420
|
+
let logits: MlxArray;
|
|
421
|
+
if (logitsAll) {
|
|
422
|
+
const V = logitsAll.shape[2]!;
|
|
423
|
+
logits = logitsAll.slice([0, accepted - 1, 0], [1, accepted, V]);
|
|
424
|
+
} else {
|
|
425
|
+
const hLast = hf.slice([0, Lf - 1, 0], [1, Lf, Hf]);
|
|
426
|
+
logits = phases.logits(1)(hLast);
|
|
427
|
+
hLast.dispose();
|
|
428
|
+
}
|
|
429
|
+
const sn = sampleStep(logits, willGen);
|
|
430
|
+
nextPending = sn.tok;
|
|
431
|
+
nextExtras = sn.extras;
|
|
432
|
+
logits.dispose();
|
|
433
|
+
ops.asyncEvalAll([nextPending, ...stepExtrasArrays(nextExtras)]);
|
|
434
|
+
}
|
|
435
|
+
} finally {
|
|
436
|
+
// A throw mid-round would otherwise leave the recurrent caches armed
|
|
437
|
+
// (their next forward raises "spec round already recorded").
|
|
438
|
+
if (roundOpen) for (const c of cache) rewindable(c).specRoundCommit?.();
|
|
439
|
+
logitsAll?.dispose();
|
|
440
|
+
hf?.dispose();
|
|
441
|
+
}
|
|
442
|
+
if (verify) fill.noteVerifyEvent(checkpointMs);
|
|
443
|
+
fill.commit(proposal, accepted);
|
|
444
|
+
if (fillTrace)
|
|
445
|
+
console.error(
|
|
446
|
+
`[fill] ${proposal.origin}/${proposal.policy} ${accepted}/${ids.length} ` +
|
|
447
|
+
`after ${generatedNow} generated (events ${fill.stats.events}, ` +
|
|
448
|
+
`injected ${fill.stats.injected}, checkpoint ${checkpointMs.toFixed(2)}ms)`,
|
|
449
|
+
);
|
|
450
|
+
return ids.slice(0, accepted);
|
|
451
|
+
};
|
|
452
|
+
|
|
453
|
+
let nextCheckpoint = options.checkpointEveryTokens && options.checkpointEveryTokens > 0
|
|
454
|
+
? (Math.floor(generated / options.checkpointEveryTokens) + 1) * options.checkpointEveryTokens
|
|
455
|
+
: Number.POSITIVE_INFINITY;
|
|
456
|
+
let tPrefill = performance.now();
|
|
457
|
+
|
|
458
|
+
try {
|
|
459
|
+
if (resuming) {
|
|
460
|
+
// The checkpoint cache already covers the complete replay prefix. Its
|
|
461
|
+
// pending token was sampled from that exact state before the snapshot,
|
|
462
|
+
// so resume starts directly at the decode loop without another forward.
|
|
463
|
+
if (needsTokenHistory) stepSampler.seedHistory(promptTokens);
|
|
464
|
+
// Device samplers return uint32. Preserve that signature on restore so
|
|
465
|
+
// compiled decode does not trace a different signed-token graph first.
|
|
466
|
+
using restoredToken = ops.fromInt32([options.initialPendingToken!], [1]);
|
|
467
|
+
pending = restoredToken.astype(Dtype.uint32);
|
|
468
|
+
ops.asyncEvalAll([pending]);
|
|
469
|
+
prefillMs = 0;
|
|
470
|
+
closePrefill?.();
|
|
471
|
+
tDecode = performance.now();
|
|
472
|
+
} else {
|
|
473
|
+
// ---- prefill ---- (composed caches convert inside their own appends)
|
|
474
|
+
tPrefill = performance.now();
|
|
475
|
+
let h0: MlxArray;
|
|
476
|
+
if (options.promptEmbeddings) {
|
|
477
|
+
if (cachedTokens !== 0)
|
|
478
|
+
throw new Error("promptEmbeddings cannot be combined with a pre-warmed cache");
|
|
479
|
+
if (needsTokenHistory)
|
|
480
|
+
stepSampler.seedHistory(promptTokens);
|
|
481
|
+
// e2b/e4b need the spliced token ids to build per-layer inputs
|
|
482
|
+
// (multimodal soft-token positions zeroed inside forwardEmbeddings).
|
|
483
|
+
const embedIds = ops.fromInt32(promptTokens, [1, promptTokens.length]);
|
|
484
|
+
h0 = binding.prefillEmbeddings!(
|
|
485
|
+
options.promptEmbeddings, cache, options.imageMask ?? null, embedIds,
|
|
486
|
+
options.multimodalMask ?? null, (promptTokens.length),
|
|
487
|
+
);
|
|
488
|
+
embedIds.dispose();
|
|
489
|
+
} else {
|
|
490
|
+
let pos = cachedTokens;
|
|
491
|
+
const snapshotAt = options.onPrefillDone && options.snapshotAt !== undefined
|
|
492
|
+
? Math.min(Math.max(options.snapshotAt, cachedTokens), promptTokens.length)
|
|
493
|
+
: undefined;
|
|
494
|
+
const tailSplit = binding.prefillTailSplit ?? COMPOSITION_DEFAULTS.prefillTailSplit;
|
|
495
|
+
|
|
496
|
+
while (true) {
|
|
497
|
+
options.signal?.throwIfAborted();
|
|
498
|
+
const step = nextPrefillStep({ length: promptTokens.length, position: pos,
|
|
499
|
+
chunkSize: prefillChunkSize, tailSplit, snapshotAt });
|
|
500
|
+
if (step.kind === "final" && needsTokenHistory) stepSampler.seedHistory(promptTokens);
|
|
501
|
+
const closeChunk = diagnostics.trace?.begin("prefill.chunk", {
|
|
502
|
+
mechanism: diagnostics.mechanism ?? "serial", startToken: step.start,
|
|
503
|
+
tokens: step.end - step.start, ...(step.kind === "final" ? { final: true } : {}),
|
|
504
|
+
});
|
|
505
|
+
let hidden: MlxArray | null;
|
|
506
|
+
try { hidden = await executeMlxPrefillStep(phases, cache, promptTokens, step); }
|
|
507
|
+
finally { closeChunk?.(); }
|
|
508
|
+
pos = step.end;
|
|
509
|
+
if (hidden) { h0 = hidden; break; }
|
|
510
|
+
if (runtime.value("MLX_BUN_PREFILL_MEM_LOG") === "1")
|
|
511
|
+
console.error(`[prefill-mem] ${pos} active ${(activeMemory() / 2 ** 30).toFixed(2)} peak ${(peakMemory() / 2 ** 30).toFixed(2)}`);
|
|
512
|
+
await new Promise<void>((resolve) => setImmediate(resolve));
|
|
513
|
+
if (step.snapshot) options.onPrefillDone?.();
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
if (options.snapshotAt === undefined || options.snapshotAt >= promptTokens.length)
|
|
517
|
+
options.onPrefillDone?.();
|
|
518
|
+
// The trace's prefill span is the uncached model-forward portion only.
|
|
519
|
+
// Token-0 lm-head, sampling, and readback are a separate additive stage.
|
|
520
|
+
if (diagnostics.trace && runtime.value("MLX_BUN_P2R_SYNC") === "1")
|
|
521
|
+
synchronize(gpuStream);
|
|
522
|
+
closePrefill?.();
|
|
523
|
+
closeTokenZero = diagnostics.trace?.begin("token_zero.total", {
|
|
524
|
+
mechanism: diagnostics.mechanism ?? "serial",
|
|
525
|
+
});
|
|
526
|
+
const [, L0, H] = h0.shape as [number, number, number];
|
|
527
|
+
const hLast = h0.slice([0, L0 - 1, 0], [1, L0, H]);
|
|
528
|
+
h0.dispose();
|
|
529
|
+
const logits0 = phases.logits(1)(hLast);
|
|
530
|
+
hLast.dispose();
|
|
531
|
+
const s0 = sampleStep(logits0, 0); // token array [1] (+ optional extras)
|
|
532
|
+
pending = s0.tok;
|
|
533
|
+
pendingExtras = s0.extras;
|
|
534
|
+
logits0.dispose();
|
|
535
|
+
// mirror mlx-lm generate_step: async-dispatch the first token's
|
|
536
|
+
// compute; the prefill clock keeps running until the token ARRIVES
|
|
537
|
+
// (first itemUint32 below). mlx-lm stops its prompt clock at the
|
|
538
|
+
// first yielded token, which bills the prefill→decode boundary
|
|
539
|
+
// (allocator reclaim of prefill transients + first-step dispatch)
|
|
540
|
+
// to prompt_time, not decode — replicated so cross-stack decode
|
|
541
|
+
// tok/s measure the same quantity. The boundary cost is real and
|
|
542
|
+
// scales with prompt length; it belongs to "having prefilled".
|
|
543
|
+
ops.asyncEvalAll([pending, ...stepExtrasArrays(pendingExtras)]);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// ---- decode (pipelined) ----
|
|
547
|
+
// The compiled step is the graph binding's, chosen once for this run's caches; it has no fallback.
|
|
548
|
+
decoder = options.decodePolicy?.compiledDecode === false ? null
|
|
549
|
+
: binding.createDecode?.({ hasAdapters: !!options.adapters?.length }) ?? null;
|
|
550
|
+
let stop = false;
|
|
551
|
+
/** Token id read eagerly at the top of the loop for grammar (reused for
|
|
552
|
+
* the yield, avoiding a second readback). -1 when grammar is off — the
|
|
553
|
+
* pipelined path keeps its deferred itemUint32 below. */
|
|
554
|
+
let grammarTok = -1;
|
|
555
|
+
// Jump-forward decoding in this direct loop (opt-in, the composition's
|
|
556
|
+
// grammarJump; the scheduler's grammar rows jump through the span method
|
|
557
|
+
// the planner binds): when the grammar forces a unique continuation, emit its retokenized ids without
|
|
558
|
+
// per-token forwards — ONE multi-token forward carries them into the KV
|
|
559
|
+
// (see GrammarController.jumpForward for the contract + the fidelity
|
|
560
|
+
// note on why this is opt-in). Excluded when logprobs are requested
|
|
561
|
+
// (jumped tokens are never sampled, so they'd have no logprobs rows).
|
|
562
|
+
const grammarJump = options.decodePolicy?.grammarJump ?? shouldUseGrammarJump(options, binding.grammarJump);
|
|
563
|
+
// Yield token zero before its own decode forward. The captured runtime
|
|
564
|
+
// keeps this scheduling choice stable across consumer awaits.
|
|
565
|
+
const earlyFirstToken = runtime.flag("MLX_BUN_EARLY_FIRST_TOKEN", true) &&
|
|
566
|
+
maxTokens > 1 && !resuming && !fillOn && !options.grammar;
|
|
567
|
+
while (!stop) {
|
|
568
|
+
options.signal?.throwIfAborted();
|
|
569
|
+
const cur = pending!;
|
|
570
|
+
const stepIndex = generated;
|
|
571
|
+
let curExtras = pendingExtras;
|
|
572
|
+
let yieldedEarly = false;
|
|
573
|
+
if (earlyFirstToken && stepIndex === 0) {
|
|
574
|
+
const firstToken = ops.itemUint32(cur);
|
|
575
|
+
options.signal?.throwIfAborted();
|
|
576
|
+
if (!eosTokenIds.includes(firstToken)) {
|
|
577
|
+
prefillMs = performance.now() - tPrefill;
|
|
578
|
+
closeTokenZero?.();
|
|
579
|
+
closeTokenZero = undefined;
|
|
580
|
+
tDecode = performance.now();
|
|
581
|
+
const logprobs = readStepExtras(curExtras);
|
|
582
|
+
curExtras = null;
|
|
583
|
+
pendingExtras = null;
|
|
584
|
+
// Count before yielding so an early return reports this token. Its
|
|
585
|
+
// KV is still absent; forwarded describes the prompt-only cache.
|
|
586
|
+
generated++;
|
|
587
|
+
yieldedEarly = true;
|
|
588
|
+
earlyUnforwardedToken = firstToken;
|
|
589
|
+
yield { token: firstToken, index: stepIndex, ...(logprobs ? { logprobs } : {}) };
|
|
590
|
+
earlyUnforwardedToken = null;
|
|
591
|
+
options.signal?.throwIfAborted();
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
// Keep current extras owned by the run until readback succeeds. A
|
|
595
|
+
// forward/grammar failure must leave them reachable by finally.
|
|
596
|
+
// Grammar advance (packages/inference/src/sampling/grammar.ts): acceptToken needs the token id as a
|
|
597
|
+
// JS number, which the pipelined loop defers (it operates on device
|
|
598
|
+
// arrays). So grammar requests eager-read cur here, advance the matcher,
|
|
599
|
+
// and await the async mask precompute — which overlaps the GPU forward
|
|
600
|
+
// dispatched just below. This trades the readback/forward overlap for
|
|
601
|
+
// correctness (the mask for step n+1 must reflect token n). Non-grammar
|
|
602
|
+
// requests keep the fast pipelined loop untouched.
|
|
603
|
+
//
|
|
604
|
+
// F1 fix (batched-lane plan): always eager-read `grammarTok` when
|
|
605
|
+
// grammar is on, even at the max_tokens boundary — the emitted token
|
|
606
|
+
// reuses it unconditionally. The OLD code gated the READ on
|
|
607
|
+
// generated+1 < maxTokens but emitted grammarTok regardless, so the LAST
|
|
608
|
+
// iteration skipped the refresh and emitted a stale/garbage token (the
|
|
609
|
+
// previous step's token, or -1 when max_tokens=1) → truncated JSON ended
|
|
610
|
+
// on a corrupted token + cacheTokens recorded the wrong id. Now only
|
|
611
|
+
// accept()/ready() (which prepare the NEXT step's mask) are gated.
|
|
612
|
+
/** Forced ids to emit after cur this iteration (jump-forward), else null. */
|
|
613
|
+
let jumpEmit: number[] | null = null;
|
|
614
|
+
if (options.grammar) {
|
|
615
|
+
grammarTok = ops.itemUint32(cur);
|
|
616
|
+
if (stepIndex + 1 < maxTokens) {
|
|
617
|
+
options.grammar.accept(grammarTok);
|
|
618
|
+
await options.grammar.ready();
|
|
619
|
+
if (grammarJump && !options.grammar.isTerminated) {
|
|
620
|
+
jumpEmit = options.grammar.jumpForward(maxTokens - (stepIndex + 1));
|
|
621
|
+
// jumpForward advanced the matcher and fired the post-jump mask
|
|
622
|
+
// fill; it must be ready before this iteration's sampleStep.
|
|
623
|
+
if (jumpEmit) await options.grammar.ready();
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
// build step n+1's graph from the *unread* pending token
|
|
628
|
+
nextPending = null;
|
|
629
|
+
nextExtras = null;
|
|
630
|
+
// When the grammar has terminated (a complete valid JSON/schema
|
|
631
|
+
// accepted), there are no valid tokens left — skip building the next
|
|
632
|
+
// step so the sampler never sees an all--inf distribution.
|
|
633
|
+
if (jumpEmit) {
|
|
634
|
+
// JUMP iteration: one [1, 1+m] forward carries cur AND the forced ids
|
|
635
|
+
// into the KV (they are all committed content — jumpForward's
|
|
636
|
+
// contract); the next sampled token, if the budget and grammar allow
|
|
637
|
+
// one, comes from its last position. Compiled decode resumes on the
|
|
638
|
+
// following iteration over the same caches.
|
|
639
|
+
pushHistory(cur);
|
|
640
|
+
stepSampler.commitNumbers(jumpEmit);
|
|
641
|
+
const chunk = [grammarTok, ...jumpEmit];
|
|
642
|
+
const willGen = stepIndex + 1 + jumpEmit.length;
|
|
643
|
+
await appendGrammarSpan(phases, cache, chunk,
|
|
644
|
+
ids => { forwarded.push(...ids); },
|
|
645
|
+
willGen < maxTokens && !options.grammar!.isTerminated ? logits => {
|
|
646
|
+
const sampled = sampleStep(logits, willGen);
|
|
647
|
+
nextPending = sampled.tok;
|
|
648
|
+
nextExtras = sampled.extras;
|
|
649
|
+
} : undefined);
|
|
650
|
+
if (nextPending) ops.asyncEvalAll([nextPending, ...stepExtrasArrays(nextExtras)]);
|
|
651
|
+
} else if (stepIndex + 1 < maxTokens && !options.grammar?.isTerminated) {
|
|
652
|
+
pushHistory(cur);
|
|
653
|
+
let logits: MlxArray;
|
|
654
|
+
let evalWith: MlxArray[] = [];
|
|
655
|
+
if (decoder) ({ logits, evalWith } = decoder.step(cur, cache));
|
|
656
|
+
else {
|
|
657
|
+
const ids = ops.reshape(cur, [1, 1]);
|
|
658
|
+
const h = await phases.decode(1)(ids, cache, null);
|
|
659
|
+
ids.dispose();
|
|
660
|
+
logits = phases.logits(1)(h);
|
|
661
|
+
h.dispose();
|
|
662
|
+
}
|
|
663
|
+
const sn = sampleStep(logits, stepIndex + 1);
|
|
664
|
+
nextPending = sn.tok;
|
|
665
|
+
nextExtras = sn.extras;
|
|
666
|
+
logits.dispose();
|
|
667
|
+
ops.asyncEvalAll([nextPending, ...stepExtrasArrays(nextExtras), ...evalWith]);
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
// sync-read step n's token while n+1 computes
|
|
671
|
+
// sync-read step n's token while n+1 computes (grammar already read it
|
|
672
|
+
// eagerly above — reuse to avoid a second GPU sync)
|
|
673
|
+
const token = options.grammar ? grammarTok : ops.itemUint32(cur);
|
|
674
|
+
options.signal?.throwIfAborted();
|
|
675
|
+
if (generated === 0) {
|
|
676
|
+
// first token arrived: prompt clock stops, decode clock starts
|
|
677
|
+
// (mlx-lm stream_generate's n==0 clock swap; the first token is
|
|
678
|
+
// "free" on the decode clock there too)
|
|
679
|
+
prefillMs = performance.now() - tPrefill;
|
|
680
|
+
closeTokenZero?.();
|
|
681
|
+
closeTokenZero = undefined;
|
|
682
|
+
tDecode = performance.now();
|
|
683
|
+
}
|
|
684
|
+
cur.dispose();
|
|
685
|
+
pending = null;
|
|
686
|
+
if (!yieldedEarly) generated++;
|
|
687
|
+
// if a next-step graph was built, this token's KV entered the cache
|
|
688
|
+
// (jump iterations pushed the whole chunk already)
|
|
689
|
+
if (nextPending !== null && !jumpEmit) forwarded.push(token);
|
|
690
|
+
|
|
691
|
+
if (eosTokenIds.includes(token)) {
|
|
692
|
+
finishReason = "stop";
|
|
693
|
+
disposeStepExtras(curExtras);
|
|
694
|
+
pendingExtras = null;
|
|
695
|
+
nextPending?.dispose();
|
|
696
|
+
nextPending = null;
|
|
697
|
+
disposeStepExtras(nextExtras);
|
|
698
|
+
nextExtras = null;
|
|
699
|
+
stop = true;
|
|
700
|
+
} else {
|
|
701
|
+
// ---- token fast-forwarding (K3): APPEND, then yield ----------------
|
|
702
|
+
// The engine already knows the next m tokens, so it writes them into
|
|
703
|
+
// the KV itself with an append and resumes sampling after them. No
|
|
704
|
+
// draft, no verify, no rollback: an injected token is indistinguishable
|
|
705
|
+
// to the model from one it sampled. The append happens BEFORE any of
|
|
706
|
+
// the burst's yields, which is what makes a consumer break mid-burst
|
|
707
|
+
// safe — `forwarded` already describes the cache exactly.
|
|
708
|
+
let fillEmit: number[] | null = null;
|
|
709
|
+
if (fillOn) {
|
|
710
|
+
if (jumpEmit)
|
|
711
|
+
throw new Error("fill and grammar jump-forward cannot share an iteration");
|
|
712
|
+
// A fill needs an in-flight step to ride; with nextPending null this
|
|
713
|
+
// token's KV was never written and the generation ends here. push()
|
|
714
|
+
// still runs so the session's history (and echo index) stay exact.
|
|
715
|
+
const proposal = options.fill!.push(
|
|
716
|
+
token, nextPending !== null ? maxTokens - generated : 0,
|
|
717
|
+
);
|
|
718
|
+
if (proposal && nextPending !== null) {
|
|
719
|
+
const emitted = await applyProposal(proposal, generated);
|
|
720
|
+
if (emitted.length) fillEmit = emitted;
|
|
721
|
+
} else if (proposal) {
|
|
722
|
+
options.fill!.commit(proposal, 0); // unreachable at budget 0; belt
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
// readExtras before the yield: if the consumer breaks at this yield,
|
|
726
|
+
// the extras are already read and disposed.
|
|
727
|
+
const logprobs = readStepExtras(curExtras);
|
|
728
|
+
pendingExtras = null;
|
|
729
|
+
if (!yieldedEarly) yield { token, index: generated - 1, ...(logprobs ? { logprobs } : {}) };
|
|
730
|
+
options.signal?.throwIfAborted();
|
|
731
|
+
// mlx-lm generate_step: clear_cache after token 0 (drops the
|
|
732
|
+
// remaining prefill transients) and every 256 tokens after
|
|
733
|
+
if ((generated - 1) % 256 === 0) clearCache();
|
|
734
|
+
// Jump-forward burst: the forced ids follow cur, one yield each (the
|
|
735
|
+
// consumer's stop-sequence matcher and detokenizer see the same
|
|
736
|
+
// one-at-a-time stream shape as always). Their KV is already in the
|
|
737
|
+
// cache (the chunk forward above); a consumer break mid-burst is
|
|
738
|
+
// safe — `forwarded` already reflects the cache exactly.
|
|
739
|
+
if (jumpEmit) {
|
|
740
|
+
for (const jt of jumpEmit) {
|
|
741
|
+
options.signal?.throwIfAborted();
|
|
742
|
+
generated++;
|
|
743
|
+
yield { token: jt, index: generated - 1 };
|
|
744
|
+
if ((generated - 1) % 256 === 0) clearCache();
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
// Fill burst: same one-yield-per-token shape as the jump burst, so
|
|
748
|
+
// CompletionSink / StopMatcher / StreamDecoder see an ordinary stream
|
|
749
|
+
// (a stop sequence inside an injected span fires exactly where it
|
|
750
|
+
// would have in an unfilled run).
|
|
751
|
+
if (fillEmit) {
|
|
752
|
+
for (const ft of fillEmit) {
|
|
753
|
+
options.signal?.throwIfAborted();
|
|
754
|
+
generated++;
|
|
755
|
+
yield { token: ft, index: generated - 1 };
|
|
756
|
+
if ((generated - 1) % 256 === 0) clearCache();
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
if (nextPending === null) {
|
|
760
|
+
stop = true;
|
|
761
|
+
} else {
|
|
762
|
+
pending = nextPending;
|
|
763
|
+
nextPending = null;
|
|
764
|
+
pendingExtras = nextExtras;
|
|
765
|
+
nextExtras = null;
|
|
766
|
+
}
|
|
767
|
+
if (
|
|
768
|
+
pending !== null &&
|
|
769
|
+
options.onDecodeCheckpoint &&
|
|
770
|
+
generated >= nextCheckpoint
|
|
771
|
+
) {
|
|
772
|
+
const pendingToken = ops.itemUint32(pending);
|
|
773
|
+
await options.onDecodeCheckpoint({
|
|
774
|
+
cacheTokens: [...promptTokens, ...forwarded],
|
|
775
|
+
caches: cache,
|
|
776
|
+
generatedTokens: generated,
|
|
777
|
+
pendingToken,
|
|
778
|
+
});
|
|
779
|
+
const every = options.checkpointEveryTokens!;
|
|
780
|
+
while (nextCheckpoint <= generated) nextCheckpoint += every;
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
decodeMs = performance.now() - tDecode;
|
|
785
|
+
finished = true;
|
|
786
|
+
return makeStats();
|
|
787
|
+
} catch (e) {
|
|
788
|
+
threw = true;
|
|
789
|
+
executionError = e;
|
|
790
|
+
throw e;
|
|
791
|
+
} finally {
|
|
792
|
+
try {
|
|
793
|
+
// An early consumer return leaves token zero outside the retained KV.
|
|
794
|
+
// Finish that one M=1 forward before handing caller-owned caches back,
|
|
795
|
+
// matching the ordinary pipeline's boundary. Otherwise a later prefix
|
|
796
|
+
// hit folds the token into an M>1 prefill and changes native reductions.
|
|
797
|
+
// Aborted requests and caches owned by this run need no retained state.
|
|
798
|
+
if (!ownsCache && !threw && !options.signal?.aborted && earlyUnforwardedToken !== null) {
|
|
799
|
+
const ids = ops.reshape(pending!, [1, 1]);
|
|
800
|
+
try {
|
|
801
|
+
const hidden = await phases.decode(1)(ids, cache, null);
|
|
802
|
+
try { evalCacheState(cache); }
|
|
803
|
+
finally { hidden.dispose(); }
|
|
804
|
+
} finally { ids.dispose(); }
|
|
805
|
+
forwarded.push(earlyUnforwardedToken);
|
|
806
|
+
}
|
|
807
|
+
} catch (error) {
|
|
808
|
+
threw = true;
|
|
809
|
+
executionError = error;
|
|
810
|
+
throw error;
|
|
811
|
+
} finally {
|
|
812
|
+
if (!finished) {
|
|
813
|
+
pending?.dispose();
|
|
814
|
+
nextPending?.dispose();
|
|
815
|
+
disposeStepExtras(pendingExtras);
|
|
816
|
+
disposeStepExtras(nextExtras);
|
|
817
|
+
}
|
|
818
|
+
try {
|
|
819
|
+
const closing = decoder?.close();
|
|
820
|
+
if (closing) await closing;
|
|
821
|
+
} catch (error) {
|
|
822
|
+
if (threw) throw new AggregateError([executionError, error],
|
|
823
|
+
"AR execution and decoder cleanup failed", { cause: executionError });
|
|
824
|
+
throw error;
|
|
825
|
+
} finally {
|
|
826
|
+
if (ownsCache) for (const c of cache) c.dispose();
|
|
827
|
+
// TokenizerInfo is process-cached; this request owns the matcher.
|
|
828
|
+
options.grammar?.dispose();
|
|
829
|
+
stepSampler.dispose();
|
|
830
|
+
}
|
|
831
|
+
if (!finished && !threw) {
|
|
832
|
+
// forced early return (consumer break at a yield): still report
|
|
833
|
+
// stats — `forwarded` only lists tokens whose KV actually entered
|
|
834
|
+
// the cache, so cacheTokens stays exact for PromptCache.put().
|
|
835
|
+
decodeMs = performance.now() - tDecode;
|
|
836
|
+
return makeStats();
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
}
|