mlx-bun 0.2.0 → 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 -588
- package/docs/README.md +91 -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/{design → archive/investigations}/optimization_plan.md +1 -1
- package/docs/{design → archive/investigations}/pre-colibri-stabilization.md +5 -5
- package/docs/{design → archive/investigations}/the-dreaming-handoff.md +2 -2
- 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 +2 -2
- package/docs/reference/benchmarks.md +1162 -0
- package/docs/reference/cli.md +394 -159
- package/docs/reference/distribution.md +321 -182
- package/docs/reference/environment.md +348 -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 +104 -6
- package/docs/reference/server-api.md +886 -425
- package/docs/reference/server-config.md +638 -330
- package/docs/reference/training.md +393 -254
- package/docs/reference/troubleshooting.md +166 -0
- package/package.json +9 -4
- 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 +58 -25
- 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 +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 +1 -1
- package/src/generate.ts +656 -268
- 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/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-store.ts +109 -49
- 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/{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 +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 +9 -10
- 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/batched-mask.ts +8 -2
- package/src/model/batched-quant.ts +1 -1
- package/src/model/batched-rotating-quant.ts +2 -2
- package/src/model/batched-rotating.ts +1 -1
- package/src/model/compiled-decode.ts +1 -1
- 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/gemma4-base.ts +26 -9
- package/src/model/gemma4.ts +6 -6
- package/src/model/generated/gemma4-12b.ts +2 -2
- package/src/model/generated/gemma4-26b.ts +2 -2
- package/src/model/generated/gemma4-e4b.ts +2 -2
- package/src/model/glm52-dsa.ts +13 -9
- package/src/model/glm52-memory.ts +1 -1
- package/src/model/implementation.ts +69 -0
- package/src/model/minicpm5.ts +6 -8
- 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_5-checkpoint.ts +164 -0
- package/src/model/qwen3_5.ts +140 -56
- 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-pack.ts +6 -5
- package/src/pi-provider.ts +6 -64
- package/src/pi-web.ts +8 -315
- package/src/prompt-cache.ts +96 -53
- 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 +4 -4
- 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 +154 -193
- 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 +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/request-plan.ts +19 -15
- 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 +5 -5
- package/src/serve/text-completion-stage.ts +104 -0
- package/src/serve/token-streams.ts +519 -0
- package/src/server.ts +317 -2649
- package/src/spec/assistant-source.ts +10 -44
- package/src/spec/deepspec-source.ts +6 -6
- package/src/spec/dflash-source.ts +8 -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 +4 -3
- 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 +1 -1
- package/src/spec/ngram-source.ts +1 -1
- package/src/spec/prefix-state.ts +45 -0
- package/src/spec/qwen-mtp-source.ts +166 -69
- package/src/spec/serve-loop.ts +154 -156
- package/src/spec/source.ts +42 -10
- package/src/spec/two-model.ts +1 -1
- package/src/ssd-cache.ts +80 -12
- package/src/ssd-durability.ts +25 -13
- 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 +5 -3
- 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
|
|
@@ -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
|
|
50
|
+
import { maybeQuantizeKv, withModelUsageFlush, withModelWiredLimit, type GenerateOptions, type GenerateStats } from "../generate";
|
|
51
|
+
import { runtimeConfig, withRuntimeConfig } from "../runtime-config";
|
|
49
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,9 +79,36 @@ 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
113
|
const stepSampler = makeStepSampler(options, {
|
|
136
114
|
tokenRepresentation: "number",
|
|
@@ -142,6 +120,8 @@ export async function specServeRun(
|
|
|
142
120
|
sampler,
|
|
143
121
|
});
|
|
144
122
|
const gamma = Math.max(1, numDraftTokens);
|
|
123
|
+
const prefillChunk = options.prefillChunkSize ??
|
|
124
|
+
runtimeConfig().number("MLX_BUN_RD_PREFILL_CHUNK", PREFILL_CHUNK);
|
|
145
125
|
|
|
146
126
|
// Allocated INSIDE the try below (2026-07-07 review): provider.open()
|
|
147
127
|
// throws on a mismatched (target, drafter) pairing — as pre-try consts a
|
|
@@ -205,11 +185,48 @@ export async function specServeRun(
|
|
|
205
185
|
return flat;
|
|
206
186
|
};
|
|
207
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
|
+
|
|
208
195
|
try {
|
|
209
|
-
|
|
210
|
-
|
|
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);
|
|
211
205
|
source = src;
|
|
212
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
|
+
};
|
|
213
230
|
// ---- prefill (target, chunked; optionally tapped for DSpark), then seed
|
|
214
231
|
// the source. Order: the source's prefill needs the tapped context that the
|
|
215
232
|
// target prefill produces, so target-first (two-model's own draft prefill
|
|
@@ -240,77 +257,71 @@ export async function specServeRun(
|
|
|
240
257
|
const t0 = performance.now();
|
|
241
258
|
const tailSplit =
|
|
242
259
|
src.prefillMode !== "full" &&
|
|
243
|
-
|
|
260
|
+
binding.prefillTailSplit;
|
|
244
261
|
const oracleShape = tailSplit && promptIds.length > 1;
|
|
245
262
|
if (oracleShape) {
|
|
246
263
|
let pos = 0;
|
|
247
264
|
const end = promptIds.length - 1; // last prompt token NEVER prefilled
|
|
248
265
|
while (pos < end) {
|
|
249
|
-
|
|
266
|
+
options.signal?.throwIfAborted();
|
|
267
|
+
const n = Math.min(prefillChunk, end - pos);
|
|
250
268
|
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();
|
|
269
|
+
const { hidden: h, ctxML } = await forwardTokens(chunk, tapLayers);
|
|
259
270
|
if (ctxML) ctxParts.push(ctxML);
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
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);
|
|
266
279
|
clearCache();
|
|
267
280
|
pos += n;
|
|
281
|
+
if (pos < end) await new Promise<void>((resolve) => setImmediate(resolve));
|
|
268
282
|
}
|
|
269
283
|
} else {
|
|
270
284
|
// Legacy shape: full chunked prefill (final chunk included), token0
|
|
271
285
|
// sampled from the last position below.
|
|
272
|
-
for (let off =
|
|
273
|
-
|
|
274
|
-
const
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
caches,
|
|
279
|
-
tapLayers,
|
|
280
|
-
);
|
|
281
|
-
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);
|
|
282
292
|
if (ctxML) ctxParts.push(ctxML);
|
|
283
|
-
if (
|
|
293
|
+
try { if (end >= promptIds.length) {
|
|
284
294
|
const L = h.shape[1]!;
|
|
285
295
|
const H = h.shape[2]!;
|
|
286
296
|
anchorHidden = h.slice([0, L - 1, 0], [1, L, H]); // [1,1,H], retained
|
|
287
|
-
const lg =
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
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);
|
|
291
307
|
}
|
|
292
|
-
h.dispose();
|
|
293
308
|
clearCache();
|
|
309
|
+
off = end;
|
|
310
|
+
if (off < promptIds.length)
|
|
311
|
+
await new Promise<void>((resolve) => setImmediate(resolve));
|
|
294
312
|
}
|
|
295
313
|
}
|
|
296
314
|
extras.targetCalls++;
|
|
315
|
+
options.signal?.throwIfAborted();
|
|
297
316
|
// Seed the source: two-model prefills its own draft cache; DSpark seeds
|
|
298
317
|
// H_ctx from the tapped prompt context (ownership transfers to prefill);
|
|
299
318
|
// 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
|
-
}
|
|
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);
|
|
312
323
|
stats.prefillMs = performance.now() - t0;
|
|
313
|
-
stats.prefillTps = (promptIds.length / Math.max(stats.prefillMs, 1e-6)) * 1000;
|
|
324
|
+
stats.prefillTps = ((promptIds.length - cached) / Math.max(stats.prefillMs, 1e-6)) * 1000;
|
|
314
325
|
|
|
315
326
|
// ---- first pending token ----
|
|
316
327
|
// Oracle shape: pending = the UNPROCESSED last prompt token — never
|
|
@@ -326,11 +337,12 @@ export async function specServeRun(
|
|
|
326
337
|
pending = await samplePos(lastLogits!, 0);
|
|
327
338
|
lastLogits!.dispose();
|
|
328
339
|
lastLogits = null;
|
|
340
|
+
stats.generatedTokens++;
|
|
329
341
|
if (eos.includes(pending)) {
|
|
342
|
+
stats.finishReason = "stop";
|
|
330
343
|
stats.decodeMs = performance.now() - tDecode;
|
|
331
344
|
return stats;
|
|
332
345
|
}
|
|
333
|
-
stats.generatedTokens++;
|
|
334
346
|
if (
|
|
335
347
|
(await onToken(pending)) === false ||
|
|
336
348
|
stats.generatedTokens >= maxTokens ||
|
|
@@ -347,28 +359,24 @@ export async function specServeRun(
|
|
|
347
359
|
|
|
348
360
|
// ---- rounds ----
|
|
349
361
|
decode: while (stats.generatedTokens < maxTokens) {
|
|
362
|
+
options.signal?.throwIfAborted();
|
|
350
363
|
if (!speculating) {
|
|
351
364
|
// 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();
|
|
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(); }
|
|
365
373
|
extras.targetCalls++;
|
|
366
374
|
const tok = await samplePos(roundRow, stats.generatedTokens);
|
|
367
375
|
roundRow.dispose();
|
|
368
376
|
roundRow = null;
|
|
369
377
|
clearCache();
|
|
370
|
-
if (eos.includes(tok)) break;
|
|
371
378
|
stats.generatedTokens++;
|
|
379
|
+
if (eos.includes(tok)) { stats.finishReason = "stop"; break; }
|
|
372
380
|
pending = tok;
|
|
373
381
|
if ((await onToken(tok)) === false) break;
|
|
374
382
|
if (grammar?.isTerminated) break; // grammar closed — finish "stop"
|
|
@@ -387,11 +395,7 @@ export async function specServeRun(
|
|
|
387
395
|
// spec-eligible through the round snapshot/replay contract instead:
|
|
388
396
|
// specRoundBegin before the verify forward, then commit or a bit-exact
|
|
389
397
|
// 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
|
-
);
|
|
398
|
+
const roundFits = rollback.canBegin(n);
|
|
395
399
|
if (!roundFits) {
|
|
396
400
|
console.warn(
|
|
397
401
|
"spec: sliding window nearly wrapped — finishing without speculation",
|
|
@@ -432,21 +436,20 @@ export async function specServeRun(
|
|
|
432
436
|
// the emitted position is the next round's anchor hidden.
|
|
433
437
|
// Arm recurrent caches' spec round around the verify forward (no-op for
|
|
434
438
|
// trimmable caches, which roll back via trim() below).
|
|
435
|
-
|
|
439
|
+
rollback.begin(d);
|
|
436
440
|
const vIds = ops.fromInt32([pending, ...drafts], [1, d + 1]);
|
|
437
|
-
|
|
438
|
-
"setSpecKernelPinned" in model;
|
|
439
|
-
if (pinTarget) model.setSpecKernelPinned(true);
|
|
441
|
+
let pin: { close(): void } | undefined;
|
|
440
442
|
try {
|
|
441
|
-
|
|
443
|
+
pin = src.pinTargetKernelFamily ? binding.pinVerify?.() : undefined;
|
|
444
|
+
const ft = await binding.forward(vIds, caches, tapLayers);
|
|
442
445
|
// Assign these before the lm-head call so the outer cleanup owns them
|
|
443
446
|
// even if logits construction throws.
|
|
444
447
|
vHidden = ft.hidden;
|
|
445
448
|
vCtxML = ft.ctxML;
|
|
446
|
-
vLogits =
|
|
449
|
+
vLogits = binding.projectLogits(vHidden);
|
|
447
450
|
} finally {
|
|
448
451
|
vIds.dispose();
|
|
449
|
-
|
|
452
|
+
pin?.close();
|
|
450
453
|
}
|
|
451
454
|
extras.targetCalls++;
|
|
452
455
|
// The lm-head remains one logical batched operation. Native GLM MTP's
|
|
@@ -502,10 +505,18 @@ export async function specServeRun(
|
|
|
502
505
|
|
|
503
506
|
// (d) emit the round's tokens through onToken, one at a time
|
|
504
507
|
for (const tok of emitted) {
|
|
508
|
+
options.signal?.throwIfAborted();
|
|
505
509
|
stats.generatedTokens++;
|
|
506
510
|
if ((await onToken(tok)) === false) { halted = true; break; }
|
|
507
511
|
if (stats.generatedTokens >= maxTokens) break;
|
|
508
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
|
+
}
|
|
509
520
|
extras.tokensPerForward = extras.rounds > 0
|
|
510
521
|
? stats.generatedTokens / extras.rounds
|
|
511
522
|
: 0;
|
|
@@ -526,14 +537,7 @@ export async function specServeRun(
|
|
|
526
537
|
// drafts). Trimmable caches drop the rejected tail; recurrent caches
|
|
527
538
|
// restore their pre-round snapshot and bit-exactly replay those
|
|
528
539
|
// 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
|
-
}
|
|
540
|
+
rollback.resolve(kAccept);
|
|
537
541
|
await src.commit(
|
|
538
542
|
d,
|
|
539
543
|
kAccept,
|
|
@@ -565,21 +569,15 @@ export async function specServeRun(
|
|
|
565
569
|
? (stats.generatedTokens / stats.decodeMs) * 1000
|
|
566
570
|
: 0;
|
|
567
571
|
return stats;
|
|
572
|
+
} catch (error) {
|
|
573
|
+
failure = { error };
|
|
574
|
+
throw error;
|
|
568
575
|
} 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();
|
|
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);
|
|
584
582
|
}
|
|
585
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";
|