mlx-bun 0.2.0 → 0.4.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 -588
- package/docs/README.md +94 -0
- package/docs/{design → archive/investigations}/audio-input-plan.md +4 -4
- package/docs/{design → archive/investigations}/diffusion-gemma-port.md +3 -3
- package/docs/{design → archive/investigations}/faithful-l1-consolidation.md +4 -4
- package/docs/{design → archive/investigations}/minicpm5-decode-megakernel.md +11 -11
- package/docs/archive/investigations/openjarvis-fit.md +198 -0
- package/docs/{design → archive/investigations}/optimization_plan.md +1 -1
- package/docs/{design → archive/investigations}/pre-colibri-stabilization.md +5 -5
- package/docs/archive/investigations/qwen38-27b-campaign-log.md +1092 -0
- package/docs/{design → archive/investigations}/the-dreaming-handoff.md +2 -2
- package/docs/archive/planning/release-notes-v0.3.0.md +35 -0
- package/docs/design/batching.md +1841 -0
- package/docs/design/decode-speed-program.md +2346 -169
- package/docs/design/dreaming-nightly-pipeline.md +675 -282
- package/docs/design/generic-model-support.md +655 -334
- package/docs/design/kv-cache.md +824 -0
- package/docs/design/orpo-training.md +673 -1239
- package/docs/design/speculative-decoding.md +1990 -0
- package/docs/design/turboquant.md +2212 -0
- package/docs/design/unified-engine-frontier-plan.md +1833 -727
- package/docs/design/web-chat-redesign.md +666 -1083
- package/docs/planning/PRODUCT_ROADMAP.md +50 -1
- package/docs/planning/release-notes-v0.4.0.md +45 -0
- package/docs/planning/video-series.md +2 -2
- package/docs/reference/benchmarks.md +3613 -0
- package/docs/reference/cli.md +434 -160
- package/docs/reference/distribution.md +328 -182
- package/docs/reference/environment.md +398 -0
- package/docs/reference/glossary.md +39 -0
- package/docs/reference/library-api.md +566 -168
- package/docs/reference/memory.md +227 -64
- package/docs/reference/models.md +113 -6
- package/docs/reference/server-api.md +944 -429
- package/docs/reference/server-config.md +740 -338
- package/docs/reference/training.md +393 -254
- package/docs/reference/troubleshooting.md +158 -0
- package/package.json +9 -4
- package/src/audio/decode.ts +2 -2
- package/src/audio/features.ts +2 -2
- package/src/backends/mlx/assistant-target.ts +46 -0
- package/src/backends/mlx/autoregressive-draft-rows.ts +120 -0
- package/src/backends/mlx/autoregressive.ts +108 -0
- package/src/backends/mlx/batch-group.ts +1790 -0
- package/src/backends/mlx/cache-attachment.ts +67 -0
- package/src/backends/mlx/cache-layout.ts +31 -0
- package/src/backends/mlx/checkpoint-state.ts +53 -0
- package/src/backends/mlx/continuation-persistence.ts +106 -0
- package/src/backends/mlx/continuation-request.ts +43 -0
- package/src/backends/mlx/continuation.ts +77 -0
- package/src/backends/mlx/diffusion.ts +39 -0
- package/src/backends/mlx/draft-target.ts +32 -0
- package/src/backends/mlx/evaluation.ts +105 -0
- package/src/backends/mlx/fill-append.ts +45 -0
- package/src/backends/mlx/full-kv-row-append.ts +32 -0
- package/src/backends/mlx/full-kv-row-donor.ts +54 -0
- package/src/backends/mlx/full-transitioning-kv-rows.ts +65 -0
- package/src/backends/mlx/gateway-binding.ts +130 -0
- package/src/backends/mlx/graph.ts +38 -0
- package/src/backends/mlx/kv-codec.ts +9 -0
- package/src/backends/mlx/kv-maintenance.ts +133 -0
- package/src/backends/mlx/kv-tensor-rows.ts +212 -0
- package/src/backends/mlx/memory-client.ts +189 -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-cohort.ts +89 -0
- package/src/backends/mlx/prefill-rows.ts +200 -0
- package/src/backends/mlx/prefill.ts +33 -0
- package/src/backends/mlx/projected-context-rows.ts +72 -0
- package/src/backends/mlx/request-policy.ts +20 -0
- package/src/backends/mlx/request-state-policy.ts +41 -0
- package/src/backends/mlx/rollback.ts +89 -0
- package/src/backends/mlx/row-sampling.ts +49 -0
- package/src/backends/mlx/serial-executor.ts +242 -0
- package/src/backends/mlx/speculative-group.ts +359 -0
- package/src/backends/mlx/speculative-round.ts +161 -0
- package/src/backends/mlx/speculative.ts +114 -0
- package/src/backends/mlx/state-rows.ts +104 -0
- package/src/backends/mlx/state-views.ts +46 -0
- package/src/backends/mlx/target-layout-capability.ts +35 -0
- package/src/backends/mlx/text-engine.ts +73 -0
- package/src/backends/mlx/transitioning-kv-rows.ts +165 -0
- package/src/cli.ts +66 -58
- package/src/client.ts +66 -0
- package/src/config.ts +21 -4
- package/src/contracts/completion.ts +16 -0
- package/src/contracts/execution.ts +60 -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/prefix-cache.ts +23 -0
- package/src/contracts/resources.ts +25 -0
- package/src/contracts/sampling.ts +23 -0
- package/src/contracts/scheduling.ts +45 -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 +53 -0
- package/src/engine/resources.ts +53 -0
- package/src/engine/scheduler.ts +72 -0
- package/src/engine/session.ts +247 -0
- package/src/eval/README.md +44 -1
- package/src/eval/kl.ts +236 -3
- package/src/eval/reference-logits.ts +207 -0
- package/src/eval/runner.ts +8 -96
- 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 +30 -16
- package/src/generate.ts +709 -417
- package/src/grammar.ts +12 -7
- package/src/harness-pi.ts +14 -33
- package/src/hub-rest.ts +4 -4
- package/src/index.ts +7 -0
- package/src/inference/denoising.ts +17 -0
- package/src/inference/draft-acceptance.ts +41 -0
- package/src/inference/draft-output.ts +49 -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 +12 -12
- 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 +70 -12
- package/src/kv-store.ts +177 -63
- 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} +5 -5
- package/src/lab/paged-kv/paged-kv-rows.ts +83 -0
- package/src/{model → lab/paged-kv}/paged-kv.ts +32 -15
- package/src/library.ts +40 -0
- package/src/lora.ts +18 -0
- package/src/media-fetch.ts +1 -1
- package/src/memory/article.ts +1 -1
- package/src/memory/chunk.ts +1 -1
- package/src/memory/model.ts +30 -169
- 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 +1 -3
- package/src/mlx/array.ts +11 -11
- package/src/mlx/compile.ts +12 -2
- package/src/mlx/ffi.ts +58 -11
- package/src/mlx/materialize.ts +23 -0
- package/src/mlx/metal-kernel.ts +10 -11
- package/src/mlx/ops.ts +33 -13
- 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/artifact-identity.ts +73 -0
- package/src/model/batched-kv.ts +235 -0
- package/src/model/batched-mask.ts +20 -6
- package/src/model/batched-quant.ts +9 -3
- package/src/model/batched-quantized-kv.ts +144 -0
- package/src/model/batched-rotating-quant.ts +78 -17
- package/src/model/batched-rotating-state.ts +76 -0
- package/src/model/batched-rotating.ts +65 -23
- package/src/model/batched-row-storage.ts +52 -0
- package/src/model/batched-ssm.ts +52 -0
- package/src/model/batched-turboquant-kv.ts +93 -0
- package/src/model/compiled-decode.ts +8 -2
- package/src/model/decoded-kv-donor.ts +12 -0
- package/src/model/delayed-quantized-kv.ts +72 -0
- package/src/model/delayed-rotating-quantized-kv.ts +93 -0
- package/src/model/delayed-turboquant-kv.ts +55 -0
- package/src/model/diffusion-gemma.ts +8 -8
- package/src/model/factory.ts +76 -33
- package/src/model/fingerprint.ts +1 -1
- package/src/model/flash-attention.ts +3 -3
- package/src/model/full-prefill-padding.ts +56 -0
- package/src/model/full-prefill-row.ts +105 -0
- package/src/model/gemma4-base.ts +179 -80
- package/src/model/gemma4.ts +40 -32
- package/src/model/generated/gemma4-12b.ts +6 -3
- package/src/model/generated/gemma4-26b.ts +6 -3
- package/src/model/generated/gemma4-e4b.ts +6 -3
- package/src/model/glm52-cache.ts +80 -4
- package/src/model/glm52-dsa.ts +13 -9
- package/src/model/glm52-memory.ts +1 -1
- package/src/model/glm52-mla.ts +12 -6
- package/src/model/glm52.ts +19 -40
- package/src/model/implementation.ts +69 -0
- package/src/model/kv-attention-view.ts +67 -0
- package/src/model/minicpm5.ts +17 -15
- package/src/model/profile.ts +12 -2
- package/src/model/qwen-append.ts +12 -0
- package/src/model/qwen-conv.ts +52 -0
- package/src/model/qwen3-delta-state.ts +74 -0
- package/src/model/qwen3-delta.ts +59 -36
- package/src/model/qwen3_5-checkpoint.ts +164 -0
- package/src/model/qwen3_5.ts +202 -78
- package/src/model/rotating-kv-donor.ts +35 -0
- package/src/model/rotating-kv-layout.ts +262 -0
- package/src/model/rotating-row-transaction.ts +35 -0
- package/src/model/speculative-rotating-kv.ts +101 -0
- package/src/model/ssm-prefill-padding.ts +39 -0
- 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/turboquant-codec.ts +83 -0
- package/src/model/universal/dense.ts +15 -14
- package/src/model/universal/modules.ts +8 -1
- package/src/native-pack.ts +6 -5
- package/src/pi-provider.ts +6 -64
- package/src/pi-web.ts +8 -315
- package/src/preflight.ts +31 -15
- package/src/prompt-cache.ts +110 -69
- package/src/quantize/allocator.ts +1 -1
- package/src/quantize/config-writer.ts +10 -3
- package/src/quantize/rotate.ts +4 -4
- package/src/quantize/trellis.ts +586 -0
- package/src/responses.ts +28 -1
- package/src/runtime-config.ts +18 -4
- package/src/sampler.ts +40 -29
- package/src/serve/batch-scheduler.ts +8 -1671
- 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 +15 -7
- package/src/serve/discovery-routes.ts +12 -13
- package/src/serve/generation-gateway.ts +183 -213
- 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/lab-routes.ts +3 -0
- package/src/serve/lane-registry.ts +2 -1
- package/src/serve/model-admin-routes.ts +7 -5
- package/src/serve/model-binding.ts +43 -0
- package/src/serve/model-host.ts +511 -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/request-plan.ts +23 -18
- package/src/serve/request-prep.ts +344 -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 +5 -5
- package/src/serve/text-completion-stage.ts +104 -0
- package/src/serve/token-streams.ts +519 -0
- package/src/server.ts +335 -2711
- package/src/spec/assistant-rows.ts +92 -0
- package/src/spec/assistant-source.ts +31 -55
- package/src/spec/cache-identity.ts +22 -0
- package/src/spec/deepspec-rows.ts +29 -0
- package/src/spec/deepspec-source.ts +16 -14
- package/src/spec/dflash-source.ts +36 -12
- package/src/spec/drafter.ts +153 -142
- package/src/spec/dspark/ab-stats.ts +3 -3
- package/src/spec/dspark/calibration.ts +2 -2
- package/src/spec/dspark/deepspec-module.ts +97 -122
- 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 +129 -172
- 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-graph.ts +29 -0
- package/src/spec/glm52-mtp-rows.ts +132 -0
- package/src/spec/glm52-mtp-source.ts +54 -137
- package/src/spec/glm52-mtp-state.ts +33 -0
- package/src/spec/ngram-source.ts +93 -21
- package/src/spec/projected-draft-rows.ts +73 -0
- package/src/spec/qwen-mtp-module.ts +171 -0
- package/src/spec/qwen-mtp-rows.ts +219 -0
- package/src/spec/qwen-mtp-source.ts +147 -195
- package/src/spec/qwen-mtp-state.ts +30 -0
- package/src/spec/serve-loop.ts +201 -201
- package/src/spec/source.ts +110 -13
- package/src/spec/two-model-rows.ts +61 -0
- package/src/spec/two-model.ts +44 -102
- package/src/ssd-cache.ts +99 -19
- package/src/ssd-durability.ts +36 -20
- package/src/tool-call.ts +51 -14
- package/src/train/flash-cce.ts +2 -2
- package/src/train/forward.ts +6 -0
- package/src/train/loss.ts +1 -1
- package/src/train/prefix-shared.ts +5 -1
- package/src/train/segmented.ts +5 -3
- package/src/train/trainer.ts +6 -6
- package/src/vision/prompt.ts +1 -1
- package/src/vision/qwen3vl-tower.ts +52 -10
- package/src/web/app.html +1 -1
- package/src/web/app.js +62 -60
- package/src/web/src/adapters-panel.ts +3 -3
- package/src/web/src/assistant.ts +2 -36
- package/src/web/src/chat.ts +4 -4
- 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 +55 -9
- package/docs/archive/investigations/curve-runs/bisector-route.json +0 -98
- package/docs/archive/investigations/curve-runs/bisector-trace.json +0 -1393
- package/docs/archive/investigations/curve-runs/curve-terrain.json +0 -1179
- package/docs/archive/investigations/curve-runs/curve-terrain.svg +0 -42
- package/docs/archive/investigations/hlg-ab.html +0 -34
- package/docs/archive/investigations/hlg-explorer.html +0 -238
- package/docs/archive/investigations/hlg-figs/cliff.svg +0 -1
- package/docs/archive/investigations/hlg-figs/cross.svg +0 -1
- package/docs/archive/investigations/hlg-figs/edges.svg +0 -1
- package/docs/archive/investigations/hlg-figs/k20-grids.svg +0 -1
- package/docs/archive/investigations/hlg-figs/k20-peaks.svg +0 -1
- package/docs/archive/investigations/hlg-figs/lw-curve.svg +0 -1
- package/docs/archive/investigations/hlg-figs/terrain.svg +0 -1
- package/docs/archive/investigations/hlg-report.html +0 -262
- package/docs/archive/investigations/hlg-runs/hlg-ab.json +0 -149
- package/docs/archive/investigations/hlg-runs/hlg-blind-prompts.md +0 -87
- package/docs/archive/investigations/hlg-runs/hlg-blind.json +0 -107
- package/docs/archive/investigations/hlg-runs/hlg-cliff.json +0 -197
- package/docs/archive/investigations/hlg-runs/hlg-cross.json +0 -505
- package/docs/archive/investigations/hlg-runs/hlg-edge.json +0 -388
- package/docs/archive/investigations/hlg-runs/hlg-eval1.json +0 -25
- package/docs/archive/investigations/hlg-runs/hlg-frontier.json +0 -86
- package/docs/archive/investigations/hlg-runs/hlg-grid.json +0 -242
- package/docs/archive/investigations/hlg-runs/hlg-grid2.json +0 -343
- package/docs/archive/investigations/hlg-runs/hlg-lw-coarse.json +0 -110
- package/docs/archive/investigations/hlg-runs/hlg-map.json +0 -383
- package/docs/archive/investigations/hlg-runs/run0-original.log +0 -159
- package/docs/archive/investigations/hlg-runs/runA-recommended.log +0 -131
- package/docs/archive/investigations/hlg-runs/runAA-cliff-cpm5.log +0 -26
- package/docs/archive/investigations/hlg-runs/runAB-grid2-e4b.log +0 -18
- package/docs/archive/investigations/hlg-runs/runAC-peak-e4b-partial.log +0 -25
- package/docs/archive/investigations/hlg-runs/runAD-ab-samples.log +0 -8
- package/docs/archive/investigations/hlg-runs/runAE-edge-e4b.log +0 -51
- package/docs/archive/investigations/hlg-runs/runAF-logit-calibration.log +0 -10
- package/docs/archive/investigations/hlg-runs/runAG-eval-config2.log +0 -16
- package/docs/archive/investigations/hlg-runs/runB-gain-variants.log +0 -192
- package/docs/archive/investigations/hlg-runs/runC-pivots.log +0 -162
- package/docs/archive/investigations/hlg-runs/runD-oetf.log +0 -132
- package/docs/archive/investigations/hlg-runs/runE-eotf.log +0 -162
- package/docs/archive/investigations/hlg-runs/runF-shaper-working.log +0 -86
- package/docs/archive/investigations/hlg-runs/runG-shaper-sweep.log +0 -101
- package/docs/archive/investigations/hlg-runs/runH-wsweep.log +0 -176
- package/docs/archive/investigations/hlg-runs/runI-asweep.log +0 -176
- package/docs/archive/investigations/hlg-runs/runJ-knobmap.log +0 -49
- package/docs/archive/investigations/hlg-runs/runK-frontier.log +0 -19
- package/docs/archive/investigations/hlg-runs/runL-widemap.log +0 -65
- package/docs/archive/investigations/hlg-runs/runM-frontier-emb.log +0 -15
- package/docs/archive/investigations/hlg-runs/runN-verify.log +0 -116
- package/docs/archive/investigations/hlg-runs/runO-frontier-seed5000.log +0 -15
- package/docs/archive/investigations/hlg-runs/runP-grid-AxSm.log +0 -15
- package/docs/archive/investigations/hlg-runs/runQ-grid-tgW.log +0 -15
- package/docs/archive/investigations/hlg-runs/runR-blind-transcript.log +0 -79
- package/docs/archive/investigations/hlg-runs/runS-cross-coherence.log +0 -33
- package/docs/archive/investigations/hlg-runs/runT-cross-cpm5.log +0 -33
- package/docs/archive/investigations/hlg-runs/runU-cross-12b.log +0 -33
- package/docs/archive/investigations/hlg-runs/runV-lw-e4b.log +0 -22
- package/docs/archive/investigations/hlg-runs/runW-lw-12b.log +0 -22
- package/docs/archive/investigations/hlg-runs/runX-lw-cpm5.log +0 -22
- package/docs/archive/investigations/hlg-runs/runY-cliff-e4b.log +0 -26
- package/docs/archive/investigations/hlg-runs/runZ-cliff-12b.log +0 -26
- 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 -318
- 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-serving-execution-seam.md +0 -81
- 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 -231
- 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 -415
- 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/turboquant-weights.md +0 -467
- 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/planning/IDEAS.md +0 -69
- package/docs/reference/embedding.md +0 -197
- package/docs/reference/features-matrix.md +0 -101
- package/docs/reference/orpo-quickstart.md +0 -135
- package/src/flags.ts +0 -11
- /package/docs/{design → archive/investigations}/colibri-glm52-port.md +0 -0
- /package/docs/{planning → archive/planning}/chunk-task-roadmap.md +0 -0
- /package/docs/{planning → archive/planning}/curve-sampler-research-plan.md +0 -0
- /package/docs/{planning → archive/planning}/memory-docs-and-dag-plan.md +0 -0
- /package/docs/{planning → archive/planning}/pi-integration-review.md +0 -0
- /package/docs/{planning → archive/planning}/release-notes-v0.2.0.md +0 -0
- /package/src/{memory-plan.ts → admission-plan.ts} +0 -0
- /package/{docs/archive/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
|
|
@@ -17,11 +17,9 @@
|
|
|
17
17
|
// trimmable mid-generation; a serve endpoint must not 500 mid-stream, so
|
|
18
18
|
// we STOP SPECULATING and finish the generation with plain single-token
|
|
19
19
|
// decode (bit-equivalent continuation — the target's own samples).
|
|
20
|
-
// -
|
|
21
|
-
//
|
|
22
|
-
//
|
|
23
|
-
// SSD tier is a tracked follow-up in the integration plan, not silently
|
|
24
|
-
// absent.
|
|
20
|
+
// - Sources with checkpoint support use the injected prefix-cache service.
|
|
21
|
+
// Target and companion state share the existing RAM/SSD lifecycle. Other
|
|
22
|
+
// sources begin with fresh state until they supply a checkpoint codec.
|
|
25
23
|
//
|
|
26
24
|
// Grammar × spec (Phase C, the constrained verify walk — novel: NO runtime
|
|
27
25
|
// serves both; mlx-lm has no grammar, oMLX no spec): the drafter runs FREE
|
|
@@ -31,7 +29,7 @@
|
|
|
31
29
|
// so rejected drafts never touch grammar state and no matcher rollback is
|
|
32
30
|
// needed. Grammar termination mid-burst truncates the round (the all--inf
|
|
33
31
|
// guarantee). Gates (no oracle): greedy grammar+spec ≡ greedy grammar-only
|
|
34
|
-
// long-prefix + 100% schema validity (tests/spec-serve.test.ts).
|
|
32
|
+
// long-prefix + 100% schema validity (tests/parity/spec-serve.test.ts).
|
|
35
33
|
//
|
|
36
34
|
// Emission discipline: tokens flow through the caller's onToken ONE AT A
|
|
37
35
|
// TIME, in order (bursts of ≤ n+1 per round) — the server's stop-sequence
|
|
@@ -40,70 +38,25 @@
|
|
|
40
38
|
// false halts the generation mid-burst.
|
|
41
39
|
|
|
42
40
|
import { MlxArray } from "../mlx/array";
|
|
41
|
+
import { sampleSpeculativeRows } from "../backends/mlx/speculative-round";
|
|
43
42
|
import * as ops from "../mlx/ops";
|
|
44
43
|
import { clearCache } from "../mlx/ffi";
|
|
45
|
-
import {
|
|
44
|
+
import { cleanupFailure, disposeResources } from "../engine/resources";
|
|
45
|
+
import type { DisposableResource } from "../contracts/resources";
|
|
46
|
+
import { assertMlxSpeculativeBinding, bindLegacySpeculativeModel, type MlxSpeculativeBinding } from "../backends/mlx/speculative";
|
|
46
47
|
import type { RuntimeModel } from "../model/factory";
|
|
48
|
+
import { cloneKvCaches } from "../kv-store";
|
|
49
|
+
import { disposeAttachments, type CheckpointAttachment } from "../backends/mlx/checkpoint-state";
|
|
47
50
|
import type { Cache } from "../model/gemma4";
|
|
48
|
-
import type
|
|
51
|
+
import { withModelUsageFlush, withModelWiredLimit, type GenerateOptions, type GenerateStats } from "../generate";
|
|
52
|
+
import { createKvMaintenance } from "../backends/mlx/kv-maintenance";
|
|
53
|
+
import { runtimeConfig, withRuntimeConfig } from "../runtime-config";
|
|
49
54
|
import { makeSampler, makeStepSampler } from "../sampler";
|
|
50
55
|
import type { OnToken } from "../serve/generation-gateway";
|
|
51
56
|
import type { DraftProvider } from "./source";
|
|
52
57
|
|
|
53
58
|
const PREFILL_CHUNK = 2048;
|
|
54
59
|
|
|
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
60
|
export interface SpecServeExtras {
|
|
108
61
|
drafted: number;
|
|
109
62
|
accepted: number;
|
|
@@ -115,12 +68,19 @@ export interface SpecServeExtras {
|
|
|
115
68
|
forwardsSaved: number;
|
|
116
69
|
/** Per-draft-position counters (index = position within a round's block,
|
|
117
70
|
* 0..γ-1): how many rounds drafted/accepted at that position. Drives the
|
|
118
|
-
* Phase-1c per-position acceptance report (scripts/dspark
|
|
71
|
+
* Phase-1c per-position acceptance report (scripts/dspark.ts ab)
|
|
119
72
|
* — near-zero cost, always populated. */
|
|
120
73
|
draftedByPos: number[];
|
|
121
74
|
acceptedByPos: number[];
|
|
122
75
|
}
|
|
123
76
|
|
|
77
|
+
export interface SpecRunServices {
|
|
78
|
+
readonly prefixCache?: import("../backends/mlx/checkpoint-state").MlxPrefixCache;
|
|
79
|
+
/** Mounted adapter revision, resolved while the execution lease is held. */
|
|
80
|
+
readonly cacheNamespace?: string;
|
|
81
|
+
readonly cloneState?: (caches: Cache[]) => Cache[];
|
|
82
|
+
}
|
|
83
|
+
|
|
124
84
|
export async function specServeRun(
|
|
125
85
|
model: RuntimeModel,
|
|
126
86
|
provider: DraftProvider,
|
|
@@ -128,9 +88,40 @@ export async function specServeRun(
|
|
|
128
88
|
promptIds: number[],
|
|
129
89
|
options: GenerateOptions & { stopSequences?: string[] },
|
|
130
90
|
onToken: OnToken,
|
|
91
|
+
services: SpecRunServices = {},
|
|
92
|
+
): Promise<GenerateStats> {
|
|
93
|
+
return specRun(bindLegacySpeculativeModel(model, provider), numDraftTokens, promptIds, options, onToken, services);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** The verifier consumes a bound target and source factory, independent of
|
|
97
|
+
* concrete model classes. Compatibility callers use specServeRun above. */
|
|
98
|
+
export async function specRun(
|
|
99
|
+
binding: MlxSpeculativeBinding,
|
|
100
|
+
numDraftTokens: number,
|
|
101
|
+
promptIds: number[],
|
|
102
|
+
options: GenerateOptions & { stopSequences?: string[] },
|
|
103
|
+
onToken: OnToken,
|
|
104
|
+
services: SpecRunServices = {},
|
|
105
|
+
): Promise<GenerateStats> {
|
|
106
|
+
return withRuntimeConfig(binding.runtime ?? runtimeConfig(), () => {
|
|
107
|
+
const run = () => specRunInner(binding, numDraftTokens, promptIds, options, onToken, services);
|
|
108
|
+
return binding.memory
|
|
109
|
+
? withModelWiredLimit(binding.memory, () => withModelUsageFlush(binding.memory!, run))
|
|
110
|
+
: run();
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async function specRunInner(
|
|
115
|
+
binding: MlxSpeculativeBinding,
|
|
116
|
+
numDraftTokens: number,
|
|
117
|
+
promptIds: number[],
|
|
118
|
+
options: GenerateOptions & { stopSequences?: string[] },
|
|
119
|
+
onToken: OnToken,
|
|
120
|
+
services: SpecRunServices = {},
|
|
131
121
|
): Promise<GenerateStats> {
|
|
132
122
|
const maxTokens = options.maxTokens ?? 512;
|
|
133
|
-
const
|
|
123
|
+
const maintainKv = createKvMaintenance(options);
|
|
124
|
+
const eos = options.eosTokenIds ?? [...binding.eosTokenIds];
|
|
134
125
|
const sampler = makeSampler(options);
|
|
135
126
|
const stepSampler = makeStepSampler(options, {
|
|
136
127
|
tokenRepresentation: "number",
|
|
@@ -142,6 +133,8 @@ export async function specServeRun(
|
|
|
142
133
|
sampler,
|
|
143
134
|
});
|
|
144
135
|
const gamma = Math.max(1, numDraftTokens);
|
|
136
|
+
const prefillChunk = options.prefillChunkSize ??
|
|
137
|
+
runtimeConfig().number("MLX_BUN_RD_PREFILL_CHUNK", PREFILL_CHUNK);
|
|
145
138
|
|
|
146
139
|
// Allocated INSIDE the try below (2026-07-07 review): provider.open()
|
|
147
140
|
// throws on a mismatched (target, drafter) pairing — as pre-try consts a
|
|
@@ -149,6 +142,7 @@ export async function specServeRun(
|
|
|
149
142
|
// and turned a config error into a per-request 500. loadContext now
|
|
150
143
|
// probe-opens the pairing at startup, so a throw here is belt+suspenders.
|
|
151
144
|
let caches: Cache[] = [];
|
|
145
|
+
let prefixRetain: (() => void) | undefined;
|
|
152
146
|
let source: import("./source").DraftSource | null = null;
|
|
153
147
|
let tapLayers: number[] | undefined;
|
|
154
148
|
// Target final hidden [1,1,H] at the anchor position — the assistant source
|
|
@@ -195,21 +189,73 @@ export async function specServeRun(
|
|
|
195
189
|
const samplePos = async (logits1V: MlxArray, step: number): Promise<number> =>
|
|
196
190
|
(await stepSampler.sample(logits1V, step)).token;
|
|
197
191
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
const sl = logitsWindow.slice([0, pos, 0], [1, pos + 1, V]);
|
|
203
|
-
const flat = ops.reshape(sl, [1, V]);
|
|
204
|
-
sl.dispose();
|
|
205
|
-
return flat;
|
|
192
|
+
const forwardTokens = async (tokens: number[], taps?: number[]) => {
|
|
193
|
+
const ids = ops.fromInt32(tokens, [1, tokens.length]);
|
|
194
|
+
try { return await binding.forward(ids, caches, taps); }
|
|
195
|
+
finally { ids.dispose(); }
|
|
206
196
|
};
|
|
197
|
+
let failure: { error: unknown } | undefined;
|
|
207
198
|
|
|
208
199
|
try {
|
|
209
|
-
|
|
210
|
-
|
|
200
|
+
options.signal?.throwIfAborted();
|
|
201
|
+
if (!Number.isSafeInteger(prefillChunk) || prefillChunk < 1)
|
|
202
|
+
throw new Error("speculative prefill chunk must be a positive integer");
|
|
203
|
+
assertMlxSpeculativeBinding(binding);
|
|
204
|
+
if (options.kvConfig?.length ||
|
|
205
|
+
(options.turboQuant && (options.quantizedKvStart ?? 0) !== 0) ||
|
|
206
|
+
(options.kvBits && ((options.kvBits !== 4 && options.kvBits !== 8) || options.quantizedKvStart !== 0)))
|
|
207
|
+
throw new Error("speculative KV requires uniform KV4/KV8 or TurboQuant start=0");
|
|
208
|
+
caches = binding.makeCache();
|
|
209
|
+
const src = binding.openDraft(sampler, caches);
|
|
211
210
|
source = src;
|
|
212
211
|
tapLayers = src.tapLayers;
|
|
212
|
+
const checkpoint = src.prefillMode === "full" ? src.checkpoint : undefined;
|
|
213
|
+
const prefixCache = checkpoint ? services.prefixCache : undefined;
|
|
214
|
+
const prefixNamespace = JSON.stringify({ method: checkpoint?.namespace,
|
|
215
|
+
adapters: services.cacheNamespace ?? options.adapters ?? [], kvBits: options.kvBits ?? null,
|
|
216
|
+
kvGroupSize: options.kvGroupSize ?? 64, quantizedKvStart: options.quantizedKvStart ?? null,
|
|
217
|
+
turboQuant: options.turboQuant ?? null });
|
|
218
|
+
let cached = 0;
|
|
219
|
+
const hit = prefixCache?.take(promptIds, prefixNamespace);
|
|
220
|
+
if (hit) {
|
|
221
|
+
try {
|
|
222
|
+
disposeResources(caches);
|
|
223
|
+
caches.splice(0, caches.length, ...hit.caches.splice(0));
|
|
224
|
+
prefixRetain = hit.retain;
|
|
225
|
+
hit.retain = undefined;
|
|
226
|
+
checkpoint!.restore(hit.tokens.length, hit.attachments![0]!);
|
|
227
|
+
cached = hit.tokens.length;
|
|
228
|
+
} finally {
|
|
229
|
+
disposeResources([...hit.caches, { dispose: () => disposeAttachments(hit.attachments) },
|
|
230
|
+
{ dispose: () => hit.retain?.() }]);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
stats.cachedTokens = cached;
|
|
234
|
+
const capture = (tokens: number[]) => {
|
|
235
|
+
let state: Cache[] = [];
|
|
236
|
+
let attachments: CheckpointAttachment[] = [];
|
|
237
|
+
try {
|
|
238
|
+
state = (services.cloneState ?? cloneKvCaches)(caches);
|
|
239
|
+
attachments.push(checkpoint!.capture(tokens.length));
|
|
240
|
+
prefixCache!.put(tokens, state, prefixNamespace, undefined, attachments);
|
|
241
|
+
state = []; attachments = [];
|
|
242
|
+
} finally {
|
|
243
|
+
disposeResources([...state, { dispose: () => disposeAttachments(attachments) }]);
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
const snapshotAt = prefixCache
|
|
247
|
+
? Math.min(options.snapshotAt ?? promptIds.length, promptIds.length - 1) : -1;
|
|
248
|
+
const seedSource = async (end: number) => {
|
|
249
|
+
if (ctxParts.length === 1) prefillCtx = ctxParts[0]!;
|
|
250
|
+
else if (ctxParts.length > 1) {
|
|
251
|
+
prefillCtx = ops.concatAxis(ctxParts, 1);
|
|
252
|
+
for (const part of ctxParts) part.dispose();
|
|
253
|
+
}
|
|
254
|
+
ctxParts.length = 0;
|
|
255
|
+
const context = prefillCtx;
|
|
256
|
+
prefillCtx = null; // source.prefill owns context on both success and failure
|
|
257
|
+
await src.prefill(promptIds.slice(0, end), context ?? undefined);
|
|
258
|
+
};
|
|
213
259
|
// ---- prefill (target, chunked; optionally tapped for DSpark), then seed
|
|
214
260
|
// the source. Order: the source's prefill needs the tapped context that the
|
|
215
261
|
// target prefill produces, so target-first (two-model's own draft prefill
|
|
@@ -240,77 +286,71 @@ export async function specServeRun(
|
|
|
240
286
|
const t0 = performance.now();
|
|
241
287
|
const tailSplit =
|
|
242
288
|
src.prefillMode !== "full" &&
|
|
243
|
-
|
|
289
|
+
binding.prefillTailSplit;
|
|
244
290
|
const oracleShape = tailSplit && promptIds.length > 1;
|
|
245
291
|
if (oracleShape) {
|
|
246
292
|
let pos = 0;
|
|
247
293
|
const end = promptIds.length - 1; // last prompt token NEVER prefilled
|
|
248
294
|
while (pos < end) {
|
|
249
|
-
|
|
295
|
+
options.signal?.throwIfAborted();
|
|
296
|
+
const n = Math.min(prefillChunk, end - pos);
|
|
250
297
|
const chunk = promptIds.slice(pos, pos + n);
|
|
251
|
-
const
|
|
252
|
-
const { hidden: h, ctxML } = await forwardMaybeTap(
|
|
253
|
-
model,
|
|
254
|
-
ids,
|
|
255
|
-
caches,
|
|
256
|
-
tapLayers,
|
|
257
|
-
);
|
|
258
|
-
ids.dispose();
|
|
298
|
+
const { hidden: h, ctxML } = await forwardTokens(chunk, tapLayers);
|
|
259
299
|
if (ctxML) ctxParts.push(ctxML);
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
300
|
+
try {
|
|
301
|
+
if (pos + n >= end) {
|
|
302
|
+
const L = h.shape[1]!;
|
|
303
|
+
const H = h.shape[2]!;
|
|
304
|
+
anchorHidden = h.slice([0, L - 1, 0], [1, L, H]); // position len-2
|
|
305
|
+
}
|
|
306
|
+
} finally { h.dispose(); } // no logits during the drain
|
|
307
|
+
maintainKv(caches);
|
|
266
308
|
clearCache();
|
|
267
309
|
pos += n;
|
|
310
|
+
if (pos < end) await new Promise<void>((resolve) => setImmediate(resolve));
|
|
268
311
|
}
|
|
269
312
|
} else {
|
|
270
313
|
// Legacy shape: full chunked prefill (final chunk included), token0
|
|
271
314
|
// sampled from the last position below.
|
|
272
|
-
for (let off =
|
|
273
|
-
|
|
274
|
-
const
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
caches,
|
|
279
|
-
tapLayers,
|
|
280
|
-
);
|
|
281
|
-
ids.dispose();
|
|
315
|
+
for (let off = cached; off < promptIds.length;) {
|
|
316
|
+
options.signal?.throwIfAborted();
|
|
317
|
+
const end = Math.min(off + prefillChunk, promptIds.length,
|
|
318
|
+
snapshotAt > off ? snapshotAt : Infinity);
|
|
319
|
+
const chunk = promptIds.slice(off, end);
|
|
320
|
+
const { hidden: h, ctxML } = await forwardTokens(chunk, tapLayers);
|
|
282
321
|
if (ctxML) ctxParts.push(ctxML);
|
|
283
|
-
if (
|
|
322
|
+
try { if (end >= promptIds.length) {
|
|
284
323
|
const L = h.shape[1]!;
|
|
285
324
|
const H = h.shape[2]!;
|
|
286
325
|
anchorHidden = h.slice([0, L - 1, 0], [1, L, H]); // [1,1,H], retained
|
|
287
|
-
const lg =
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
326
|
+
const lg = binding.projectLogits(anchorHidden);
|
|
327
|
+
try {
|
|
328
|
+
const V = lg.shape[lg.shape.length - 1]!;
|
|
329
|
+
lastLogits = ops.reshape(lg, [1, V]);
|
|
330
|
+
} finally { lg.dispose(); }
|
|
331
|
+
} } finally { h.dispose(); }
|
|
332
|
+
maintainKv(caches);
|
|
333
|
+
if (end === snapshotAt && snapshotAt > cached) {
|
|
334
|
+
await seedSource(end);
|
|
335
|
+
capture(promptIds.slice(0, end));
|
|
291
336
|
}
|
|
292
|
-
h.dispose();
|
|
293
337
|
clearCache();
|
|
338
|
+
off = end;
|
|
339
|
+
if (off < promptIds.length)
|
|
340
|
+
await new Promise<void>((resolve) => setImmediate(resolve));
|
|
294
341
|
}
|
|
295
342
|
}
|
|
296
343
|
extras.targetCalls++;
|
|
344
|
+
options.signal?.throwIfAborted();
|
|
297
345
|
// Seed the source: two-model prefills its own draft cache; DSpark seeds
|
|
298
346
|
// H_ctx from the tapped prompt context (ownership transfers to prefill);
|
|
299
347
|
// the assistant is a no-op.
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
for (const p of ctxParts) p.dispose();
|
|
305
|
-
}
|
|
306
|
-
ctxParts.length = 0; // parts consumed (transferred as prefillCtx or disposed)
|
|
307
|
-
await src.prefill(promptIds, prefillCtx ?? undefined); // takes ownership of prefillCtx
|
|
308
|
-
prefillCtx = null; // ownership transferred
|
|
309
|
-
} else {
|
|
310
|
-
await src.prefill(promptIds);
|
|
311
|
-
}
|
|
348
|
+
await seedSource(promptIds.length);
|
|
349
|
+
// Conversion replaces plain cache objects. Bind only after prefill has
|
|
350
|
+
// converted every attention layer; rollback must own the active objects.
|
|
351
|
+
const rollback = binding.bindRollback(caches);
|
|
312
352
|
stats.prefillMs = performance.now() - t0;
|
|
313
|
-
stats.prefillTps = (promptIds.length / Math.max(stats.prefillMs, 1e-6)) * 1000;
|
|
353
|
+
stats.prefillTps = ((promptIds.length - cached) / Math.max(stats.prefillMs, 1e-6)) * 1000;
|
|
314
354
|
|
|
315
355
|
// ---- first pending token ----
|
|
316
356
|
// Oracle shape: pending = the UNPROCESSED last prompt token — never
|
|
@@ -326,11 +366,12 @@ export async function specServeRun(
|
|
|
326
366
|
pending = await samplePos(lastLogits!, 0);
|
|
327
367
|
lastLogits!.dispose();
|
|
328
368
|
lastLogits = null;
|
|
369
|
+
stats.generatedTokens++;
|
|
329
370
|
if (eos.includes(pending)) {
|
|
371
|
+
stats.finishReason = "stop";
|
|
330
372
|
stats.decodeMs = performance.now() - tDecode;
|
|
331
373
|
return stats;
|
|
332
374
|
}
|
|
333
|
-
stats.generatedTokens++;
|
|
334
375
|
if (
|
|
335
376
|
(await onToken(pending)) === false ||
|
|
336
377
|
stats.generatedTokens >= maxTokens ||
|
|
@@ -347,28 +388,24 @@ export async function specServeRun(
|
|
|
347
388
|
|
|
348
389
|
// ---- rounds ----
|
|
349
390
|
decode: while (stats.generatedTokens < maxTokens) {
|
|
391
|
+
options.signal?.throwIfAborted();
|
|
350
392
|
if (!speculating) {
|
|
351
393
|
// plain single-token continuation (post-ring-wrap fallback)
|
|
352
|
-
const
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
const lg = model.logitsFromHidden(h);
|
|
361
|
-
h.dispose();
|
|
362
|
-
const V = lg.shape[lg.shape.length - 1]!;
|
|
363
|
-
roundRow = ops.reshape(lg, [1, V]);
|
|
364
|
-
lg.dispose();
|
|
394
|
+
const { hidden: h } = await forwardTokens([pending], undefined);
|
|
395
|
+
try {
|
|
396
|
+
const lg = binding.projectLogits(h);
|
|
397
|
+
try {
|
|
398
|
+
const V = lg.shape[lg.shape.length - 1]!;
|
|
399
|
+
roundRow = ops.reshape(lg, [1, V]);
|
|
400
|
+
} finally { lg.dispose(); }
|
|
401
|
+
} finally { h.dispose(); }
|
|
365
402
|
extras.targetCalls++;
|
|
366
403
|
const tok = await samplePos(roundRow, stats.generatedTokens);
|
|
367
404
|
roundRow.dispose();
|
|
368
405
|
roundRow = null;
|
|
369
406
|
clearCache();
|
|
370
|
-
if (eos.includes(tok)) break;
|
|
371
407
|
stats.generatedTokens++;
|
|
408
|
+
if (eos.includes(tok)) { stats.finishReason = "stop"; break; }
|
|
372
409
|
pending = tok;
|
|
373
410
|
if ((await onToken(tok)) === false) break;
|
|
374
411
|
if (grammar?.isTerminated) break; // grammar closed — finish "stop"
|
|
@@ -387,11 +424,7 @@ export async function specServeRun(
|
|
|
387
424
|
// spec-eligible through the round snapshot/replay contract instead:
|
|
388
425
|
// specRoundBegin before the verify forward, then commit or a bit-exact
|
|
389
426
|
// rollback(keep) after the accept walk.
|
|
390
|
-
const roundFits =
|
|
391
|
-
"maxSize" in c && typeof (c as { maxSize?: number }).maxSize === "number"
|
|
392
|
-
? c.offset + n + 1 < (c as { maxSize: number }).maxSize
|
|
393
|
-
: c.isTrimmable() || typeof c.specRoundBegin === "function",
|
|
394
|
-
);
|
|
427
|
+
const roundFits = rollback.canBegin(n);
|
|
395
428
|
if (!roundFits) {
|
|
396
429
|
console.warn(
|
|
397
430
|
"spec: sliding window nearly wrapped — finishing without speculation",
|
|
@@ -432,21 +465,20 @@ export async function specServeRun(
|
|
|
432
465
|
// the emitted position is the next round's anchor hidden.
|
|
433
466
|
// Arm recurrent caches' spec round around the verify forward (no-op for
|
|
434
467
|
// trimmable caches, which roll back via trim() below).
|
|
435
|
-
|
|
468
|
+
rollback.begin(d);
|
|
436
469
|
const vIds = ops.fromInt32([pending, ...drafts], [1, d + 1]);
|
|
437
|
-
|
|
438
|
-
"setSpecKernelPinned" in model;
|
|
439
|
-
if (pinTarget) model.setSpecKernelPinned(true);
|
|
470
|
+
let pin: { close(): void } | undefined;
|
|
440
471
|
try {
|
|
441
|
-
|
|
472
|
+
pin = src.pinTargetKernelFamily ? binding.pinVerify?.() : undefined;
|
|
473
|
+
const ft = await binding.forward(vIds, caches, tapLayers);
|
|
442
474
|
// Assign these before the lm-head call so the outer cleanup owns them
|
|
443
475
|
// even if logits construction throws.
|
|
444
476
|
vHidden = ft.hidden;
|
|
445
477
|
vCtxML = ft.ctxML;
|
|
446
|
-
vLogits =
|
|
478
|
+
vLogits = binding.projectLogits(vHidden);
|
|
447
479
|
} finally {
|
|
448
480
|
vIds.dispose();
|
|
449
|
-
|
|
481
|
+
pin?.close();
|
|
450
482
|
}
|
|
451
483
|
extras.targetCalls++;
|
|
452
484
|
// The lm-head remains one logical batched operation. Native GLM MTP's
|
|
@@ -457,39 +489,12 @@ export async function specServeRun(
|
|
|
457
489
|
// accept while it reproduces the draft. Sampling is sequential because
|
|
458
490
|
// the processor history (and, in Phase C, the grammar mask) at position
|
|
459
491
|
// i depends on the tokens accepted at positions < i.
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
492
|
+
const [result] = await sampleSpeculativeRows([{
|
|
493
|
+
pending, step: stats.generatedTokens, remaining: maxTokens - stats.generatedTokens,
|
|
494
|
+
eosTokenIds: eos, sampling: stepSampler, grammarDone: () => grammar?.isTerminated ?? false,
|
|
495
|
+
}], [drafts], vLogits!);
|
|
496
|
+
const { accepted: kAccept, correction, sawEos, emitted, grammarDone } = result!.acceptance;
|
|
463
497
|
let halted = false;
|
|
464
|
-
const emitted: number[] = [];
|
|
465
|
-
let grammarDone = false;
|
|
466
|
-
for (let i = 0; i <= d; i++) {
|
|
467
|
-
roundRow = logitsRow(vLogits!, i);
|
|
468
|
-
const tok = await samplePos(roundRow, stats.generatedTokens + emitted.length);
|
|
469
|
-
roundRow.dispose();
|
|
470
|
-
roundRow = null;
|
|
471
|
-
if (i < d && tok === drafts[i]) {
|
|
472
|
-
kAccept++;
|
|
473
|
-
// EOS is never content — even when it arrives as an ACCEPTED DRAFT
|
|
474
|
-
// (the correction/bonus branch below always excluded it; this
|
|
475
|
-
// branch pushed-then-broke, leaking the EOS through onToken —
|
|
476
|
-
// caught by the 2026-07-07 live oracle gate: streams were
|
|
477
|
-
// bit-identical, ours emitted one extra "content" token: <|eot_id|>).
|
|
478
|
-
if (eos.includes(tok)) { sawEos = true; break; }
|
|
479
|
-
emitted.push(tok);
|
|
480
|
-
// grammar termination mid-burst truncates the round — nothing may
|
|
481
|
-
// be sampled past a satisfied grammar (the all--inf guarantee).
|
|
482
|
-
if (grammar?.isTerminated) { grammarDone = true; break; }
|
|
483
|
-
if (stats.generatedTokens + emitted.length >= maxTokens) break;
|
|
484
|
-
continue;
|
|
485
|
-
}
|
|
486
|
-
// mismatch (target replaces the draft) or bonus (i === n)
|
|
487
|
-
correction = tok;
|
|
488
|
-
if (!eos.includes(tok)) emitted.push(tok);
|
|
489
|
-
else sawEos = true;
|
|
490
|
-
if (grammar?.isTerminated) grammarDone = true;
|
|
491
|
-
break;
|
|
492
|
-
}
|
|
493
498
|
vLogits!.dispose();
|
|
494
499
|
vLogits = null;
|
|
495
500
|
extras.accepted += kAccept;
|
|
@@ -502,10 +507,18 @@ export async function specServeRun(
|
|
|
502
507
|
|
|
503
508
|
// (d) emit the round's tokens through onToken, one at a time
|
|
504
509
|
for (const tok of emitted) {
|
|
510
|
+
options.signal?.throwIfAborted();
|
|
505
511
|
stats.generatedTokens++;
|
|
506
512
|
if ((await onToken(tok)) === false) { halted = true; break; }
|
|
507
513
|
if (stats.generatedTokens >= maxTokens) break;
|
|
508
514
|
}
|
|
515
|
+
// Native generation and mlx-lm count the stopping EOS in usage while
|
|
516
|
+
// keeping it out of content. A callback or budget may stop earlier in
|
|
517
|
+
// this verified burst; that unused suffix must not enter usage.
|
|
518
|
+
if (sawEos && !halted && stats.generatedTokens < maxTokens) {
|
|
519
|
+
stats.generatedTokens++;
|
|
520
|
+
stats.finishReason = "stop";
|
|
521
|
+
}
|
|
509
522
|
extras.tokensPerForward = extras.rounds > 0
|
|
510
523
|
? stats.generatedTokens / extras.rounds
|
|
511
524
|
: 0;
|
|
@@ -526,14 +539,7 @@ export async function specServeRun(
|
|
|
526
539
|
// drafts). Trimmable caches drop the rejected tail; recurrent caches
|
|
527
540
|
// restore their pre-round snapshot and bit-exactly replay those
|
|
528
541
|
// kAccept+1 kept tokens. On full accept the round just commits.
|
|
529
|
-
|
|
530
|
-
for (const c of caches) {
|
|
531
|
-
if (c.specRoundRollback) c.specRoundRollback(kAccept + 1);
|
|
532
|
-
else c.trim(d - kAccept);
|
|
533
|
-
}
|
|
534
|
-
} else {
|
|
535
|
-
for (const c of caches) c.specRoundCommit?.();
|
|
536
|
-
}
|
|
542
|
+
rollback.resolve(kAccept);
|
|
537
543
|
await src.commit(
|
|
538
544
|
d,
|
|
539
545
|
kAccept,
|
|
@@ -565,21 +571,15 @@ export async function specServeRun(
|
|
|
565
571
|
? (stats.generatedTokens / stats.decodeMs) * 1000
|
|
566
572
|
: 0;
|
|
567
573
|
return stats;
|
|
574
|
+
} catch (error) {
|
|
575
|
+
failure = { error };
|
|
576
|
+
throw error;
|
|
568
577
|
} finally {
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
vLogits?.dispose();
|
|
576
|
-
roundRow?.dispose();
|
|
577
|
-
vHidden?.dispose();
|
|
578
|
-
vCtxML?.dispose();
|
|
579
|
-
for (const c of caches) c.dispose();
|
|
580
|
-
source?.dispose();
|
|
581
|
-
stepSampler.dispose();
|
|
582
|
-
options.grammar?.dispose(); // Phase C will consume it; never leak either way
|
|
583
|
-
clearCache();
|
|
578
|
+
const resources = [anchorHidden, lastLogits, prefillCtx, ...ctxParts,
|
|
579
|
+
vLogits, roundRow, vHidden, vCtxML, ...caches, source, stepSampler,
|
|
580
|
+
options.grammar, { dispose: () => prefixRetain?.() }, { dispose: clearCache }]
|
|
581
|
+
.filter((resource): resource is DisposableResource => resource != null);
|
|
582
|
+
if (failure) cleanupFailure(failure.error, () => disposeResources(resources));
|
|
583
|
+
else disposeResources(resources);
|
|
584
584
|
}
|
|
585
585
|
}
|