@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,464 @@
|
|
|
1
|
+
// Token fast-forwarding (K3) — the per-request proposal table.
|
|
2
|
+
//
|
|
3
|
+
// DOCTRINE. This is PURE CONTEXT EXTENSION, not speculation. A transformer is
|
|
4
|
+
// a next-token function over a token sequence; it has no memory of who wrote
|
|
5
|
+
// which token, so an APPENDED token is indistinguishable from a sampled one.
|
|
6
|
+
// When the engine already knows the next m tokens (a chat template's tool-call
|
|
7
|
+
// scaffold is a deterministic function of the request's `tools`), it appends
|
|
8
|
+
// them itself in one append and resumes sampling after them. The append can
|
|
9
|
+
// use several execution chunks while retaining the complete span. No
|
|
10
|
+
// draft, no verify, no rollback, and no comparison against what the model
|
|
11
|
+
// "would have" produced — nothing here routes through
|
|
12
|
+
// packages/inference/src/generation/speculative/.
|
|
13
|
+
//
|
|
14
|
+
// The echo tier (K3c) adds a weaker claim over the SAME mechanism: a span
|
|
15
|
+
// copied from earlier in the session, held under policy "verify" — the engine
|
|
16
|
+
// keeps the prefix the model agrees with (read from the free logits of the
|
|
17
|
+
// very same forward) and rewinds the rest. See ./proposal.ts for the two
|
|
18
|
+
// policies and ./echo-index.ts for the branch-point rule.
|
|
19
|
+
//
|
|
20
|
+
// This module is bookkeeping only: it owns the running history, runs the
|
|
21
|
+
// sources in priority order, clamps what they return, and counts. It never
|
|
22
|
+
// touches MLX, the tokenizer, or the template. Row COMPILATION is
|
|
23
|
+
// ./schema-rows.ts.
|
|
24
|
+
import { runtimeFlag, runtimeNumber, runtimeValue } from "../../runtime/config";
|
|
25
|
+
import { EchoSource, type EchoConfig } from "./echo-index";
|
|
26
|
+
import type {
|
|
27
|
+
Proposal, ProposalOrigin, ProposalPolicy, ProposalSource, TokenView,
|
|
28
|
+
} from "./proposal";
|
|
29
|
+
|
|
30
|
+
export type { Proposal, ProposalPolicy, ProposalSource, TokenView };
|
|
31
|
+
|
|
32
|
+
/** Which producer a strict row came from (telemetry + the mismatch policy). */
|
|
33
|
+
export type FillKind = "scaffold" | "name" | "key" | "close" | "turn-end";
|
|
34
|
+
|
|
35
|
+
export interface FillRow {
|
|
36
|
+
/** Token ids that must be the TAIL of the generated history for this row to
|
|
37
|
+
* fire (matched as an exact suffix ending at the token just pushed). */
|
|
38
|
+
trigger: number[];
|
|
39
|
+
/** Ids appended immediately after the trigger. Sliced out of a full
|
|
40
|
+
* template rendering — never `encode(fragment)` in isolation. */
|
|
41
|
+
emit: number[];
|
|
42
|
+
kind: FillKind;
|
|
43
|
+
/** Template-derived rows need a parser-owned context before asserting. */
|
|
44
|
+
requiresContext?: true;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface StrictFillContext {
|
|
48
|
+
observe(ids: readonly number[]): void;
|
|
49
|
+
allows(row: FillRow): boolean;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Echo-tier (K3c) configuration; null disables the source entirely. */
|
|
53
|
+
export interface FillEchoConfig {
|
|
54
|
+
k: number;
|
|
55
|
+
maxSpan: number;
|
|
56
|
+
maxCandidates: number;
|
|
57
|
+
indexMax: number;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface FillPlan {
|
|
61
|
+
rows: FillRow[];
|
|
62
|
+
/** Echo tier: null = strict rows only (MLX_BUN_FILL=strict). */
|
|
63
|
+
echo: FillEchoConfig | null;
|
|
64
|
+
/** Token ids that legitimately END an injected span — the closing quote /
|
|
65
|
+
* markup that follows an argument value, derived from the template by the
|
|
66
|
+
* strict-row compiler. A span is cut so it ends AT the first delimiter it
|
|
67
|
+
* contains, never past one. */
|
|
68
|
+
delimiters?: ReadonlySet<number>;
|
|
69
|
+
/** EOS ids. An injected span is cut BEFORE the first one: end-of-generation
|
|
70
|
+
* is the model's decision, never ours. (The grammar jump burst yields its
|
|
71
|
+
* ids with NO eos check — that bug is deliberately not inherited.) */
|
|
72
|
+
eos: number[];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface FillStats {
|
|
76
|
+
/** Fills that put at least one token into the stream. */
|
|
77
|
+
events: number;
|
|
78
|
+
/** Total injected tokens (billed as generated). */
|
|
79
|
+
injected: number;
|
|
80
|
+
/** …from strict schema/template rows (always policy "assert"). */
|
|
81
|
+
strict: number;
|
|
82
|
+
/** …from the echo index. */
|
|
83
|
+
echo: number;
|
|
84
|
+
/** Length of each injected span, in event order. */
|
|
85
|
+
spanLens: number[];
|
|
86
|
+
/** In-flight sampled tokens dropped unexamined at an ASSERT fill — a
|
|
87
|
+
* discarded pipeline dispatch, NOT a rejected draft. */
|
|
88
|
+
wastedSamples: number;
|
|
89
|
+
/** Tool-call parses that failed after strict rows had fired. */
|
|
90
|
+
parseFallback: number;
|
|
91
|
+
/** Spans shortened by a clamp (EOS / delimiter / max span / budget). */
|
|
92
|
+
indexTruncated: number;
|
|
93
|
+
/** Tokens the model actually sampled (the real decode steps). */
|
|
94
|
+
decodeSteps: number;
|
|
95
|
+
/** Verify-policy proposals that reached the forward. */
|
|
96
|
+
verifyEvents: number;
|
|
97
|
+
/** Tokens accepted under verify (the model's own argmax agreed). */
|
|
98
|
+
verifyAccepted: number;
|
|
99
|
+
/** Tokens proposed under verify and dropped (incl. rejected-at-position-0,
|
|
100
|
+
* which costs nothing — that check happens before the forward). */
|
|
101
|
+
verifyRejected: number;
|
|
102
|
+
/** Verify proposals dropped because this model's caches cannot rewind. */
|
|
103
|
+
verifyUnsupported: number;
|
|
104
|
+
/** Wall time in cache checkpoint / rollback / commit (verify only). */
|
|
105
|
+
checkpointMs: number;
|
|
106
|
+
/** Echo spans that stopped because the session history forked. */
|
|
107
|
+
branchStops: number;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const emptyStats = (): FillStats => ({
|
|
111
|
+
events: 0, injected: 0, strict: 0, echo: 0, spanLens: [],
|
|
112
|
+
wastedSamples: 0, parseFallback: 0, indexTruncated: 0, decodeSteps: 0,
|
|
113
|
+
verifyEvents: 0, verifyAccepted: 0, verifyRejected: 0, verifyUnsupported: 0,
|
|
114
|
+
checkpointMs: 0, branchStops: 0,
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
export type FillMode = "off" | "strict" | "echo";
|
|
118
|
+
|
|
119
|
+
/** MLX_BUN_FILL=off|strict|echo (default off). `echo` is additive: the strict
|
|
120
|
+
* rows still apply (and stay policy "assert"); the echo index joins as a
|
|
121
|
+
* second, weaker source. Lab tier — default off, A/B before any default. */
|
|
122
|
+
export function resolveFillMode(raw = runtimeValue("MLX_BUN_FILL")): FillMode {
|
|
123
|
+
if (raw === undefined || raw === "" || raw === "off" || raw === "0") return "off";
|
|
124
|
+
if (raw === "strict" || raw === "1") return "strict";
|
|
125
|
+
if (raw === "echo") return "echo";
|
|
126
|
+
throw new Error(`MLX_BUN_FILL=${raw}: expected off|strict|echo`);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** Hard cap on one injected span (MLX_BUN_FILL_MAX_SPAN, default 32). */
|
|
130
|
+
export function fillMaxSpan(): number {
|
|
131
|
+
const n = Math.floor(runtimeNumber("MLX_BUN_FILL_MAX_SPAN", 32));
|
|
132
|
+
return n >= 2 ? n : 2;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** Assert-append chunk cap; zero selects the model's qualified limit. */
|
|
136
|
+
export function fillAppendChunkSize(): number {
|
|
137
|
+
return Math.max(0, Math.floor(runtimeNumber("MLX_BUN_FILL_APPEND_CHUNK_SIZE", 0)));
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** Echo-tier knobs. k: anchor length (MLX_BUN_FILL_K, default 8 — the corpus
|
|
141
|
+
* study's token-level threshold). candidates: nearest-occurrence bucket cap
|
|
142
|
+
* (MLX_BUN_FILL_CANDIDATES, default 24). indexMax: token cap on the growing
|
|
143
|
+
* index (MLX_BUN_FILL_INDEX_MAX, default 131072). */
|
|
144
|
+
export function fillEchoConfig(): FillEchoConfig {
|
|
145
|
+
return {
|
|
146
|
+
k: Math.max(2, Math.floor(runtimeNumber("MLX_BUN_FILL_K", 8))),
|
|
147
|
+
maxSpan: fillMaxSpan(),
|
|
148
|
+
maxCandidates: Math.max(1, Math.floor(runtimeNumber("MLX_BUN_FILL_CANDIDATES", 24))),
|
|
149
|
+
indexMax: Math.max(1024, Math.floor(runtimeNumber("MLX_BUN_FILL_INDEX_MAX", 131072))),
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** MLX_BUN_FILL_TRACE=1 — cache-alignment assertions + per-event logging on
|
|
154
|
+
* the engine's fill path. Off by default (the invariant it checks is the
|
|
155
|
+
* easiest bug in the feature, and the check costs a JS compare). */
|
|
156
|
+
export function fillTraceEnabled(): boolean {
|
|
157
|
+
return runtimeFlag("MLX_BUN_FILL_TRACE", false) || fillTracePath() !== null;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** MLX_BUN_FILL_TRACE=<file.jsonl> — additionally append one record per
|
|
161
|
+
* proposal: the proposed ids/text next to what the model's OWN logits said
|
|
162
|
+
* at every span position (position 0 = the in-flight sample, j>0 = argmax
|
|
163
|
+
* after ids[j-1]), for BOTH policies. Under `assert` the readback is
|
|
164
|
+
* trace-only (one lm_head over the span) — the served path never pays it.
|
|
165
|
+
* This is the list that answers "would the model have produced this span?"
|
|
166
|
+
* without a verify pass in production. */
|
|
167
|
+
export function fillTracePath(): string | null {
|
|
168
|
+
const raw = runtimeValue("MLX_BUN_FILL_TRACE");
|
|
169
|
+
if (raw === undefined || raw === "" || raw === "0" || raw === "1" || raw === "true") return null;
|
|
170
|
+
return raw;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export interface FillTraceRecord {
|
|
174
|
+
ts: string;
|
|
175
|
+
origin: string;
|
|
176
|
+
policy: string;
|
|
177
|
+
generated: number;
|
|
178
|
+
proposedLen: number;
|
|
179
|
+
accepted: number;
|
|
180
|
+
/** Index of the first position where the model disagreed, or -1. */
|
|
181
|
+
firstMismatch: number;
|
|
182
|
+
proposed: number[];
|
|
183
|
+
actual: number[];
|
|
184
|
+
proposedText?: string;
|
|
185
|
+
actualText?: string;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** Strict schema/template rows as a ProposalSource. Always policy "assert":
|
|
189
|
+
* the spans are sliced out of the model's own chat template, so they are
|
|
190
|
+
* determined by construction, not guessed. */
|
|
191
|
+
export class StrictRowSource implements ProposalSource {
|
|
192
|
+
readonly name = "strict";
|
|
193
|
+
readonly #byLastTrigger = new Map<number, FillRow[]>();
|
|
194
|
+
/** Longest trigger — the tail this source needs to match against. */
|
|
195
|
+
readonly maxTrigger: number = 1;
|
|
196
|
+
get windowNeeded(): number { return this.maxTrigger; }
|
|
197
|
+
#enabled = true;
|
|
198
|
+
|
|
199
|
+
constructor(rows: readonly FillRow[], readonly context?: StrictFillContext) {
|
|
200
|
+
for (const row of rows) {
|
|
201
|
+
if (row.trigger.length === 0 || row.emit.length === 0) continue;
|
|
202
|
+
this.maxTrigger = Math.max(this.maxTrigger, row.trigger.length);
|
|
203
|
+
const last = row.trigger[row.trigger.length - 1]!;
|
|
204
|
+
const bucket = this.#byLastTrigger.get(last);
|
|
205
|
+
if (bucket) bucket.push(row);
|
|
206
|
+
else this.#byLastTrigger.set(last, [row]);
|
|
207
|
+
}
|
|
208
|
+
// Longest trigger wins (most specific row); plan order breaks ties.
|
|
209
|
+
for (const bucket of this.#byLastTrigger.values())
|
|
210
|
+
bucket.sort((a, b) => b.trigger.length - a.trigger.length);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
get enabled(): boolean { return this.#enabled; }
|
|
214
|
+
disable(): void { this.#enabled = false; }
|
|
215
|
+
|
|
216
|
+
observe(ids: readonly number[]): void { this.context?.observe(ids); }
|
|
217
|
+
|
|
218
|
+
propose(view: TokenView): Proposal | null {
|
|
219
|
+
if (!this.#enabled) return null;
|
|
220
|
+
const tail = view.tail(this.maxTrigger);
|
|
221
|
+
const last = tail[tail.length - 1];
|
|
222
|
+
if (last === undefined) return null;
|
|
223
|
+
const bucket = this.#byLastTrigger.get(last);
|
|
224
|
+
if (!bucket) return null;
|
|
225
|
+
for (const row of bucket) {
|
|
226
|
+
const t = row.trigger;
|
|
227
|
+
if (t.length > tail.length) continue;
|
|
228
|
+
const base = tail.length - t.length;
|
|
229
|
+
let ok = true;
|
|
230
|
+
for (let i = 0; i < t.length; i++) {
|
|
231
|
+
if (tail[base + i] !== t[i]) { ok = false; break; }
|
|
232
|
+
}
|
|
233
|
+
if (ok && (!row.requiresContext || this.context?.allows(row))) {
|
|
234
|
+
return {
|
|
235
|
+
ids: [...row.emit],
|
|
236
|
+
policy: "assert",
|
|
237
|
+
// The scaffold and the close come from the TEMPLATE; the name and
|
|
238
|
+
// key spans are what the request's SCHEMA determines.
|
|
239
|
+
origin: row.kind === "name" || row.kind === "key" ? "schema" : "template",
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/** One request's proposal sources plus the running match state.
|
|
248
|
+
*
|
|
249
|
+
* CONTRACT. push() returns a proposal; the caller MUST then call commit()
|
|
250
|
+
* exactly once with the number of ids it actually emitted (all of them for an
|
|
251
|
+
* assert, the accepted prefix for a verify, 0 when it declined). Nothing
|
|
252
|
+
* enters the session's history until commit — so a partial accept needs no
|
|
253
|
+
* rollback here. */
|
|
254
|
+
export class FillSession {
|
|
255
|
+
readonly stats: FillStats = emptyStats();
|
|
256
|
+
/** Execution chunk size, independent of the number of tokens injected. */
|
|
257
|
+
readonly appendChunkSize: number;
|
|
258
|
+
readonly #sources: ProposalSource[];
|
|
259
|
+
readonly #strict: StrictRowSource;
|
|
260
|
+
readonly #eos: Set<number>;
|
|
261
|
+
readonly #delimiters: ReadonlySet<number>;
|
|
262
|
+
readonly #maxSpan: number;
|
|
263
|
+
readonly #window: number;
|
|
264
|
+
/** Bounded tail of the token history — all a source can see directly (the
|
|
265
|
+
* echo index keeps its own full copy via observe()). */
|
|
266
|
+
#tail: number[] = [];
|
|
267
|
+
#length = 0;
|
|
268
|
+
#budget = Number.POSITIVE_INFINITY;
|
|
269
|
+
|
|
270
|
+
/** Optional token→text decoder for the proposal trace (set by the serve
|
|
271
|
+
* layer, which owns the tokenizer). */
|
|
272
|
+
readonly decode: ((ids: readonly number[]) => string) | null;
|
|
273
|
+
|
|
274
|
+
constructor(
|
|
275
|
+
readonly plan: FillPlan,
|
|
276
|
+
promptIds: readonly number[],
|
|
277
|
+
options: {
|
|
278
|
+
maxSpan?: number;
|
|
279
|
+
appendChunkSize?: number;
|
|
280
|
+
sources?: ProposalSource[];
|
|
281
|
+
decode?: (ids: readonly number[]) => string;
|
|
282
|
+
strictContext?: StrictFillContext;
|
|
283
|
+
} = {},
|
|
284
|
+
) {
|
|
285
|
+
this.decode = options.decode ?? null;
|
|
286
|
+
this.appendChunkSize = options.appendChunkSize ?? fillAppendChunkSize();
|
|
287
|
+
if (!Number.isSafeInteger(this.appendChunkSize) || this.appendChunkSize < 0)
|
|
288
|
+
throw new Error("fill appendChunkSize must be a nonnegative safe integer");
|
|
289
|
+
this.#eos = new Set(plan.eos);
|
|
290
|
+
this.#delimiters = plan.delimiters ?? new Set<number>();
|
|
291
|
+
this.#maxSpan = Math.max(2, Math.floor(options.maxSpan ?? fillMaxSpan()));
|
|
292
|
+
this.#strict = new StrictRowSource(plan.rows, options.strictContext);
|
|
293
|
+
const echo = plan.echo
|
|
294
|
+
? new EchoSource(
|
|
295
|
+
{ ...plan.echo, maxSpan: this.#maxSpan, delimiters: this.#delimiters } as EchoConfig,
|
|
296
|
+
promptIds,
|
|
297
|
+
)
|
|
298
|
+
: null;
|
|
299
|
+
// Priority: determined before guessed, then anything the caller injected
|
|
300
|
+
// (the seam the spec lane's draft rows would adapt onto — see
|
|
301
|
+
// proposal.ts MIGRATION NOTE).
|
|
302
|
+
this.#sources = [
|
|
303
|
+
this.#strict,
|
|
304
|
+
...(echo ? [echo] : []),
|
|
305
|
+
...(options.sources ?? []),
|
|
306
|
+
];
|
|
307
|
+
// Every source declares the tail it needs; the session retains the max.
|
|
308
|
+
this.#window = Math.max(
|
|
309
|
+
1, ...this.#sources.map((s) => s.windowNeeded ?? 1),
|
|
310
|
+
);
|
|
311
|
+
// A row may be triggered by the prompt's own tail (a re-rendered turn that
|
|
312
|
+
// ends mid-scaffold), so the history starts seeded.
|
|
313
|
+
this.#tail = promptIds.slice(Math.max(0, promptIds.length - this.#window));
|
|
314
|
+
this.#length = promptIds.length;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/** Are strict rows still armed? (Cleared by noteParseFailure.) */
|
|
318
|
+
get strictEnabled(): boolean { return this.#strict.enabled; }
|
|
319
|
+
|
|
320
|
+
/** Feed one SAMPLED token, then ask the sources what follows. `budget` is
|
|
321
|
+
* the caller's remaining allowance (max_tokens − generated). */
|
|
322
|
+
push(tokenId: number, budget: number = Number.POSITIVE_INFINITY): Proposal | null {
|
|
323
|
+
this.observe(tokenId);
|
|
324
|
+
return this.propose(budget);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/** Record sampled output independently of when the method requests proposals. */
|
|
328
|
+
observe(tokenId: number): void {
|
|
329
|
+
this.stats.decodeSteps++;
|
|
330
|
+
this.#append([tokenId]);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/** Select a continuation after a committed output boundary. Methods may
|
|
334
|
+
* request only verified proposals while retaining ordinary sampled policy. */
|
|
335
|
+
propose(budget: number = Number.POSITIVE_INFINITY, policy?: ProposalPolicy): Proposal | null {
|
|
336
|
+
this.#budget = budget;
|
|
337
|
+
for (const source of this.#sources) {
|
|
338
|
+
const proposal = source.propose(this);
|
|
339
|
+
if (!proposal || (policy && proposal.policy !== policy)) continue;
|
|
340
|
+
if (proposal.branchStop) this.stats.branchStops++;
|
|
341
|
+
const clamped = this.#clamp(proposal.ids, budget, proposal.origin === "echo");
|
|
342
|
+
if (!clamped) continue;
|
|
343
|
+
// An assert claim that a clamp cut short for any reason OTHER than
|
|
344
|
+
// ending at a delimiter is no longer the claim the source made. Strict
|
|
345
|
+
// rows are exempt: they are determined by construction, and a budget
|
|
346
|
+
// clamp shortens the span without weakening it.
|
|
347
|
+
const weakened = proposal.origin === "echo" &&
|
|
348
|
+
clamped.ids.length !== proposal.ids.length &&
|
|
349
|
+
!this.#delimiters.has(clamped.ids[clamped.ids.length - 1]!);
|
|
350
|
+
return {
|
|
351
|
+
...proposal,
|
|
352
|
+
ids: clamped.ids,
|
|
353
|
+
policy: weakened ? "verify" : proposal.policy,
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
return null;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/** Record what the engine actually emitted from `proposal` (all of it for an
|
|
360
|
+
* assert; the accepted prefix for a verify; 0 when the engine declined).
|
|
361
|
+
* MUST be called exactly once per proposal returned by push(). */
|
|
362
|
+
commit(proposal: Proposal, accepted: number): void {
|
|
363
|
+
const n = Math.max(0, Math.min(accepted, proposal.ids.length));
|
|
364
|
+
const rejected = proposal.ids.length - n;
|
|
365
|
+
if (proposal.policy === "verify") {
|
|
366
|
+
this.stats.verifyAccepted += n;
|
|
367
|
+
this.stats.verifyRejected += rejected;
|
|
368
|
+
}
|
|
369
|
+
if (n === 0) return;
|
|
370
|
+
const ids = proposal.ids.slice(0, n);
|
|
371
|
+
this.#append(ids);
|
|
372
|
+
this.stats.events++;
|
|
373
|
+
this.stats.injected += n;
|
|
374
|
+
if (proposal.origin === "echo") this.stats.echo += n;
|
|
375
|
+
else this.stats.strict += n;
|
|
376
|
+
this.stats.spanLens.push(n);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/** A verify proposal reached the forward (the checkpoint was taken). */
|
|
380
|
+
noteVerifyEvent(checkpointMs: number): void {
|
|
381
|
+
this.stats.verifyEvents++;
|
|
382
|
+
this.stats.checkpointMs += checkpointMs;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/** A verify proposal was dropped: this model's caches cannot rewind. */
|
|
386
|
+
noteVerifyUnsupported(): void {
|
|
387
|
+
this.stats.verifyUnsupported++;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/** The engine dropped an in-flight sampled token to take an ASSERT fill. */
|
|
391
|
+
noteWastedSample(): void {
|
|
392
|
+
this.stats.wastedSamples++;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/** The tool-call parser rejected this request's markup. Strict rows are
|
|
396
|
+
* disarmed for the rest of the request — the template's own rendering
|
|
397
|
+
* disagreed with what the model actually emits, so every further row is
|
|
398
|
+
* suspect. (Echo rows are unaffected: they never claimed to be schema.)
|
|
399
|
+
*
|
|
400
|
+
* REALITY NOTE: the served parse (ToolAwareStream.toolCalls →
|
|
401
|
+
* parseGeneratedToolCalls) runs at sink FLUSH, i.e. after the generation
|
|
402
|
+
* has ended, so within one request this is telemetry and the disarm has no
|
|
403
|
+
* remaining tokens to act on. The seam is here so a future incremental
|
|
404
|
+
* parse (or a plan cached across requests) disarms for real. */
|
|
405
|
+
noteParseFailure(): void {
|
|
406
|
+
if (this.stats.strict === 0) return; // no strict row armed this request
|
|
407
|
+
this.stats.parseFallback++;
|
|
408
|
+
this.#strict.disable();
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// --- TokenView ---------------------------------------------------------
|
|
412
|
+
get length(): number { return this.#length; }
|
|
413
|
+
get budget(): number { return this.#budget; }
|
|
414
|
+
tail(n: number): readonly number[] {
|
|
415
|
+
return n >= this.#tail.length ? this.#tail : this.#tail.slice(this.#tail.length - n);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
#append(ids: readonly number[]): void {
|
|
419
|
+
this.#length += ids.length;
|
|
420
|
+
for (const source of this.#sources) source.observe?.(ids);
|
|
421
|
+
this.#tail.push(...ids);
|
|
422
|
+
if (this.#tail.length > this.#window)
|
|
423
|
+
this.#tail.splice(0, this.#tail.length - this.#window);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/** Clamp order: EOS → delimiter → max span / caller budget → reject <2.
|
|
427
|
+
* A one-token span saves no forward (the engine already had that token's
|
|
428
|
+
* successor in flight), so it is never worth the discarded dispatch.
|
|
429
|
+
*
|
|
430
|
+
* `applyDelimiters` is ECHO-ONLY. A delimiter marks where an argument VALUE
|
|
431
|
+
* ends, which is exactly where a copied span must stop — but a template
|
|
432
|
+
* scaffold legitimately contains the same tokens as structure (`{"name": "`
|
|
433
|
+
* is three quotes deep), and it is determined by construction. Clamping
|
|
434
|
+
* strict rows on delimiters would cut every scaffold at its first quote. */
|
|
435
|
+
#clamp(
|
|
436
|
+
emit: readonly number[], budget: number, applyDelimiters: boolean,
|
|
437
|
+
): { ids: number[]; truncated: boolean } | null {
|
|
438
|
+
let ids = emit as number[];
|
|
439
|
+
let truncated = false;
|
|
440
|
+
for (let i = 0; i < ids.length; i++) {
|
|
441
|
+
if (this.#eos.has(ids[i]!)) { ids = ids.slice(0, i); truncated = true; break; }
|
|
442
|
+
}
|
|
443
|
+
if (applyDelimiters && this.#delimiters.size) {
|
|
444
|
+
for (let i = 0; i < ids.length; i++) {
|
|
445
|
+
// A span may END at a delimiter — injecting the closing quote is the
|
|
446
|
+
// point — but never continue past one.
|
|
447
|
+
if (this.#delimiters.has(ids[i]!)) {
|
|
448
|
+
if (i + 1 < ids.length) { ids = ids.slice(0, i + 1); truncated = true; }
|
|
449
|
+
break;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
const cap = Math.min(
|
|
454
|
+
this.#maxSpan,
|
|
455
|
+
Number.isFinite(budget) ? Math.max(0, Math.floor(budget)) : this.#maxSpan,
|
|
456
|
+
);
|
|
457
|
+
if (ids.length > cap) { ids = ids.slice(0, cap); truncated = true; }
|
|
458
|
+
if (ids.length < 2) return null;
|
|
459
|
+
if (truncated) this.stats.indexTruncated++;
|
|
460
|
+
return { ids: ids === emit ? [...ids] : ids, truncated };
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
export type { ProposalOrigin };
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import type { ToolDefinition } from "../../input/chat-template";
|
|
2
|
+
import { parseStrictToolCall } from "../../input/tool-call";
|
|
3
|
+
import type { FillRow, StrictFillContext } from "./session";
|
|
4
|
+
|
|
5
|
+
const OPEN = "<tool_call>", CLOSE = "</tool_call>";
|
|
6
|
+
const THINK_OPEN = "<think>", THINK_CLOSE = "</think>";
|
|
7
|
+
const MAX_CONTEXT_TOKENS = 65_536;
|
|
8
|
+
|
|
9
|
+
/** Conservative for raw XML argument text. An unfinished literal can still
|
|
10
|
+
* contain a closing-tag example; leaving it to generation loses only a fill. */
|
|
11
|
+
function literalsClosed(text: string): boolean {
|
|
12
|
+
let quote = "", escaped = false;
|
|
13
|
+
for (const c of text) {
|
|
14
|
+
if (escaped) { escaped = false; continue; }
|
|
15
|
+
if (c === "\\" && quote !== "'") { escaped = true; continue; }
|
|
16
|
+
if (quote) { if (c === quote) quote = ""; }
|
|
17
|
+
else if (c === '"' || c === "'" || c === "`") quote = c;
|
|
18
|
+
}
|
|
19
|
+
return quote === "" && !escaped;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function reasoningClosed(text: string): boolean {
|
|
23
|
+
if (text.includes(THINK_OPEN)) return false;
|
|
24
|
+
let fence = "", prose = "";
|
|
25
|
+
for (const line of text.split("\n")) {
|
|
26
|
+
const marker = /^\s*(`{3,}|~{3,})(.*)$/.exec(line);
|
|
27
|
+
if (fence) {
|
|
28
|
+
if (marker && marker[1]![0] === fence[0] &&
|
|
29
|
+
marker[1]!.length >= fence.length && !marker[2]!.trim()) fence = "";
|
|
30
|
+
} else if (marker) fence = marker[1]!;
|
|
31
|
+
else prose += line + "\n";
|
|
32
|
+
}
|
|
33
|
+
return !fence && literalsClosed(prose);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Request-local proof for template-derived rows. Only a tool-only assistant
|
|
37
|
+
* turn, optionally following its reasoning block, qualifies. Prose, quoted
|
|
38
|
+
* examples and unsupported formats retain ordinary generation. Decode runs
|
|
39
|
+
* only when a full token trigger matches, not for every generated token. */
|
|
40
|
+
export class ToolCallFillContext implements StrictFillContext {
|
|
41
|
+
#ids: number[] = [];
|
|
42
|
+
#overflow = false;
|
|
43
|
+
readonly #startsInReasoning: boolean;
|
|
44
|
+
|
|
45
|
+
constructor(
|
|
46
|
+
readonly decode: (ids: readonly number[]) => string,
|
|
47
|
+
readonly tools: ToolDefinition[],
|
|
48
|
+
promptText: string,
|
|
49
|
+
) {
|
|
50
|
+
this.#startsInReasoning = /<think>\s*$/.test(promptText);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
observe(ids: readonly number[]): void {
|
|
54
|
+
if (this.#overflow) return;
|
|
55
|
+
if (this.#ids.length + ids.length > MAX_CONTEXT_TOKENS) {
|
|
56
|
+
this.#overflow = true;
|
|
57
|
+
this.#ids = [];
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
for (const id of ids) this.#ids.push(id);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
allows(row: FillRow): boolean {
|
|
64
|
+
if (this.#overflow) return false;
|
|
65
|
+
let text = this.decode(this.#ids);
|
|
66
|
+
if (this.#startsInReasoning) {
|
|
67
|
+
const end = text.indexOf(THINK_CLOSE);
|
|
68
|
+
if (end < 0 || !reasoningClosed(text.slice(0, end))) return false;
|
|
69
|
+
text = text.slice(end + THINK_CLOSE.length);
|
|
70
|
+
}
|
|
71
|
+
text = text.trimStart();
|
|
72
|
+
if (text.startsWith(THINK_OPEN)) {
|
|
73
|
+
const end = text.indexOf(THINK_CLOSE, THINK_OPEN.length);
|
|
74
|
+
if (end < 0 || !reasoningClosed(text.slice(THINK_OPEN.length, end))) return false;
|
|
75
|
+
text = text.slice(end + THINK_CLOSE.length).trimStart();
|
|
76
|
+
}
|
|
77
|
+
// Earlier calls must be complete and valid before another can open.
|
|
78
|
+
for (;;) {
|
|
79
|
+
if (!text.startsWith(OPEN)) return false;
|
|
80
|
+
const end = text.indexOf(CLOSE, OPEN.length);
|
|
81
|
+
if (end < 0) break;
|
|
82
|
+
const stop = end + CLOSE.length;
|
|
83
|
+
if (!parseStrictToolCall(text.slice(0, stop), this.tools)) return false;
|
|
84
|
+
text = text.slice(stop).trimStart();
|
|
85
|
+
}
|
|
86
|
+
if (text.indexOf(OPEN, OPEN.length) !== -1) return false;
|
|
87
|
+
if (row.kind === "scaffold" || row.kind === "name" || row.kind === "key") {
|
|
88
|
+
const trigger = this.decode(row.trigger);
|
|
89
|
+
const start = trigger.indexOf(OPEN);
|
|
90
|
+
return start >= 0 && text === trigger.slice(start);
|
|
91
|
+
}
|
|
92
|
+
if (row.kind !== "close") return false;
|
|
93
|
+
const call = parseStrictToolCall(text + this.decode(row.emit), this.tools);
|
|
94
|
+
if (!call) return false;
|
|
95
|
+
const tool = this.tools.find(tool => tool.function?.name === call.name);
|
|
96
|
+
const schema = tool?.function?.parameters;
|
|
97
|
+
const required = schema?.required;
|
|
98
|
+
if (!Array.isArray(required) || required.length !== 1 ||
|
|
99
|
+
Object.keys(call.arguments).length !== 1 || !Object.hasOwn(call.arguments, required[0])) return false;
|
|
100
|
+
const value = call.arguments[required[0]];
|
|
101
|
+
return typeof value !== "string" || literalsClosed(value);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
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 { withResource } from "../runtime/resources";
|
|
5
|
+
import { leaseCacheStates } from "../state/leases";
|
|
6
|
+
|
|
7
|
+
/** Append an already accepted span without intermediate vocabulary heads.
|
|
8
|
+
* Chunking changes the graph shape, so callers must qualify its numerics for
|
|
9
|
+
* their model. Verification uses its separate round operation because
|
|
10
|
+
* recurrent rollback records one forward per round. Append precedes emission/cancellation
|
|
11
|
+
* boundaries, preserving the cache-covered prefix on a consumer break.
|
|
12
|
+
*/
|
|
13
|
+
export async function appendFillHidden(
|
|
14
|
+
forward: (ids: MlxArray, cache: Cache[], positions: number) => MlxArray | Promise<MlxArray>,
|
|
15
|
+
cache: Cache[],
|
|
16
|
+
ids: number[],
|
|
17
|
+
chunkSize: (cache: readonly Cache[]) => number,
|
|
18
|
+
): Promise<MlxArray> {
|
|
19
|
+
const input = ops.fromInt32(ids, [1, ids.length]);
|
|
20
|
+
try { return await appendHiddenRows(forward, cache, input, chunkSize); }
|
|
21
|
+
finally { input.dispose(); }
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Append equal-length committed token rows [B,L]. The caller owns the input;
|
|
25
|
+
* the returned hidden rows belong to the caller. Model policy receives B so
|
|
26
|
+
* a larger cohort cannot silently cross its qualified projection geometry. */
|
|
27
|
+
export async function appendHiddenRows(
|
|
28
|
+
forward: (ids: MlxArray, cache: Cache[], positions: number) => MlxArray | Promise<MlxArray>,
|
|
29
|
+
cache: Cache[],
|
|
30
|
+
input: MlxArray,
|
|
31
|
+
chunkSize: (cache: readonly Cache[], rows: number) => number,
|
|
32
|
+
): Promise<MlxArray> {
|
|
33
|
+
const [rows, length] = input.shape as [number, number];
|
|
34
|
+
const hidden: MlxArray[] = [];
|
|
35
|
+
try {
|
|
36
|
+
for (let start = 0; start < length;) {
|
|
37
|
+
const limit = chunkSize(cache, rows);
|
|
38
|
+
if (!Number.isSafeInteger(limit) || limit < 1)
|
|
39
|
+
throw new Error("Model append chunk limit must be a positive integer");
|
|
40
|
+
const end = Math.min(length, start + limit);
|
|
41
|
+
if (start === 0 && end === length) return await forward(input, cache, length);
|
|
42
|
+
const part = input.slice([0, start], [rows, end]);
|
|
43
|
+
try {
|
|
44
|
+
const h = await forward(part, cache, end - start);
|
|
45
|
+
hidden.push(h);
|
|
46
|
+
// Bound transient graphs while retaining all positions for an optional
|
|
47
|
+
// diagnostic trace. Normal generation projects only the last position.
|
|
48
|
+
withResource(leaseCacheStates(cache), (state) => ops.evalAll([h, ...state]));
|
|
49
|
+
} finally {
|
|
50
|
+
part.dispose();
|
|
51
|
+
}
|
|
52
|
+
start = end;
|
|
53
|
+
}
|
|
54
|
+
return ops.concatAxis(hidden, 1);
|
|
55
|
+
} finally {
|
|
56
|
+
for (const h of hidden) h.dispose();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
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 {
|
|
11
|
+
bindMlxAutoregressiveGraph
|
|
12
|
+
} from "./bindings/autoregressive";
|
|
13
|
+
import { bindLegacyDenoisingModel } from "./bindings/denoising";
|
|
14
|
+
import { declaredGraph } from "../models/capabilities";
|
|
15
|
+
import type { MlxTokenGraph } from "../models/graph";
|
|
16
|
+
import { generateAutoregressive } from "./autoregressive";
|
|
17
|
+
import { generateDenoising } from "./denoising";
|
|
18
|
+
import { Generation } from "./result";
|
|
19
|
+
import { GenerateDiagnostics,GenerateOptions } from "./types";
|
|
20
|
+
|
|
21
|
+
export function generate(
|
|
22
|
+
model: MlxTokenGraph,
|
|
23
|
+
promptTokens: number[],
|
|
24
|
+
options: GenerateOptions = {},
|
|
25
|
+
diagnostics: GenerateDiagnostics = {},
|
|
26
|
+
): Generation {
|
|
27
|
+
return bindGeneration(model)(promptTokens, options, diagnostics);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Resolve the compatibility model once at host construction. Every request
|
|
31
|
+
* borrows the same binding and immutable runtime snapshot. */
|
|
32
|
+
export function bindGeneration(model: MlxTokenGraph): (
|
|
33
|
+
promptTokens: number[], options?: GenerateOptions, diagnostics?: GenerateDiagnostics,
|
|
34
|
+
) => Generation {
|
|
35
|
+
// DiffusionGemma is non-autoregressive: route to the denoising engine instead
|
|
36
|
+
// of the AR decode loop. Same Generation/GenerateStats contract so the CLI and
|
|
37
|
+
// server stream it through the existing token machinery.
|
|
38
|
+
if (declaredGraph(model).graphCapabilities.method !== "denoising") {
|
|
39
|
+
const binding = bindMlxAutoregressiveGraph(model);
|
|
40
|
+
return (prompt, options, diagnostics) => generateAutoregressive(binding, prompt, options, diagnostics);
|
|
41
|
+
}
|
|
42
|
+
const binding = bindLegacyDenoisingModel(model);
|
|
43
|
+
return (prompt, options) => generateDenoising(binding, prompt, options);
|
|
44
|
+
}
|