mlx-bun 0.0.13 → 0.2.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.
Files changed (224) hide show
  1. package/README.md +31 -21
  2. package/bin/mlx-bun.mjs +1 -1
  3. package/docs/{design → archive}/hlg-sampling.md +3 -3
  4. package/docs/{investigations → archive/investigations}/hlg-sampling-investigation.md +1 -1
  5. package/docs/archive/investigations/pareto-specialized-runtime-findings.md +92 -0
  6. package/docs/{planning → archive/planning}/chat-ui-vision.md +1 -1
  7. package/docs/{planning → archive/planning}/release-notes-v0.0.9.md +1 -1
  8. package/docs/{planning → archive/planning}/website-readme-pass-plan.md +1 -1
  9. package/docs/design/colibri-glm52-port.md +1 -1
  10. package/docs/design/compat-cli-surface-design.md +1 -1
  11. package/docs/design/decode-speed-program.md +1 -1
  12. package/docs/design/docs-reorg-plan.md +1 -1
  13. package/docs/design/dspark-serving-program.md +1 -1
  14. package/docs/design/dspark-speculative-decoding.md +53 -3
  15. package/docs/design/grammar-spec-batching-integration.md +1 -1
  16. package/docs/design/mlx-lm-serving-execution-seam.md +81 -0
  17. package/docs/design/orpo-dynamic-lambda.md +1 -1
  18. package/docs/design/orpo-future-enhancements.md +1 -1
  19. package/docs/design/orpo-training.md +2 -2
  20. package/docs/design/parity-tier-dag.md +1 -1
  21. package/docs/design/repo-cleanup-plan.md +46 -4
  22. package/docs/design/segmented-backward-training.md +2 -2
  23. package/docs/design/ssd-kv-cold-tier.md +127 -0
  24. package/docs/design/turboquant-weights.md +467 -0
  25. package/docs/design/w4a16-compute-precision-spike.md +1 -1
  26. package/docs/design/web-chat-redesign.md +5 -5
  27. package/docs/planning/chunk-task-roadmap.md +1 -1
  28. package/docs/planning/curve-sampler-research-plan.md +4 -4
  29. package/docs/planning/memory-docs-and-dag-plan.md +1 -1
  30. package/docs/planning/release-notes-v0.2.0.md +49 -0
  31. package/docs/planning/video-series.md +421 -0
  32. package/docs/reference/cli.md +15 -4
  33. package/docs/reference/distribution.md +1 -0
  34. package/docs/reference/features-matrix.md +3 -2
  35. package/docs/reference/library-api.md +41 -1
  36. package/docs/reference/models.md +33 -0
  37. package/docs/reference/server-api.md +69 -14
  38. package/docs/reference/server-config.md +57 -33
  39. package/package.json +2 -2
  40. package/src/anthropic.ts +92 -3
  41. package/src/cli.ts +131 -36
  42. package/src/eval/kl.ts +27 -20
  43. package/src/eval/runner.ts +23 -4
  44. package/src/eval/tasks/bfcl.ts +2 -1
  45. package/src/eval/tasks/gsm8k.ts +2 -1
  46. package/src/eval/tasks/hashhop.ts +2 -1
  47. package/src/eval/tasks/humaneval.ts +5 -4
  48. package/src/eval/tasks/ifeval.ts +2 -1
  49. package/src/eval/tasks/mmlu.ts +2 -1
  50. package/src/expert-io.ts +3 -1
  51. package/src/expert-offload.ts +3 -1
  52. package/src/expert-trace.ts +3 -1
  53. package/src/fit.ts +24 -43
  54. package/src/flags.ts +7 -9
  55. package/src/generate.ts +107 -92
  56. package/src/grammar.ts +2 -1
  57. package/src/harness-pi.ts +1 -1
  58. package/src/index.ts +20 -0
  59. package/src/jobs/job-entry.ts +3 -2
  60. package/src/kv-scheme.ts +199 -0
  61. package/src/kv-store.ts +370 -205
  62. package/src/media-fetch.ts +19 -3
  63. package/src/memory/model.ts +2 -1
  64. package/src/memory/vault.ts +2 -1
  65. package/src/memory-plan.ts +18 -0
  66. package/src/mlx/ffi.ts +8 -11
  67. package/src/mlx/ops.ts +31 -6
  68. package/src/model/batched-mask.ts +1 -1
  69. package/src/model/batched-quant.ts +1 -1
  70. package/src/model/batched-rotating-quant.ts +43 -97
  71. package/src/model/batched-rotating-state.ts +73 -0
  72. package/src/model/batched-rotating.ts +56 -105
  73. package/src/model/batched-row-storage.ts +127 -0
  74. package/src/model/compiled-decode.ts +3 -2
  75. package/src/model/factory.ts +44 -49
  76. package/src/model/gemma4-base.ts +163 -10
  77. package/src/model/generated/gemma4-12b.ts +50 -146
  78. package/src/model/generated/gemma4-26b.ts +32 -92
  79. package/src/model/generated/gemma4-e4b.ts +26 -74
  80. package/src/model/glm52-cache.ts +19 -9
  81. package/src/model/glm52-memory.ts +28 -1
  82. package/src/model/minicpm5.ts +2 -1
  83. package/src/model/profile.ts +433 -0
  84. package/src/model/qwen3-delta.ts +98 -2
  85. package/src/model/qwen3-mrope.ts +252 -0
  86. package/src/model/qwen3_5.ts +217 -25
  87. package/src/native/frame_extract.swift +140 -0
  88. package/src/native-pack.ts +13 -6
  89. package/src/pi-web.ts +6 -5
  90. package/src/prompt-cache.ts +2 -2
  91. package/src/quantize/atomic-output.ts +38 -0
  92. package/src/quantize/calibration.ts +2 -1
  93. package/src/quantize/config-writer.ts +6 -0
  94. package/src/quantize/index.ts +28 -1
  95. package/src/quantize/job.ts +11 -0
  96. package/src/quantize/quantizer.ts +265 -100
  97. package/src/quantize/rotate.ts +603 -0
  98. package/src/quantize/safetensors-writer.ts +79 -1
  99. package/src/quantize/sensitivity.ts +41 -59
  100. package/src/quantize/weight-transform.ts +292 -0
  101. package/src/responses.ts +102 -2
  102. package/src/runtime-config.ts +67 -0
  103. package/src/sampler.ts +272 -0
  104. package/src/serve/admin-routes.ts +184 -0
  105. package/src/serve/aux-routes.ts +132 -0
  106. package/src/serve/batch-scheduler.ts +200 -87
  107. package/src/serve/completion-executor.ts +346 -0
  108. package/src/serve/completion-sink.ts +157 -0
  109. package/src/serve/discovery-routes.ts +205 -0
  110. package/src/serve/generation-gateway.ts +116 -109
  111. package/src/serve/kv-budget.ts +14 -0
  112. package/src/serve/lab-routes.ts +338 -0
  113. package/src/serve/model-admin-routes.ts +153 -0
  114. package/src/serve/prompt-response-trace.ts +147 -0
  115. package/src/serve/request-plan.ts +158 -0
  116. package/src/serve/static-routes.ts +101 -0
  117. package/src/server.ts +704 -1400
  118. package/src/spec/dflash-source.ts +2 -1
  119. package/src/spec/dspark/module-dflash.ts +1 -1
  120. package/src/spec/glm52-mtp-source.ts +8 -0
  121. package/src/spec/qwen-mtp-source.ts +431 -0
  122. package/src/spec/serve-loop.ts +36 -68
  123. package/src/ssd-cache.ts +17 -14
  124. package/src/ssd-durability.ts +195 -0
  125. package/src/train/flash-cce.ts +12 -11
  126. package/src/train/forward.ts +32 -1
  127. package/src/train/loss.ts +2 -1
  128. package/src/train/segmented.ts +4 -3
  129. package/src/train/trainer.ts +3 -2
  130. package/src/vision/preprocess.ts +1 -1
  131. package/src/vision/prompt.ts +47 -1
  132. package/src/vision/qwen3vl-preprocess.ts +409 -0
  133. package/src/vision/qwen3vl-prompt.ts +196 -0
  134. package/src/vision/qwen3vl-tower.ts +513 -0
  135. package/src/vision/video-frames.ts +148 -0
  136. package/src/web/app.js +10 -3
  137. package/src/web/src/chat.ts +12 -5
  138. package/src/weights.ts +81 -26
  139. /package/docs/{investigations → archive/investigations}/chunk-finetune-experiment.md +0 -0
  140. /package/docs/{investigations → archive/investigations}/colibri-oracle-pin.md +0 -0
  141. /package/docs/{investigations → archive/investigations}/curve-bisector-routing.md +0 -0
  142. /package/docs/{investigations → archive/investigations}/curve-runs/bisector-route.json +0 -0
  143. /package/docs/{investigations → archive/investigations}/curve-runs/bisector-trace.json +0 -0
  144. /package/docs/{investigations → archive/investigations}/curve-runs/curve-terrain.json +0 -0
  145. /package/docs/{investigations → archive/investigations}/curve-runs/curve-terrain.svg +0 -0
  146. /package/docs/{investigations → archive/investigations}/curve-terrain.html +0 -0
  147. /package/docs/{investigations → archive/investigations}/decode-roofline-lookagain.md +0 -0
  148. /package/docs/{investigations → archive/investigations}/dspark-handoff.md +0 -0
  149. /package/docs/{investigations → archive/investigations}/expert-offload-single-user-moe.md +0 -0
  150. /package/docs/{investigations → archive/investigations}/hlg-ab.html +0 -0
  151. /package/docs/{investigations → archive/investigations}/hlg-explorer.html +0 -0
  152. /package/docs/{investigations → archive/investigations}/hlg-figs/cliff.svg +0 -0
  153. /package/docs/{investigations → archive/investigations}/hlg-figs/cross.svg +0 -0
  154. /package/docs/{investigations → archive/investigations}/hlg-figs/edges.svg +0 -0
  155. /package/docs/{investigations → archive/investigations}/hlg-figs/k20-grids.svg +0 -0
  156. /package/docs/{investigations → archive/investigations}/hlg-figs/k20-peaks.svg +0 -0
  157. /package/docs/{investigations → archive/investigations}/hlg-figs/lw-curve.svg +0 -0
  158. /package/docs/{investigations → archive/investigations}/hlg-figs/terrain.svg +0 -0
  159. /package/docs/{investigations → archive/investigations}/hlg-report.html +0 -0
  160. /package/docs/{investigations → archive/investigations}/hlg-runs/hlg-ab.json +0 -0
  161. /package/docs/{investigations → archive/investigations}/hlg-runs/hlg-blind-prompts.md +0 -0
  162. /package/docs/{investigations → archive/investigations}/hlg-runs/hlg-blind.json +0 -0
  163. /package/docs/{investigations → archive/investigations}/hlg-runs/hlg-cliff.json +0 -0
  164. /package/docs/{investigations → archive/investigations}/hlg-runs/hlg-cross.json +0 -0
  165. /package/docs/{investigations → archive/investigations}/hlg-runs/hlg-edge.json +0 -0
  166. /package/docs/{investigations → archive/investigations}/hlg-runs/hlg-eval1.json +0 -0
  167. /package/docs/{investigations → archive/investigations}/hlg-runs/hlg-frontier.json +0 -0
  168. /package/docs/{investigations → archive/investigations}/hlg-runs/hlg-grid.json +0 -0
  169. /package/docs/{investigations → archive/investigations}/hlg-runs/hlg-grid2.json +0 -0
  170. /package/docs/{investigations → archive/investigations}/hlg-runs/hlg-lw-coarse.json +0 -0
  171. /package/docs/{investigations → archive/investigations}/hlg-runs/hlg-map.json +0 -0
  172. /package/docs/{investigations → archive/investigations}/hlg-runs/run0-original.log +0 -0
  173. /package/docs/{investigations → archive/investigations}/hlg-runs/runA-recommended.log +0 -0
  174. /package/docs/{investigations → archive/investigations}/hlg-runs/runAA-cliff-cpm5.log +0 -0
  175. /package/docs/{investigations → archive/investigations}/hlg-runs/runAB-grid2-e4b.log +0 -0
  176. /package/docs/{investigations → archive/investigations}/hlg-runs/runAC-peak-e4b-partial.log +0 -0
  177. /package/docs/{investigations → archive/investigations}/hlg-runs/runAD-ab-samples.log +0 -0
  178. /package/docs/{investigations → archive/investigations}/hlg-runs/runAE-edge-e4b.log +0 -0
  179. /package/docs/{investigations → archive/investigations}/hlg-runs/runAF-logit-calibration.log +0 -0
  180. /package/docs/{investigations → archive/investigations}/hlg-runs/runAG-eval-config2.log +0 -0
  181. /package/docs/{investigations → archive/investigations}/hlg-runs/runB-gain-variants.log +0 -0
  182. /package/docs/{investigations → archive/investigations}/hlg-runs/runC-pivots.log +0 -0
  183. /package/docs/{investigations → archive/investigations}/hlg-runs/runD-oetf.log +0 -0
  184. /package/docs/{investigations → archive/investigations}/hlg-runs/runE-eotf.log +0 -0
  185. /package/docs/{investigations → archive/investigations}/hlg-runs/runF-shaper-working.log +0 -0
  186. /package/docs/{investigations → archive/investigations}/hlg-runs/runG-shaper-sweep.log +0 -0
  187. /package/docs/{investigations → archive/investigations}/hlg-runs/runH-wsweep.log +0 -0
  188. /package/docs/{investigations → archive/investigations}/hlg-runs/runI-asweep.log +0 -0
  189. /package/docs/{investigations → archive/investigations}/hlg-runs/runJ-knobmap.log +0 -0
  190. /package/docs/{investigations → archive/investigations}/hlg-runs/runK-frontier.log +0 -0
  191. /package/docs/{investigations → archive/investigations}/hlg-runs/runL-widemap.log +0 -0
  192. /package/docs/{investigations → archive/investigations}/hlg-runs/runM-frontier-emb.log +0 -0
  193. /package/docs/{investigations → archive/investigations}/hlg-runs/runN-verify.log +0 -0
  194. /package/docs/{investigations → archive/investigations}/hlg-runs/runO-frontier-seed5000.log +0 -0
  195. /package/docs/{investigations → archive/investigations}/hlg-runs/runP-grid-AxSm.log +0 -0
  196. /package/docs/{investigations → archive/investigations}/hlg-runs/runQ-grid-tgW.log +0 -0
  197. /package/docs/{investigations → archive/investigations}/hlg-runs/runR-blind-transcript.log +0 -0
  198. /package/docs/{investigations → archive/investigations}/hlg-runs/runS-cross-coherence.log +0 -0
  199. /package/docs/{investigations → archive/investigations}/hlg-runs/runT-cross-cpm5.log +0 -0
  200. /package/docs/{investigations → archive/investigations}/hlg-runs/runU-cross-12b.log +0 -0
  201. /package/docs/{investigations → archive/investigations}/hlg-runs/runV-lw-e4b.log +0 -0
  202. /package/docs/{investigations → archive/investigations}/hlg-runs/runW-lw-12b.log +0 -0
  203. /package/docs/{investigations → archive/investigations}/hlg-runs/runX-lw-cpm5.log +0 -0
  204. /package/docs/{investigations → archive/investigations}/hlg-runs/runY-cliff-e4b.log +0 -0
  205. /package/docs/{investigations → archive/investigations}/hlg-runs/runZ-cliff-12b.log +0 -0
  206. /package/docs/{investigations → archive/investigations}/indexshare-performance-spike.md +0 -0
  207. /package/docs/{investigations → archive/investigations}/kernel-perf-review-2026-07.md +0 -0
  208. /package/docs/{investigations → archive/investigations}/lab-build-journal.md +0 -0
  209. /package/docs/{investigations → archive/investigations}/openwiki-evaluation.md +0 -0
  210. /package/docs/{investigations → archive/investigations}/orpo-base-uf-experiment-and-directions.md +0 -0
  211. /package/docs/{investigations → archive/investigations}/orpo-flash-cce-pin-leak.md +0 -0
  212. /package/docs/{investigations → archive/investigations}/orpo-uf-testing-handoff.md +0 -0
  213. /package/docs/{investigations → archive/investigations}/pi-builtin-investigation.md +0 -0
  214. /package/docs/{investigations → archive/investigations}/segmented-backward-handoff.md +0 -0
  215. /package/docs/{investigations → archive/investigations}/starter-model-port-handoff.md +0 -0
  216. /package/docs/{investigations → archive/investigations}/steel-flash-cce-handoff.md +0 -0
  217. /package/docs/{investigations → archive/investigations}/trainer-validation-experiment.md +0 -0
  218. /package/docs/{planning → archive/planning}/chat-ui-vision-memory-first.md +0 -0
  219. /package/docs/{planning → archive/planning}/journal.md +0 -0
  220. /package/docs/{planning → archive/planning}/release-notes-v0.0.10.md +0 -0
  221. /package/docs/{planning → archive/planning}/release-notes-v0.0.11.md +0 -0
  222. /package/docs/{planning → archive/planning}/release-notes-v0.0.12.md +0 -0
  223. /package/docs/{planning → archive/planning}/release-notes-v0.0.13.md +0 -0
  224. /package/docs/{planning → archive/planning}/web-ui-pass-plan.md +0 -0
@@ -45,7 +45,7 @@ check). Untracked ≠ deleted ≠ unrunnable.
45
45
  + the two docs that reference `repro/` (grep first: `grep -rn "repro/"
46
46
  docs/ *.md`).
47
47
  - Also updated every path reference (CLAUDE.md, PLAN.md, PLAN-archive.md,
48
- docs/investigations/lab-build-journal.md, docs/design/docs-reorg-plan.md,
48
+ docs/archive/investigations/lab-build-journal.md, docs/design/docs-reorg-plan.md,
49
49
  and the `repro/`-pointing comments in src/mlx/{ffi,array,autograd,ops}.ts
50
50
  + tests/{ffi-jit,train-autograd}.test.ts + the repro's own ISSUE.md).
51
51
  - `[x]` **A2. root strays** (all untracked; no git surgery): delete
@@ -168,12 +168,12 @@ suspenders until both machines are re-cloned.
168
168
  threads). The closed `oMLX` phase (2026-07-02) and Phase 19 are
169
169
  *newer* than 17, so they stay in PLAN.md as recent history per the
170
170
  criterion. Revisit D1 when a <17 phase is confirmed closed.
171
- - `[x]` **D2.** `docs/investigations/curve-runs/` + two untracked HTML
171
+ - `[x]` **D2.** `docs/archive/investigations/curve-runs/` + two untracked HTML
172
172
  strays in docs/investigations: gitignore or move under `reports/`.
173
- - Already satisfied: `docs/investigations/curve-runs/` is gitignored
173
+ - Already satisfied: `docs/archive/investigations/curve-runs/` is gitignored
174
174
  (`.gitignore`), and the four HTML strays (`curve-terrain.html`,
175
175
  `hlg-ab.html`, `hlg-report.html`, `hlg-explorer.html`) are gitignored
176
- via `docs/investigations/*.html`. No action needed.
176
+ via `docs/archive/investigations/*.html`. No action needed.
177
177
  - `[x]` **D3.** The CLAUDE.md doc-map lists are hand-maintained; add a
178
178
  `scripts/check-hygiene.ts` assertion that every `docs/**/*.md` appears in
179
179
  the map (same guardrail spirit as B1).
@@ -187,3 +187,45 @@ A and D anytime (safe, agent-executable solo) — DONE 2026-07-02. B gate
187
187
  DONE 2026-07-02 (B2 adapter-untrack deferred to Josh — see B2 finding).
188
188
  C executed 2026-07-02 on Josh's explicit go — only remaining tail is the
189
189
  M4 Pro reset + deleting the backup tarball once both boxes are green.
190
+
191
+ ## Phase D — work-artifact hygiene + rules (2026-08-18) — DONE
192
+
193
+ Trigger: Josh — "this is a software project; we should not be adding all
194
+ these random work things into it." Compared against ml-explore/mlx-lm's
195
+ root (9 files, 4 dirs, zero work artifacts).
196
+
197
+ - **D1 root sweep**: six tracked `benchmarks-serve-2026-07-0*.md` dumps
198
+ git-rm'd (they leaked past a gitignore pattern that only covered
199
+ `benchmarks-h2h-*`); seven untracked root bench artifacts moved to
200
+ `reports/bench-archive/`; `tmp/` deleted. `.gitignore` now blocks
201
+ `/benchmarks-*`, `/bench-*`, `/tmp/`, `/*.log` at root.
202
+ - **D2 guardrail**: `scripts/check-hygiene.ts` gained a third gate —
203
+ tracked ROOT files must be on an explicit allowlist (16 meta files).
204
+ - **D3 CONTRIBUTING.md**: the rules (what-goes-where table; no dated
205
+ artifacts; models → `~/models`; docs lifecycle; PLAN/STATUS discipline;
206
+ external oracle venv is not a repo).
207
+ - **D4 docs archive**: all 20 `docs/investigations/` →
208
+ `docs/archive/investigations/` (investigations document finished work);
209
+ 5 release-notes + journal + 2 chat-ui visions + 2 pass-plans →
210
+ `docs/archive/planning/`; `hlg-sampling.md` (self-declared superseded) →
211
+ `docs/archive/`. All inbound links rewritten; docs-map gate green.
212
+ - **D5 journal trim (rule 4)**: STATUS.md 1648 → 106 lines (Colibri
213
+ gate-history + all "where we were" + the stale July ranked backlog →
214
+ PLAN-archive under "STATUS archive 2026-08-18"); PLAN.md 3815 → 2679
215
+ (8 closed `[x]` phase blocks → PLAN-archive with one-line pointers).
216
+ - Model artifacts moved out of the tree entirely (`~/models/`) earlier
217
+ the same day.
218
+
219
+ Remaining (curated, not mechanical — needs judgment per file):
220
+ - `docs/design/` still holds 46 docs; many describe LANDED work and
221
+ should archive under rule 3 (candidates: optimization_plan,
222
+ faithful-base superseded pair, diffusion/audio/omlx/dspark docs whose
223
+ phases closed). Do this per-doc with link fixes, not as a bulk move.
224
+ - `docs/planning/` leftovers (chunk-task-roadmap, curve-sampler-research-
225
+ plan, memory-docs-and-dag-plan, pi-integration-review) — keep-or-archive
226
+ per Josh.
227
+ - `scripts/experiments/` = 199 files; prune ones whose findings are fully
228
+ recorded in archived docs (keep regen/oracle scripts).
229
+ - `runs/` is 32 GB machine-local (Colibri evidence + quant lab arms) —
230
+ delete lab arms once the TQ/GPTQ program closes; Colibri evidence dirs
231
+ are cited from the archive and stay.
@@ -35,7 +35,7 @@ All committed-worthy, validated this session:
35
35
  Baseline 11.89/100 → fine-tuned **91.70/100** on lucien's 25-case chunk holdout
36
36
  (valid-JSON rate 12% → 100%). Full mlx-bun loop: train → save adapter (hot-swap
37
37
  format) → `AdapterManager.mount` → generate. Writeup:
38
- `docs/investigations/chunk-finetune-experiment.md`. Scripts: `scripts/chunk-eval.ts`,
38
+ `docs/archive/investigations/chunk-finetune-experiment.md`. Scripts: `scripts/chunk-eval.ts`,
39
39
  `scripts/chunk-finetune.ts`, `scripts/chunk-filter.ts`. Adapter:
40
40
  `~/.cache/mlx-bun-finetunes/minicpm5-chunk-final`.
41
41
 
@@ -69,7 +69,7 @@ active-after 8.66 + cache-after 6.61 = 15.27 > peak 14.90, impossible if peak we
69
69
  total). MLX holds freed buffers in a **cache** (not returned to OS) so RSS overstates
70
70
  live memory; use `activeMemory()` / `cacheMemory()` / `clearCache()` to separate
71
71
  them (`src/mlx/ffi.ts`). Caveat from the MoE expert-offload work
72
- (`docs/investigations/expert-offload-single-user-moe.md`): `clearCache()` does NOT
72
+ (`docs/archive/investigations/expert-offload-single-user-moe.md`): `clearCache()` does NOT
73
73
  reliably return device-buffer RAM to the OS — only the mmap clean-page path does.
74
74
 
75
75
  Facts established:
@@ -286,3 +286,130 @@ SIGINT/SIGTERM during (or within the debounce after) traffic loses queued
286
286
  write-behind entries; restart survival degrades to whatever already
287
287
  flushed. This is a best-effort cache tier; the cost of a lost entry is one
288
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
+ ```