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/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,60 @@
|
|
|
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
|
+
import type { CheckpointAttachment } from "../backends/mlx/checkpoint-state";
|
|
27
|
+
import type { MlxDraftRows } from "../backends/mlx/speculative-round";
|
|
28
|
+
import type { PreparedStateChange } from "../contracts/resources";
|
|
26
29
|
|
|
27
|
-
/**
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
30
|
+
/** Numerical ports are backend-specific; returned arrays are caller-owned. */
|
|
31
|
+
export interface DraftProjection {
|
|
32
|
+
readonly embed: {
|
|
33
|
+
encode(ids: MlxArray): MlxArray;
|
|
34
|
+
readonly scales: { readonly dtype: Dtype };
|
|
35
|
+
};
|
|
36
|
+
logitsFromHidden(hidden: MlxArray): MlxArray;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Read-only target state and embedding, independent of draft scheduling. */
|
|
40
|
+
export interface AssistantRowsTarget {
|
|
41
|
+
readonly hiddenSize: number;
|
|
42
|
+
embed(ids: MlxArray): MlxArray;
|
|
43
|
+
readDonors(): import("./drafter").AssistantDonors & {
|
|
44
|
+
readonly positions: readonly number[];
|
|
45
|
+
dispose(): void;
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface QwenMtpTarget {
|
|
50
|
+
readonly hiddenSize: number;
|
|
51
|
+
readonly layerCount: number;
|
|
52
|
+
embed(ids: MlxArray): MlxArray;
|
|
53
|
+
logitsFromHidden(hidden: MlxArray): MlxArray;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Graph-declared extensions over the target's live state. Sources request
|
|
57
|
+
* only the ports they consume; they never inspect a concrete model or cache.
|
|
58
|
+
* Absence refuses an unsupported pairing before draft-side allocation. */
|
|
31
59
|
export interface TargetView {
|
|
32
|
-
|
|
33
|
-
readonly
|
|
60
|
+
/** Opaque identity for providers borrowing weights from one exact target. */
|
|
61
|
+
readonly identity: object;
|
|
62
|
+
readonly assistantRows?: AssistantRowsTarget;
|
|
63
|
+
readonly gemmaTaps?: { readonly layerCount: number; readonly projection: DraftProjection };
|
|
64
|
+
readonly qwenMtp?: QwenMtpTarget;
|
|
34
65
|
}
|
|
35
66
|
|
|
36
67
|
/** A per-request draft-token producer. Created per generation (owns its own
|
|
37
68
|
* draft-side state), disposed by the serve loop's finally. */
|
|
38
69
|
export interface DraftSource {
|
|
70
|
+
/** Method-owned companion state, independent of retention and storage.
|
|
71
|
+
* Capture returns owned immutable views. Restore borrows its attachment;
|
|
72
|
+
* the source retains the views it needs before returning. */
|
|
73
|
+
readonly checkpoint?: {
|
|
74
|
+
readonly namespace: string;
|
|
75
|
+
capture(processedTokens: number): import("../backends/mlx/checkpoint-state").CheckpointAttachment;
|
|
76
|
+
restore(processedTokens: number, attachment: import("../backends/mlx/checkpoint-state").CheckpointAttachment): void;
|
|
77
|
+
};
|
|
39
78
|
/** Target prefill shape required by this source's oracle. Most mlx-lm
|
|
40
79
|
* sources leave the final prompt token pending; native Colibri MTP starts
|
|
41
80
|
* from a full-prompt target forward. */
|
|
@@ -54,8 +93,9 @@ export interface DraftSource {
|
|
|
54
93
|
/** Process the prompt (two-model: prefill the draft model's cache;
|
|
55
94
|
* assistant/dflash: read the target's state — mostly a no-op, but DSpark
|
|
56
95
|
* seeds H_ctx from `ctxML`, the tapped prefill context [1,Lp,m*H], present
|
|
57
|
-
* iff tapLayers is set).
|
|
58
|
-
|
|
96
|
+
* iff tapLayers is set). A composed prefill may supply its exact processed
|
|
97
|
+
* token count, so a history source need not infer the target's boundary. */
|
|
98
|
+
prefill(promptIds: number[], ctxML?: MlxArray, processedTokens?: number): void | Promise<void>;
|
|
59
99
|
|
|
60
100
|
/** Propose 0..n tokens (RETURN LENGTH IS AUTHORITATIVE — a source may
|
|
61
101
|
* return fewer than n, e.g. DSpark's confidence-scheduled draft-length
|
|
@@ -100,18 +140,75 @@ export interface DraftSource {
|
|
|
100
140
|
dispose(): void;
|
|
101
141
|
}
|
|
102
142
|
|
|
143
|
+
export interface DraftRowSampling {
|
|
144
|
+
/** Borrow [B,V] log-probabilities; return owned [B] IDs on device. */
|
|
145
|
+
sample(logprobs: MlxArray, steps: readonly number[]): MlxArray;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export interface DraftRowCheckpoint {
|
|
149
|
+
readonly processedTokens: number;
|
|
150
|
+
readonly attachment: CheckpointAttachment;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** Method-owned state membership, independent of scheduling and cache tiers.
|
|
154
|
+
* Open/append borrow checkpoints; capture returns owned immutable state.
|
|
155
|
+
* Membership changes and capture occur only between committed rounds.
|
|
156
|
+
* The caller retains the provider's residency lease for the group's lifetime. */
|
|
157
|
+
export interface DraftRowGroup extends MlxDraftRows {
|
|
158
|
+
readonly namespace: string;
|
|
159
|
+
readonly tapLayers: readonly number[];
|
|
160
|
+
readonly rowCount: number;
|
|
161
|
+
append(checkpoints: readonly DraftRowCheckpoint[]): void;
|
|
162
|
+
prepareAppend(checkpoints: readonly DraftRowCheckpoint[]): PreparedStateChange;
|
|
163
|
+
filterRows(keep: readonly number[]): void;
|
|
164
|
+
capture(row: number): DraftRowCheckpoint;
|
|
165
|
+
dispose(): void;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** Draft preparation consumes each target chunk at the same batch geometry.
|
|
169
|
+
* Inputs are borrowed and contain only new tokens/context, with equal chunk
|
|
170
|
+
* lengths across rows. A null checkpoint appends a cold request. The provider
|
|
171
|
+
* owns alignment and retained hidden state; scheduling and persistence do not.
|
|
172
|
+
* Membership and capture occur between prefill calls. Capture returns owned
|
|
173
|
+
* state after at least one target token has been consumed for that row. */
|
|
174
|
+
export interface DraftPrefillGroup {
|
|
175
|
+
readonly namespace: string;
|
|
176
|
+
readonly prefillMode: NonNullable<DraftSource["prefillMode"]>;
|
|
177
|
+
readonly tapLayers: readonly number[];
|
|
178
|
+
readonly rowCount: number;
|
|
179
|
+
append(checkpoints: readonly (DraftRowCheckpoint | null)[]): void;
|
|
180
|
+
prepareAppend(checkpoints: readonly (DraftRowCheckpoint | null)[]): PreparedStateChange;
|
|
181
|
+
prefill(tokens: MlxArray, context?: MlxArray): void | Promise<void>;
|
|
182
|
+
/** Resolve borrowed backing before its external restore lease is released. */
|
|
183
|
+
materialize(): void;
|
|
184
|
+
filterRows(keep: readonly number[]): void;
|
|
185
|
+
capture(row: number): DraftRowCheckpoint;
|
|
186
|
+
dispose(): void;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export interface GroupedDraftProvider {
|
|
190
|
+
/** Draft state remains valid when target forwards run under a mounted
|
|
191
|
+
* adapter context. Unqualified learned draft graphs leave this absent. */
|
|
192
|
+
readonly supportsTargetAdapters?: boolean;
|
|
193
|
+
openPrefill(options: { target: TargetView;
|
|
194
|
+
checkpoints: readonly (DraftRowCheckpoint | null)[] }): DraftPrefillGroup;
|
|
195
|
+
open(options: { target: TargetView; sampling: DraftRowSampling;
|
|
196
|
+
checkpoints: readonly DraftRowCheckpoint[] }): DraftRowGroup;
|
|
197
|
+
}
|
|
198
|
+
|
|
103
199
|
/** Server-lifetime owner of the draft machinery (the loaded draft model);
|
|
104
|
-
*
|
|
200
|
+
* open() mints a per-request DraftSource; grouped opens shared row state. */
|
|
105
201
|
export interface DraftProvider {
|
|
106
202
|
/** Human-readable id (registry id / path tail) for logs + cache namespacing. */
|
|
107
203
|
readonly id: string;
|
|
108
204
|
readonly weightsBytes: number;
|
|
205
|
+
readonly grouped?: GroupedDraftProvider;
|
|
109
206
|
open(opts: {
|
|
110
207
|
/** The request's sampler over logprobs [1,V] → token array [1] (the SAME
|
|
111
208
|
* sampler as the target — mlx-lm parity; greedy drafting under a
|
|
112
209
|
* temperature>0 request is NOT parity). */
|
|
113
210
|
sampler: (logprobs: MlxArray, step: number) => MlxArray;
|
|
114
|
-
/**
|
|
211
|
+
/** Bound target capabilities, with explicit donor-view ownership. */
|
|
115
212
|
target: TargetView;
|
|
116
213
|
}): DraftSource;
|
|
117
214
|
dispose(): void;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { MlxAutoregressiveBinding } from "../backends/mlx/autoregressive";
|
|
2
|
+
import { MlxAutoregressiveDraftRows, type MlxDraftLayout } from "../backends/mlx/autoregressive-draft-rows";
|
|
3
|
+
import { prefillCacheLayout } from "../backends/mlx/cache-layout";
|
|
4
|
+
import { MlxStateRows } from "../backends/mlx/state-rows";
|
|
5
|
+
import { captureCacheAttachment, restoreCacheAttachment } from "../backends/mlx/cache-attachment";
|
|
6
|
+
import { applyStateChanges, cleanupFailure, disposeResources } from "../engine/resources";
|
|
7
|
+
import type { Cache } from "../model/gemma4-base";
|
|
8
|
+
import type { DraftPrefillGroup, DraftRowCheckpoint, DraftRowGroup, DraftRowSampling, GroupedDraftProvider } from "./source";
|
|
9
|
+
import type { PreparedStateChange } from "../contracts/resources";
|
|
10
|
+
import type { MlxArray } from "../mlx/array";
|
|
11
|
+
|
|
12
|
+
export interface StandaloneDraftRows extends DraftPrefillGroup {
|
|
13
|
+
draft: DraftRowGroup["draft"];
|
|
14
|
+
commit(accepted: readonly number[], context?: MlxArray): Promise<void>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** The standalone model supplies graph/state capabilities once. Target
|
|
18
|
+
* verification, scheduling and cache tiers use the existing provider ports. */
|
|
19
|
+
export function openStandaloneDraftRows(binding: MlxAutoregressiveBinding, namespace: string,
|
|
20
|
+
sampling: DraftRowSampling | null, checkpoints: readonly (DraftRowCheckpoint | null)[]): StandaloneDraftRows {
|
|
21
|
+
const empty = binding.makeCache(), layouts: MlxDraftLayout[] = [];
|
|
22
|
+
try { for (const cache of empty) layouts.push(prefillCacheLayout(cache) as MlxDraftLayout); }
|
|
23
|
+
catch (error) { disposeResources(layouts); throw error; }
|
|
24
|
+
finally { disposeResources(empty); }
|
|
25
|
+
const state = new MlxStateRows(layouts), rows = new MlxAutoregressiveDraftRows(binding.graph, state, sampling);
|
|
26
|
+
const prepareAppend = (checkpoints: readonly (DraftRowCheckpoint | null)[]): PreparedStateChange => {
|
|
27
|
+
const restored: Cache[][] = [], pending: Array<number | null> = [];
|
|
28
|
+
try {
|
|
29
|
+
for (const checkpoint of checkpoints) {
|
|
30
|
+
restored.push(checkpoint ? restoreCacheAttachment(checkpoint.attachment, binding.makeCache) : binding.makeCache());
|
|
31
|
+
pending.push(checkpoint && checkpoint.attachment.metadata.pendingLast !== false ? Number(checkpoint.attachment.metadata.pendingLast) : null);
|
|
32
|
+
}
|
|
33
|
+
return rows.prepareAppend(restored, pending);
|
|
34
|
+
} finally { disposeResources(restored.flat()); }
|
|
35
|
+
};
|
|
36
|
+
const append = (checkpoints: readonly (DraftRowCheckpoint | null)[]) => applyStateChanges([() => prepareAppend(checkpoints)]);
|
|
37
|
+
try { append(checkpoints); }
|
|
38
|
+
catch (error) { return cleanupFailure(error, () => rows.dispose()); }
|
|
39
|
+
return {
|
|
40
|
+
namespace, prefillMode: "tail-split", tapLayers: [],
|
|
41
|
+
get rowCount() { return state.rowCount; },
|
|
42
|
+
append, prepareAppend, prefill: tokens => rows.prefill(tokens), materialize: () => rows.materialize(),
|
|
43
|
+
filterRows: keep => rows.filterRows(keep), draft: rows.draft.bind(rows), commit: accepted => rows.commit(accepted),
|
|
44
|
+
capture(row): DraftRowCheckpoint {
|
|
45
|
+
const caches = state.extractRow(row), pendingLast = rows.pendingLast[row] ?? null;
|
|
46
|
+
try {
|
|
47
|
+
const processedTokens = caches[0]!.offset + Number(pendingLast !== null);
|
|
48
|
+
return { processedTokens, attachment: captureCacheAttachment("standalone-draft-v1", caches,
|
|
49
|
+
{ processedTokens, pendingLast: pendingLast ?? false }) };
|
|
50
|
+
} finally { disposeResources(caches); }
|
|
51
|
+
},
|
|
52
|
+
dispose: () => rows.dispose(),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function standaloneDraftGroups(binding: MlxAutoregressiveBinding, namespace: string): GroupedDraftProvider {
|
|
57
|
+
return {
|
|
58
|
+
open: options => openStandaloneDraftRows(binding, namespace, options.sampling, options.checkpoints),
|
|
59
|
+
openPrefill: options => openStandaloneDraftRows(binding, namespace, null, options.checkpoints),
|
|
60
|
+
};
|
|
61
|
+
}
|
package/src/spec/two-model.ts
CHANGED
|
@@ -16,13 +16,15 @@
|
|
|
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";
|
|
23
|
-
import type {
|
|
24
|
-
import {
|
|
25
|
-
import
|
|
23
|
+
import type { DraftProvider, DraftSource, GroupedDraftProvider } from "./source";
|
|
24
|
+
import { standaloneDraftGroups, openStandaloneDraftRows } from "./two-model-rows";
|
|
25
|
+
import { bindLegacyAutoregressiveModel } from "../backends/mlx/autoregressive";
|
|
26
|
+
import { artifactIdentity } from "../model/artifact-identity";
|
|
27
|
+
import { configFingerprint } from "../model/fingerprint";
|
|
26
28
|
|
|
27
29
|
/** mlx-lm's prefill_step_size — the draft drain chunks at the same stride
|
|
28
30
|
* as the target's (serve-loop.ts); chunking is numerically exact for
|
|
@@ -32,13 +34,16 @@ const PREFILL_CHUNK = 2048;
|
|
|
32
34
|
export class TwoModelProvider implements DraftProvider {
|
|
33
35
|
readonly id: string;
|
|
34
36
|
readonly weightsBytes: number;
|
|
37
|
+
readonly grouped: GroupedDraftProvider;
|
|
35
38
|
|
|
36
39
|
private constructor(
|
|
37
40
|
readonly model: RuntimeModel,
|
|
38
41
|
private readonly weights: Weights,
|
|
39
42
|
id: string,
|
|
43
|
+
private readonly namespace: string,
|
|
40
44
|
) {
|
|
41
45
|
this.id = id;
|
|
46
|
+
this.grouped = standaloneDraftGroups(bindLegacyAutoregressiveModel(model), namespace);
|
|
42
47
|
this.weightsBytes = [...weights.shards.files.values()].reduce(
|
|
43
48
|
(a, f) => a + f.mmap.size,
|
|
44
49
|
0,
|
|
@@ -51,20 +56,25 @@ export class TwoModelProvider implements DraftProvider {
|
|
|
51
56
|
static async load(modelDir: string, targetVocabSize?: number): Promise<TwoModelProvider> {
|
|
52
57
|
const config = await loadModelConfig(modelDir);
|
|
53
58
|
const weights = await Weights.open(modelDir);
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
`
|
|
62
|
-
|
|
63
|
-
|
|
59
|
+
try {
|
|
60
|
+
const model = createModel(weights, config);
|
|
61
|
+
if (
|
|
62
|
+
targetVocabSize !== undefined &&
|
|
63
|
+
config.text.vocabSize !== targetVocabSize
|
|
64
|
+
)
|
|
65
|
+
console.warn(
|
|
66
|
+
`draft model vocab size ${config.text.vocabSize} != target ${targetVocabSize} — ` +
|
|
67
|
+
`speculation will accept nothing useful (mlx-lm warns identically)`,
|
|
68
|
+
);
|
|
69
|
+
const identity = await artifactIdentity(configFingerprint(config),
|
|
70
|
+
[...weights.shards.files].map(([name, shard]) => ({ name, path: shard.path })));
|
|
71
|
+
return new TwoModelProvider(model, weights, modelDir.split("/").filter(Boolean).at(-1)!,
|
|
72
|
+
`standalone-draft-v1:${identity}`);
|
|
73
|
+
} catch (error) { weights.dispose(); throw error; }
|
|
64
74
|
}
|
|
65
75
|
|
|
66
76
|
open(opts: Parameters<DraftProvider["open"]>[0]): DraftSource {
|
|
67
|
-
return new TwoModelSource(this.model, opts.sampler);
|
|
77
|
+
return new TwoModelSource(this.model, opts.sampler, this.namespace);
|
|
68
78
|
}
|
|
69
79
|
|
|
70
80
|
dispose(): void {
|
|
@@ -72,100 +82,32 @@ export class TwoModelProvider implements DraftProvider {
|
|
|
72
82
|
}
|
|
73
83
|
}
|
|
74
84
|
|
|
85
|
+
/** Compatibility adapter: the existing request loop also uses the shared
|
|
86
|
+
* draft graph at B=1. Proposal generation and state recovery have one owner. */
|
|
75
87
|
class TwoModelSource implements DraftSource {
|
|
76
|
-
readonly weightsBytes = 0;
|
|
77
|
-
private
|
|
78
|
-
|
|
79
|
-
constructor(
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
) {
|
|
83
|
-
this.caches = this.model.makeCache();
|
|
88
|
+
readonly weightsBytes = 0;
|
|
89
|
+
private readonly rows: ReturnType<typeof openStandaloneDraftRows>;
|
|
90
|
+
private processed = 0;
|
|
91
|
+
constructor(model: RuntimeModel, sampler: (lp: MlxArray, step: number) => MlxArray, namespace: string) {
|
|
92
|
+
this.rows = openStandaloneDraftRows(bindLegacyAutoregressiveModel(model), namespace,
|
|
93
|
+
{ sample: (logprobs, steps) => sampler(logprobs, steps[0]!) }, [null]);
|
|
84
94
|
}
|
|
85
|
-
|
|
86
|
-
prefill(promptIds: number[]): void {
|
|
87
|
-
// Oracle convention (mlx-lm speculative_generate_step._prefill, re-anchored
|
|
88
|
-
// 2026-07-07): the DRAFT model also drains only to len-1 (`while y.size >
|
|
89
|
-
// 1`) — the last prompt token stays unprocessed and is consumed by the
|
|
90
|
-
// FIRST draft() round's feed ([pending] = that same token under the serve
|
|
91
|
-
// loop's oracle shape), mirroring _draft_generate's first _step. Draft-side
|
|
92
|
-
// full-prefill was the residual knife-edge flipper in the 2026-07-07 live
|
|
93
|
-
// oracle gate (γ=2 haiku cell). MLX_BUN_PREFILL_TAIL_SPLIT=0 reverts.
|
|
94
|
-
// (mlx-lm chunks its drain at prefill_step_size=2048; chunking is
|
|
95
|
-
// numerically exact for causal attention — same KV, same positions —
|
|
96
|
-
// so we chunk too. Pre-2026-07-07 this was a SINGLE forward of the
|
|
97
|
-
// whole head: nothing enforced the "<2048 serve regime" the old
|
|
98
|
-
// comment assumed, and a 32k prompt through the spec lane ran one
|
|
99
|
-
// 32k-position draft forward — a large activation/mask transient on a
|
|
100
|
-
// 24 GB box, and untested numerics vs the chunked oracle.)
|
|
95
|
+
async prefill(promptIds: number[]): Promise<void> {
|
|
101
96
|
const tailSplit = flagOn("MLX_BUN_PREFILL_TAIL_SPLIT", true);
|
|
102
97
|
const upTo = tailSplit && promptIds.length > 1 ? promptIds.length - 1 : promptIds.length;
|
|
103
|
-
for (let at =
|
|
98
|
+
for (let at = this.processed; at < upTo; at += PREFILL_CHUNK) {
|
|
104
99
|
const end = Math.min(at + PREFILL_CHUNK, upTo);
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
ids.dispose();
|
|
108
|
-
// The head's last-position logits are not needed: the serve loop's
|
|
109
|
-
// first `feed` is consumed at the top of draft().
|
|
110
|
-
h.dispose();
|
|
111
|
-
clearCache();
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/** One forward over `feed`, sample the first draft from its last position,
|
|
116
|
-
* then n-1 single-token steps — mirrors _draft_generate's autoregressive
|
|
117
|
-
* chain (with async_eval pipelining upstream; eager here, the draft is
|
|
118
|
-
* small and the serve loop overlaps nothing with it in v1). */
|
|
119
|
-
draft(feed: number[], n: number, stepBase: number): number[] {
|
|
120
|
-
const drafts: number[] = [];
|
|
121
|
-
let input = feed;
|
|
122
|
-
for (let k = 0; k < n; k++) {
|
|
123
|
-
const ids = ops.fromInt32(input, [1, input.length]);
|
|
124
|
-
const h = this.model.forwardHidden(ids, this.caches);
|
|
125
|
-
ids.dispose();
|
|
126
|
-
const L = h.shape[1]!;
|
|
127
|
-
const H = h.shape[2]!;
|
|
128
|
-
const hLast = h.slice([0, L - 1, 0], [1, L, H]);
|
|
129
|
-
h.dispose();
|
|
130
|
-
const logits = this.model.logitsFromHidden(hLast);
|
|
131
|
-
hLast.dispose();
|
|
132
|
-
const V = logits.shape[logits.shape.length - 1]!;
|
|
133
|
-
const flat = ops.reshape(logits, [1, V]);
|
|
134
|
-
logits.dispose();
|
|
135
|
-
const lp = toLogprobs(flat);
|
|
136
|
-
flat.dispose();
|
|
137
|
-
const tokArr = this.sampler(lp, stepBase + k);
|
|
138
|
-
lp.dispose();
|
|
139
|
-
const tok = ops.itemUint32(tokArr);
|
|
140
|
-
tokArr.dispose();
|
|
141
|
-
drafts.push(tok);
|
|
142
|
-
input = [tok];
|
|
100
|
+
using ids = ops.fromInt32(promptIds.slice(at, end), [1, end - at]);
|
|
101
|
+
await this.rows.prefill(ids); clearCache();
|
|
143
102
|
}
|
|
144
|
-
|
|
103
|
+
this.processed = upTo;
|
|
145
104
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
const rewind = Math.max(n - kAccept - 1, 0);
|
|
149
|
-
if (rewind > 0) {
|
|
150
|
-
for (const c of this.caches) {
|
|
151
|
-
if (!c.isTrimmable()) {
|
|
152
|
-
// The serve loop stops speculating on the TARGET-side trim check
|
|
153
|
-
// before this can matter; a wrapped draft ring here just means the
|
|
154
|
-
// source is done being useful. Recreate is the safe recovery.
|
|
155
|
-
this.resetCaches();
|
|
156
|
-
return;
|
|
157
|
-
}
|
|
158
|
-
c.trim(rewind);
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
private resetCaches(): void {
|
|
164
|
-
for (const c of this.caches) c.dispose();
|
|
165
|
-
this.caches = this.model.makeCache();
|
|
105
|
+
async draft(feed: number[], count: number, step: number): Promise<number[]> {
|
|
106
|
+
return (await this.rows.draft([feed.at(-1)!], count, [step]))[0]!;
|
|
166
107
|
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
108
|
+
async commit(_drafted: number, accepted: number): Promise<void> {
|
|
109
|
+
await this.rows.commit([accepted]);
|
|
110
|
+
this.processed += accepted + 1;
|
|
170
111
|
}
|
|
112
|
+
dispose(): void { this.rows.dispose(); }
|
|
171
113
|
}
|
package/src/ssd-cache.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
import type { CheckpointAttachment } from "./backends/mlx/checkpoint-state";
|
|
2
|
+
// SSD cold tier for the prompt/KV cache (docs/design/kv-cache.md).
|
|
2
3
|
//
|
|
3
4
|
// Files ARE the database: no sidecar index. Layout
|
|
4
5
|
// <dir>/<configFingerprint>/<nsHash>/<uuid>.mlxkv
|
|
@@ -22,10 +23,11 @@ import { randomUUID } from "node:crypto";
|
|
|
22
23
|
import { commonPrefixLength } from "./prompt-cache";
|
|
23
24
|
import {
|
|
24
25
|
saveKvCache, saveKvCacheAsync, loadKvCache, readKvHeader,
|
|
25
|
-
cacheHeadersTrimmable,
|
|
26
|
+
cacheHeadersTrimmable, cacheHeadersMinimumReusableOffset, legacyCacheCodecs, type CacheCodecProvider,
|
|
26
27
|
type KvSaveMeta, type KvLoadExpect, type LoadedKvCache,
|
|
27
28
|
} from "./kv-store";
|
|
28
29
|
import type { Cache } from "./model/gemma4-base";
|
|
30
|
+
import { minimumReusableOffset } from "./backends/mlx/state-views";
|
|
29
31
|
|
|
30
32
|
export interface SsdIndexEntry {
|
|
31
33
|
path: string;
|
|
@@ -39,9 +41,12 @@ export interface SsdIndexEntry {
|
|
|
39
41
|
* over a shorter usable one (the 2026-07-06 restart-0 defect: the
|
|
40
42
|
* [prompt+gen] file always won, always got rejected after restore). */
|
|
41
43
|
trimmable: boolean;
|
|
44
|
+
minimumReusableOffset?: number;
|
|
45
|
+
generationCheckpoint?: NonNullable<KvSaveMeta["generationCheckpoint"]>;
|
|
42
46
|
}
|
|
43
47
|
|
|
44
48
|
export interface SsdStoreOptions {
|
|
49
|
+
codecs?: CacheCodecProvider;
|
|
45
50
|
dir: string;
|
|
46
51
|
maxBytes: number;
|
|
47
52
|
/** Server identity — written into every file, enforced on scan/load. */
|
|
@@ -57,6 +62,7 @@ const nsHash = (ns: string): string =>
|
|
|
57
62
|
|
|
58
63
|
export class SsdCacheStore {
|
|
59
64
|
readonly #opts: SsdStoreOptions;
|
|
65
|
+
readonly #codecs: CacheCodecProvider;
|
|
60
66
|
readonly #root: string; // <dir>/<configFingerprint>
|
|
61
67
|
#index: SsdIndexEntry[] = [];
|
|
62
68
|
#warnedWriteFailure = false;
|
|
@@ -64,6 +70,7 @@ export class SsdCacheStore {
|
|
|
64
70
|
|
|
65
71
|
constructor(opts: SsdStoreOptions) {
|
|
66
72
|
this.#opts = opts;
|
|
73
|
+
this.#codecs = opts.codecs ?? legacyCacheCodecs;
|
|
67
74
|
this.#root = join(opts.dir, opts.configFingerprint);
|
|
68
75
|
}
|
|
69
76
|
|
|
@@ -88,7 +95,7 @@ export class SsdCacheStore {
|
|
|
88
95
|
* trimmable descendant is equivalent to an exact boundary snapshot. */
|
|
89
96
|
hasDurablePrefix(tokens: number[], ns = ""): boolean {
|
|
90
97
|
return this.#index.some((entry) => {
|
|
91
|
-
if (entry.ns !== ns || entry.tokens.length < tokens.length) return false;
|
|
98
|
+
if (entry.ns !== ns || entry.tokens.length < tokens.length || tokens.length < (entry.minimumReusableOffset ?? 0)) return false;
|
|
92
99
|
if (commonPrefixLength(entry.tokens, tokens) !== tokens.length) return false;
|
|
93
100
|
return entry.tokens.length === tokens.length || entry.trimmable;
|
|
94
101
|
});
|
|
@@ -121,10 +128,15 @@ export class SsdCacheStore {
|
|
|
121
128
|
h.tokenizerHash !== this.#opts.tokenizerHash
|
|
122
129
|
)
|
|
123
130
|
throw new Error("metadata mismatch");
|
|
131
|
+
if ((h.codecProvider ?? legacyCacheCodecs.id) !== this.#codecs.id) continue;
|
|
124
132
|
const st = statSync(path);
|
|
125
133
|
this.#index.push({
|
|
126
134
|
path, ns: h.ns ?? "", tokens: h.tokens, bytes: st.size, mtimeMs: st.mtimeMs,
|
|
127
|
-
trimmable: cacheHeadersTrimmable(h.caches),
|
|
135
|
+
trimmable: !h.attachments?.length && cacheHeadersTrimmable(h.caches, this.#codecs),
|
|
136
|
+
minimumReusableOffset: cacheHeadersMinimumReusableOffset(h.caches),
|
|
137
|
+
...(h.generationCheckpoint
|
|
138
|
+
? { generationCheckpoint: h.generationCheckpoint }
|
|
139
|
+
: {}),
|
|
128
140
|
});
|
|
129
141
|
} catch {
|
|
130
142
|
try { rmSync(path, { force: true }); } catch {}
|
|
@@ -149,7 +161,7 @@ export class SsdCacheStore {
|
|
|
149
161
|
for (const e of this.#index) {
|
|
150
162
|
if (e.ns !== ns) continue;
|
|
151
163
|
const p = Math.min(commonPrefixLength(e.tokens, prompt), prompt.length - 1);
|
|
152
|
-
if (p === 0 || p <= bestLen) continue;
|
|
164
|
+
if (p === 0 || p <= bestLen || p < (e.minimumReusableOffset ?? 0)) continue;
|
|
153
165
|
if (e.tokens.length - p > 0 && !e.trimmable) continue;
|
|
154
166
|
bestLen = p;
|
|
155
167
|
best = e;
|
|
@@ -171,7 +183,7 @@ export class SsdCacheStore {
|
|
|
171
183
|
ns: entry.ns,
|
|
172
184
|
verify: this.#opts.verify,
|
|
173
185
|
};
|
|
174
|
-
const loaded = loadKvCache(entry.path, model, expect);
|
|
186
|
+
const loaded = loadKvCache(entry.path, model, expect, this.#codecs);
|
|
175
187
|
const now = new Date();
|
|
176
188
|
try { utimesSync(entry.path, now, now); entry.mtimeMs = now.getTime(); } catch {}
|
|
177
189
|
this.stats.restores++;
|
|
@@ -193,13 +205,13 @@ export class SsdCacheStore {
|
|
|
193
205
|
* the same ns whose tokens are a PREFIX of the new entry are superseded
|
|
194
206
|
* (the agent-conversation pattern: one growing entry, not N generations
|
|
195
207
|
* of it). Returns true if stored. */
|
|
196
|
-
store(tokens: number[], caches: Cache[], ns = ""): boolean {
|
|
208
|
+
store(tokens: number[], caches: Cache[], ns = "", attachments?: CheckpointAttachment[]): boolean {
|
|
197
209
|
const dir = join(this.#root, nsHash(ns));
|
|
198
210
|
const path = join(dir, `${randomUUID()}.mlxkv`);
|
|
199
211
|
try {
|
|
200
212
|
mkdirSync(dir, { recursive: true });
|
|
201
|
-
saveKvCache(path, tokens, caches, this.#meta(ns));
|
|
202
|
-
return this.#indexStored(path, tokens, caches, ns);
|
|
213
|
+
saveKvCache(path, tokens, caches, { ...this.#meta(ns), attachments }, this.#codecs);
|
|
214
|
+
return this.#indexStored(path, tokens, caches, ns, undefined, attachments);
|
|
203
215
|
} catch (err) {
|
|
204
216
|
return this.#storeFailed(path, err);
|
|
205
217
|
}
|
|
@@ -210,21 +222,81 @@ export class SsdCacheStore {
|
|
|
210
222
|
* yields the event loop between tensors so serving interleaves.
|
|
211
223
|
* Caller passes zero-copy CLONES it owns (a consistent snapshot no
|
|
212
224
|
* matter what the live entry does meanwhile) and disposes them after.
|
|
213
|
-
* `
|
|
214
|
-
* server passes the gateway's
|
|
215
|
-
*
|
|
216
|
-
async storeAsync(
|
|
225
|
+
* `runStep` owns every per-tensor step (see saveKvCacheAsync) — the
|
|
226
|
+
* server passes the gateway's exclusive runner so a request cannot start
|
|
227
|
+
* between an idle check and the blocking MLX readback. */
|
|
228
|
+
async storeAsync(
|
|
229
|
+
tokens: number[], caches: Cache[], ns = "",
|
|
230
|
+
runStep?: <T>(step: () => T) => Promise<T>,
|
|
231
|
+
attachments?: CheckpointAttachment[],
|
|
232
|
+
): Promise<boolean> {
|
|
217
233
|
const dir = join(this.#root, nsHash(ns));
|
|
218
234
|
const path = join(dir, `${randomUUID()}.mlxkv`);
|
|
219
235
|
try {
|
|
220
236
|
mkdirSync(dir, { recursive: true });
|
|
221
|
-
await saveKvCacheAsync(path, tokens, caches, this.#meta(ns),
|
|
222
|
-
return this.#indexStored(path, tokens, caches, ns);
|
|
237
|
+
await saveKvCacheAsync(path, tokens, caches, { ...this.#meta(ns), attachments }, runStep, this.#codecs);
|
|
238
|
+
return this.#indexStored(path, tokens, caches, ns, undefined, attachments);
|
|
223
239
|
} catch (err) {
|
|
224
240
|
return this.#storeFailed(path, err);
|
|
225
241
|
}
|
|
226
242
|
}
|
|
227
243
|
|
|
244
|
+
/** Longest durable in-flight generation for an identical request. The
|
|
245
|
+
* checkpoint namespace is separate from the ordinary prompt-cache tier,
|
|
246
|
+
* so incomplete assistant output can never seed an unrelated prefill. */
|
|
247
|
+
findGenerationCheckpoint(
|
|
248
|
+
prompt: number[], key: string, cacheNs = "",
|
|
249
|
+
): SsdIndexEntry | null {
|
|
250
|
+
let best: SsdIndexEntry | null = null;
|
|
251
|
+
for (const entry of this.#index) {
|
|
252
|
+
const checkpoint = entry.generationCheckpoint;
|
|
253
|
+
if (!checkpoint || checkpoint.key !== key || checkpoint.cacheNs !== cacheNs)
|
|
254
|
+
continue;
|
|
255
|
+
if (checkpoint.originalPromptTokens !== prompt.length) continue;
|
|
256
|
+
if (entry.tokens.length <= prompt.length) continue;
|
|
257
|
+
if (commonPrefixLength(entry.tokens, prompt) !== prompt.length) continue;
|
|
258
|
+
if (!best || entry.tokens.length > best.tokens.length) best = entry;
|
|
259
|
+
}
|
|
260
|
+
return best;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/** Persist one in-flight generation atomically. A completed newer
|
|
264
|
+
* checkpoint supersedes older checkpoints for the same request only after
|
|
265
|
+
* its rename succeeds, so a crash during a write leaves the prior point. */
|
|
266
|
+
async storeGenerationCheckpoint(
|
|
267
|
+
tokens: number[], caches: Cache[], checkpoint: NonNullable<KvSaveMeta["generationCheckpoint"]>,
|
|
268
|
+
runStep?: <T>(step: () => T) => Promise<T>,
|
|
269
|
+
): Promise<boolean> {
|
|
270
|
+
const ns = `__generation_checkpoint__:${checkpoint.key}`;
|
|
271
|
+
const dir = join(this.#root, nsHash(ns));
|
|
272
|
+
const path = join(dir, `${randomUUID()}.mlxkv`);
|
|
273
|
+
try {
|
|
274
|
+
if (runStep) await runStep(() => mkdirSync(dir, { recursive: true }));
|
|
275
|
+
else mkdirSync(dir, { recursive: true });
|
|
276
|
+
await saveKvCacheAsync(path, tokens, caches, {
|
|
277
|
+
...this.#meta(ns), generationCheckpoint: checkpoint,
|
|
278
|
+
}, runStep, this.#codecs);
|
|
279
|
+
const index = () => {
|
|
280
|
+
const stored = this.#indexStored(path, tokens, caches, ns, checkpoint);
|
|
281
|
+
if (stored) {
|
|
282
|
+
for (const entry of [...this.#index]) {
|
|
283
|
+
if (entry.path === path) continue;
|
|
284
|
+
if (entry.generationCheckpoint?.key === checkpoint.key) this.remove(entry.path);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return stored;
|
|
288
|
+
};
|
|
289
|
+
return runStep ? await runStep(index) : index();
|
|
290
|
+
} catch (err) {
|
|
291
|
+
return runStep ? await runStep(() => this.#storeFailed(path, err)) : this.#storeFailed(path, err);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
removeGenerationCheckpoints(key: string): void {
|
|
296
|
+
for (const entry of [...this.#index])
|
|
297
|
+
if (entry.generationCheckpoint?.key === key) this.remove(entry.path);
|
|
298
|
+
}
|
|
299
|
+
|
|
228
300
|
#meta(ns: string): KvSaveMeta {
|
|
229
301
|
return {
|
|
230
302
|
modelId: this.#opts.modelId,
|
|
@@ -234,14 +306,18 @@ export class SsdCacheStore {
|
|
|
234
306
|
};
|
|
235
307
|
}
|
|
236
308
|
|
|
237
|
-
#indexStored(
|
|
309
|
+
#indexStored(
|
|
310
|
+
path: string, tokens: number[], caches: Cache[], ns: string,
|
|
311
|
+
generationCheckpoint?: NonNullable<KvSaveMeta["generationCheckpoint"]>,
|
|
312
|
+
attachments?: CheckpointAttachment[],
|
|
313
|
+
): boolean {
|
|
238
314
|
const st = statSync(path);
|
|
239
315
|
if (st.size > this.#opts.maxBytes) {
|
|
240
316
|
rmSync(path, { force: true });
|
|
241
317
|
console.warn(`[ssd-cache] entry not stored: ${st.size} bytes exceeds the ${this.#opts.maxBytes}-byte cap`);
|
|
242
318
|
return false;
|
|
243
319
|
}
|
|
244
|
-
const trimmable = caches.every((c) => c.isTrimmable());
|
|
320
|
+
const trimmable = !attachments?.length && caches.every((c) => c.isTrimmable());
|
|
245
321
|
// Exact duplicates (same tokens) are replaced regardless of
|
|
246
322
|
// trimmability — the new file serves exactly the old one's matches.
|
|
247
323
|
for (const e of [...this.#index]) {
|
|
@@ -256,11 +332,15 @@ export class SsdCacheStore {
|
|
|
256
332
|
// unrestorable one landed.
|
|
257
333
|
if (trimmable) {
|
|
258
334
|
for (const e of [...this.#index]) {
|
|
259
|
-
if (e.ns !== ns || e.tokens.length >= tokens.length) continue;
|
|
335
|
+
if (e.ns !== ns || e.tokens.length >= tokens.length || e.tokens.length < minimumReusableOffset(caches)) continue;
|
|
260
336
|
if (commonPrefixLength(e.tokens, tokens) === e.tokens.length) this.remove(e.path);
|
|
261
337
|
}
|
|
262
338
|
}
|
|
263
|
-
this.#index.push({
|
|
339
|
+
this.#index.push({
|
|
340
|
+
path, ns, tokens, bytes: st.size, mtimeMs: Date.now(), trimmable,
|
|
341
|
+
minimumReusableOffset: minimumReusableOffset(caches),
|
|
342
|
+
...(generationCheckpoint ? { generationCheckpoint } : {}),
|
|
343
|
+
});
|
|
264
344
|
this.stats.spills++;
|
|
265
345
|
this.evictToCap();
|
|
266
346
|
return true;
|