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
|
@@ -1,229 +0,0 @@
|
|
|
1
|
-
# Spec decode on larger targets — does the Gemma drafter pay on 12B / 26B?
|
|
2
|
-
|
|
3
|
-
Status: **12B γ=1 is a WIN after the verify fix; matches optiq bit-exactly** (2026-06-14; 26B not run)
|
|
4
|
-
Owner: decode / spec
|
|
5
|
-
Default: **off** (speculation is opt-in; non-spec greedy is untouched)
|
|
6
|
-
|
|
7
|
-
## Result (2026-06-14, v2) — fixed the wrong-oracle bug; γ=1 now wins
|
|
8
|
-
|
|
9
|
-
The first 12B measurement (v1, below) was made through a verify path that
|
|
10
|
-
targeted the **wrong oracle**. Fixing it changed the conclusion.
|
|
11
|
-
|
|
12
|
-
### The wrong-oracle bug
|
|
13
|
-
|
|
14
|
-
Assistant-drafter speculation is an **optiq** feature — mlx-lm has only
|
|
15
|
-
generic standalone-`draft_model` two-model spec and structurally cannot
|
|
16
|
-
drive a KV-borrowing drafter. So the correctness oracle is optiq's
|
|
17
|
-
`spec_generate`. **Both optiq and mlx-lm BATCH the verify lm-head** (one
|
|
18
|
-
matmul over the γ+1 window, then argmax per position); neither is bit-
|
|
19
|
-
exact to stock token-at-a-time decode. Our port instead verified
|
|
20
|
-
**per-position** to be bit-exact to *stock decode* — a target no real
|
|
21
|
-
implementation has, and one that re-reads the ~1 GB tied lm-head matrix
|
|
22
|
-
γ+1× per iteration instead of once. So we were exact to the wrong thing
|
|
23
|
-
*and* paying a γ-scaling tax for it. Fix: `picksBatched` in
|
|
24
|
-
`src/spec/generate.ts` — one batched verify lm-head, matching optiq.
|
|
25
|
-
|
|
26
|
-
### Do we match optiq? YES — bit-exact (e4b)
|
|
27
|
-
|
|
28
|
-
Cross-checked our `specGenerate` against optiq's `spec_generate` on the
|
|
29
|
-
identical prompt ids (`scripts/oracle-spec.py` + `scripts/spec-dump.ts`),
|
|
30
|
-
e4b γ=2: **identical 48-token output AND identical accept/reject trace**
|
|
31
|
-
(drafted 60, accepted 17, target calls 31 in both). Before the fix we
|
|
32
|
-
matched *stock*, not optiq, at knife-edges; now we match optiq.
|
|
33
|
-
|
|
34
|
-
### Is it still a loss? Not at γ=1 (12B)
|
|
35
|
-
|
|
36
|
-
12B pair, batched verify (loaded machine — ratios paired/trustworthy,
|
|
37
|
-
absolutes depressed & not quotable):
|
|
38
|
-
|
|
39
|
-
| γ | v2 speedup (batched) | v1 (per-position) | acceptance |
|
|
40
|
-
|---|----------------------|-------------------|-----------|
|
|
41
|
-
| 1 | **1.09× (WIN)** | 0.96× | 42% |
|
|
42
|
-
| 2 | 0.91× (near break-even) | 0.77× | 29% |
|
|
43
|
-
| 3 | 0.72× | 0.67× | 23% |
|
|
44
|
-
| 4 | 0.56× | 0.52× | 17% |
|
|
45
|
-
|
|
46
|
-
Acceptance is unchanged (the fix only touches verify cost, not which
|
|
47
|
-
tokens are accepted). The per-position verify was a large self-inflicted
|
|
48
|
-
tax: removing it flips γ=1 from loss to **win** and moves γ=2 to the edge.
|
|
49
|
-
γ≥2 still loses because the *drafter's own* cost dominates there — the
|
|
50
|
-
12B drafter is heavy (hidden 1024 vs e4b 256, 16 heads, and a full 262k
|
|
51
|
-
tied-embedding head **every draft step**) and acceptance falls with depth.
|
|
52
|
-
|
|
53
|
-
**So the v1 "hypothesis refuted / net loss at every γ" was itself partly
|
|
54
|
-
an artifact of the wrong-oracle verify.** Corrected conclusion: γ=1
|
|
55
|
-
speculation on 12B is a real ~9% decode win (pending clean-machine
|
|
56
|
-
confirmation before quoting); γ≥2 doesn't pay with the shipped heavy
|
|
57
|
-
drafter.
|
|
58
|
-
|
|
59
|
-
### Bonus finding: we're ahead of optiq on the unified drafters
|
|
60
|
-
|
|
61
|
-
optiq's *installed* runtime can't actually drive the 12B/26B
|
|
62
|
-
`gemma4_unified_assistant` drafters: its head detection is config-based
|
|
63
|
-
(`use_ordered_embeddings = num_centroids and top_k`), but those artifacts
|
|
64
|
-
declare `num_centroids` while shipping **no** centroid tensors and **no**
|
|
65
|
-
`lm_head` (tied `embed_tokens` instead). optiq loads the centroid path
|
|
66
|
-
with zero-init weights → garbage drafts → **0% acceptance** (measured:
|
|
67
|
-
drafted 46, accepted 0 on 12B). Our `drafter.ts` detects the head by
|
|
68
|
-
**tensor presence** and uses the tied-embedding path → 29% acceptance on
|
|
69
|
-
the same artifact.
|
|
70
|
-
|
|
71
|
-
### Remaining levers / next
|
|
72
|
-
|
|
73
|
-
- **Clean-machine rerun** before promoting any number to RESULTS.md
|
|
74
|
-
(baseline jumped 15.4→24.4 between runs under memory pressure — exactly
|
|
75
|
-
why only paired ratios are trusted here).
|
|
76
|
-
- **Cheaper drafter head**: the 262k tied-embed argmax every draft step is
|
|
77
|
-
the dominant remaining γ≥2 cost; capping/approximating it (e4b-style)
|
|
78
|
-
could extend the win past γ=1.
|
|
79
|
-
- **26B-MoE**: still unlikely — MoE decode reads only top-8/128 experts
|
|
80
|
-
(~2.4 GB active), so per-token target cost is *lower* than its size →
|
|
81
|
-
harder to beat. Measure if curious.
|
|
82
|
-
- **Optional strict mode**: the old per-position verify gave "spec output
|
|
83
|
-
identical to spec-off" — a product nicety no oracle provides. Could be
|
|
84
|
-
kept as a default-off `strictVerify` flag rather than deleted
|
|
85
|
-
([[dont-delete-optionality-on-one-measurement]]) — not implemented; ask.
|
|
86
|
-
|
|
87
|
-
## v1 (superseded) — per-position verify, "net loss at every γ"
|
|
88
|
-
|
|
89
|
-
> Kept for the record. The numbers (0.96/0.77/0.67/0.52×) were real but
|
|
90
|
-
> measured through the wrong-oracle per-position verify; v2 above is the
|
|
91
|
-
> optiq-faithful measurement.
|
|
92
|
-
|
|
93
|
-
## TL;DR
|
|
94
|
-
|
|
95
|
-
The Gemma `-assistant-bf16` drafters are Gemma's substitute for an MTP
|
|
96
|
-
head: a 4-layer Q-only module that reuses the target's KV + hidden state
|
|
97
|
-
to draft tokens (`src/spec/drafter.ts`), verified by the target's own
|
|
98
|
-
greedy pick (`src/spec/generate.ts`). True MTP (a trained
|
|
99
|
-
`mtp.safetensors` head + an `optiq/runtime/mtp` backend) **does not exist
|
|
100
|
-
for Gemma** and never will — that's the Qwen path (Phase 14). The
|
|
101
|
-
assistant drafter is the functional equivalent we already have.
|
|
102
|
-
|
|
103
|
-
It was measured a **net loss on e4b** (Phase 6): γ=2 ran at 0.78× the
|
|
104
|
-
54 tok/s baseline, ~23% acceptance — because e4b decode is already too
|
|
105
|
-
fast for a low-acceptance drafter to beat. **This doc is the experiment
|
|
106
|
-
to find out whether the same drafter wins on a slower target**, where
|
|
107
|
-
each skipped target-forward is worth more.
|
|
108
|
-
|
|
109
|
-
## The hypothesis
|
|
110
|
-
|
|
111
|
-
The drafter's per-step cost is roughly fixed. The *value* of each
|
|
112
|
-
accepted draft = one skipped target forward, which grows as the target
|
|
113
|
-
gets slower per token. e4b is the worst case for speculation (fastest
|
|
114
|
-
target). So the SAME acceptance rate can flip net-positive as the target
|
|
115
|
-
slows:
|
|
116
|
-
|
|
117
|
-
| target | non-spec decode | per-accepted-draft value | drafter | status |
|
|
118
|
-
|--------|-----------------|--------------------------|---------|--------|
|
|
119
|
-
| e4b | ~54 tok/s | low | 160 MB (cached) | **measured net loss** (0.91/0.78/0.63/0.51× @ γ=1–4) |
|
|
120
|
-
| 12B | ~25 tok/s | ~2× e4b | 846 MB (cached) | **measured net loss** — see Result above (hypothesis refuted) |
|
|
121
|
-
| 26B-A4B (MoE) | ~32 tok/s | high (MoE forward) | 839 MB (HF, **not downloaded**) | untested (follow-on; expect loss) |
|
|
122
|
-
|
|
123
|
-
> The reasoning below is the *pre-measurement* hypothesis — kept for the
|
|
124
|
-
> record. It predicted the crossover assuming a fixed-cost drafter; the
|
|
125
|
-
> 12B drafter is not fixed-cost (it scales with the target), which is
|
|
126
|
-
> exactly why the Result section above refutes it.
|
|
127
|
-
|
|
128
|
-
Crossover intuition: a drafter is a net win when
|
|
129
|
-
`acceptance × (target_step_cost − draft_step_cost) > verify_overhead`.
|
|
130
|
-
On e4b the target step is cheap, so the bar isn't cleared at 23%. On 12B
|
|
131
|
-
the target step is ~2× more expensive while the drafter cost barely
|
|
132
|
-
moves — same acceptance, very different sign. **Measure, don't assume**
|
|
133
|
-
(it could still lose; this is exactly the kind of thing the e4b result
|
|
134
|
-
warned against assuming).
|
|
135
|
-
|
|
136
|
-
Acceptance itself may also differ on 12B (a stronger target predicts
|
|
137
|
-
itself differently). We don't know the direction — that's why we run it.
|
|
138
|
-
|
|
139
|
-
## What is already wired (this change)
|
|
140
|
-
|
|
141
|
-
- **`scripts/spec-bench.ts`** — now multi-pair, **one pair per process**
|
|
142
|
-
(memory-safe; 12B+drafter ≈ 8 GB, 26B ≈ 17 GB, never co-resident):
|
|
143
|
-
- `bun scripts/spec-bench.ts e4b` (default) — reproduces the baseline.
|
|
144
|
-
- `bun scripts/spec-bench.ts 12B` — the experiment.
|
|
145
|
-
- `bun scripts/spec-bench.ts 26B` — follow-on.
|
|
146
|
-
- Auto-detects whether each `(target, drafter)` snapshot pair is
|
|
147
|
-
present; exits with a clear "download with: hf download …" message
|
|
148
|
-
otherwise. Records baseline + γ-sweep (acceptance + tok/s) to the
|
|
149
|
-
eval DB tagged `spec-bench[<id>]`.
|
|
150
|
-
- **`tests/spec-decode.test.ts`** — added a `(12B)` parity block,
|
|
151
|
-
auto-skipped until the drafter is downloaded. Gate = long-prefix
|
|
152
|
-
agreement (greedy spec vs greedy non-spec). It guards the shared
|
|
153
|
-
accept/reject/**rollback + cache-trim** machinery against 12B's
|
|
154
|
-
distinct layer/cache shapes — NOT drafter correctness (see the
|
|
155
|
-
important nuance below). The calibrated e4b block (exact `toEqual` +
|
|
156
|
-
knife-edge) is unchanged.
|
|
157
|
-
|
|
158
|
-
The port (`drafter.ts` + `generate.ts`) is already generic over the
|
|
159
|
-
target's `backbone_hidden_size` — drafters are target-bonded artifacts
|
|
160
|
-
(e4b 2560, 12B 3840, 26B 2816) but there's no per-pair code, just the
|
|
161
|
-
matching weights. Expect to *validate* each cell per the project's
|
|
162
|
-
per-model-specialization reality, not assume it's free.
|
|
163
|
-
|
|
164
|
-
## Important nuance: a broken drafter still produces correct tokens
|
|
165
|
-
|
|
166
|
-
Speculation can't corrupt output. The target verifies every position and
|
|
167
|
-
replaces every rejected draft with its own greedy pick; drafts only ever
|
|
168
|
-
*skip* target forwards. So a mis-wired drafter (wrong KV borrow, wrong
|
|
169
|
-
projection) shows up as **low/zero acceptance — a perf signal**, not bad
|
|
170
|
-
tokens. This splits the validation cleanly:
|
|
171
|
-
|
|
172
|
-
- **Correctness** (parity test): does the shared accept/reject/rollback
|
|
173
|
-
+ cache-trim logic still hold on 12B's cache shapes? A trim bug *would*
|
|
174
|
-
corrupt output, shortly after the first rejection — that's what the
|
|
175
|
-
long-prefix gate catches. (Shape-incompatible KV borrow throws, it
|
|
176
|
-
doesn't silently corrupt.)
|
|
177
|
-
- **Payoff** (bench): is acceptance high enough, and the target slow
|
|
178
|
-
enough, that γ>0 beats the non-spec baseline? This is the actual
|
|
179
|
-
question.
|
|
180
|
-
|
|
181
|
-
## Procedure (once the 12B drafter is downloaded)
|
|
182
|
-
|
|
183
|
-
1. `hf download mlx-community/gemma-4-12B-it-assistant-bf16`
|
|
184
|
-
(846 MB; `HF_HUB_DISABLE_XET=1`). The 12B OptiQ-4bit target is
|
|
185
|
-
already cached.
|
|
186
|
-
2. **Correctness first** — `bun test tests/spec-decode.test.ts`
|
|
187
|
-
(the `(12B)` block now runs). Long-prefix gate must pass for γ=1,2,3.
|
|
188
|
-
If it fails early → rollback/trim bug on 12B caches, fix before
|
|
189
|
-
trusting any number.
|
|
190
|
-
3. **Calibrate + promote (optional)** — find a tie-free prompt
|
|
191
|
-
on-device, promote the 12B gate from long-prefix to exact `toEqual`
|
|
192
|
-
like e4b, for a stronger standing guard.
|
|
193
|
-
4. **Measure** — `bun scripts/spec-bench.ts 12B` on a clean machine
|
|
194
|
-
(preflight matters: dirty-machine numbers are garbage). Read
|
|
195
|
-
acceptance + speedup-vs-baseline per γ.
|
|
196
|
-
5. **Record + decide** — numbers land in the eval DB. Promote into
|
|
197
|
-
`benchmarks/RESULTS.md` only if quotable (clean machine).
|
|
198
|
-
6. **26B follow-on** — repeat with `26B` if 12B is encouraging; MoE
|
|
199
|
-
target adds a validation cell (donor KV borrow through the MoE
|
|
200
|
-
decoder), and a slower/heavier forward that should favor speculation
|
|
201
|
-
even more — but the MoE forward also reads only top-8/128 experts at
|
|
202
|
-
decode, so the per-token cost advantage is smaller than weight size
|
|
203
|
-
suggests. Measure.
|
|
204
|
-
|
|
205
|
-
## Exit criteria
|
|
206
|
-
|
|
207
|
-
- 12B spec parity gate green (rollback/trim correct on 12B caches).
|
|
208
|
-
- 12B acceptance + tok/s-vs-baseline recorded in the eval DB across
|
|
209
|
-
γ=1–4.
|
|
210
|
-
- A decision written down: ship speculation as the **default** only for
|
|
211
|
-
the (model, context) cells where it measurably wins; otherwise record
|
|
212
|
-
it as a characterized default-off lever (same disposition as the e4b
|
|
213
|
-
net-loss result — a documented option, never a deletion).
|
|
214
|
-
|
|
215
|
-
## Caveats / interactions
|
|
216
|
-
|
|
217
|
-
- **Batched serving (Phase 18).** Speculation needs trimmable caches for
|
|
218
|
-
partial-accept rollback — `generate.ts` throws once a `RotatingKVCache`
|
|
219
|
-
ring wraps past the sliding window — and variable accept-lengths per
|
|
220
|
-
slot break a uniform-B batch. Speculation is naturally a **B=1 latency**
|
|
221
|
-
optimization; batched-throughput serving is a different regime. Layering
|
|
222
|
-
both is real work, not free composition.
|
|
223
|
-
- **One serve path, two backends.** The assistant-drafter loop and the
|
|
224
|
-
Qwen MTP runtime are two backends of the *same* "draft γ → verify in one
|
|
225
|
-
forward → accept-longest-prefix → rollback" abstraction. As Phase 14
|
|
226
|
-
(MTP) and Phase 18 (slots) land, build one verify/accept executor, not
|
|
227
|
-
two — the drafter vs MTP-head difference is just what fills the draft.
|
|
228
|
-
- **Denominator discipline.** Speedups are only ever vs the *same model's*
|
|
229
|
-
non-spec baseline. e4b's 54 tok/s is the wrong denominator for 12B.
|
|
@@ -1,415 +0,0 @@
|
|
|
1
|
-
# SSD cold tier for the prompt/KV cache
|
|
2
|
-
|
|
3
|
-
Status: **LANDED P1–P3 (2026-07-02, same day)** — P4 hardening extras remain.
|
|
4
|
-
**Layer-0 upgrade (2026-07-05, unified-engine plan):** D2 finished properly —
|
|
5
|
-
the two-tier take/restore/trim dance moved INSIDE `PromptCache.take()`
|
|
6
|
-
(structural `ColdTier` interface; the server binds SsdCacheStore + model into
|
|
7
|
-
it), so EVERY consumer tiers automatically: the serial lane's hand-rolled
|
|
8
|
-
block in runGeneration is gone, and the BATCH SCHEDULER restores prefixes
|
|
9
|
-
from disk at admission (gated end-to-end: tests/batch-scheduler.test.ts
|
|
10
|
-
"SSD tier through the batch lane"). `PromptCache.onPut` drives the debounced
|
|
11
|
-
write-behind snapshot for both lanes' puts. NEW: **idle demotion**
|
|
12
|
-
(`--ssd-demote-idle`, default 300 s with the tier on) — entries unused past
|
|
13
|
-
the threshold spill to disk and free their GPU arrays; RAM drains between
|
|
14
|
-
agent bursts while prefixes stay reachable via zero-copy restore. The sweep
|
|
15
|
-
runs only when the gateway is fully idle (a runExclusive would otherwise
|
|
16
|
-
drain a live batch). Rationale (Josh): prefill is the cost being avoided —
|
|
17
|
-
12B prefill is 168 tok/s, so a 30k agent context is ~3 min of GPU compute vs
|
|
18
|
-
a ~1 s NVMe restore; SSD-vs-RAM (40×) is the wrong comparison, SSD-vs-
|
|
19
|
-
recompute (50–180×, growing with context) is the real one.
|
|
20
|
-
|
|
21
|
-
**Measured (M1 Max, MiniCPM5, 13.7k-token prefix, kv_config quant):**
|
|
22
|
-
- Restart TTFT: **12,083 ms (full prefill) → 236 ms** restored — 2% of full
|
|
23
|
-
prefill, 13,675 tokens back via a 3 ms zero-copy mmap. Beats oMLX's 1–3 s
|
|
24
|
-
claim and both exit criteria (<2.5 s, ≤10%).
|
|
25
|
-
- Steady-state decode: **0% overhead** (192.0 vs 192.3 tok/s, flag off/on) —
|
|
26
|
-
nothing runs on the token loop, as designed (vs oMLX's measured ~20%).
|
|
27
|
-
- Correctness: SSD-restored + trimmed continuation is **identical to the RAM
|
|
28
|
-
tier's cache-hit output** (control experiment); divergence from a fresh
|
|
29
|
-
full prefill is the pre-existing bf16 prefix-reuse property both tiers
|
|
30
|
-
share, not introduced by persistence.
|
|
31
|
-
|
|
32
|
-
What landed: kv-store v2 (`src/kv-store.ts` — all five cache kinds incl.
|
|
33
|
-
quantized + SSM, invalidation metadata, per-tensor hashes, atomic writes;
|
|
34
|
-
`restoreState` added to both quantized cache classes), `src/ssd-cache.ts`
|
|
35
|
-
(SsdCacheStore per D4), tier integration (`src/prompt-cache.ts` spill hook +
|
|
36
|
-
retain thunks; `src/server.ts` tiered take with post-restore trim,
|
|
37
|
-
debounced write-behind snapshots under the gateway lock, `/stats.ssd_cache`),
|
|
38
|
-
flags `--ssd-cache <dir>` / `--ssd-cache-max <GB>` / `--ssd-cache-verify`.
|
|
39
|
-
Tests: `tests/kv-store.test.ts` (fork-compare token-identical for bf16,
|
|
40
|
-
quantized, SSM), `tests/ssd-cache.test.ts` (restart recovery, quarantine,
|
|
41
|
-
corruption, eviction, supersede). One deviation from the plan below: find()
|
|
42
|
-
returns PARTIAL prefix matches and the server trims after restore
|
|
43
|
-
(divergent-tail conversations — full-entry-only missed the real pattern). Inspired by oMLX's paged SSD cache
|
|
44
|
-
(Apache 2.0 — `omlx/cache/paged_ssd_cache.py`, `boundary_snapshot_store.py`;
|
|
45
|
-
we port ideas, not code — add a `THIRD_PARTY_LICENSES.md` note if any code
|
|
46
|
-
ends up derived). Their headline: coding-agent TTFT 30–90 s → 1–3 s by
|
|
47
|
-
restoring long prefixes from SSD across requests AND restarts. Their
|
|
48
|
-
measured costs (independent tests): ~2–3× cold TTFT and ~20% steady-state
|
|
49
|
-
decode overhead — this design exists to get the win without those costs.
|
|
50
|
-
Flag-gated, off by default.
|
|
51
|
-
|
|
52
|
-
**Head start**: `src/kv-store.ts` already implements page-aligned KV
|
|
53
|
-
persistence (`MLXBUNKV1`, 16 KiB-aligned tensors) with zero-copy COW mmap
|
|
54
|
-
reload (`MmapFile.open(path, "cow")` → `MlxArray.fromPointer` — exactly
|
|
55
|
-
the Metal page-alignment constraint, proven by expert-offload), including
|
|
56
|
-
mid-rotation `RotatingKVCache` restore (`restoreState`,
|
|
57
|
-
`src/model/gemma4-base.ts:880`). `scripts/experiments/cold-start.ts` is a
|
|
58
|
-
working sub-1 s cold-restore harness. The SSD tier is wiring +
|
|
59
|
-
quantized-cache support + an index/eviction/recovery layer.
|
|
60
|
-
|
|
61
|
-
## Current state / gaps
|
|
62
|
-
|
|
63
|
-
- `src/prompt-cache.ts`: byte-capped LRU RAM tier (default 2 GB,
|
|
64
|
-
`--prompt-cache`), prefix-entry based, adapter-namespaced, serial lane
|
|
65
|
-
only, race-free under the gateway lock. **A restart loses everything.**
|
|
66
|
-
- `kv-store.ts` gaps: (1) no quantized kinds — `saveKvCache` throws for
|
|
67
|
-
`QuantizedKVCache`/`RotatingQuantizedKVCache` and neither has
|
|
68
|
-
`restoreState`, yet serving defaults to the model's `kv_config.json`
|
|
69
|
-
scheme, so the default production cache can't persist today; quantized
|
|
70
|
-
state is 6 arrays/layer (packed u32 + scales/biases bf16 × K/V);
|
|
71
|
-
(2) no invalidation metadata / checksums / atomic writes; (3) latent
|
|
72
|
-
bug: `loadKvCache` checks `caches.length !== model.layers.length` but
|
|
73
|
-
`makeCache()` returns donor layers only — wrong for KV-shared e4b
|
|
74
|
-
(verify in Phase 1).
|
|
75
|
-
- Versioning primitive exists: `configFingerprint`
|
|
76
|
-
(`src/model/fingerprint.ts`) — sha256 over graph-shaping config incl.
|
|
77
|
-
`kvQuant`.
|
|
78
|
-
|
|
79
|
-
## Design decisions
|
|
80
|
-
|
|
81
|
-
**D1 — Whole-prefix-entry spill, not paged blocks.** oMLX pages for
|
|
82
|
-
multi-user shared-prefix dedup; we are single-user with a prefix-entry
|
|
83
|
-
cache that grows monotonically per conversation. Paging is exactly where
|
|
84
|
-
their overheads come from: per-block SHA256 + save-queue bookkeeping
|
|
85
|
-
*during decode* (their ~20% tax) and block-granular safetensors restore
|
|
86
|
-
with copies (their 2–3× cold TTFT). Ours: **zero work on the token loop**
|
|
87
|
-
(prefix tokens ARE the key), spill/persist only at request boundaries and
|
|
88
|
-
evictions, restore = one zero-copy mmap with lazy page fault-in (~5 GB/s
|
|
89
|
-
NVMe; 2 GB entry ≈ 0.4 s amortized into the first forward). Partial-prefix
|
|
90
|
-
reuse works without paging: restore + `Cache.trim()`. Revisit paging only
|
|
91
|
-
for multi-user batch dedup (v2, out of scope).
|
|
92
|
-
|
|
93
|
-
**D2 — One store, two tiers.** `PromptCache` grows a cold tier behind the
|
|
94
|
-
same `take`/`put` API:
|
|
95
|
-
- RAM→SSD: `put()`'s evict loop spills (snapshot bytes → async writer →
|
|
96
|
-
dispose GPU arrays) instead of just disposing.
|
|
97
|
-
- SSD→RAM: `take()` prefers a strictly-longer SSD prefix; restored entry
|
|
98
|
-
carries its `MmapFile`, unmapped on entry dispose (extensions copy into
|
|
99
|
-
fresh anonymous buffers via concat-grow anyway).
|
|
100
|
-
- **Restart persistence needs write-behind at request completion**, not
|
|
101
|
-
just spill-on-evict: after `promptCache.put(...)` in `runGeneration`
|
|
102
|
-
(`src/server.ts:1019`), a debounced, coalesced-per-ns async snapshot.
|
|
103
|
-
Byte extraction (the only GPU-thread work) runs on the idle serial lane
|
|
104
|
-
between requests; file I/O is async. (oMLX's boundary-snapshot idea at
|
|
105
|
-
whole-entry granularity.)
|
|
106
|
-
|
|
107
|
-
**D3 — Extend the existing format to `MLXBUNKV2`**, not safetensors
|
|
108
|
-
(safetensors offsets aren't page-aligned; oMLX pays a copy via `mx.load`).
|
|
109
|
-
Add: `"qkv"`/`"rotating-qkv"` kinds (six tensor slots + groupSize/bits;
|
|
110
|
-
slice quantized buffers to `[.., :offset, :]` — don't persist 256-step
|
|
111
|
-
slack), header fields `formatVersion`/`modelId`/`configFingerprint`/`ns`/
|
|
112
|
-
`tokenizerHash`/`createdAt`, per-tensor checksums + header checksum.
|
|
113
|
-
Integrity: header checksum always verified on scan; full verification
|
|
114
|
-
opt-in (`--ssd-cache-verify`) because eager reads destroy lazy fault-in;
|
|
115
|
-
default trusts atomic writes (tmp + fsync + rename). Corrupt/incompatible
|
|
116
|
-
→ delete + log, never fatal. `loadKvCache` becomes model-generic
|
|
117
|
-
(accept `{ makeCache(): Cache[] }`, donor-count check).
|
|
118
|
-
|
|
119
|
-
**D4 — Files are the database.**
|
|
120
|
-
`<ssd-dir>/<configFingerprint>/<nsHash>/<uuid>.mlxkv`. Startup recovery =
|
|
121
|
-
header-only directory scan → in-memory `SsdIndex`
|
|
122
|
-
(`{ns, tokens, path, bytes, mtimeMs}[]`); tokens live in the header JSON.
|
|
123
|
-
LRU = file mtime (`utimes()` on hit); byte cap enforced at write time by
|
|
124
|
-
oldest-mtime eviction; disk-full → drop write, warn once, keep serving.
|
|
125
|
-
|
|
126
|
-
**D5 — Flags & interactions.** `--ssd-cache <dir>` +
|
|
127
|
-
`--ssd-cache-max <GiB>` (default 32) → `ServerOptions.ssdCacheDir/MaxBytes`.
|
|
128
|
-
Requires the RAM tier (`--prompt-cache 0` + `--ssd-cache` = startup
|
|
129
|
-
error). `--memory-budget` unchanged (restored caches are clean file-backed
|
|
130
|
-
pages until dirtied; admission already assumes worst-case bf16). `/status`
|
|
131
|
-
gains `ssd: { entries, bytes, max_bytes, restores, spills, restore_ms_last }`.
|
|
132
|
-
v1 is serial-lane only (where `PromptCache` lives); batch-lane interplay is
|
|
133
|
-
docs/design/batching-perf-path.md P3's problem.
|
|
134
|
-
|
|
135
|
-
## Phases
|
|
136
|
-
|
|
137
|
-
- **P0 — Baseline bench (½ d)**: `scripts/experiments/ssd-cache-bench.ts`
|
|
138
|
-
grown from `cold-start.ts`: ≥16k-token prefix on the standard snapshot,
|
|
139
|
-
kv_config quant active; measure full-prefill TTFT / warm RAM-hit TTFT /
|
|
140
|
-
steady-state decode. Exit: baseline table committed.
|
|
141
|
-
- **P1 — kv-store v2 (2–3 d, highest uncertainty)**: `restoreState` on
|
|
142
|
-
both quantized cache classes; all four kinds in save/load; header v2;
|
|
143
|
-
atomic writes; model-generic load. Exit: round-trip tests all kinds
|
|
144
|
-
(incl. wrapped ring) + **greedy decode parity** — N tokens continued
|
|
145
|
-
from a restored cache bit-exact vs never-persisted; `tsc` 0.
|
|
146
|
-
- **P2 — `src/ssd-cache.ts` store (1–2 d)**: `SsdCacheStore
|
|
147
|
-
{ scan, find(prompt, ns), store (async, extraction split from I/O),
|
|
148
|
-
evictToCap, remove, stats }`. Exit: unit tests — restart recovery,
|
|
149
|
-
incompatible-fingerprint quarantine, corrupt-header deletion, mtime cap
|
|
150
|
-
eviction, disk-full soft-fail.
|
|
151
|
-
- **P3 — Tier integration + flags (2–3 d)**: cold tier in `PromptCache`,
|
|
152
|
-
debounced end-of-request snapshots, CLI/`/status`. **Exit (headline)**:
|
|
153
|
-
(1) restart TTFT on a 16k prefix **< 2.5 s and ≤ 10% of full-prefill**
|
|
154
|
-
(beat oMLX's 1–3 s; a true-cold miss runs the unchanged prefill path);
|
|
155
|
-
(2) steady-state decode overhead **< 2%** vs flag off (vs their ~20%);
|
|
156
|
-
(3) flag absent ⇒ zero behavior change.
|
|
157
|
-
- **P4 — Hardening + docs (1–2 d)**: kill-during-write crash test,
|
|
158
|
-
adapter-ns isolation, kv-quant-scheme-change invalidation, README/help,
|
|
159
|
-
license note. Exit: suite green; on/off × cold/warm/restart matrix
|
|
160
|
-
recorded.
|
|
161
|
-
|
|
162
|
-
Total ~7–10 working days.
|
|
163
|
-
|
|
164
|
-
## Risks
|
|
165
|
-
|
|
166
|
-
- **mmap lifetime vs mlx laziness**: unmap only in entry `dispose()` after
|
|
167
|
-
successor state is evaluated; COW mapping already covers buffer donation.
|
|
168
|
-
- **Byte-extraction stall on multi-GB entries**: runs between requests on
|
|
169
|
-
the idle lane; measure in P3; chunk with yields if needed.
|
|
170
|
-
- **COW dirty-page accounting**: extended restored caches migrate to
|
|
171
|
-
anonymous memory — probe with `phys_footprint` (expert-offload
|
|
172
|
-
discipline) before making footprint claims.
|
|
173
|
-
- **Stale-token poisoning**: `configFingerprint` + `tokenizerHash` + ns;
|
|
174
|
-
chat-template drift degrades to a shorter matched prefix (safe).
|
|
175
|
-
|
|
176
|
-
## Addendum 2026-07-07 — A7 closure: bounded memory on BOTH sides of the file
|
|
177
|
-
|
|
178
|
-
The 07-06/07 serve benches flagged elevated ctx/restart-leg RSS on
|
|
179
|
-
--ssd-cache arms ("A7"). Root-caused in three parts; the first two were
|
|
180
|
-
real defects, both fixed in `src/kv-store.ts`:
|
|
181
|
-
|
|
182
|
-
- **Write side** (residual after the v3 streaming writer of 4f9bad9): the
|
|
183
|
-
per-tensor `rawBytes()` readback ended in a JS-heap `.slice()`; under GC
|
|
184
|
-
lag the dead copies outlive the flush loop, re-spiking RSS toward the
|
|
185
|
-
whole entry. The writer now hashes and writes from a ZERO-COPY view of
|
|
186
|
-
the contiguous mlx buffer (`MlxArray.rawBytesView`) — no JS-heap copy
|
|
187
|
-
exists at any point; save-transient mlx memory measured 0 bytes for
|
|
188
|
-
contiguous sources (one tensor otherwise).
|
|
189
|
-
- **Restore side** (never covered by A7's original scope): the zero-copy
|
|
190
|
-
restore (COW mmap + `fromPointer`) became a per-restore PROCESS-LIFETIME
|
|
191
|
-
mapping leak once the 2026-07-06 FFI-dtor fix removed the unmap signal,
|
|
192
|
-
and the exactly-offset-sized restored buffers made the first
|
|
193
|
-
post-restore decode step concat-copy the entire entry into fresh mlx
|
|
194
|
-
memory. Restore is now a **streamed copy** — each tensor is copied into
|
|
195
|
-
an mlx-owned leaf (`fromBytesCopy`, no dtor contract), its clean file
|
|
196
|
-
pages dropped (`MADV_DONTNEED`; tensor offsets are 16 KiB-aligned = the
|
|
197
|
-
arm64 page size), the mapping unmapped before `loadKvCache` returns, and
|
|
198
|
-
plain-KV tensors land in STEP-rounded capacity with ≥1 token of slack so
|
|
199
|
-
the first step updates in place. Measured (512 MB synthetic entry): mlx
|
|
200
|
-
peak during restore+first-step = live entry + ONE tensor (552 vs 520 MB
|
|
201
|
-
active); `vmmap` confirms no `.mlxkv` mapping survives; 12B real-model
|
|
202
|
-
cold cache-load → first token 277 ms (parity with the old zero-copy
|
|
203
|
-
~240 ms claim — the old path paid the same whole-entry copy on the first
|
|
204
|
-
decode step anyway). Byte identity of the copy-restore is pinned for all
|
|
205
|
-
five cache kinds (save → load(verify) → re-save hash-identical;
|
|
206
|
-
tests/kv-store.test.ts "copy-restore byte identity") plus the real-model
|
|
207
|
-
bf16/quantized/SSM continuation suites.
|
|
208
|
-
- **What was NOT a defect**: most of the benched leg delta is `ps` RSS
|
|
209
|
-
ACCOUNTING — the write-behind's hash+write CPU-touches the live KV
|
|
210
|
-
entry, which makes those already-allocated unified-memory pages visible
|
|
211
|
-
to `ps` (GPU-written buffers and python-arm KV never show). The bench
|
|
212
|
-
report now footnotes this instead of promising a pending fix.
|
|
213
|
-
|
|
214
|
-
Supersedes the D2 line "restored entry carries its MmapFile, unmapped on
|
|
215
|
-
entry dispose" and the D1 "restore = one zero-copy mmap with lazy page
|
|
216
|
-
fault-in" mechanics above — the economics (SSD-vs-recompute) and the
|
|
217
|
-
zero-work-on-token-loop property are unchanged.
|
|
218
|
-
|
|
219
|
-
## Addendum 2026-07-07 — write-behind scheduling contract: flush only while idle
|
|
220
|
-
|
|
221
|
-
The 07-07 serve bench showed decode@ctx losing to mlx-lm on the bun arms
|
|
222
|
-
(e4b −9.3%, 12B −3.9%) while short-ctx decode won on every model — and the
|
|
223
|
-
mixed arm (4× smaller flush bytes) BEAT the bf16 arms at decode@ctx. Root
|
|
224
|
-
cause: "non-blocking" write-behind was only non-blocking at the event-loop
|
|
225
|
-
level. Every per-tensor flush step is (1) `ops.contiguous` — a kernel on
|
|
226
|
-
the SAME GPU stream decode uses — then (2) `rawBytesView()` → a synchronous
|
|
227
|
-
`mlx_array_eval` that blocks the JS thread until the stream drains, then
|
|
228
|
-
(3) a synchronous multi-MB `writeSync`. The `setImmediate` pacing
|
|
229
|
-
interleaved those slices exactly between decode tokens, so a ~16k entry's
|
|
230
|
-
flush overlapping the bench's cached ctx repeats taxed their 64-token
|
|
231
|
-
decode windows (reproduced standalone: e4b 9.5k-token entry, repeat decode
|
|
232
|
-
37.9 vs cold 47.1 tok/s; flat 44–46 across all three samples post-fix).
|
|
233
|
-
mlx-lm runs no equivalent background work — the gap was pure self-inflicted
|
|
234
|
-
contention.
|
|
235
|
-
|
|
236
|
-
**The contract now:** the flush only progresses while the engine is idle.
|
|
237
|
-
- `GenerationGateway.busy` covers BOTH lanes (mutex held/awaited — the
|
|
238
|
-
serial lane shows zero rows — OR batch rows active/pending);
|
|
239
|
-
`gateway.onIdle()` is the poll-based waiter (`generation-gateway.ts`).
|
|
240
|
-
- `saveKvCacheAsync`/`SsdCacheStore.storeAsync` accept a per-step
|
|
241
|
-
`waitTurn` gate awaited before EVERY tensor step (including the first),
|
|
242
|
-
so a request arriving MID-flush pauses the remaining tensors until idle
|
|
243
|
-
again; the server passes `() => gateway.onIdle()` at both chain sites
|
|
244
|
-
(write-behind snapshots AND eviction/demotion spills). Gate failures are
|
|
245
|
-
swallowed — scheduling advice must never corrupt the write path.
|
|
246
|
-
- `MLX_BUN_SSD_WRITEBEHIND=0` disables write-behind snapshots entirely
|
|
247
|
-
(kill switch + paired-A/B lever; restart survival then degrades to
|
|
248
|
-
spill-on-evict only).
|
|
249
|
-
|
|
250
|
-
Accepted tradeoffs: durability waits for a quiet moment (single-user
|
|
251
|
-
serving quiesces constantly; sustained hammering defers the flush AND the
|
|
252
|
-
spill clones' GPU-memory release, bounded by the serial chain); one
|
|
253
|
-
in-flight tensor step (~10–15 MB) can still land ahead of a just-arrived
|
|
254
|
-
request; and the restart window tightens — the flush now runs in idle gaps
|
|
255
|
-
instead of overlapping decodes, so the bench's 2.5 s pre-kill beat must
|
|
256
|
-
absorb it (verified surviving: e4b 9.5k entry, restart cached=9575 with
|
|
257
|
-
the gate on).
|
|
258
|
-
|
|
259
|
-
## Addendum 2026-07-07 (later) — bounded retention + admission-safe snapshots
|
|
260
|
-
|
|
261
|
-
The post-merge review found two operational holes in the contract above:
|
|
262
|
-
|
|
263
|
-
1. **Snapshot timer could stall batch admission.** `scheduleSsdSnapshot`'s
|
|
264
|
-
debounced timer fired `gateway.runExclusive` unconditionally; the
|
|
265
|
-
exclusive registers a serial WAITER, which holds batch admission until
|
|
266
|
-
every running row drains naturally — a background durability timer
|
|
267
|
-
periodically freezing admission under `--batch N` sustained load. Fixed
|
|
268
|
-
with the same guard `demoteIdle` always had: the timer checks
|
|
269
|
-
`activeRows/pendingRows` first and RE-ARMS (5 s, unref'd) while busy —
|
|
270
|
-
entries are immutable, a late snapshot is equally valid.
|
|
271
|
-
2. **Starved-gate clone retention was unbounded.** Every eviction/demotion/
|
|
272
|
-
snapshot chained GPU-pinning zero-copy clones onto a bare serial promise
|
|
273
|
-
chain, and every flush step awaits `onIdle()` — under continuous traffic
|
|
274
|
-
the chain starves and resident memory = prompt-cache cap + every queued
|
|
275
|
-
clone, defeating the byte cap exactly under the load causing evictions.
|
|
276
|
-
Now all three producers go through **`SpillQueue`** (kv-store.ts):
|
|
277
|
-
queued bytes are capped (default 2 GB = a quarter of the 8 GB RAM-cache
|
|
278
|
-
default; `MLX_BUN_SSD_SPILL_QUEUE_GB` overrides) — over cap the OLDEST
|
|
279
|
-
not-mid-store item drops with its clones disposed immediately. A dropped
|
|
280
|
-
spill is a future cache miss, never a wrong result: the contention-free
|
|
281
|
-
alternative to letting the flush cut into decode. `/stats.ssd_cache`
|
|
282
|
-
exposes `pending_spills` / `pending_spill_bytes` / `dropped_spills`.
|
|
283
|
-
|
|
284
|
-
Explicitly accepted (documented, not built): **no shutdown flush** — a
|
|
285
|
-
SIGINT/SIGTERM during (or within the debounce after) traffic loses queued
|
|
286
|
-
write-behind entries; restart survival degrades to whatever already
|
|
287
|
-
flushed. This is a best-effort cache tier; the cost of a lost entry is one
|
|
288
|
-
re-prefill. `SpillQueue.drain()` exists if a shutdown hook is ever wanted.
|
|
289
|
-
|
|
290
|
-
This acceptance was superseded by the 2026-08-22 durability fix below.
|
|
291
|
-
|
|
292
|
-
## Bug brief 2026-08-22: restart can outrun SSD write-behind
|
|
293
|
-
|
|
294
|
-
### User-visible failure
|
|
295
|
-
|
|
296
|
-
A completed long-context request can report a full RAM-cache hit on repeat,
|
|
297
|
-
then lose that prefix when the server restarts a few seconds later. The next
|
|
298
|
-
request pays a full prefill even though `--ssd-cache` was enabled.
|
|
299
|
-
|
|
300
|
-
The 2026-08-22 M1 Max serve matrix reproduced this with the standard restart
|
|
301
|
-
leg, which waits 2.5 seconds after the long request before sending SIGTERM:
|
|
302
|
-
|
|
303
|
-
| model and mode | cached tokens after restart | expected |
|
|
304
|
-
|---|---:|---:|
|
|
305
|
-
| MiniCPM5, unified bf16 | 15,817 | about 15.8k |
|
|
306
|
-
| e4b, unified bf16 | 4 | about 15.9k |
|
|
307
|
-
| e4b, serial bf16 | 15,940 | about 15.9k |
|
|
308
|
-
| 12B, unified bf16 | 4 | about 15.9k |
|
|
309
|
-
| 12B, serial bf16 | 15,866 | about 15.9k |
|
|
310
|
-
| Qwen3.8, unified bf16 | 0 | about 15.1k |
|
|
311
|
-
| Qwen3.8, serial bf16 | 0 | about 15.1k |
|
|
312
|
-
| Qwen3.8, unified mixed KV | 15,112 | about 15.1k |
|
|
313
|
-
|
|
314
|
-
The Bun 1.4 rerun reproduced the lane split before this brief was written:
|
|
315
|
-
e4b unified restored 2 of 15,942 tokens, while e4b serial restored 15,939.
|
|
316
|
-
The completed run also restored only 4 tokens for unified 12B versus 15,794
|
|
317
|
-
for serial 12B, and 5 for mixed-KV 12B. Qwen bf16 restored zero in both
|
|
318
|
-
lanes, while mixed-KV Qwen restored all 15,114 tokens. This is not a Bun
|
|
319
|
-
1.3-only result.
|
|
320
|
-
|
|
321
|
-
### Expected contract
|
|
322
|
-
|
|
323
|
-
After a request completes and the server has reported the cache durable, a
|
|
324
|
-
normal SIGTERM and restart must restore the longest compatible prefix. For
|
|
325
|
-
the benchmark's exact-repeat request, `cached_tokens` should be at least
|
|
326
|
-
`prompt_tokens - 1`. The server must never imply durability while a matching
|
|
327
|
-
snapshot is still waiting on its debounce timer or the spill queue.
|
|
328
|
-
|
|
329
|
-
### Evidence and current diagnosis
|
|
330
|
-
|
|
331
|
-
The read and reconstruction path works when the file reaches disk. MiniCPM,
|
|
332
|
-
serial e4b and 12B, and mixed-KV Qwen all restore a full prefix through the
|
|
333
|
-
same `PromptCache.take()` and `SsdCacheStore.restore()` path. That makes a
|
|
334
|
-
model-format or prefix-matching defect unlikely as the primary cause.
|
|
335
|
-
|
|
336
|
-
The likely failure is the durability boundary:
|
|
337
|
-
|
|
338
|
-
1. `PromptCache.onPut` schedules a snapshot with a one-second debounce.
|
|
339
|
-
2. A busy gateway re-arms that timer for five seconds.
|
|
340
|
-
3. The snapshot then joins a serial `SpillQueue`; large bf16 entries can still
|
|
341
|
-
be queued or in flight when the benchmark's 2.5-second wait expires.
|
|
342
|
-
4. SIGTERM stops the process without forcing pending timers to fire or awaiting
|
|
343
|
-
`SpillQueue.drain()`. The queue's own comment calls drain a future shutdown
|
|
344
|
-
hook.
|
|
345
|
-
|
|
346
|
-
Entry size explains the broad pattern. Small MiniCPM and smaller mixed-KV Qwen
|
|
347
|
-
files finish in time; large Qwen bf16 files do not. The remaining unified versus
|
|
348
|
-
serial split needs telemetry before assigning a second root cause. The unified
|
|
349
|
-
lane may enqueue more snapshots, take longer to extract row caches, or hit the
|
|
350
|
-
queue cap. The current report does not capture that state before termination.
|
|
351
|
-
|
|
352
|
-
### Fix plan
|
|
353
|
-
|
|
354
|
-
1. Expose `pending_snapshots`, `pending_spills`, `pending_spill_bytes`,
|
|
355
|
-
`dropped_spills`, and the longest durable prefix in `/stats.ssd_cache`.
|
|
356
|
-
`pending_snapshots` must include debounce and busy-rearm timers, not only
|
|
357
|
-
entries already placed in `SpillQueue`.
|
|
358
|
-
2. Add one async durability operation that cancels debounce timers, snapshots
|
|
359
|
-
matching RAM entries under the gateway lock, and awaits
|
|
360
|
-
`SpillQueue.drain()`. Return success only after the atomic rename completes.
|
|
361
|
-
3. Route graceful SIGINT/SIGTERM through that operation with a bounded timeout.
|
|
362
|
-
Stop new admission first. A timeout may drop the cache, but must log the
|
|
363
|
-
number of pending and dropped snapshots.
|
|
364
|
-
4. Change the benchmark restart leg to request or poll durability instead of
|
|
365
|
-
sleeping for 2.5 seconds. A fixed sleep measures SSD timing luck, not the
|
|
366
|
-
restart contract.
|
|
367
|
-
5. Capture pre-kill stats and the recovered entry count in every restart row so
|
|
368
|
-
a write miss cannot masquerade as a restore miss again.
|
|
369
|
-
|
|
370
|
-
### Implemented 2026-08-22
|
|
371
|
-
|
|
372
|
-
- `SsdDurabilityCoordinator` owns debounce, busy retry, queue completion, and
|
|
373
|
-
explicit flush as one state machine. A snapshot stays dirty until the atomic
|
|
374
|
-
store succeeds. Queue drops and write failures remain retryable.
|
|
375
|
-
- Dirty keys include the exact token sequence, so two conversations with the
|
|
376
|
-
same namespace and token count cannot cancel each other.
|
|
377
|
-
- `POST /admin/cache/flush` returns `200` only at the durability boundary and
|
|
378
|
-
exposes pending, dropped, failed, entry-count, and longest-prefix evidence.
|
|
379
|
-
- `mlx-bun serve` handles `SIGINT` and `SIGTERM` by stopping admission,
|
|
380
|
-
draining active requests, and awaiting the same flush, bounded by
|
|
381
|
-
`MLX_BUN_SHUTDOWN_TIMEOUT_MS`.
|
|
382
|
-
- The standard restart benchmark calls the endpoint instead of sleeping and
|
|
383
|
-
records its evidence in the Markdown report and eval database notes.
|
|
384
|
-
- Model-free regression coverage holds an atomic store open and proves flush
|
|
385
|
-
waits, forces the busy-rearm path, retries a failed write, preserves
|
|
386
|
-
equal-length conversations, and reports a missing RAM boundary honestly.
|
|
387
|
-
|
|
388
|
-
Focused real-server smoke on unified e4b passed the repaired path: a
|
|
389
|
-
4,026-token prompt flushed in 408 ms with `durable=true`, zero pending,
|
|
390
|
-
dropped, or failed work, then restored 4,025 cached tokens after SIGTERM and
|
|
391
|
-
respawn. The machine failed the clean-performance preflight, so this is cache
|
|
392
|
-
correctness evidence only, not a performance result. The full clean 16k
|
|
393
|
-
e4b/12B/Qwen matrix remains the acceptance run.
|
|
394
|
-
|
|
395
|
-
### Acceptance tests
|
|
396
|
-
|
|
397
|
-
- A deterministic coordinator test holds `storeAsync`, requests a flush,
|
|
398
|
-
releases the store, and proves the durability boundary waits for the atomic
|
|
399
|
-
file. The focused real-server smoke exercises the SIGTERM path through that
|
|
400
|
-
same operation before restart.
|
|
401
|
-
- A busy-gateway test forces the five-second re-arm path, then requests a flush
|
|
402
|
-
and proves the timer becomes an immediate queued snapshot without blocking an
|
|
403
|
-
active generation.
|
|
404
|
-
- Batch 8 and `--batch 1` both restore at least `prompt_tokens - 1` for e4b,
|
|
405
|
-
12B, and Qwen3.8 at roughly 16k context.
|
|
406
|
-
- The mixed-KV and SSM cache kinds retain their existing continuation parity.
|
|
407
|
-
- Steady-state decode remains unchanged because normal writes still pause while
|
|
408
|
-
the engine is busy. Queued clone memory stays within the spill cap.
|
|
409
|
-
|
|
410
|
-
Focused reproduction:
|
|
411
|
-
|
|
412
|
-
```sh
|
|
413
|
-
./benchmark.sh --models e4b,12B,qwen27b \
|
|
414
|
-
--arms mlx-bun,mlx-bun-serial,mlx-bun-mixed
|
|
415
|
-
```
|