mlx-bun 0.0.13 → 0.3.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/README.md +71 -578
- package/bin/mlx-bun.mjs +1 -1
- package/docs/README.md +91 -0
- package/docs/{design → archive}/hlg-sampling.md +3 -3
- package/docs/{design → archive/investigations}/audio-input-plan.md +4 -4
- package/docs/{design → archive/investigations}/colibri-glm52-port.md +1 -1
- package/docs/{design → archive/investigations}/diffusion-gemma-port.md +3 -3
- package/docs/{design → archive/investigations}/faithful-l1-consolidation.md +4 -4
- package/docs/{investigations → archive/investigations}/hlg-sampling-investigation.md +1 -1
- package/docs/{design → archive/investigations}/minicpm5-decode-megakernel.md +11 -11
- package/docs/{design → archive/investigations}/optimization_plan.md +1 -1
- package/docs/archive/investigations/pareto-specialized-runtime-findings.md +92 -0
- package/docs/{design → archive/investigations}/pre-colibri-stabilization.md +5 -5
- package/docs/{design → archive/investigations}/the-dreaming-handoff.md +2 -2
- package/docs/{planning → archive/planning}/chat-ui-vision.md +1 -1
- package/docs/{planning → archive/planning}/chunk-task-roadmap.md +1 -1
- package/docs/{planning → archive/planning}/curve-sampler-research-plan.md +4 -4
- package/docs/{planning → archive/planning}/memory-docs-and-dag-plan.md +1 -1
- package/docs/{planning → archive/planning}/release-notes-v0.0.9.md +1 -1
- package/docs/archive/planning/release-notes-v0.2.0.md +49 -0
- package/docs/{planning → archive/planning}/website-readme-pass-plan.md +1 -1
- package/docs/design/batching.md +569 -0
- package/docs/design/decode-speed-program.md +3178 -169
- package/docs/design/dreaming-nightly-pipeline.md +675 -282
- package/docs/design/generic-model-support.md +579 -334
- package/docs/design/kv-cache.md +680 -0
- package/docs/design/orpo-training.md +673 -1239
- package/docs/design/speculative-decoding.md +1627 -0
- package/docs/design/turboquant.md +2102 -0
- package/docs/design/unified-engine-frontier-plan.md +1831 -727
- package/docs/design/web-chat-redesign.md +666 -1083
- package/docs/planning/PRODUCT_ROADMAP.md +34 -1
- package/docs/planning/release-notes-v0.3.0.md +35 -0
- package/docs/planning/video-series.md +421 -0
- package/docs/reference/benchmarks.md +1162 -0
- package/docs/reference/cli.md +395 -149
- package/docs/reference/distribution.md +321 -181
- package/docs/reference/environment.md +348 -0
- package/docs/reference/glossary.md +39 -0
- package/docs/reference/library-api.md +580 -142
- package/docs/reference/memory.md +227 -64
- package/docs/reference/models.md +137 -6
- package/docs/reference/server-api.md +938 -422
- package/docs/reference/server-config.md +652 -320
- package/docs/reference/training.md +393 -254
- package/docs/reference/troubleshooting.md +166 -0
- package/package.json +10 -5
- package/src/admission-plan.ts +18 -0
- package/src/anthropic.ts +92 -3
- package/src/audio/decode.ts +2 -2
- package/src/audio/features.ts +2 -2
- package/src/backends/mlx/autoregressive.ts +108 -0
- package/src/backends/mlx/batch-group.ts +1646 -0
- package/src/backends/mlx/diffusion.ts +39 -0
- package/src/backends/mlx/draft-target.ts +47 -0
- package/src/backends/mlx/evaluation.ts +105 -0
- package/src/backends/mlx/fill-append.ts +45 -0
- package/src/backends/mlx/gateway-binding.ts +71 -0
- package/src/backends/mlx/graph.ts +38 -0
- package/src/backends/mlx/memory-client.ts +189 -0
- package/src/backends/mlx/memory-guard.ts +40 -0
- package/src/backends/mlx/methods.ts +90 -0
- package/src/backends/mlx/model-prompt.ts +164 -0
- package/src/backends/mlx/model-serving.ts +79 -0
- package/src/backends/mlx/prefill.ts +33 -0
- package/src/backends/mlx/request-policy.ts +20 -0
- package/src/backends/mlx/rollback.ts +51 -0
- package/src/backends/mlx/serial-executor.ts +273 -0
- package/src/backends/mlx/speculative.ts +114 -0
- package/src/backends/mlx/state-views.ts +26 -0
- package/src/backends/mlx/text-engine.ts +73 -0
- package/src/cli.ts +189 -61
- package/src/client.ts +66 -0
- package/src/config.ts +21 -4
- package/src/contracts/completion.ts +16 -0
- package/src/contracts/execution.ts +49 -0
- package/src/contracts/generation.ts +103 -0
- package/src/contracts/host.ts +7 -0
- package/src/contracts/jobs.ts +66 -0
- package/src/contracts/pi-provider.ts +30 -0
- package/src/contracts/pi.ts +259 -0
- package/src/contracts/resources.ts +18 -0
- package/src/contracts/scheduling.ts +27 -0
- package/src/contracts/tsconfig.json +14 -0
- package/src/diffusion/diffusion-generate.ts +218 -153
- package/src/embed.ts +1 -1
- package/src/engine/admission.ts +59 -0
- package/src/engine/cancellation.ts +38 -0
- package/src/engine/engine.ts +56 -0
- package/src/engine/execution-coordinator.ts +70 -0
- package/src/engine/execution-plan.ts +51 -0
- package/src/engine/resources.ts +42 -0
- package/src/engine/scheduler.ts +67 -0
- package/src/engine/session.ts +247 -0
- package/src/eval/README.md +44 -1
- package/src/eval/kl.ts +263 -23
- package/src/eval/reference-logits.ts +207 -0
- package/src/eval/runner.ts +10 -79
- package/src/eval/tasks/bfcl.ts +2 -1
- package/src/eval/tasks/gsm8k.ts +2 -1
- package/src/eval/tasks/hashhop.ts +2 -1
- package/src/eval/tasks/humaneval.ts +5 -4
- package/src/eval/tasks/ifeval.ts +2 -1
- package/src/eval/tasks/mmlu.ts +2 -1
- package/src/expert-io.ts +3 -1
- package/src/expert-offload.ts +3 -1
- package/src/fill/echo-index.ts +188 -0
- package/src/fill/fill-session.ts +452 -0
- package/src/fill/proposal.ts +68 -0
- package/src/fill/schema-rows.ts +506 -0
- package/src/fill/tool-boundary.ts +103 -0
- package/src/fit.ts +24 -43
- package/src/generate.ts +740 -337
- package/src/grammar.ts +14 -8
- package/src/harness-pi.ts +15 -34
- package/src/hub-rest.ts +4 -4
- package/src/index.ts +27 -0
- package/src/inference/denoising.ts +17 -0
- package/src/inference/graph.ts +33 -0
- package/src/inference/prefill.ts +39 -0
- package/src/inference/rollback.ts +7 -0
- package/src/jobs/db.ts +4 -0
- package/src/jobs/index.ts +2 -0
- package/src/jobs/job-entry.ts +15 -14
- package/src/jobs/runner.ts +98 -13
- package/src/jobs/task-client.ts +19 -0
- package/src/jobs/types.ts +2 -58
- package/src/kv-scheme.ts +199 -0
- package/src/kv-store.ts +459 -234
- package/src/lab/README.md +18 -0
- package/src/{assets → lab/curve}/curve-designer.html +2 -2
- package/src/{curve-sampler.ts → lab/curve/curve-sampler.ts} +2 -2
- package/src/{expert-trace.ts → lab/expert-trace/expert-trace.ts} +7 -5
- package/src/{model → lab/paged-kv}/paged-kv.ts +9 -6
- package/src/library.ts +40 -0
- package/src/lora.ts +18 -0
- package/src/media-fetch.ts +20 -4
- package/src/memory/article.ts +1 -1
- package/src/memory/chunk.ts +1 -1
- package/src/memory/model.ts +32 -170
- package/src/memory/prompts.ts +1 -1
- package/src/memory/rest.ts +1 -1
- package/src/memory/schedule.ts +1 -1
- package/src/memory/stages.ts +4 -1
- package/src/memory/vault.ts +3 -4
- package/src/mlx/array.ts +9 -10
- package/src/mlx/compile.ts +12 -2
- package/src/mlx/ffi.ts +66 -22
- package/src/mlx/materialize.ts +23 -0
- package/src/mlx/metal-kernel.ts +10 -11
- package/src/mlx/ops.ts +64 -19
- package/src/mlx/quantized-rows.ts +36 -0
- package/src/mlx/token-bitmask.ts +52 -0
- package/src/mlx/turboquant-kv-decode.ts +82 -0
- package/src/mlx/turboquant-kv-inverse.ts +122 -0
- package/src/mlx/turboquant-ops.ts +33 -3
- package/src/mlx/turboquant-tables.ts +2 -2
- package/src/model/batched-mask.ts +9 -3
- package/src/model/batched-quant.ts +2 -2
- package/src/model/batched-rotating-quant.ts +45 -99
- package/src/model/batched-rotating-state.ts +73 -0
- package/src/model/batched-rotating.ts +57 -106
- package/src/model/batched-row-storage.ts +127 -0
- package/src/model/compiled-decode.ts +4 -3
- package/src/model/diffusion-gemma.ts +8 -8
- package/src/model/factory.ts +87 -49
- package/src/model/fingerprint.ts +1 -1
- package/src/model/flash-attention.ts +3 -3
- package/src/model/gemma4-base.ts +187 -17
- package/src/model/gemma4.ts +6 -6
- package/src/model/generated/gemma4-12b.ts +52 -148
- package/src/model/generated/gemma4-26b.ts +34 -94
- package/src/model/generated/gemma4-e4b.ts +28 -76
- package/src/model/glm52-cache.ts +19 -9
- package/src/model/glm52-dsa.ts +13 -9
- package/src/model/glm52-memory.ts +28 -1
- package/src/model/implementation.ts +69 -0
- package/src/model/minicpm5.ts +7 -8
- package/src/model/profile.ts +443 -0
- package/src/model/qwen-append.ts +12 -0
- package/src/model/qwen-conv.ts +52 -0
- package/src/model/qwen3-delta.ts +98 -2
- package/src/model/qwen3-mrope.ts +252 -0
- package/src/model/qwen3_5-checkpoint.ts +164 -0
- package/src/model/qwen3_5.ts +336 -60
- package/src/model/support.ts +1 -1
- package/src/model/trellis-balanced-scatter.ts +51 -0
- package/src/model/trellis-linear.ts +672 -0
- package/src/model/trellis-shared-m.ts +151 -0
- package/src/model/trellis-shared-scatter.ts +144 -0
- package/src/model/trellis-splitk-prefill.ts +158 -0
- package/src/model/trellis-tiled-prefill.ts +136 -0
- package/src/model/trellis-vector-expand.ts +62 -0
- package/src/model/trellis-wide-prefill.ts +94 -0
- package/src/model/universal/dense.ts +1 -1
- package/src/model/universal/modules.ts +8 -1
- package/src/native/frame_extract.swift +140 -0
- package/src/native-pack.ts +15 -7
- package/src/pi-provider.ts +6 -64
- package/src/pi-web.ts +14 -320
- package/src/prompt-cache.ts +97 -54
- package/src/quantize/allocator.ts +1 -1
- package/src/quantize/atomic-output.ts +38 -0
- package/src/quantize/calibration.ts +2 -1
- package/src/quantize/config-writer.ts +16 -3
- package/src/quantize/index.ts +28 -1
- package/src/quantize/job.ts +11 -0
- package/src/quantize/quantizer.ts +265 -100
- package/src/quantize/rotate.ts +603 -0
- package/src/quantize/safetensors-writer.ts +79 -1
- package/src/quantize/sensitivity.ts +41 -59
- package/src/quantize/trellis.ts +586 -0
- package/src/quantize/weight-transform.ts +292 -0
- package/src/responses.ts +130 -3
- package/src/runtime-config.ts +81 -0
- package/src/sampler.ts +276 -4
- package/src/serve/admin-routes.ts +184 -0
- package/src/serve/aux-routes.ts +132 -0
- package/src/serve/batch-scheduler.ts +8 -1558
- package/src/serve/chat-request.ts +391 -0
- package/src/serve/chat-stage.ts +195 -0
- package/src/serve/checkpoint-identity.ts +64 -0
- package/src/serve/completion-executor.ts +354 -0
- package/src/serve/completion-sink.ts +157 -0
- package/src/serve/discovery-routes.ts +204 -0
- package/src/serve/generation-gateway.ts +211 -243
- package/src/serve/http.ts +159 -0
- package/src/serve/inference-request.ts +79 -0
- package/src/serve/isolate.ts +297 -105
- package/src/serve/kv-budget.ts +14 -0
- package/src/serve/lab-routes.ts +341 -0
- package/src/serve/lane-registry.ts +2 -1
- package/src/serve/model-admin-routes.ts +155 -0
- package/src/serve/model-binding.ts +43 -0
- package/src/serve/model-host.ts +512 -0
- package/src/serve/openai-wire.ts +103 -0
- package/src/serve/parent-application.ts +56 -0
- package/src/serve/pipeline.ts +33 -0
- package/src/serve/preparation.ts +33 -0
- package/src/serve/prompt-response-trace.ts +147 -0
- package/src/serve/request-plan.ts +162 -0
- package/src/serve/request-prep.ts +346 -0
- package/src/serve/response-lease.ts +24 -0
- package/src/serve/responses-client.ts +69 -0
- package/src/serve/session-completion-engine.ts +110 -0
- package/src/serve/session-search.ts +1 -1
- package/src/serve/static-routes.ts +101 -0
- package/src/serve/text-completion-stage.ts +104 -0
- package/src/serve/token-streams.ts +519 -0
- package/src/server.ts +523 -3551
- package/src/spec/assistant-source.ts +10 -44
- package/src/spec/deepspec-source.ts +6 -6
- package/src/spec/dflash-source.ts +9 -8
- package/src/spec/dspark/ab-stats.ts +3 -3
- package/src/spec/dspark/calibration.ts +2 -2
- package/src/spec/dspark/deepspec-module.ts +2 -2
- package/src/spec/dspark/generate-dflash.ts +2 -2
- package/src/spec/dspark/generate.ts +2 -2
- package/src/spec/dspark/loader.ts +1 -1
- package/src/spec/dspark/module-dflash.ts +5 -4
- package/src/spec/dspark/module.ts +3 -3
- package/src/spec/dspark/quantize-drafter.ts +1 -1
- package/src/spec/generate.ts +1 -1
- package/src/spec/glm52-mtp-source.ts +9 -1
- package/src/spec/ngram-source.ts +1 -1
- package/src/spec/prefix-state.ts +45 -0
- package/src/spec/qwen-mtp-source.ts +528 -0
- package/src/spec/serve-loop.ts +179 -213
- package/src/spec/source.ts +42 -10
- package/src/spec/two-model.ts +1 -1
- package/src/ssd-cache.ts +95 -24
- package/src/ssd-durability.ts +207 -0
- package/src/tool-call.ts +51 -14
- package/src/train/flash-cce.ts +14 -13
- package/src/train/forward.ts +38 -1
- package/src/train/loss.ts +3 -2
- package/src/train/prefix-shared.ts +5 -1
- package/src/train/segmented.ts +9 -6
- package/src/train/trainer.ts +9 -8
- package/src/vision/preprocess.ts +1 -1
- package/src/vision/prompt.ts +48 -2
- package/src/vision/qwen3vl-preprocess.ts +409 -0
- package/src/vision/qwen3vl-prompt.ts +196 -0
- package/src/vision/qwen3vl-tower.ts +555 -0
- package/src/vision/video-frames.ts +148 -0
- package/src/web/app.html +1 -1
- package/src/web/app.js +15 -6
- package/src/web/src/adapters-panel.ts +3 -3
- package/src/web/src/assistant.ts +2 -36
- package/src/web/src/chat.ts +16 -9
- package/src/web/src/composer.ts +4 -4
- package/src/web/src/hub.ts +1 -1
- package/src/web/src/markdown.ts +0 -0
- package/src/web/src/memory-panel.ts +1 -1
- package/src/web/src/model-picker.ts +1 -1
- package/src/web/src/palette.ts +1 -1
- package/src/web/src/protocol.ts +3 -39
- package/src/web/src/sessions.ts +1 -1
- package/src/web/src/ui-catalog.ts +4 -18
- package/src/web-assets.ts +47 -0
- package/src/weights.ts +134 -33
- package/docs/archive/mlx-bun-lab-report.html +0 -275
- package/docs/archive/wwdc-mlx-bun.html +0 -848
- package/docs/design/adapters-end-to-end.md +0 -205
- package/docs/design/batching-perf-path.md +0 -255
- package/docs/design/batching-v2-plan.md +0 -529
- package/docs/design/bucketing-stage.md +0 -197
- package/docs/design/compat-cli-surface-design.md +0 -230
- package/docs/design/docs-reorg-plan.md +0 -165
- package/docs/design/dspark-serving-program.md +0 -371
- package/docs/design/dspark-speculative-decoding.md +0 -268
- package/docs/design/grammar-spec-batching-integration.md +0 -301
- package/docs/design/memory-inference-path.md +0 -422
- package/docs/design/memory-synthesis.md +0 -287
- package/docs/design/memory-system.md +0 -237
- package/docs/design/mlx-lm-tool-parity-plan.md +0 -545
- package/docs/design/omlx-adoption-map.md +0 -98
- package/docs/design/orpo-dynamic-lambda.md +0 -220
- package/docs/design/orpo-future-enhancements.md +0 -340
- package/docs/design/paged-kv-cache.md +0 -214
- package/docs/design/parallel-slots.md +0 -567
- package/docs/design/parity-tier-dag.md +0 -161
- package/docs/design/repo-cleanup-plan.md +0 -189
- package/docs/design/runtime-isolation.md +0 -263
- package/docs/design/segmented-backward-training.md +0 -543
- package/docs/design/spec-decode-larger-targets.md +0 -229
- package/docs/design/ssd-kv-cold-tier.md +0 -288
- package/docs/design/structured-output.md +0 -484
- package/docs/design/tauri-desktop-app.md +0 -127
- package/docs/design/the-dreaming-master-plan.md +0 -445
- package/docs/design/turboquant-kv.md +0 -235
- package/docs/design/w4a16-compute-precision-spike.md +0 -232
- package/docs/design/web-chat-beat-matrix.md +0 -315
- package/docs/design/write-pipeline-entity.md +0 -468
- package/docs/investigations/curve-runs/bisector-route.json +0 -98
- package/docs/investigations/curve-runs/bisector-trace.json +0 -1393
- package/docs/investigations/curve-runs/curve-terrain.json +0 -1179
- package/docs/investigations/curve-runs/curve-terrain.svg +0 -42
- package/docs/investigations/hlg-ab.html +0 -34
- package/docs/investigations/hlg-explorer.html +0 -238
- package/docs/investigations/hlg-figs/cliff.svg +0 -1
- package/docs/investigations/hlg-figs/cross.svg +0 -1
- package/docs/investigations/hlg-figs/edges.svg +0 -1
- package/docs/investigations/hlg-figs/k20-grids.svg +0 -1
- package/docs/investigations/hlg-figs/k20-peaks.svg +0 -1
- package/docs/investigations/hlg-figs/lw-curve.svg +0 -1
- package/docs/investigations/hlg-figs/terrain.svg +0 -1
- package/docs/investigations/hlg-report.html +0 -262
- package/docs/investigations/hlg-runs/hlg-ab.json +0 -149
- package/docs/investigations/hlg-runs/hlg-blind-prompts.md +0 -87
- package/docs/investigations/hlg-runs/hlg-blind.json +0 -107
- package/docs/investigations/hlg-runs/hlg-cliff.json +0 -197
- package/docs/investigations/hlg-runs/hlg-cross.json +0 -505
- package/docs/investigations/hlg-runs/hlg-edge.json +0 -388
- package/docs/investigations/hlg-runs/hlg-eval1.json +0 -25
- package/docs/investigations/hlg-runs/hlg-frontier.json +0 -86
- package/docs/investigations/hlg-runs/hlg-grid.json +0 -242
- package/docs/investigations/hlg-runs/hlg-grid2.json +0 -343
- package/docs/investigations/hlg-runs/hlg-lw-coarse.json +0 -110
- package/docs/investigations/hlg-runs/hlg-map.json +0 -383
- package/docs/investigations/hlg-runs/run0-original.log +0 -159
- package/docs/investigations/hlg-runs/runA-recommended.log +0 -131
- package/docs/investigations/hlg-runs/runAA-cliff-cpm5.log +0 -26
- package/docs/investigations/hlg-runs/runAB-grid2-e4b.log +0 -18
- package/docs/investigations/hlg-runs/runAC-peak-e4b-partial.log +0 -25
- package/docs/investigations/hlg-runs/runAD-ab-samples.log +0 -8
- package/docs/investigations/hlg-runs/runAE-edge-e4b.log +0 -51
- package/docs/investigations/hlg-runs/runAF-logit-calibration.log +0 -10
- package/docs/investigations/hlg-runs/runAG-eval-config2.log +0 -16
- package/docs/investigations/hlg-runs/runB-gain-variants.log +0 -192
- package/docs/investigations/hlg-runs/runC-pivots.log +0 -162
- package/docs/investigations/hlg-runs/runD-oetf.log +0 -132
- package/docs/investigations/hlg-runs/runE-eotf.log +0 -162
- package/docs/investigations/hlg-runs/runF-shaper-working.log +0 -86
- package/docs/investigations/hlg-runs/runG-shaper-sweep.log +0 -101
- package/docs/investigations/hlg-runs/runH-wsweep.log +0 -176
- package/docs/investigations/hlg-runs/runI-asweep.log +0 -176
- package/docs/investigations/hlg-runs/runJ-knobmap.log +0 -49
- package/docs/investigations/hlg-runs/runK-frontier.log +0 -19
- package/docs/investigations/hlg-runs/runL-widemap.log +0 -65
- package/docs/investigations/hlg-runs/runM-frontier-emb.log +0 -15
- package/docs/investigations/hlg-runs/runN-verify.log +0 -116
- package/docs/investigations/hlg-runs/runO-frontier-seed5000.log +0 -15
- package/docs/investigations/hlg-runs/runP-grid-AxSm.log +0 -15
- package/docs/investigations/hlg-runs/runQ-grid-tgW.log +0 -15
- package/docs/investigations/hlg-runs/runR-blind-transcript.log +0 -79
- package/docs/investigations/hlg-runs/runS-cross-coherence.log +0 -33
- package/docs/investigations/hlg-runs/runT-cross-cpm5.log +0 -33
- package/docs/investigations/hlg-runs/runU-cross-12b.log +0 -33
- package/docs/investigations/hlg-runs/runV-lw-e4b.log +0 -22
- package/docs/investigations/hlg-runs/runW-lw-12b.log +0 -22
- package/docs/investigations/hlg-runs/runX-lw-cpm5.log +0 -22
- package/docs/investigations/hlg-runs/runY-cliff-e4b.log +0 -26
- package/docs/investigations/hlg-runs/runZ-cliff-12b.log +0 -26
- package/docs/planning/IDEAS.md +0 -69
- package/docs/reference/embedding.md +0 -197
- package/docs/reference/features-matrix.md +0 -100
- package/docs/reference/orpo-quickstart.md +0 -135
- package/src/flags.ts +0 -13
- /package/docs/{investigations → archive/investigations}/chunk-finetune-experiment.md +0 -0
- /package/docs/{investigations → archive/investigations}/colibri-oracle-pin.md +0 -0
- /package/docs/{investigations → archive/investigations}/curve-bisector-routing.md +0 -0
- /package/docs/{investigations → archive/investigations}/decode-roofline-lookagain.md +0 -0
- /package/docs/{investigations → archive/investigations}/dspark-handoff.md +0 -0
- /package/docs/{investigations → archive/investigations}/expert-offload-single-user-moe.md +0 -0
- /package/docs/{investigations → archive/investigations}/indexshare-performance-spike.md +0 -0
- /package/docs/{investigations → archive/investigations}/kernel-perf-review-2026-07.md +0 -0
- /package/docs/{investigations → archive/investigations}/lab-build-journal.md +0 -0
- /package/docs/{investigations → archive/investigations}/openwiki-evaluation.md +0 -0
- /package/docs/{investigations → archive/investigations}/orpo-base-uf-experiment-and-directions.md +0 -0
- /package/docs/{investigations → archive/investigations}/orpo-flash-cce-pin-leak.md +0 -0
- /package/docs/{investigations → archive/investigations}/orpo-uf-testing-handoff.md +0 -0
- /package/docs/{investigations → archive/investigations}/pi-builtin-investigation.md +0 -0
- /package/docs/{investigations → archive/investigations}/segmented-backward-handoff.md +0 -0
- /package/docs/{investigations → archive/investigations}/starter-model-port-handoff.md +0 -0
- /package/docs/{investigations → archive/investigations}/steel-flash-cce-handoff.md +0 -0
- /package/docs/{investigations → archive/investigations}/trainer-validation-experiment.md +0 -0
- /package/docs/{planning → archive/planning}/chat-ui-vision-memory-first.md +0 -0
- /package/docs/{planning → archive/planning}/journal.md +0 -0
- /package/docs/{planning → archive/planning}/pi-integration-review.md +0 -0
- /package/docs/{planning → archive/planning}/release-notes-v0.0.10.md +0 -0
- /package/docs/{planning → archive/planning}/release-notes-v0.0.11.md +0 -0
- /package/docs/{planning → archive/planning}/release-notes-v0.0.12.md +0 -0
- /package/docs/{planning → archive/planning}/release-notes-v0.0.13.md +0 -0
- /package/docs/{planning → archive/planning}/web-ui-pass-plan.md +0 -0
- /package/{docs/investigations → src/serve/assets}/curve-terrain.html +0 -0
- /package/{docs/dag → src/serve/assets}/training-inference-map.html +0 -0
package/src/spec/serve-loop.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Serve-time speculative decoding — the ONE verify/accept executor behind
|
|
2
|
-
// `serve --draft-model` (docs/
|
|
3
|
-
// by docs/design/
|
|
2
|
+
// `serve --draft-model` (docs/reference/cli.md §7; sequenced
|
|
3
|
+
// by docs/design/batching.md Phase B).
|
|
4
4
|
//
|
|
5
5
|
// Faithful to mlx-lm's speculative_generate_step (generate.py:473-654, read
|
|
6
6
|
// from the oracle venv): per round, draft n tokens from the DraftSource, run
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
// so rejected drafts never touch grammar state and no matcher rollback is
|
|
32
32
|
// needed. Grammar termination mid-burst truncates the round (the all--inf
|
|
33
33
|
// guarantee). Gates (no oracle): greedy grammar+spec ≡ greedy grammar-only
|
|
34
|
-
// long-prefix + 100% schema validity (tests/spec-serve.test.ts).
|
|
34
|
+
// long-prefix + 100% schema validity (tests/parity/spec-serve.test.ts).
|
|
35
35
|
//
|
|
36
36
|
// Emission discipline: tokens flow through the caller's onToken ONE AT A
|
|
37
37
|
// TIME, in order (bursts of ≤ n+1 per round) — the server's stop-sequence
|
|
@@ -42,68 +42,19 @@
|
|
|
42
42
|
import { MlxArray } from "../mlx/array";
|
|
43
43
|
import * as ops from "../mlx/ops";
|
|
44
44
|
import { clearCache } from "../mlx/ffi";
|
|
45
|
-
import {
|
|
45
|
+
import { cleanupFailure, disposeResources } from "../engine/resources";
|
|
46
|
+
import type { DisposableResource } from "../contracts/resources";
|
|
47
|
+
import { assertMlxSpeculativeBinding, bindLegacySpeculativeModel, type MlxSpeculativeBinding } from "../backends/mlx/speculative";
|
|
46
48
|
import type { RuntimeModel } from "../model/factory";
|
|
47
49
|
import type { Cache } from "../model/gemma4";
|
|
48
|
-
import type
|
|
49
|
-
import {
|
|
50
|
+
import { maybeQuantizeKv, withModelUsageFlush, withModelWiredLimit, type GenerateOptions, type GenerateStats } from "../generate";
|
|
51
|
+
import { runtimeConfig, withRuntimeConfig } from "../runtime-config";
|
|
52
|
+
import { makeSampler, makeStepSampler } from "../sampler";
|
|
50
53
|
import type { OnToken } from "../serve/generation-gateway";
|
|
51
54
|
import type { DraftProvider } from "./source";
|
|
52
55
|
|
|
53
56
|
const PREFILL_CHUNK = 2048;
|
|
54
57
|
|
|
55
|
-
/** Run one target forward, and when `tapLayers` is set (⟹ a Gemma4 target — a
|
|
56
|
-
* DSpark-style source), also return the captured multi-layer context
|
|
57
|
-
* [1,L,m*H] (tapLayers concatenated on the feature axis; index nLayers is the
|
|
58
|
-
* post-finalNorm sentinel). Non-tapping sources get ctxML=null and never
|
|
59
|
-
* touch model.hiddenTap. Mirrors generate-dflash.ts forwardTapped. */
|
|
60
|
-
async function forwardMaybeTap(
|
|
61
|
-
model: RuntimeModel,
|
|
62
|
-
ids: MlxArray,
|
|
63
|
-
caches: Cache[],
|
|
64
|
-
tapLayers: number[] | undefined,
|
|
65
|
-
): Promise<{ hidden: MlxArray; ctxML: MlxArray | null }> {
|
|
66
|
-
if (!tapLayers) {
|
|
67
|
-
const asyncModel = model as RuntimeModel & {
|
|
68
|
-
forwardHiddenAsync?: (
|
|
69
|
-
ids: MlxArray,
|
|
70
|
-
caches: Cache[],
|
|
71
|
-
) => Promise<MlxArray>;
|
|
72
|
-
};
|
|
73
|
-
const hidden = typeof asyncModel.forwardHiddenAsync === "function"
|
|
74
|
-
? await asyncModel.forwardHiddenAsync(ids, caches)
|
|
75
|
-
: model.forwardHidden(ids, caches);
|
|
76
|
-
return { hidden, ctxML: null };
|
|
77
|
-
}
|
|
78
|
-
const m = model as unknown as {
|
|
79
|
-
hiddenTap: { layers: Set<number>; captured: Map<number, MlxArray> } | null;
|
|
80
|
-
};
|
|
81
|
-
const cap = new Map<number, MlxArray>();
|
|
82
|
-
m.hiddenTap = { layers: new Set(tapLayers), captured: cap };
|
|
83
|
-
let hidden: MlxArray | null = null;
|
|
84
|
-
try {
|
|
85
|
-
hidden = model.forwardHidden(ids, caches);
|
|
86
|
-
const perLayer = tapLayers.map((li) => {
|
|
87
|
-
const a = cap.get(li);
|
|
88
|
-
if (!a) throw new Error(`spec tap: layer ${li} not captured`);
|
|
89
|
-
return a;
|
|
90
|
-
});
|
|
91
|
-
const ctxML = ops.concatAxis(perLayer, 2); // [1,L,m*H]
|
|
92
|
-
for (const [, a] of cap) a.dispose();
|
|
93
|
-
cap.clear(); // consumed — the finally must not double-dispose
|
|
94
|
-
const out = { hidden, ctxML };
|
|
95
|
-
hidden = null; // ownership returned to the caller
|
|
96
|
-
return out;
|
|
97
|
-
} finally {
|
|
98
|
-
// On any throw (forward mid-capture, missing layer, concat), free the
|
|
99
|
-
// partially-captured tap tensors and the orphaned hidden. On success both
|
|
100
|
-
// are already gone (cap cleared, hidden nulled).
|
|
101
|
-
hidden?.dispose();
|
|
102
|
-
for (const [, a] of cap) a.dispose();
|
|
103
|
-
m.hiddenTap = null;
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
58
|
export interface SpecServeExtras {
|
|
108
59
|
drafted: number;
|
|
109
60
|
accepted: number;
|
|
@@ -115,7 +66,7 @@ export interface SpecServeExtras {
|
|
|
115
66
|
forwardsSaved: number;
|
|
116
67
|
/** Per-draft-position counters (index = position within a round's block,
|
|
117
68
|
* 0..γ-1): how many rounds drafted/accepted at that position. Drives the
|
|
118
|
-
* Phase-1c per-position acceptance report (scripts/dspark
|
|
69
|
+
* Phase-1c per-position acceptance report (scripts/dspark.ts ab)
|
|
119
70
|
* — near-zero cost, always populated. */
|
|
120
71
|
draftedByPos: number[];
|
|
121
72
|
acceptedByPos: number[];
|
|
@@ -128,12 +79,49 @@ export async function specServeRun(
|
|
|
128
79
|
promptIds: number[],
|
|
129
80
|
options: GenerateOptions & { stopSequences?: string[] },
|
|
130
81
|
onToken: OnToken,
|
|
82
|
+
): Promise<GenerateStats> {
|
|
83
|
+
return specRun(bindLegacySpeculativeModel(model, provider), numDraftTokens, promptIds, options, onToken);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** The verifier consumes a bound target and source factory, independent of
|
|
87
|
+
* concrete model classes. Compatibility callers use specServeRun above. */
|
|
88
|
+
export async function specRun(
|
|
89
|
+
binding: MlxSpeculativeBinding,
|
|
90
|
+
numDraftTokens: number,
|
|
91
|
+
promptIds: number[],
|
|
92
|
+
options: GenerateOptions & { stopSequences?: string[] },
|
|
93
|
+
onToken: OnToken,
|
|
94
|
+
): Promise<GenerateStats> {
|
|
95
|
+
return withRuntimeConfig(binding.runtime ?? runtimeConfig(), () => {
|
|
96
|
+
const run = () => specRunInner(binding, numDraftTokens, promptIds, options, onToken);
|
|
97
|
+
return binding.memory
|
|
98
|
+
? withModelWiredLimit(binding.memory, () => withModelUsageFlush(binding.memory!, run))
|
|
99
|
+
: run();
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async function specRunInner(
|
|
104
|
+
binding: MlxSpeculativeBinding,
|
|
105
|
+
numDraftTokens: number,
|
|
106
|
+
promptIds: number[],
|
|
107
|
+
options: GenerateOptions & { stopSequences?: string[] },
|
|
108
|
+
onToken: OnToken,
|
|
131
109
|
): Promise<GenerateStats> {
|
|
132
110
|
const maxTokens = options.maxTokens ?? 512;
|
|
133
|
-
const eos = options.eosTokenIds ??
|
|
111
|
+
const eos = options.eosTokenIds ?? [...binding.eosTokenIds];
|
|
134
112
|
const sampler = makeSampler(options);
|
|
135
|
-
const
|
|
113
|
+
const stepSampler = makeStepSampler(options, {
|
|
114
|
+
tokenRepresentation: "number",
|
|
115
|
+
grammarWait: "before-sample",
|
|
116
|
+
historyUpdate: "after-sample",
|
|
117
|
+
initialHistory: promptIds,
|
|
118
|
+
acceptGrammar: true,
|
|
119
|
+
eosTokenIds: eos,
|
|
120
|
+
sampler,
|
|
121
|
+
});
|
|
136
122
|
const gamma = Math.max(1, numDraftTokens);
|
|
123
|
+
const prefillChunk = options.prefillChunkSize ??
|
|
124
|
+
runtimeConfig().number("MLX_BUN_RD_PREFILL_CHUNK", PREFILL_CHUNK);
|
|
137
125
|
|
|
138
126
|
// Allocated INSIDE the try below (2026-07-07 review): provider.open()
|
|
139
127
|
// throws on a mismatched (target, drafter) pairing — as pre-try consts a
|
|
@@ -159,11 +147,6 @@ export async function specServeRun(
|
|
|
159
147
|
let vCtxML: MlxArray | null = null;
|
|
160
148
|
let vLogits: MlxArray | null = null;
|
|
161
149
|
let roundRow: MlxArray | null = null; // the in-flight verify/continuation row
|
|
162
|
-
// Device-side token history for the logits processors (repetition/presence/
|
|
163
|
-
// frequency penalties, logit_bias) — generate()'s pushHistory discipline.
|
|
164
|
-
let history: MlxArray | null =
|
|
165
|
-
processors.length > 0 ? ops.fromInt32(promptIds, [promptIds.length]) : null;
|
|
166
|
-
|
|
167
150
|
const extras: SpecServeExtras = {
|
|
168
151
|
drafted: 0,
|
|
169
152
|
accepted: 0,
|
|
@@ -185,65 +168,12 @@ export async function specServeRun(
|
|
|
185
168
|
spec: extras,
|
|
186
169
|
};
|
|
187
170
|
|
|
188
|
-
/**
|
|
189
|
-
*
|
|
190
|
-
*
|
|
191
|
-
* log-softmax → sampler. The GRAMMAR walk is inherently sequential: the
|
|
192
|
-
* mask at position i reflects every token emitted before it, so this
|
|
193
|
-
* awaits the matcher's async fill (fired by the previous accept) before
|
|
194
|
-
* masking, samples, then advances the matcher — the matcher only ever
|
|
195
|
-
* moves on tokens that are (about to be) EMITTED, which is what makes
|
|
196
|
-
* rollback-free v1 correct: rejected DRAFTS never touch grammar state.
|
|
197
|
-
* Appends the sampled token to the processor history. */
|
|
171
|
+
/** The speculative walk needs eager token ids and waits for each grammar
|
|
172
|
+
* mask. StepSampler keeps its processor, mask, sample, and history ordering
|
|
173
|
+
* identical to the serial and batched lanes. */
|
|
198
174
|
const grammar = options.grammar;
|
|
199
|
-
const samplePos = async (logits1V: MlxArray, step: number): Promise<number> =>
|
|
200
|
-
|
|
201
|
-
// fill reject, applyMask/toLogprobs/sampler error (2026-07-07 review:
|
|
202
|
-
// these were the one spot the header's hoist discipline missed; a
|
|
203
|
-
// grammar+penalties request whose matcher fill rejects orphaned one
|
|
204
|
-
// [1,V] row per occurrence). `cur` is owned only once it diverges from
|
|
205
|
-
// the caller's row.
|
|
206
|
-
let cur: MlxArray = logits1V;
|
|
207
|
-
let lp: MlxArray | null = null;
|
|
208
|
-
let tokArr: MlxArray | null = null;
|
|
209
|
-
try {
|
|
210
|
-
for (const p of processors) {
|
|
211
|
-
const next = p(history, cur);
|
|
212
|
-
if (cur !== logits1V) cur.dispose();
|
|
213
|
-
cur = next;
|
|
214
|
-
}
|
|
215
|
-
if (grammar && !grammar.isTerminated) {
|
|
216
|
-
await grammar.ready();
|
|
217
|
-
const masked = grammar.applyMask(cur);
|
|
218
|
-
if (cur !== logits1V) cur.dispose();
|
|
219
|
-
cur = masked;
|
|
220
|
-
}
|
|
221
|
-
lp = toLogprobs(cur);
|
|
222
|
-
if (cur !== logits1V) cur.dispose();
|
|
223
|
-
cur = logits1V; // consumed — nothing owned under this name now
|
|
224
|
-
tokArr = sampler(lp, step);
|
|
225
|
-
lp.dispose();
|
|
226
|
-
lp = null;
|
|
227
|
-
const tok = ops.itemUint32(tokArr);
|
|
228
|
-
tokArr.dispose();
|
|
229
|
-
tokArr = null;
|
|
230
|
-
// Advance the matcher on the emitted token (fires the next async fill).
|
|
231
|
-
// EOS is never content and never grammar-valid — don't feed it.
|
|
232
|
-
if (grammar && !eos.includes(tok)) grammar.accept(tok);
|
|
233
|
-
if (history) {
|
|
234
|
-
const t1 = ops.fromInt32([tok], [1]);
|
|
235
|
-
const prev = history;
|
|
236
|
-
history = ops.concatAxis([prev, t1], 0);
|
|
237
|
-
prev.dispose();
|
|
238
|
-
t1.dispose();
|
|
239
|
-
}
|
|
240
|
-
return tok;
|
|
241
|
-
} finally {
|
|
242
|
-
if (cur !== logits1V) cur.dispose();
|
|
243
|
-
lp?.dispose();
|
|
244
|
-
tokArr?.dispose();
|
|
245
|
-
}
|
|
246
|
-
};
|
|
175
|
+
const samplePos = async (logits1V: MlxArray, step: number): Promise<number> =>
|
|
176
|
+
(await stepSampler.sample(logits1V, step)).token;
|
|
247
177
|
|
|
248
178
|
/** The [1,V] logits row at position `pos` of a hidden window — batched
|
|
249
179
|
* lm-head is applied by the caller ONCE; this slices its output. */
|
|
@@ -255,11 +185,48 @@ export async function specServeRun(
|
|
|
255
185
|
return flat;
|
|
256
186
|
};
|
|
257
187
|
|
|
188
|
+
const forwardTokens = async (tokens: number[], taps?: number[]) => {
|
|
189
|
+
const ids = ops.fromInt32(tokens, [1, tokens.length]);
|
|
190
|
+
try { return await binding.forward(ids, caches, taps); }
|
|
191
|
+
finally { ids.dispose(); }
|
|
192
|
+
};
|
|
193
|
+
let failure: { error: unknown } | undefined;
|
|
194
|
+
|
|
258
195
|
try {
|
|
259
|
-
|
|
260
|
-
|
|
196
|
+
options.signal?.throwIfAborted();
|
|
197
|
+
if (!Number.isSafeInteger(prefillChunk) || prefillChunk < 1)
|
|
198
|
+
throw new Error("speculative prefill chunk must be a positive integer");
|
|
199
|
+
assertMlxSpeculativeBinding(binding);
|
|
200
|
+
if (options.turboQuant || options.kvConfig?.length ||
|
|
201
|
+
(options.kvBits && (options.kvBits !== 4 || options.quantizedKvStart !== 0)))
|
|
202
|
+
throw new Error("speculative KV requires the qualified uniform KV4 start=0 policy");
|
|
203
|
+
caches = binding.makeCache();
|
|
204
|
+
const src = binding.openDraft(sampler, caches);
|
|
261
205
|
source = src;
|
|
262
206
|
tapLayers = src.tapLayers;
|
|
207
|
+
const prefixBudget = Math.max(0, options.speculativeCacheBytes ?? 0);
|
|
208
|
+
const prefix = src.prefillMode === "full" ? src.prefix : undefined;
|
|
209
|
+
const prefixNamespace = JSON.stringify({ adapters: options.adapters ?? [],
|
|
210
|
+
kvBits: options.kvBits ?? null, kvGroupSize: options.kvGroupSize ?? 64,
|
|
211
|
+
quantizedKvStart: options.quantizedKvStart ?? null });
|
|
212
|
+
const cached = prefix?.restore(promptIds, caches, prefixNamespace, prefixBudget) ?? 0;
|
|
213
|
+
stats.cachedTokens = cached;
|
|
214
|
+
const snapshotAt = prefix && prefixBudget > 0
|
|
215
|
+
? Math.min(options.snapshotAt ?? promptIds.length, promptIds.length - 1)
|
|
216
|
+
: -1;
|
|
217
|
+
if (prefix && prefixBudget > 0 && cached > 0 && snapshotAt <= cached)
|
|
218
|
+
prefix.capture(promptIds.slice(0, cached), caches, prefixNamespace, prefixBudget);
|
|
219
|
+
const seedSource = async (end: number) => {
|
|
220
|
+
if (ctxParts.length === 1) prefillCtx = ctxParts[0]!;
|
|
221
|
+
else if (ctxParts.length > 1) {
|
|
222
|
+
prefillCtx = ops.concatAxis(ctxParts, 1);
|
|
223
|
+
for (const part of ctxParts) part.dispose();
|
|
224
|
+
}
|
|
225
|
+
ctxParts.length = 0;
|
|
226
|
+
const context = prefillCtx;
|
|
227
|
+
prefillCtx = null; // source.prefill owns context on both success and failure
|
|
228
|
+
await src.prefill(promptIds.slice(0, end), context ?? undefined);
|
|
229
|
+
};
|
|
263
230
|
// ---- prefill (target, chunked; optionally tapped for DSpark), then seed
|
|
264
231
|
// the source. Order: the source's prefill needs the tapped context that the
|
|
265
232
|
// target prefill produces, so target-first (two-model's own draft prefill
|
|
@@ -290,77 +257,71 @@ export async function specServeRun(
|
|
|
290
257
|
const t0 = performance.now();
|
|
291
258
|
const tailSplit =
|
|
292
259
|
src.prefillMode !== "full" &&
|
|
293
|
-
|
|
260
|
+
binding.prefillTailSplit;
|
|
294
261
|
const oracleShape = tailSplit && promptIds.length > 1;
|
|
295
262
|
if (oracleShape) {
|
|
296
263
|
let pos = 0;
|
|
297
264
|
const end = promptIds.length - 1; // last prompt token NEVER prefilled
|
|
298
265
|
while (pos < end) {
|
|
299
|
-
|
|
266
|
+
options.signal?.throwIfAborted();
|
|
267
|
+
const n = Math.min(prefillChunk, end - pos);
|
|
300
268
|
const chunk = promptIds.slice(pos, pos + n);
|
|
301
|
-
const
|
|
302
|
-
const { hidden: h, ctxML } = await forwardMaybeTap(
|
|
303
|
-
model,
|
|
304
|
-
ids,
|
|
305
|
-
caches,
|
|
306
|
-
tapLayers,
|
|
307
|
-
);
|
|
308
|
-
ids.dispose();
|
|
269
|
+
const { hidden: h, ctxML } = await forwardTokens(chunk, tapLayers);
|
|
309
270
|
if (ctxML) ctxParts.push(ctxML);
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
271
|
+
try {
|
|
272
|
+
if (pos + n >= end) {
|
|
273
|
+
const L = h.shape[1]!;
|
|
274
|
+
const H = h.shape[2]!;
|
|
275
|
+
anchorHidden = h.slice([0, L - 1, 0], [1, L, H]); // position len-2
|
|
276
|
+
}
|
|
277
|
+
} finally { h.dispose(); } // no logits during the drain
|
|
278
|
+
maybeQuantizeKv(caches, options);
|
|
316
279
|
clearCache();
|
|
317
280
|
pos += n;
|
|
281
|
+
if (pos < end) await new Promise<void>((resolve) => setImmediate(resolve));
|
|
318
282
|
}
|
|
319
283
|
} else {
|
|
320
284
|
// Legacy shape: full chunked prefill (final chunk included), token0
|
|
321
285
|
// sampled from the last position below.
|
|
322
|
-
for (let off =
|
|
323
|
-
|
|
324
|
-
const
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
caches,
|
|
329
|
-
tapLayers,
|
|
330
|
-
);
|
|
331
|
-
ids.dispose();
|
|
286
|
+
for (let off = cached; off < promptIds.length;) {
|
|
287
|
+
options.signal?.throwIfAborted();
|
|
288
|
+
const end = Math.min(off + prefillChunk, promptIds.length,
|
|
289
|
+
snapshotAt > off ? snapshotAt : Infinity);
|
|
290
|
+
const chunk = promptIds.slice(off, end);
|
|
291
|
+
const { hidden: h, ctxML } = await forwardTokens(chunk, tapLayers);
|
|
332
292
|
if (ctxML) ctxParts.push(ctxML);
|
|
333
|
-
if (
|
|
293
|
+
try { if (end >= promptIds.length) {
|
|
334
294
|
const L = h.shape[1]!;
|
|
335
295
|
const H = h.shape[2]!;
|
|
336
296
|
anchorHidden = h.slice([0, L - 1, 0], [1, L, H]); // [1,1,H], retained
|
|
337
|
-
const lg =
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
297
|
+
const lg = binding.projectLogits(anchorHidden);
|
|
298
|
+
try {
|
|
299
|
+
const V = lg.shape[lg.shape.length - 1]!;
|
|
300
|
+
lastLogits = ops.reshape(lg, [1, V]);
|
|
301
|
+
} finally { lg.dispose(); }
|
|
302
|
+
} } finally { h.dispose(); }
|
|
303
|
+
maybeQuantizeKv(caches, options);
|
|
304
|
+
if (end === snapshotAt && snapshotAt > cached) {
|
|
305
|
+
await seedSource(end);
|
|
306
|
+
prefix!.capture(promptIds.slice(0, end), caches, prefixNamespace, prefixBudget);
|
|
341
307
|
}
|
|
342
|
-
h.dispose();
|
|
343
308
|
clearCache();
|
|
309
|
+
off = end;
|
|
310
|
+
if (off < promptIds.length)
|
|
311
|
+
await new Promise<void>((resolve) => setImmediate(resolve));
|
|
344
312
|
}
|
|
345
313
|
}
|
|
346
314
|
extras.targetCalls++;
|
|
315
|
+
options.signal?.throwIfAborted();
|
|
347
316
|
// Seed the source: two-model prefills its own draft cache; DSpark seeds
|
|
348
317
|
// H_ctx from the tapped prompt context (ownership transfers to prefill);
|
|
349
318
|
// the assistant is a no-op.
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
for (const p of ctxParts) p.dispose();
|
|
355
|
-
}
|
|
356
|
-
ctxParts.length = 0; // parts consumed (transferred as prefillCtx or disposed)
|
|
357
|
-
await src.prefill(promptIds, prefillCtx ?? undefined); // takes ownership of prefillCtx
|
|
358
|
-
prefillCtx = null; // ownership transferred
|
|
359
|
-
} else {
|
|
360
|
-
await src.prefill(promptIds);
|
|
361
|
-
}
|
|
319
|
+
await seedSource(promptIds.length);
|
|
320
|
+
// Conversion replaces plain cache objects. Bind only after prefill has
|
|
321
|
+
// converted every attention layer; rollback must own the active objects.
|
|
322
|
+
const rollback = binding.bindRollback(caches);
|
|
362
323
|
stats.prefillMs = performance.now() - t0;
|
|
363
|
-
stats.prefillTps = (promptIds.length / Math.max(stats.prefillMs, 1e-6)) * 1000;
|
|
324
|
+
stats.prefillTps = ((promptIds.length - cached) / Math.max(stats.prefillMs, 1e-6)) * 1000;
|
|
364
325
|
|
|
365
326
|
// ---- first pending token ----
|
|
366
327
|
// Oracle shape: pending = the UNPROCESSED last prompt token — never
|
|
@@ -376,11 +337,12 @@ export async function specServeRun(
|
|
|
376
337
|
pending = await samplePos(lastLogits!, 0);
|
|
377
338
|
lastLogits!.dispose();
|
|
378
339
|
lastLogits = null;
|
|
340
|
+
stats.generatedTokens++;
|
|
379
341
|
if (eos.includes(pending)) {
|
|
342
|
+
stats.finishReason = "stop";
|
|
380
343
|
stats.decodeMs = performance.now() - tDecode;
|
|
381
344
|
return stats;
|
|
382
345
|
}
|
|
383
|
-
stats.generatedTokens++;
|
|
384
346
|
if (
|
|
385
347
|
(await onToken(pending)) === false ||
|
|
386
348
|
stats.generatedTokens >= maxTokens ||
|
|
@@ -397,28 +359,24 @@ export async function specServeRun(
|
|
|
397
359
|
|
|
398
360
|
// ---- rounds ----
|
|
399
361
|
decode: while (stats.generatedTokens < maxTokens) {
|
|
362
|
+
options.signal?.throwIfAborted();
|
|
400
363
|
if (!speculating) {
|
|
401
364
|
// plain single-token continuation (post-ring-wrap fallback)
|
|
402
|
-
const
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
const lg = model.logitsFromHidden(h);
|
|
411
|
-
h.dispose();
|
|
412
|
-
const V = lg.shape[lg.shape.length - 1]!;
|
|
413
|
-
roundRow = ops.reshape(lg, [1, V]);
|
|
414
|
-
lg.dispose();
|
|
365
|
+
const { hidden: h } = await forwardTokens([pending], undefined);
|
|
366
|
+
try {
|
|
367
|
+
const lg = binding.projectLogits(h);
|
|
368
|
+
try {
|
|
369
|
+
const V = lg.shape[lg.shape.length - 1]!;
|
|
370
|
+
roundRow = ops.reshape(lg, [1, V]);
|
|
371
|
+
} finally { lg.dispose(); }
|
|
372
|
+
} finally { h.dispose(); }
|
|
415
373
|
extras.targetCalls++;
|
|
416
374
|
const tok = await samplePos(roundRow, stats.generatedTokens);
|
|
417
375
|
roundRow.dispose();
|
|
418
376
|
roundRow = null;
|
|
419
377
|
clearCache();
|
|
420
|
-
if (eos.includes(tok)) break;
|
|
421
378
|
stats.generatedTokens++;
|
|
379
|
+
if (eos.includes(tok)) { stats.finishReason = "stop"; break; }
|
|
422
380
|
pending = tok;
|
|
423
381
|
if ((await onToken(tok)) === false) break;
|
|
424
382
|
if (grammar?.isTerminated) break; // grammar closed — finish "stop"
|
|
@@ -433,11 +391,11 @@ export async function specServeRun(
|
|
|
433
391
|
// KV would be woven into the live window (unrecoverable, upstream
|
|
434
392
|
// RAISES here, generate.py:529-533). Degrade to plain decode while the
|
|
435
393
|
// caches are still clean instead of 500ing a stream.
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
);
|
|
394
|
+
// Non-trimmable recurrent caches (SSMCache — gated-DeltaNet state) are
|
|
395
|
+
// spec-eligible through the round snapshot/replay contract instead:
|
|
396
|
+
// specRoundBegin before the verify forward, then commit or a bit-exact
|
|
397
|
+
// rollback(keep) after the accept walk.
|
|
398
|
+
const roundFits = rollback.canBegin(n);
|
|
441
399
|
if (!roundFits) {
|
|
442
400
|
console.warn(
|
|
443
401
|
"spec: sliding window nearly wrapped — finishing without speculation",
|
|
@@ -476,20 +434,22 @@ export async function specServeRun(
|
|
|
476
434
|
// (b) ONE target forward over [pending, ...drafts] (optionally tapped for
|
|
477
435
|
// DSpark's H_ctx). vHidden is retained past the accept walk: its slice at
|
|
478
436
|
// the emitted position is the next round's anchor hidden.
|
|
437
|
+
// Arm recurrent caches' spec round around the verify forward (no-op for
|
|
438
|
+
// trimmable caches, which roll back via trim() below).
|
|
439
|
+
rollback.begin(d);
|
|
479
440
|
const vIds = ops.fromInt32([pending, ...drafts], [1, d + 1]);
|
|
480
|
-
|
|
481
|
-
"setSpecKernelPinned" in model;
|
|
482
|
-
if (pinTarget) model.setSpecKernelPinned(true);
|
|
441
|
+
let pin: { close(): void } | undefined;
|
|
483
442
|
try {
|
|
484
|
-
|
|
443
|
+
pin = src.pinTargetKernelFamily ? binding.pinVerify?.() : undefined;
|
|
444
|
+
const ft = await binding.forward(vIds, caches, tapLayers);
|
|
485
445
|
// Assign these before the lm-head call so the outer cleanup owns them
|
|
486
446
|
// even if logits construction throws.
|
|
487
447
|
vHidden = ft.hidden;
|
|
488
448
|
vCtxML = ft.ctxML;
|
|
489
|
-
vLogits =
|
|
449
|
+
vLogits = binding.projectLogits(vHidden);
|
|
490
450
|
} finally {
|
|
491
451
|
vIds.dispose();
|
|
492
|
-
|
|
452
|
+
pin?.close();
|
|
493
453
|
}
|
|
494
454
|
extras.targetCalls++;
|
|
495
455
|
// The lm-head remains one logical batched operation. Native GLM MTP's
|
|
@@ -545,10 +505,18 @@ export async function specServeRun(
|
|
|
545
505
|
|
|
546
506
|
// (d) emit the round's tokens through onToken, one at a time
|
|
547
507
|
for (const tok of emitted) {
|
|
508
|
+
options.signal?.throwIfAborted();
|
|
548
509
|
stats.generatedTokens++;
|
|
549
510
|
if ((await onToken(tok)) === false) { halted = true; break; }
|
|
550
511
|
if (stats.generatedTokens >= maxTokens) break;
|
|
551
512
|
}
|
|
513
|
+
// Native generation and mlx-lm count the stopping EOS in usage while
|
|
514
|
+
// keeping it out of content. A callback or budget may stop earlier in
|
|
515
|
+
// this verified burst; that unused suffix must not enter usage.
|
|
516
|
+
if (sawEos && !halted && stats.generatedTokens < maxTokens) {
|
|
517
|
+
stats.generatedTokens++;
|
|
518
|
+
stats.finishReason = "stop";
|
|
519
|
+
}
|
|
552
520
|
extras.tokensPerForward = extras.rounds > 0
|
|
553
521
|
? stats.generatedTokens / extras.rounds
|
|
554
522
|
: 0;
|
|
@@ -565,7 +533,11 @@ export async function specServeRun(
|
|
|
565
533
|
// position (the assistant borrows it; generate.ts:230 pattern — the slice
|
|
566
534
|
// outlives its parent's dispose).
|
|
567
535
|
if (!stop) {
|
|
568
|
-
|
|
536
|
+
// The verify window kept positions 0..kAccept (pending + accepted
|
|
537
|
+
// drafts). Trimmable caches drop the rejected tail; recurrent caches
|
|
538
|
+
// restore their pre-round snapshot and bit-exactly replay those
|
|
539
|
+
// kAccept+1 kept tokens. On full accept the round just commits.
|
|
540
|
+
rollback.resolve(kAccept);
|
|
569
541
|
await src.commit(
|
|
570
542
|
d,
|
|
571
543
|
kAccept,
|
|
@@ -597,21 +569,15 @@ export async function specServeRun(
|
|
|
597
569
|
? (stats.generatedTokens / stats.decodeMs) * 1000
|
|
598
570
|
: 0;
|
|
599
571
|
return stats;
|
|
572
|
+
} catch (error) {
|
|
573
|
+
failure = { error };
|
|
574
|
+
throw error;
|
|
600
575
|
} finally {
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
vLogits?.dispose();
|
|
608
|
-
roundRow?.dispose();
|
|
609
|
-
vHidden?.dispose();
|
|
610
|
-
vCtxML?.dispose();
|
|
611
|
-
for (const c of caches) c.dispose();
|
|
612
|
-
source?.dispose();
|
|
613
|
-
history?.dispose();
|
|
614
|
-
options.grammar?.dispose(); // Phase C will consume it; never leak either way
|
|
615
|
-
clearCache();
|
|
576
|
+
const resources = [anchorHidden, lastLogits, prefillCtx, ...ctxParts,
|
|
577
|
+
vLogits, roundRow, vHidden, vCtxML, ...caches, source, stepSampler,
|
|
578
|
+
options.grammar, { dispose: clearCache }]
|
|
579
|
+
.filter((resource): resource is DisposableResource => resource != null);
|
|
580
|
+
if (failure) cleanupFailure(failure.error, () => disposeResources(resources));
|
|
581
|
+
else disposeResources(resources);
|
|
616
582
|
}
|
|
617
583
|
}
|
package/src/spec/source.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// DraftSource — the seam between the serve-time speculative verify loop
|
|
2
2
|
// (src/spec/serve-loop.ts) and whatever produces draft tokens. Designed in
|
|
3
|
-
// docs/
|
|
3
|
+
// docs/reference/cli.md §7 so every drafter shares ONE
|
|
4
4
|
// verify/accept executor:
|
|
5
5
|
// - TwoModelSource (src/spec/two-model.ts) — mlx-lm parity (L1 oracle:
|
|
6
6
|
// mlx_lm.server --draft-model), a full second model. Ignores `target`.
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
// Reads the target's donor K/V + anchor hidden each step.
|
|
10
10
|
// - DflashSource (src/spec/dflash-source.ts) — DSpark (L3, KL/quality-gated).
|
|
11
11
|
// Taps the target's multi-layer hiddens (prefill + verify) into a growing
|
|
12
|
-
// H_ctx (docs/design/
|
|
12
|
+
// H_ctx (docs/design/speculative-decoding.md).
|
|
13
13
|
// - NgramSource (src/spec/ngram-source.ts) — model-free prompt lookup.
|
|
14
14
|
// - Glm52NativeMtpSource (src/spec/glm52-mtp-source.ts) — the target
|
|
15
15
|
// artifact's native Colibri MTP row.
|
|
@@ -21,21 +21,53 @@
|
|
|
21
21
|
// two-model). See [[dspark-seam-kv-borrowing]].
|
|
22
22
|
|
|
23
23
|
import type { MlxArray } from "../mlx/array";
|
|
24
|
-
import type {
|
|
24
|
+
import type { Dtype } from "../mlx/ffi";
|
|
25
25
|
import type { Cache } from "../model/gemma4";
|
|
26
26
|
|
|
27
|
-
/**
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
27
|
+
/** Numerical ports are backend-specific; returned arrays are caller-owned. */
|
|
28
|
+
export interface DraftProjection {
|
|
29
|
+
readonly embed: {
|
|
30
|
+
encode(ids: MlxArray): MlxArray;
|
|
31
|
+
readonly scales: { readonly dtype: Dtype };
|
|
32
|
+
};
|
|
33
|
+
logitsFromHidden(hidden: MlxArray): MlxArray;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface AssistantTarget {
|
|
37
|
+
position(): number;
|
|
38
|
+
embedScaled(token: number): MlxArray;
|
|
39
|
+
/** Retained temporal views; the draft round disposes every returned array. */
|
|
40
|
+
readDonors(): { sliding: [MlxArray, MlxArray]; full: [MlxArray, MlxArray] };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface QwenMtpTarget {
|
|
44
|
+
readonly hiddenSize: number;
|
|
45
|
+
readonly layerCount: number;
|
|
46
|
+
embed(ids: MlxArray): MlxArray;
|
|
47
|
+
logitsFromHidden(hidden: MlxArray): MlxArray;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Graph-declared extensions over the target's live state. Sources request
|
|
51
|
+
* only the ports they consume; they never inspect a concrete model or cache.
|
|
52
|
+
* Absence refuses an unsupported pairing before draft-side allocation. */
|
|
31
53
|
export interface TargetView {
|
|
32
|
-
|
|
33
|
-
readonly
|
|
54
|
+
/** Opaque identity for providers borrowing weights from one exact target. */
|
|
55
|
+
readonly identity: object;
|
|
56
|
+
readonly assistant?: AssistantTarget;
|
|
57
|
+
readonly gemmaTaps?: { readonly layerCount: number; readonly projection: DraftProjection };
|
|
58
|
+
readonly qwenMtp?: QwenMtpTarget;
|
|
34
59
|
}
|
|
35
60
|
|
|
36
61
|
/** A per-request draft-token producer. Created per generation (owns its own
|
|
37
62
|
* draft-side state), disposed by the serve loop's finally. */
|
|
38
63
|
export interface DraftSource {
|
|
64
|
+
/** Optional paired target/draft prefill state. Restore transfers matching
|
|
65
|
+
* target state into the supplied array. Capture borrows evaluated state;
|
|
66
|
+
* the source owns its retained snapshot. Only full-prefill sources opt in. */
|
|
67
|
+
readonly prefix?: {
|
|
68
|
+
restore(prompt: readonly number[], caches: Cache[], namespace: string, maxBytes: number): number;
|
|
69
|
+
capture(tokens: number[], caches: Cache[], namespace: string, maxBytes: number): void;
|
|
70
|
+
};
|
|
39
71
|
/** Target prefill shape required by this source's oracle. Most mlx-lm
|
|
40
72
|
* sources leave the final prompt token pending; native Colibri MTP starts
|
|
41
73
|
* from a full-prompt target forward. */
|
|
@@ -111,7 +143,7 @@ export interface DraftProvider {
|
|
|
111
143
|
* sampler as the target — mlx-lm parity; greedy drafting under a
|
|
112
144
|
* temperature>0 request is NOT parity). */
|
|
113
145
|
sampler: (logprobs: MlxArray, step: number) => MlxArray;
|
|
114
|
-
/**
|
|
146
|
+
/** Bound target capabilities, with explicit donor-view ownership. */
|
|
115
147
|
target: TargetView;
|
|
116
148
|
}): DraftSource;
|
|
117
149
|
dispose(): void;
|
package/src/spec/two-model.ts
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
import { MlxArray } from "../mlx/array";
|
|
17
17
|
import * as ops from "../mlx/ops";
|
|
18
18
|
import { clearCache } from "../mlx/ffi";
|
|
19
|
-
import { flagOn } from "../
|
|
19
|
+
import { flagOn } from "../runtime-config";
|
|
20
20
|
import { loadModelConfig } from "../config";
|
|
21
21
|
import { Weights } from "../weights";
|
|
22
22
|
import { createModel, type RuntimeModel } from "../model/factory";
|