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.
Files changed (433) hide show
  1. package/README.md +71 -588
  2. package/docs/README.md +94 -0
  3. package/docs/{design → archive/investigations}/audio-input-plan.md +4 -4
  4. package/docs/{design → archive/investigations}/diffusion-gemma-port.md +3 -3
  5. package/docs/{design → archive/investigations}/faithful-l1-consolidation.md +4 -4
  6. package/docs/{design → archive/investigations}/minicpm5-decode-megakernel.md +11 -11
  7. package/docs/archive/investigations/openjarvis-fit.md +198 -0
  8. package/docs/{design → archive/investigations}/optimization_plan.md +1 -1
  9. package/docs/{design → archive/investigations}/pre-colibri-stabilization.md +5 -5
  10. package/docs/archive/investigations/qwen38-27b-campaign-log.md +1092 -0
  11. package/docs/{design → archive/investigations}/the-dreaming-handoff.md +2 -2
  12. package/docs/archive/planning/release-notes-v0.3.0.md +35 -0
  13. package/docs/design/batching.md +1841 -0
  14. package/docs/design/decode-speed-program.md +2346 -169
  15. package/docs/design/dreaming-nightly-pipeline.md +675 -282
  16. package/docs/design/generic-model-support.md +655 -334
  17. package/docs/design/kv-cache.md +824 -0
  18. package/docs/design/orpo-training.md +673 -1239
  19. package/docs/design/speculative-decoding.md +1990 -0
  20. package/docs/design/turboquant.md +2212 -0
  21. package/docs/design/unified-engine-frontier-plan.md +1833 -727
  22. package/docs/design/web-chat-redesign.md +666 -1083
  23. package/docs/planning/PRODUCT_ROADMAP.md +50 -1
  24. package/docs/planning/release-notes-v0.4.0.md +45 -0
  25. package/docs/planning/video-series.md +2 -2
  26. package/docs/reference/benchmarks.md +3613 -0
  27. package/docs/reference/cli.md +434 -160
  28. package/docs/reference/distribution.md +328 -182
  29. package/docs/reference/environment.md +398 -0
  30. package/docs/reference/glossary.md +39 -0
  31. package/docs/reference/library-api.md +566 -168
  32. package/docs/reference/memory.md +227 -64
  33. package/docs/reference/models.md +113 -6
  34. package/docs/reference/server-api.md +944 -429
  35. package/docs/reference/server-config.md +740 -338
  36. package/docs/reference/training.md +393 -254
  37. package/docs/reference/troubleshooting.md +158 -0
  38. package/package.json +9 -4
  39. package/src/audio/decode.ts +2 -2
  40. package/src/audio/features.ts +2 -2
  41. package/src/backends/mlx/assistant-target.ts +46 -0
  42. package/src/backends/mlx/autoregressive-draft-rows.ts +120 -0
  43. package/src/backends/mlx/autoregressive.ts +108 -0
  44. package/src/backends/mlx/batch-group.ts +1790 -0
  45. package/src/backends/mlx/cache-attachment.ts +67 -0
  46. package/src/backends/mlx/cache-layout.ts +31 -0
  47. package/src/backends/mlx/checkpoint-state.ts +53 -0
  48. package/src/backends/mlx/continuation-persistence.ts +106 -0
  49. package/src/backends/mlx/continuation-request.ts +43 -0
  50. package/src/backends/mlx/continuation.ts +77 -0
  51. package/src/backends/mlx/diffusion.ts +39 -0
  52. package/src/backends/mlx/draft-target.ts +32 -0
  53. package/src/backends/mlx/evaluation.ts +105 -0
  54. package/src/backends/mlx/fill-append.ts +45 -0
  55. package/src/backends/mlx/full-kv-row-append.ts +32 -0
  56. package/src/backends/mlx/full-kv-row-donor.ts +54 -0
  57. package/src/backends/mlx/full-transitioning-kv-rows.ts +65 -0
  58. package/src/backends/mlx/gateway-binding.ts +130 -0
  59. package/src/backends/mlx/graph.ts +38 -0
  60. package/src/backends/mlx/kv-codec.ts +9 -0
  61. package/src/backends/mlx/kv-maintenance.ts +133 -0
  62. package/src/backends/mlx/kv-tensor-rows.ts +212 -0
  63. package/src/backends/mlx/memory-client.ts +189 -0
  64. package/src/backends/mlx/methods.ts +90 -0
  65. package/src/backends/mlx/model-prompt.ts +164 -0
  66. package/src/backends/mlx/model-serving.ts +79 -0
  67. package/src/backends/mlx/prefill-cohort.ts +89 -0
  68. package/src/backends/mlx/prefill-rows.ts +200 -0
  69. package/src/backends/mlx/prefill.ts +33 -0
  70. package/src/backends/mlx/projected-context-rows.ts +72 -0
  71. package/src/backends/mlx/request-policy.ts +20 -0
  72. package/src/backends/mlx/request-state-policy.ts +41 -0
  73. package/src/backends/mlx/rollback.ts +89 -0
  74. package/src/backends/mlx/row-sampling.ts +49 -0
  75. package/src/backends/mlx/serial-executor.ts +242 -0
  76. package/src/backends/mlx/speculative-group.ts +359 -0
  77. package/src/backends/mlx/speculative-round.ts +161 -0
  78. package/src/backends/mlx/speculative.ts +114 -0
  79. package/src/backends/mlx/state-rows.ts +104 -0
  80. package/src/backends/mlx/state-views.ts +46 -0
  81. package/src/backends/mlx/target-layout-capability.ts +35 -0
  82. package/src/backends/mlx/text-engine.ts +73 -0
  83. package/src/backends/mlx/transitioning-kv-rows.ts +165 -0
  84. package/src/cli.ts +66 -58
  85. package/src/client.ts +66 -0
  86. package/src/config.ts +21 -4
  87. package/src/contracts/completion.ts +16 -0
  88. package/src/contracts/execution.ts +60 -0
  89. package/src/contracts/generation.ts +103 -0
  90. package/src/contracts/host.ts +7 -0
  91. package/src/contracts/jobs.ts +66 -0
  92. package/src/contracts/pi-provider.ts +30 -0
  93. package/src/contracts/pi.ts +259 -0
  94. package/src/contracts/prefix-cache.ts +23 -0
  95. package/src/contracts/resources.ts +25 -0
  96. package/src/contracts/sampling.ts +23 -0
  97. package/src/contracts/scheduling.ts +45 -0
  98. package/src/contracts/tsconfig.json +14 -0
  99. package/src/diffusion/diffusion-generate.ts +218 -153
  100. package/src/embed.ts +1 -1
  101. package/src/engine/admission.ts +59 -0
  102. package/src/engine/cancellation.ts +38 -0
  103. package/src/engine/engine.ts +56 -0
  104. package/src/engine/execution-coordinator.ts +70 -0
  105. package/src/engine/execution-plan.ts +53 -0
  106. package/src/engine/resources.ts +53 -0
  107. package/src/engine/scheduler.ts +72 -0
  108. package/src/engine/session.ts +247 -0
  109. package/src/eval/README.md +44 -1
  110. package/src/eval/kl.ts +236 -3
  111. package/src/eval/reference-logits.ts +207 -0
  112. package/src/eval/runner.ts +8 -96
  113. package/src/fill/echo-index.ts +188 -0
  114. package/src/fill/fill-session.ts +452 -0
  115. package/src/fill/proposal.ts +68 -0
  116. package/src/fill/schema-rows.ts +506 -0
  117. package/src/fill/tool-boundary.ts +103 -0
  118. package/src/fit.ts +30 -16
  119. package/src/generate.ts +709 -417
  120. package/src/grammar.ts +12 -7
  121. package/src/harness-pi.ts +14 -33
  122. package/src/hub-rest.ts +4 -4
  123. package/src/index.ts +7 -0
  124. package/src/inference/denoising.ts +17 -0
  125. package/src/inference/draft-acceptance.ts +41 -0
  126. package/src/inference/draft-output.ts +49 -0
  127. package/src/inference/graph.ts +33 -0
  128. package/src/inference/prefill.ts +39 -0
  129. package/src/inference/rollback.ts +7 -0
  130. package/src/jobs/db.ts +4 -0
  131. package/src/jobs/index.ts +2 -0
  132. package/src/jobs/job-entry.ts +12 -12
  133. package/src/jobs/runner.ts +98 -13
  134. package/src/jobs/task-client.ts +19 -0
  135. package/src/jobs/types.ts +2 -58
  136. package/src/kv-scheme.ts +70 -12
  137. package/src/kv-store.ts +177 -63
  138. package/src/lab/README.md +18 -0
  139. package/src/{assets → lab/curve}/curve-designer.html +2 -2
  140. package/src/{curve-sampler.ts → lab/curve/curve-sampler.ts} +2 -2
  141. package/src/{expert-trace.ts → lab/expert-trace/expert-trace.ts} +5 -5
  142. package/src/lab/paged-kv/paged-kv-rows.ts +83 -0
  143. package/src/{model → lab/paged-kv}/paged-kv.ts +32 -15
  144. package/src/library.ts +40 -0
  145. package/src/lora.ts +18 -0
  146. package/src/media-fetch.ts +1 -1
  147. package/src/memory/article.ts +1 -1
  148. package/src/memory/chunk.ts +1 -1
  149. package/src/memory/model.ts +30 -169
  150. package/src/memory/prompts.ts +1 -1
  151. package/src/memory/rest.ts +1 -1
  152. package/src/memory/schedule.ts +1 -1
  153. package/src/memory/stages.ts +4 -1
  154. package/src/memory/vault.ts +1 -3
  155. package/src/mlx/array.ts +11 -11
  156. package/src/mlx/compile.ts +12 -2
  157. package/src/mlx/ffi.ts +58 -11
  158. package/src/mlx/materialize.ts +23 -0
  159. package/src/mlx/metal-kernel.ts +10 -11
  160. package/src/mlx/ops.ts +33 -13
  161. package/src/mlx/quantized-rows.ts +36 -0
  162. package/src/mlx/token-bitmask.ts +52 -0
  163. package/src/mlx/turboquant-kv-decode.ts +82 -0
  164. package/src/mlx/turboquant-kv-inverse.ts +122 -0
  165. package/src/mlx/turboquant-ops.ts +33 -3
  166. package/src/mlx/turboquant-tables.ts +2 -2
  167. package/src/model/artifact-identity.ts +73 -0
  168. package/src/model/batched-kv.ts +235 -0
  169. package/src/model/batched-mask.ts +20 -6
  170. package/src/model/batched-quant.ts +9 -3
  171. package/src/model/batched-quantized-kv.ts +144 -0
  172. package/src/model/batched-rotating-quant.ts +78 -17
  173. package/src/model/batched-rotating-state.ts +76 -0
  174. package/src/model/batched-rotating.ts +65 -23
  175. package/src/model/batched-row-storage.ts +52 -0
  176. package/src/model/batched-ssm.ts +52 -0
  177. package/src/model/batched-turboquant-kv.ts +93 -0
  178. package/src/model/compiled-decode.ts +8 -2
  179. package/src/model/decoded-kv-donor.ts +12 -0
  180. package/src/model/delayed-quantized-kv.ts +72 -0
  181. package/src/model/delayed-rotating-quantized-kv.ts +93 -0
  182. package/src/model/delayed-turboquant-kv.ts +55 -0
  183. package/src/model/diffusion-gemma.ts +8 -8
  184. package/src/model/factory.ts +76 -33
  185. package/src/model/fingerprint.ts +1 -1
  186. package/src/model/flash-attention.ts +3 -3
  187. package/src/model/full-prefill-padding.ts +56 -0
  188. package/src/model/full-prefill-row.ts +105 -0
  189. package/src/model/gemma4-base.ts +179 -80
  190. package/src/model/gemma4.ts +40 -32
  191. package/src/model/generated/gemma4-12b.ts +6 -3
  192. package/src/model/generated/gemma4-26b.ts +6 -3
  193. package/src/model/generated/gemma4-e4b.ts +6 -3
  194. package/src/model/glm52-cache.ts +80 -4
  195. package/src/model/glm52-dsa.ts +13 -9
  196. package/src/model/glm52-memory.ts +1 -1
  197. package/src/model/glm52-mla.ts +12 -6
  198. package/src/model/glm52.ts +19 -40
  199. package/src/model/implementation.ts +69 -0
  200. package/src/model/kv-attention-view.ts +67 -0
  201. package/src/model/minicpm5.ts +17 -15
  202. package/src/model/profile.ts +12 -2
  203. package/src/model/qwen-append.ts +12 -0
  204. package/src/model/qwen-conv.ts +52 -0
  205. package/src/model/qwen3-delta-state.ts +74 -0
  206. package/src/model/qwen3-delta.ts +59 -36
  207. package/src/model/qwen3_5-checkpoint.ts +164 -0
  208. package/src/model/qwen3_5.ts +202 -78
  209. package/src/model/rotating-kv-donor.ts +35 -0
  210. package/src/model/rotating-kv-layout.ts +262 -0
  211. package/src/model/rotating-row-transaction.ts +35 -0
  212. package/src/model/speculative-rotating-kv.ts +101 -0
  213. package/src/model/ssm-prefill-padding.ts +39 -0
  214. package/src/model/support.ts +1 -1
  215. package/src/model/trellis-balanced-scatter.ts +51 -0
  216. package/src/model/trellis-linear.ts +672 -0
  217. package/src/model/trellis-shared-m.ts +151 -0
  218. package/src/model/trellis-shared-scatter.ts +144 -0
  219. package/src/model/trellis-splitk-prefill.ts +158 -0
  220. package/src/model/trellis-tiled-prefill.ts +136 -0
  221. package/src/model/trellis-vector-expand.ts +62 -0
  222. package/src/model/trellis-wide-prefill.ts +94 -0
  223. package/src/model/turboquant-codec.ts +83 -0
  224. package/src/model/universal/dense.ts +15 -14
  225. package/src/model/universal/modules.ts +8 -1
  226. package/src/native-pack.ts +6 -5
  227. package/src/pi-provider.ts +6 -64
  228. package/src/pi-web.ts +8 -315
  229. package/src/preflight.ts +31 -15
  230. package/src/prompt-cache.ts +110 -69
  231. package/src/quantize/allocator.ts +1 -1
  232. package/src/quantize/config-writer.ts +10 -3
  233. package/src/quantize/rotate.ts +4 -4
  234. package/src/quantize/trellis.ts +586 -0
  235. package/src/responses.ts +28 -1
  236. package/src/runtime-config.ts +18 -4
  237. package/src/sampler.ts +40 -29
  238. package/src/serve/batch-scheduler.ts +8 -1671
  239. package/src/serve/chat-request.ts +391 -0
  240. package/src/serve/chat-stage.ts +195 -0
  241. package/src/serve/checkpoint-identity.ts +64 -0
  242. package/src/serve/completion-executor.ts +15 -7
  243. package/src/serve/discovery-routes.ts +12 -13
  244. package/src/serve/generation-gateway.ts +183 -213
  245. package/src/serve/http.ts +159 -0
  246. package/src/serve/inference-request.ts +79 -0
  247. package/src/serve/isolate.ts +297 -105
  248. package/src/serve/lab-routes.ts +3 -0
  249. package/src/serve/lane-registry.ts +2 -1
  250. package/src/serve/model-admin-routes.ts +7 -5
  251. package/src/serve/model-binding.ts +43 -0
  252. package/src/serve/model-host.ts +511 -0
  253. package/src/serve/openai-wire.ts +103 -0
  254. package/src/serve/parent-application.ts +56 -0
  255. package/src/serve/pipeline.ts +33 -0
  256. package/src/serve/preparation.ts +33 -0
  257. package/src/serve/request-plan.ts +23 -18
  258. package/src/serve/request-prep.ts +344 -0
  259. package/src/serve/response-lease.ts +24 -0
  260. package/src/serve/responses-client.ts +69 -0
  261. package/src/serve/session-completion-engine.ts +110 -0
  262. package/src/serve/session-search.ts +1 -1
  263. package/src/serve/static-routes.ts +5 -5
  264. package/src/serve/text-completion-stage.ts +104 -0
  265. package/src/serve/token-streams.ts +519 -0
  266. package/src/server.ts +335 -2711
  267. package/src/spec/assistant-rows.ts +92 -0
  268. package/src/spec/assistant-source.ts +31 -55
  269. package/src/spec/cache-identity.ts +22 -0
  270. package/src/spec/deepspec-rows.ts +29 -0
  271. package/src/spec/deepspec-source.ts +16 -14
  272. package/src/spec/dflash-source.ts +36 -12
  273. package/src/spec/drafter.ts +153 -142
  274. package/src/spec/dspark/ab-stats.ts +3 -3
  275. package/src/spec/dspark/calibration.ts +2 -2
  276. package/src/spec/dspark/deepspec-module.ts +97 -122
  277. package/src/spec/dspark/generate-dflash.ts +2 -2
  278. package/src/spec/dspark/generate.ts +2 -2
  279. package/src/spec/dspark/loader.ts +1 -1
  280. package/src/spec/dspark/module-dflash.ts +129 -172
  281. package/src/spec/dspark/module.ts +3 -3
  282. package/src/spec/dspark/quantize-drafter.ts +1 -1
  283. package/src/spec/generate.ts +1 -1
  284. package/src/spec/glm52-mtp-graph.ts +29 -0
  285. package/src/spec/glm52-mtp-rows.ts +132 -0
  286. package/src/spec/glm52-mtp-source.ts +54 -137
  287. package/src/spec/glm52-mtp-state.ts +33 -0
  288. package/src/spec/ngram-source.ts +93 -21
  289. package/src/spec/projected-draft-rows.ts +73 -0
  290. package/src/spec/qwen-mtp-module.ts +171 -0
  291. package/src/spec/qwen-mtp-rows.ts +219 -0
  292. package/src/spec/qwen-mtp-source.ts +147 -195
  293. package/src/spec/qwen-mtp-state.ts +30 -0
  294. package/src/spec/serve-loop.ts +201 -201
  295. package/src/spec/source.ts +110 -13
  296. package/src/spec/two-model-rows.ts +61 -0
  297. package/src/spec/two-model.ts +44 -102
  298. package/src/ssd-cache.ts +99 -19
  299. package/src/ssd-durability.ts +36 -20
  300. package/src/tool-call.ts +51 -14
  301. package/src/train/flash-cce.ts +2 -2
  302. package/src/train/forward.ts +6 -0
  303. package/src/train/loss.ts +1 -1
  304. package/src/train/prefix-shared.ts +5 -1
  305. package/src/train/segmented.ts +5 -3
  306. package/src/train/trainer.ts +6 -6
  307. package/src/vision/prompt.ts +1 -1
  308. package/src/vision/qwen3vl-tower.ts +52 -10
  309. package/src/web/app.html +1 -1
  310. package/src/web/app.js +62 -60
  311. package/src/web/src/adapters-panel.ts +3 -3
  312. package/src/web/src/assistant.ts +2 -36
  313. package/src/web/src/chat.ts +4 -4
  314. package/src/web/src/composer.ts +4 -4
  315. package/src/web/src/hub.ts +1 -1
  316. package/src/web/src/markdown.ts +0 -0
  317. package/src/web/src/memory-panel.ts +1 -1
  318. package/src/web/src/model-picker.ts +1 -1
  319. package/src/web/src/palette.ts +1 -1
  320. package/src/web/src/protocol.ts +3 -39
  321. package/src/web/src/sessions.ts +1 -1
  322. package/src/web/src/ui-catalog.ts +4 -18
  323. package/src/web-assets.ts +47 -0
  324. package/src/weights.ts +55 -9
  325. package/docs/archive/investigations/curve-runs/bisector-route.json +0 -98
  326. package/docs/archive/investigations/curve-runs/bisector-trace.json +0 -1393
  327. package/docs/archive/investigations/curve-runs/curve-terrain.json +0 -1179
  328. package/docs/archive/investigations/curve-runs/curve-terrain.svg +0 -42
  329. package/docs/archive/investigations/hlg-ab.html +0 -34
  330. package/docs/archive/investigations/hlg-explorer.html +0 -238
  331. package/docs/archive/investigations/hlg-figs/cliff.svg +0 -1
  332. package/docs/archive/investigations/hlg-figs/cross.svg +0 -1
  333. package/docs/archive/investigations/hlg-figs/edges.svg +0 -1
  334. package/docs/archive/investigations/hlg-figs/k20-grids.svg +0 -1
  335. package/docs/archive/investigations/hlg-figs/k20-peaks.svg +0 -1
  336. package/docs/archive/investigations/hlg-figs/lw-curve.svg +0 -1
  337. package/docs/archive/investigations/hlg-figs/terrain.svg +0 -1
  338. package/docs/archive/investigations/hlg-report.html +0 -262
  339. package/docs/archive/investigations/hlg-runs/hlg-ab.json +0 -149
  340. package/docs/archive/investigations/hlg-runs/hlg-blind-prompts.md +0 -87
  341. package/docs/archive/investigations/hlg-runs/hlg-blind.json +0 -107
  342. package/docs/archive/investigations/hlg-runs/hlg-cliff.json +0 -197
  343. package/docs/archive/investigations/hlg-runs/hlg-cross.json +0 -505
  344. package/docs/archive/investigations/hlg-runs/hlg-edge.json +0 -388
  345. package/docs/archive/investigations/hlg-runs/hlg-eval1.json +0 -25
  346. package/docs/archive/investigations/hlg-runs/hlg-frontier.json +0 -86
  347. package/docs/archive/investigations/hlg-runs/hlg-grid.json +0 -242
  348. package/docs/archive/investigations/hlg-runs/hlg-grid2.json +0 -343
  349. package/docs/archive/investigations/hlg-runs/hlg-lw-coarse.json +0 -110
  350. package/docs/archive/investigations/hlg-runs/hlg-map.json +0 -383
  351. package/docs/archive/investigations/hlg-runs/run0-original.log +0 -159
  352. package/docs/archive/investigations/hlg-runs/runA-recommended.log +0 -131
  353. package/docs/archive/investigations/hlg-runs/runAA-cliff-cpm5.log +0 -26
  354. package/docs/archive/investigations/hlg-runs/runAB-grid2-e4b.log +0 -18
  355. package/docs/archive/investigations/hlg-runs/runAC-peak-e4b-partial.log +0 -25
  356. package/docs/archive/investigations/hlg-runs/runAD-ab-samples.log +0 -8
  357. package/docs/archive/investigations/hlg-runs/runAE-edge-e4b.log +0 -51
  358. package/docs/archive/investigations/hlg-runs/runAF-logit-calibration.log +0 -10
  359. package/docs/archive/investigations/hlg-runs/runAG-eval-config2.log +0 -16
  360. package/docs/archive/investigations/hlg-runs/runB-gain-variants.log +0 -192
  361. package/docs/archive/investigations/hlg-runs/runC-pivots.log +0 -162
  362. package/docs/archive/investigations/hlg-runs/runD-oetf.log +0 -132
  363. package/docs/archive/investigations/hlg-runs/runE-eotf.log +0 -162
  364. package/docs/archive/investigations/hlg-runs/runF-shaper-working.log +0 -86
  365. package/docs/archive/investigations/hlg-runs/runG-shaper-sweep.log +0 -101
  366. package/docs/archive/investigations/hlg-runs/runH-wsweep.log +0 -176
  367. package/docs/archive/investigations/hlg-runs/runI-asweep.log +0 -176
  368. package/docs/archive/investigations/hlg-runs/runJ-knobmap.log +0 -49
  369. package/docs/archive/investigations/hlg-runs/runK-frontier.log +0 -19
  370. package/docs/archive/investigations/hlg-runs/runL-widemap.log +0 -65
  371. package/docs/archive/investigations/hlg-runs/runM-frontier-emb.log +0 -15
  372. package/docs/archive/investigations/hlg-runs/runN-verify.log +0 -116
  373. package/docs/archive/investigations/hlg-runs/runO-frontier-seed5000.log +0 -15
  374. package/docs/archive/investigations/hlg-runs/runP-grid-AxSm.log +0 -15
  375. package/docs/archive/investigations/hlg-runs/runQ-grid-tgW.log +0 -15
  376. package/docs/archive/investigations/hlg-runs/runR-blind-transcript.log +0 -79
  377. package/docs/archive/investigations/hlg-runs/runS-cross-coherence.log +0 -33
  378. package/docs/archive/investigations/hlg-runs/runT-cross-cpm5.log +0 -33
  379. package/docs/archive/investigations/hlg-runs/runU-cross-12b.log +0 -33
  380. package/docs/archive/investigations/hlg-runs/runV-lw-e4b.log +0 -22
  381. package/docs/archive/investigations/hlg-runs/runW-lw-12b.log +0 -22
  382. package/docs/archive/investigations/hlg-runs/runX-lw-cpm5.log +0 -22
  383. package/docs/archive/investigations/hlg-runs/runY-cliff-e4b.log +0 -26
  384. package/docs/archive/investigations/hlg-runs/runZ-cliff-12b.log +0 -26
  385. package/docs/archive/mlx-bun-lab-report.html +0 -275
  386. package/docs/archive/wwdc-mlx-bun.html +0 -848
  387. package/docs/design/adapters-end-to-end.md +0 -205
  388. package/docs/design/batching-perf-path.md +0 -255
  389. package/docs/design/batching-v2-plan.md +0 -529
  390. package/docs/design/bucketing-stage.md +0 -197
  391. package/docs/design/compat-cli-surface-design.md +0 -230
  392. package/docs/design/docs-reorg-plan.md +0 -165
  393. package/docs/design/dspark-serving-program.md +0 -371
  394. package/docs/design/dspark-speculative-decoding.md +0 -318
  395. package/docs/design/grammar-spec-batching-integration.md +0 -301
  396. package/docs/design/memory-inference-path.md +0 -422
  397. package/docs/design/memory-synthesis.md +0 -287
  398. package/docs/design/memory-system.md +0 -237
  399. package/docs/design/mlx-lm-serving-execution-seam.md +0 -81
  400. package/docs/design/mlx-lm-tool-parity-plan.md +0 -545
  401. package/docs/design/omlx-adoption-map.md +0 -98
  402. package/docs/design/orpo-dynamic-lambda.md +0 -220
  403. package/docs/design/orpo-future-enhancements.md +0 -340
  404. package/docs/design/paged-kv-cache.md +0 -214
  405. package/docs/design/parallel-slots.md +0 -567
  406. package/docs/design/parity-tier-dag.md +0 -161
  407. package/docs/design/repo-cleanup-plan.md +0 -231
  408. package/docs/design/runtime-isolation.md +0 -263
  409. package/docs/design/segmented-backward-training.md +0 -543
  410. package/docs/design/spec-decode-larger-targets.md +0 -229
  411. package/docs/design/ssd-kv-cold-tier.md +0 -415
  412. package/docs/design/structured-output.md +0 -484
  413. package/docs/design/tauri-desktop-app.md +0 -127
  414. package/docs/design/the-dreaming-master-plan.md +0 -445
  415. package/docs/design/turboquant-kv.md +0 -235
  416. package/docs/design/turboquant-weights.md +0 -467
  417. package/docs/design/w4a16-compute-precision-spike.md +0 -232
  418. package/docs/design/web-chat-beat-matrix.md +0 -315
  419. package/docs/design/write-pipeline-entity.md +0 -468
  420. package/docs/planning/IDEAS.md +0 -69
  421. package/docs/reference/embedding.md +0 -197
  422. package/docs/reference/features-matrix.md +0 -101
  423. package/docs/reference/orpo-quickstart.md +0 -135
  424. package/src/flags.ts +0 -11
  425. /package/docs/{design → archive/investigations}/colibri-glm52-port.md +0 -0
  426. /package/docs/{planning → archive/planning}/chunk-task-roadmap.md +0 -0
  427. /package/docs/{planning → archive/planning}/curve-sampler-research-plan.md +0 -0
  428. /package/docs/{planning → archive/planning}/memory-docs-and-dag-plan.md +0 -0
  429. /package/docs/{planning → archive/planning}/pi-integration-review.md +0 -0
  430. /package/docs/{planning → archive/planning}/release-notes-v0.2.0.md +0 -0
  431. /package/src/{memory-plan.ts → admission-plan.ts} +0 -0
  432. /package/{docs/archive/investigations → src/serve/assets}/curve-terrain.html +0 -0
  433. /package/{docs/dag → src/serve/assets}/training-inference-map.html +0 -0
package/src/kv-store.ts CHANGED
@@ -1,5 +1,7 @@
1
+ import { type CheckpointAttachment, attachmentBytes, disposeAttachments } from "./backends/mlx/checkpoint-state";
2
+ import { cleanupFailure, disposeResources } from "./engine/resources";
1
3
  // KV-cache persistence: save prompt caches to disk, reload by streamed copy.
2
- // The serialization core of the SSD cold tier (docs/design/ssd-kv-cold-tier.md).
4
+ // The serialization core of the SSD cold tier (docs/design/kv-cache.md).
3
5
  //
4
6
  // File layout (every tensor PAGE-ALIGNED — the Phase 1 corollary: files
5
7
  // we write can be mmap'd and handed to the GPU without copies):
@@ -85,6 +87,7 @@ interface TensorSlot {
85
87
  export interface CacheHeaderEntry {
86
88
  kind: CacheKind;
87
89
  offset: number;
90
+ minimumReusableOffset?: number;
88
91
  /** rotating variants: ring write index */
89
92
  idx?: number;
90
93
  /** rotating variants: window size */
@@ -113,6 +116,7 @@ export interface CacheHeaderEntry {
113
116
  }
114
117
 
115
118
  export interface KvSaveMeta {
119
+ attachments?: CheckpointAttachment[];
116
120
  modelId?: string;
117
121
  /** configFingerprint(config) — covers every graph-shaping field incl.
118
122
  * the kv-quant scheme, so a scheme flip invalidates naturally. */
@@ -121,10 +125,28 @@ export interface KvSaveMeta {
121
125
  ns?: string;
122
126
  /** sha256 of tokenizer.json: identical ids must mean identical text. */
123
127
  tokenizerHash?: string;
128
+ /** In-flight generation state. The serialized caches cover `tokens`;
129
+ * pendingToken was sampled from that state but has not been emitted. */
130
+ generationCheckpoint?: {
131
+ key: string;
132
+ cacheNs: string;
133
+ originalPromptTokens: number;
134
+ generatedTokens: number;
135
+ pendingToken: number;
136
+ /** Actual sampler seed, including a server-generated default. Required to
137
+ * continue the random stream exactly after a restart. */
138
+ seed: number;
139
+ /** True when `seed` was part of the client request rather than generated
140
+ * by the server. */
141
+ seedWasExplicit: boolean;
142
+ };
124
143
  }
125
144
 
126
- export interface KvFileHeader extends KvSaveMeta {
127
- formatVersion: 3;
145
+ export interface KvFileHeader extends Omit<KvSaveMeta, "attachments"> {
146
+ attachments?: Array<Omit<CheckpointAttachment, "tensors"> & { tensors: TensorSlot[] }>;
147
+ /** Absent in earlier v3 files, which used mlx-cache-v3. */
148
+ codecProvider?: string;
149
+ formatVersion: 3 | 4;
128
150
  createdAt: number;
129
151
  tokens: number[];
130
152
  caches: CacheHeaderEntry[];
@@ -143,7 +165,7 @@ const alignUp = (n: number) => Math.ceil(n / ALIGN) * ALIGN;
143
165
  * materialization until rawBytes at write time). */
144
166
  interface TensorSource { arr: MlxArray; disposeAfter: boolean }
145
167
 
146
- interface SnapshotContext {
168
+ export interface SnapshotContext {
147
169
  slots: TensorSlot[];
148
170
  push(a: MlxArray, disposeAfter: boolean): void;
149
171
  liveSlice(a: MlxArray, upTo: number): MlxArray;
@@ -151,21 +173,21 @@ interface SnapshotContext {
151
173
  pushTriple(t: ops.QuantizedTensor, upTo: number | null): void;
152
174
  }
153
175
 
154
- interface CloneContext {
176
+ export interface CloneContext {
155
177
  view(a: MlxArray): MlxArray;
156
178
  liveView(a: MlxArray, upTo: number): MlxArray;
157
179
  mlaView(a: MlxArray, upTo: number): MlxArray;
158
180
  tripleView(t: ops.QuantizedTensor, upTo: number | null): ops.QuantizedTensor;
159
181
  }
160
182
 
161
- interface LoadContext {
183
+ export interface LoadContext {
162
184
  path: string;
163
185
  arr(slot: TensorSlot): MlxArray;
164
186
  grownArr(slot: TensorSlot, offset: number): MlxArray;
165
187
  triple(slots: TensorSlot[], at: number): ops.QuantizedTensor;
166
188
  }
167
189
 
168
- interface CacheCodec {
190
+ export interface CacheCodec {
169
191
  matches(cache: Cache): boolean;
170
192
  snapshot(cache: Cache, context: SnapshotContext): CacheHeaderEntry;
171
193
  clone(cache: Cache, context: CloneContext): Cache;
@@ -255,8 +277,8 @@ const CACHE_CODECS = {
255
277
  },
256
278
  clone: (cache, context) => {
257
279
  const c = cache as KVCache;
258
- if (!c.keys || !c.values) throw new Error("cannot clone an empty cache");
259
280
  const clone = new KVCache();
281
+ if (!c.keys || !c.values) return clone;
260
282
  clone.restoreState(
261
283
  context.liveView(c.keys, c.offset),
262
284
  context.liveView(c.values, c.offset),
@@ -287,8 +309,8 @@ const CACHE_CODECS = {
287
309
  },
288
310
  clone: (cache, context) => {
289
311
  const c = cache as RotatingKVCache;
290
- if (!c.keys || !c.values) throw new Error("cannot clone an empty cache");
291
312
  const clone = new RotatingKVCache(c.maxSize);
313
+ if (!c.keys || !c.values) return clone;
292
314
  clone.restoreState(context.view(c.keys), context.view(c.values), c.offset, c.ringIdx);
293
315
  return clone;
294
316
  },
@@ -314,8 +336,8 @@ const CACHE_CODECS = {
314
336
  },
315
337
  clone: (cache, context) => {
316
338
  const c = cache as QuantizedKVCache;
317
- if (!c.keys || !c.values) throw new Error("cannot clone an empty cache");
318
339
  const clone = new QuantizedKVCache(c.groupSize, c.bits);
340
+ if (!c.keys || !c.values) return clone;
319
341
  clone.restoreState(
320
342
  context.tripleView(c.keys, c.offset),
321
343
  context.tripleView(c.values, c.offset),
@@ -345,8 +367,8 @@ const CACHE_CODECS = {
345
367
  },
346
368
  clone: (cache, context) => {
347
369
  const c = cache as RotatingQuantizedKVCache;
348
- if (!c.keys || !c.values) throw new Error("cannot clone an empty cache");
349
370
  const clone = new RotatingQuantizedKVCache(c.maxSize, c.groupSize, c.bits);
371
+ if (!c.keys || !c.values) return clone;
350
372
  clone.restoreState(
351
373
  context.tripleView(c.keys, null), context.tripleView(c.values, null),
352
374
  c.offset, c.ringIdx,
@@ -458,16 +480,61 @@ const CACHE_CODECS = {
458
480
  },
459
481
  } satisfies Record<CacheKind, CacheCodec>;
460
482
 
461
- const cacheCodec = (cache: Cache): CacheCodec => {
462
- const codec = Object.values(CACHE_CODECS).find((candidate) => candidate.matches(cache));
463
- if (!codec) throw new Error(`unsupported cache signature ${cacheSignature(cache)}`);
464
- return codec;
465
- };
483
+ /** Codecs belong to the loaded backend binding. The persisted ID is checked
484
+ * before allocating state; codecs are engine code, never loaded from a file. */
485
+ export interface CacheCodecProvider {
486
+ readonly id: string;
487
+ forCache(cache: Cache): CacheCodec;
488
+ forHeader(entry: CacheHeaderEntry): CacheCodec;
489
+ }
466
490
 
467
- export const cacheHeadersTrimmable = (caches: readonly CacheHeaderEntry[]): boolean =>
468
- caches.every((entry) => CACHE_CODECS[entry.kind].headerTrimmable(entry));
491
+ export function createCacheCodecProvider(
492
+ id: string, entries: Readonly<Partial<Record<CacheKind, CacheCodec>>>,
493
+ ): CacheCodecProvider {
494
+ if (!id.trim()) throw new Error("cache codec provider needs an identity");
495
+ const table = new Map(Object.entries(entries).map(([kind, codec]) => [kind, Object.freeze({
496
+ matches: codec.matches.bind(codec), snapshot: codec.snapshot.bind(codec), clone: codec.clone.bind(codec),
497
+ load: codec.load.bind(codec), headerTrimmable: codec.headerTrimmable.bind(codec),
498
+ })]));
499
+ const candidates = [...table.values()];
500
+ const selected = new WeakMap<Cache, CacheCodec>();
501
+ return Object.freeze({
502
+ id,
503
+ forCache(cache: Cache) {
504
+ const bound = selected.get(cache);
505
+ if (bound) return bound;
506
+ const matches = candidates.filter((codec) => codec.matches(cache));
507
+ if (matches.length !== 1)
508
+ throw new Error(`cache codec provider ${id}: ${matches.length} matches for ${cacheSignature(cache)}`);
509
+ selected.set(cache, matches[0]!);
510
+ return matches[0]!;
511
+ },
512
+ forHeader(entry: CacheHeaderEntry) {
513
+ const codec = table.get(entry.kind);
514
+ if (!codec) throw new Error(`cache codec provider ${id}: unsupported cache kind ${entry.kind}`);
515
+ return codec;
516
+ },
517
+ });
518
+ }
469
519
 
470
- function snapshotCache(c: Cache, dataOffset: number): { entry: CacheHeaderEntry; sources: TensorSource[]; next: number } {
520
+ export const legacyCacheCodecs = createCacheCodecProvider("mlx-cache-v3", CACHE_CODECS);
521
+
522
+ /** Old TQ files lack the conversion boundary. Keep them reusable at their
523
+ * stored offset, but never infer that an earlier precision state is valid. */
524
+ export function cacheHeaderMinimumReusableOffset(entry: CacheHeaderEntry): number {
525
+ const offset = entry.minimumReusableOffset ?? (entry.kind === "turboquant" ? entry.offset : 0);
526
+ if (!Number.isSafeInteger(offset) || offset < 0 || offset > entry.offset)
527
+ throw new Error("invalid cache minimum reusable offset");
528
+ return offset;
529
+ }
530
+ export const cacheHeadersMinimumReusableOffset = (caches: readonly CacheHeaderEntry[]): number =>
531
+ Math.max(0, ...caches.map(cacheHeaderMinimumReusableOffset));
532
+
533
+ export const cacheHeadersTrimmable = (
534
+ caches: readonly CacheHeaderEntry[], codecs: CacheCodecProvider = legacyCacheCodecs,
535
+ ): boolean => caches.every((entry) => codecs.forHeader(entry).headerTrimmable(entry));
536
+
537
+ function snapshotCache(c: Cache, dataOffset: number, codecs: CacheCodecProvider): { entry: CacheHeaderEntry; sources: TensorSource[]; next: number } {
471
538
  const slots: TensorSlot[] = [];
472
539
  const sources: TensorSource[] = [];
473
540
  let cursor = dataOffset;
@@ -493,10 +560,14 @@ function snapshotCache(c: Cache, dataOffset: number): { entry: CacheHeaderEntry;
493
560
  }
494
561
  };
495
562
 
496
- const entry = cacheCodec(c).snapshot(c, {
497
- slots, push, liveSlice, liveMlaSlice, pushTriple,
498
- });
499
- return { entry, sources, next: cursor };
563
+ try {
564
+ const entry = codecs.forCache(c).snapshot(c, { slots, push, liveSlice, liveMlaSlice, pushTriple });
565
+ if (c.minimumReusableOffset !== undefined) entry.minimumReusableOffset = c.minimumReusableOffset;
566
+ return { entry, sources, next: cursor };
567
+ } catch (error) {
568
+ for (const source of sources) if (source.disposeAfter) source.arr.dispose();
569
+ throw error;
570
+ }
500
571
  }
501
572
 
502
573
  /** Persist `caches` (+ the exact token prefix they encode) to `path`.
@@ -513,15 +584,17 @@ function snapshotCache(c: Cache, dataOffset: number): { entry: CacheHeaderEntry;
513
584
  * called while the caches hold EXACTLY the state to snapshot (post-wrap
514
585
  * rings cannot be rewound later). Same per-kind dispatch as
515
586
  * snapshotCache/loadKvCache. */
516
- export function cloneKvCaches(caches: Cache[]): Cache[] {
517
- const view = (a: MlxArray): MlxArray => a.slice(a.shape.map(() => 0), [...a.shape]);
587
+ export function cloneKvCaches(caches: Cache[], codecs: CacheCodecProvider = legacyCacheCodecs): Cache[] {
588
+ const pending: MlxArray[] = [];
589
+ const hold = (array: MlxArray): MlxArray => { pending.push(array); return array; };
590
+ const view = (a: MlxArray): MlxArray => hold(a.slice(a.shape.map(() => 0), [...a.shape]));
518
591
  const liveView = (a: MlxArray, upTo: number): MlxArray => {
519
592
  const [B, H, , D] = a.shape as [number, number, number, number];
520
- return a.slice([0, 0, 0, 0], [B, H, upTo, D]);
593
+ return hold(a.slice([0, 0, 0, 0], [B, H, upTo, D]));
521
594
  };
522
595
  const mlaView = (a: MlxArray, upTo: number): MlxArray => {
523
596
  const [B, , D] = a.shape as [number, number, number];
524
- return a.slice([0, 0, 0], [B, upTo, D]);
597
+ return hold(a.slice([0, 0, 0], [B, upTo, D]));
525
598
  };
526
599
  const tripleView = (t: ops.QuantizedTensor, upTo: number | null): ops.QuantizedTensor => ({
527
600
  packed: upTo === null ? view(t.packed) : liveView(t.packed, upTo),
@@ -531,11 +604,13 @@ export function cloneKvCaches(caches: Cache[]): Cache[] {
531
604
  const out: Cache[] = [];
532
605
  try {
533
606
  for (const c of caches) {
534
- out.push(cacheCodec(c).clone(c, { view, liveView, mlaView, tripleView }));
607
+ const clone = codecs.forCache(c).clone(c, { view, liveView, mlaView, tripleView });
608
+ if (c.minimumReusableOffset !== undefined) clone.minimumReusableOffset = c.minimumReusableOffset;
609
+ out.push(clone);
610
+ pending.length = 0; // transferred to the returned cache
535
611
  }
536
612
  } catch (err) {
537
- for (const c of out) c.dispose();
538
- throw err;
613
+ cleanupFailure(err, () => disposeResources([...pending, ...out]));
539
614
  }
540
615
  return out;
541
616
  }
@@ -545,23 +620,37 @@ export function cloneKvCaches(caches: Cache[]): Cache[] {
545
620
  * between yields so the EVENT LOOP KEEPS SERVING while a multi-hundred-MB
546
621
  * entry flushes (the write-behind persistence contract: durability never
547
622
  * blocks a request). try/finally still runs on early generator close. */
548
- function* saveKvCacheSteps(path: string, tokens: number[], caches: Cache[], meta: KvSaveMeta): Generator<void, void, void> {
623
+ function* saveKvCacheSteps(path: string, tokens: number[], caches: Cache[], meta: KvSaveMeta, codecs: CacheCodecProvider): Generator<void, void, void> {
549
624
  // Plan pass: header entries + lazy tensor sources, NO bytes materialized.
550
625
  const entries: CacheHeaderEntry[] = [];
551
626
  const sources: TensorSource[] = [];
552
627
  let dataOffset = 0; // relative; rebased after header is sized
553
628
  try {
554
629
  for (const c of caches) {
555
- const s = snapshotCache(c, dataOffset);
630
+ const s = snapshotCache(c, dataOffset, codecs);
556
631
  entries.push(s.entry);
557
632
  sources.push(...s.sources);
558
633
  dataOffset = s.next;
559
634
  }
560
635
 
636
+ const { attachments: liveAttachments, ...identity } = meta;
637
+ const attachments = liveAttachments?.map((attachment) => ({
638
+ schema: attachment.schema,
639
+ metadata: { ...attachment.metadata },
640
+ tensors: attachment.tensors.map((arr) => {
641
+ const slot: TensorSlot = { off: dataOffset, bytes: arr.nbytes,
642
+ shape: [...arr.shape], dtype: arr.dtype, hash: "0000000000000000" };
643
+ dataOffset = alignUp(dataOffset + slot.bytes);
644
+ sources.push({ arr, disposeAfter: false });
645
+ return slot;
646
+ }),
647
+ }));
648
+
561
649
  // Header size is final NOW: hashes are fixed-width placeholders that get
562
650
  // patched in place (same byte length) after the data pass computes them.
563
651
  const header: KvFileHeader = {
564
- formatVersion: 3, createdAt: Date.now(), ...meta, tokens, caches: entries,
652
+ formatVersion: attachments?.length ? 4 : 3, createdAt: Date.now(), ...identity,
653
+ ...(attachments?.length ? { attachments } : {}), codecProvider: codecs.id, tokens, caches: entries,
565
654
  };
566
655
  const headerLen = new TextEncoder().encode(JSON.stringify(header)).length;
567
656
  const dataStart = alignUp(PREFIX_LEN + headerLen);
@@ -582,7 +671,7 @@ function* saveKvCacheSteps(path: string, tokens: number[], caches: Cache[], meta
582
671
  // pages, which makes them VISIBLE to ps RSS — accounting, not an
583
672
  // allocation (see bench-serve.ts' per-leg RSS note).
584
673
  let srcIdx = 0;
585
- for (const e of entries) {
674
+ for (const e of [...entries, ...(attachments ?? [])]) {
586
675
  for (const slot of e.tensors) {
587
676
  const src = sources[srcIdx++]!;
588
677
  const c = ops.contiguous(src.arr);
@@ -626,38 +715,41 @@ function* saveKvCacheSteps(path: string, tokens: number[], caches: Cache[], meta
626
715
  }
627
716
  }
628
717
 
629
- export function saveKvCache(path: string, tokens: number[], caches: Cache[], meta: KvSaveMeta = {}): void {
630
- for (const _ of saveKvCacheSteps(path, tokens, caches, meta)) { /* drain */ }
718
+ export function saveKvCache(path: string, tokens: number[], caches: Cache[], meta: KvSaveMeta = {}, codecs: CacheCodecProvider = legacyCacheCodecs): void {
719
+ for (const _ of saveKvCacheSteps(path, tokens, caches, meta, codecs)) { /* drain */ }
631
720
  }
632
721
 
633
722
  /** Non-blocking variant: yields the event loop after every tensor write so
634
723
  * serving interleaves with the flush. Caller owns `caches` lifetime for
635
724
  * the duration (pass zero-copy clones, dispose after).
636
725
  *
637
- * `waitTurn` (optional) is awaited BEFORE every step — including the first —
638
- * so the caller can gate the flush's schedule (the server passes the
639
- * gateway's onIdle: each tensor step is a blocking GPU sync on the decode
640
- * stream + a synchronous multi-MB writeSync, and the old unconditional
641
- * setImmediate pacing interleaved those slices exactly between decode
642
- * tokens — the 2026-07-07 decode@ctx contamination. A request arriving
643
- * MID-flush pauses the remaining tensors until idle again). Gate failures
644
- * are swallowed: scheduling advice must never corrupt the write path
645
- * (an early generator close inside the fd-open section would leak the fd). */
726
+ * `runStep` (optional) runs each blocking tensor step inside the caller's
727
+ * exclusion domain. Waiting for an idle observation is not sufficient: a
728
+ * request can acquire the engine between that check and `steps.next()`,
729
+ * putting rawBytesView's GPU sync on the decode stream during prefill. */
646
730
  export async function saveKvCacheAsync(
647
731
  path: string, tokens: number[], caches: Cache[], meta: KvSaveMeta = {},
648
- waitTurn?: () => Promise<void>,
732
+ runStep?: <T>(step: () => T) => Promise<T>, codecs: CacheCodecProvider = legacyCacheCodecs,
649
733
  ): Promise<void> {
650
- const steps = saveKvCacheSteps(path, tokens, caches, meta);
651
- while (true) {
652
- if (waitTurn) await waitTurn().catch(() => {});
653
- if (steps.next().done) break;
654
- await new Promise<void>((r) => setImmediate(r));
734
+ const steps = saveKvCacheSteps(path, tokens, caches, meta, codecs);
735
+ try {
736
+ while (true) {
737
+ const next = runStep ? await runStep(() => steps.next()) : steps.next();
738
+ if (next.done) break;
739
+ await new Promise<void>((r) => setImmediate(r));
740
+ }
741
+ } catch (error) {
742
+ // A rejected execution lease can leave the writer suspended with an
743
+ // open fd. Throw through it so its existing failure cleanup runs.
744
+ steps.throw(error);
745
+ throw error;
655
746
  }
656
747
  }
657
748
 
658
749
  /** One pending write-behind item: an entry's copied tokens + OWNED
659
750
  * zero-copy cache clones (the queue disposes them on every exit path). */
660
751
  export interface SpillItem {
752
+ attachments?: CheckpointAttachment[];
661
753
  tokens: number[];
662
754
  caches: Cache[];
663
755
  ns: string;
@@ -666,7 +758,7 @@ export interface SpillItem {
666
758
  /** Bounded write-behind queue (2026-07-07 post-merge review fix).
667
759
  *
668
760
  * Pending spill/snapshot clones pin their entries' GPU buffers until the
669
- * idle-gated flush gets a turn; the old bare promise chain queued them
761
+ * generation-locked flush gets a turn; the old bare promise chain queued them
670
762
  * WITHOUT BOUND, so under sustained traffic (gate starved, evictions
671
763
  * ongoing) resident memory = prompt-cache cap + every queued clone —
672
764
  * allocator pressure exactly under the load that caused the evictions.
@@ -702,6 +794,8 @@ export class SpillQueue {
702
794
  readonly store: (item: SpillItem) => Promise<unknown>,
703
795
  /** Clone disposal — frees the pinned GPU memory. */
704
796
  readonly disposeClones: (caches: Cache[]) => void,
797
+ /** Zero-byte control barriers may need to survive capacity shedding. */
798
+ readonly canDrop: (item: SpillItem) => boolean = () => true,
705
799
  ) {}
706
800
 
707
801
  /** Bytes pinned by queued (not yet flushed) clones. */
@@ -717,7 +811,7 @@ export class SpillQueue {
717
811
  const result = new Promise<boolean>((resolve) => { settle = resolve; });
718
812
  const rec: SpillRec = {
719
813
  ...item,
720
- bytes: this.bytesOf(item.caches),
814
+ bytes: this.bytesOf(item.caches) + attachmentBytes(item.attachments),
721
815
  dropped: false,
722
816
  settle,
723
817
  };
@@ -725,7 +819,7 @@ export class SpillQueue {
725
819
  this.#bytes += rec.bytes;
726
820
  while (this.#bytes > this.capBytes) {
727
821
  const victim = this.#queue.find(
728
- (p) => !p.dropped && p !== this.#inFlight && p !== rec,
822
+ (p) => !p.dropped && p !== this.#inFlight && p !== rec && this.canDrop(p),
729
823
  );
730
824
  if (!victim) break; // only the new item (or in-flight) left — soft cap
731
825
  this.#drop(victim);
@@ -744,12 +838,19 @@ export class SpillQueue {
744
838
  } finally {
745
839
  this.#inFlight = null;
746
840
  this.#remove(rec);
747
- this.disposeClones(rec.caches);
841
+ disposeResources([{ dispose: () => this.disposeClones(rec.caches) },
842
+ { dispose: () => disposeAttachments(rec.attachments) }]);
748
843
  }
749
844
  });
750
845
  return result;
751
846
  }
752
847
 
848
+ /** Cancel queued ownership, excluding the write already in flight. */
849
+ cancelWhere(matches: (item: SpillItem) => boolean): void {
850
+ for (const rec of [...this.#queue])
851
+ if (!rec.dropped && rec !== this.#inFlight && matches(rec)) this.#drop(rec);
852
+ }
853
+
753
854
  /** Settles when everything enqueued so far has flushed or dropped
754
855
  * (tests and graceful shutdown). */
755
856
  drain(): Promise<void> { return this.#chain; }
@@ -759,7 +860,8 @@ export class SpillQueue {
759
860
  this.#dropped++;
760
861
  rec.settle(false);
761
862
  this.#remove(rec);
762
- this.disposeClones(rec.caches);
863
+ disposeResources([{ dispose: () => this.disposeClones(rec.caches) },
864
+ { dispose: () => disposeAttachments(rec.attachments) }]);
763
865
  }
764
866
 
765
867
  #remove(rec: SpillRec): void {
@@ -789,7 +891,7 @@ export function readKvHeader(path: string): KvFileHeader & { dataStart: number }
789
891
  if (BigInt(Bun.hash(body)) !== expectHash)
790
892
  throw new Error(`${path}: header hash mismatch (truncated or corrupt)`);
791
893
  const header = JSON.parse(new TextDecoder().decode(body)) as KvFileHeader;
792
- if (header.formatVersion !== 3)
894
+ if (header.formatVersion !== 3 && header.formatVersion !== 4)
793
895
  throw new Error(`${path}: unsupported formatVersion ${header.formatVersion}`);
794
896
  return { ...header, dataStart };
795
897
  } finally {
@@ -798,6 +900,7 @@ export function readKvHeader(path: string): KvFileHeader & { dataStart: number }
798
900
  }
799
901
 
800
902
  export interface LoadedKvCache {
903
+ attachments?: CheckpointAttachment[];
801
904
  tokens: number[];
802
905
  header: KvFileHeader;
803
906
  caches: Cache[];
@@ -904,9 +1007,12 @@ function withStepCapacity(a: MlxArray, offset: number): MlxArray {
904
1007
  export function loadKvCache(
905
1008
  path: string,
906
1009
  model: { makeCache(): Cache[] },
907
- expect: KvLoadExpect = {},
1010
+ expect: KvLoadExpect = {}, codecs: CacheCodecProvider = legacyCacheCodecs,
908
1011
  ): LoadedKvCache {
909
1012
  const header = readKvHeader(path);
1013
+ if ((header.codecProvider ?? legacyCacheCodecs.id) !== codecs.id)
1014
+ throw new Error(`${path}: cache codec provider mismatch`);
1015
+ for (const entry of header.caches) codecs.forHeader(entry);
910
1016
  for (const key of [
911
1017
  "modelId",
912
1018
  "configFingerprint",
@@ -964,22 +1070,30 @@ export function loadKvCache(
964
1070
  ({ packed: arr(slots[at]!), scales: arr(slots[at + 1]!), biases: arr(slots[at + 2]!) });
965
1071
 
966
1072
  const caches: Cache[] = [];
1073
+ const attachments: CheckpointAttachment[] = [];
967
1074
  try {
968
1075
  for (const e of header.caches) {
969
- const codec = CACHE_CODECS[e.kind];
970
- if (!codec)
971
- throw new Error(`${path}: unknown cache kind ${(e as { kind: string }).kind}`);
972
- caches.push(codec.load(e, { path, arr, grownArr, triple }));
1076
+ const codec = codecs.forHeader(e);
1077
+ const minimumOffset = cacheHeaderMinimumReusableOffset(e);
1078
+ const cache = codec.load(e, { path, arr, grownArr, triple });
1079
+ cache.minimumReusableOffset = minimumOffset;
1080
+ caches.push(cache);
973
1081
  // This entry's tensors are now owned by its cache — stop tracking them
974
1082
  // (the catch must not double-free through both pending AND caches).
975
1083
  pending.length = 0;
976
1084
  }
1085
+ for (const attachment of header.attachments ?? []) {
1086
+ attachments.push({ schema: attachment.schema, metadata: { ...attachment.metadata },
1087
+ tensors: attachment.tensors.map(arr) });
1088
+ pending.length = 0;
1089
+ }
977
1090
  } catch (err) {
1091
+ disposeAttachments(attachments);
978
1092
  for (const a of pending) a.dispose(); // the mid-entry orphans
979
1093
  for (const c of caches) c.dispose();
980
1094
  throw err;
981
1095
  } finally {
982
1096
  mmap.unmap(); // every tensor was copied out — nothing aliases the file
983
1097
  }
984
- return { tokens: header.tokens, header, caches };
1098
+ return { tokens: header.tokens, header, caches, ...(attachments.length ? { attachments } : {}) };
985
1099
  }
@@ -0,0 +1,18 @@
1
+ # src/lab — quarantined, no-oracle code
2
+
3
+ Everything here is a Lab-tier module (docs/design/unified-engine-frontier-plan.md):
4
+ no external oracle, off by default, kept only while a measurement decides its
5
+ fate (PLAN.md "Maintainability program", D6 darlings). Rules, gate-enforced by
6
+ `scripts/check-hygiene.ts` (check 11):
7
+
8
+ - nothing outside `src/lab/` may import from `src/lab/` except the edges listed
9
+ in `LAB_IMPORT_ALLOWLIST` — those are recorded debt, not permission;
10
+ - a new production dependency on a Lab module is a decision, not a default:
11
+ either promote the module out of the lab with an oracle/measurement, or
12
+ route it through an option seam.
13
+
14
+ | module | flag / entry | oracle |
15
+ |---|---|---|
16
+ | `curve/` (curve sampler + designer page) | `--curve-*`, `/curves` | none (Lab) |
17
+ | `paged-kv/` | `--paged-kv` (serial-only) | parity vs plain KVCache (bit-exact), no measured win |
18
+ | `expert-trace/` | `MLX_BUN_EXPERT_TRACE` hook in gemma4 | closed study (expert-offload) |
@@ -88,7 +88,7 @@ button.ghost{background:var(--card);border:1px solid var(--bd);border-radius:8px
88
88
 
89
89
  <script>
90
90
  const ln=Math.log;
91
- const UMIN=ln(1e-6); // bottom-left / plot floor (matches src/curve-sampler.ts CURVE_UMIN)
91
+ const UMIN=ln(1e-6); // bottom-left / plot floor (matches src/lab/curve/curve-sampler.ts CURVE_UMIN)
92
92
  const ENGINE_BASE = location.protocol.startsWith("http") ? location.origin : "http://localhost:8080";
93
93
  // ---- curve state: 5 movable control points, sorted ascending in p_in ----
94
94
  function identityPoints(){ return [{x_pct:1e-4,y_pct:1e-4},{x_pct:0.1,y_pct:0.1},{x_pct:1,y_pct:1},{x_pct:9,y_pct:9},{x_pct:100,y_pct:100}]; }
@@ -102,7 +102,7 @@ const PROMPTS=[
102
102
  "Describe the feeling of a city at 3am in one vivid sentence.",
103
103
  ];
104
104
 
105
- // ---- math: monotone cubic (PCHIP), IDENTICAL to src/curve-sampler.ts ----
105
+ // ---- math: monotone cubic (PCHIP), IDENTICAL to src/lab/curve/curve-sampler.ts ----
106
106
  const Lp=pt=>[ln(Math.max(pt.x_pct,1e-9)/100),ln(Math.max(pt.y_pct,1e-9)/100)];
107
107
  function knotArrays(c){ const k=c.points.map(Lp).sort((a,b)=>a[0]-b[0]); return {X:k.map(q=>q[0]),Y:k.map(q=>q[1])}; }
108
108
  function secants(c){ const {X,Y}=knotArrays(c),s=[]; for(let i=X.length-1;i>0;i--)s.push((Y[i]-Y[i-1])/(X[i]-X[i-1])); return s; }
@@ -16,8 +16,8 @@
16
16
  // BELOW lowers them (renorm makes the literal number an intent); the SLOPE across
17
17
  // a region sets how evenly the tokens inside it spread (flat<1 even, steep>1 separated).
18
18
 
19
- import { MlxArray } from "./mlx/array";
20
- import * as ops from "./mlx/ops";
19
+ import { MlxArray } from "../../mlx/array";
20
+ import * as ops from "../../mlx/ops";
21
21
 
22
22
  export interface CurvePoint { x_pct: number; y_pct: number; }
23
23
  export interface CurveParams {
@@ -2,13 +2,13 @@
2
2
  //
3
3
  // Records every MoE router decision as JSONL — one record per router call:
4
4
  // { c: callSeq, l: layerIdx, s: [B, L, k], i: [flattened uint32 expert ids] }
5
- // Analyse with scripts/analyze-expert-trace.ts (coverage curve, working-set
5
+ // Analyse with analyze-expert-trace.ts (deleted 2026-08-23; git history) (coverage curve, working-set
6
6
  // size, within-task stability, cross-task specialisation).
7
7
  //
8
8
  // Two ways to drive it:
9
9
  // - env: `MLX_BUN_EXPERT_TRACE=<path> mlx-bun serve ...` (auto-starts here)
10
10
  // - programmatic: beginExpertTrace(path) / endExpertTrace() per domain
11
- // (scripts/run-expert-trace.ts uses this to trace several domains from
11
+ // (run-expert-trace.ts (deleted 2026-08-23; git history) uses this to trace several domains from
12
12
  // one model load).
13
13
  //
14
14
  // Recording adds a per-call GPU->host sync (it reads the routed indices back),
@@ -17,9 +17,9 @@
17
17
  // Router.forward is a single branch (zero overhead) — inert by default, same
18
18
  // discipline as the slots flag.
19
19
 
20
- import { Dtype } from "./mlx/ffi";
21
- import type { MlxArray } from "./mlx/array";
22
- import { runtimeValue } from "./runtime-config";
20
+ import { Dtype } from "../../mlx/ffi";
21
+ import type { MlxArray } from "../../mlx/array";
22
+ import { runtimeValue } from "../../runtime-config";
23
23
 
24
24
  let sink: Bun.FileSink | null = null;
25
25
  let seq = 0;
@@ -0,0 +1,83 @@
1
+ import type { BatchableCache, Cache, Mask } from "../../model/gemma4-base";
2
+ import { buildBatchedDecodeMask, mergeKVRows } from "../../model/batched-mask";
3
+ import { MlxArray } from "../../mlx/array";
4
+ import { disposeResources } from "../../engine/resources";
5
+ import { PagedKVCache, poolBlocksFor } from "./paged-kv";
6
+
7
+ /** Block storage owns row membership; attention consumes the same gathered
8
+ * K/V representation as the existing paged cache. No scheduler policy here. */
9
+ export class PagedKvRows implements BatchableCache {
10
+ #rows: PagedKVCache[] = [];
11
+ #rope?: MlxArray;
12
+ #ropeOffset = -1;
13
+
14
+ constructor(readonly capacityTokens: number, readonly blockSize: number) {}
15
+ signature(): string { return "kv:paged-rows"; }
16
+ get batchSize(): number { return this.#rows.length; }
17
+ get offset(): number { return Math.max(0, ...this.#rows.map(row => row.offset)); }
18
+ get rowOffsets(): number[] { return this.#rows.map(row => row.offset); }
19
+ get leftPad(): number[] { const width = this.offset; return this.#rows.map(row => width - row.offset); }
20
+ get ropeOffsetArr(): MlxArray | undefined {
21
+ if (this.#rows.length < 2) return undefined;
22
+ if (!this.#rope || this.#ropeOffset !== this.offset) {
23
+ this.releaseRopeArr();
24
+ this.#rope = MlxArray.fromInt32(Int32Array.from(this.rowOffsets), [this.batchSize]);
25
+ this.#ropeOffset = this.offset;
26
+ }
27
+ return this.#rope;
28
+ }
29
+ releaseRopeArr(): void { this.#rope?.dispose(); this.#rope = undefined; this.#ropeOffset = -1; }
30
+ makeEmptyBatch(): PagedKvRows { return new PagedKvRows(this.capacityTokens, this.blockSize); }
31
+
32
+ mergeRows(rows: readonly Cache[]): void {
33
+ const next: PagedKVCache[] = [];
34
+ try {
35
+ for (const source of rows) {
36
+ if (source instanceof PagedKvRows) {
37
+ for (const row of source.#rows) next.push(row.clone());
38
+ } else if (source instanceof PagedKVCache) next.push(source.clone());
39
+ else throw new Error(`Paged rows cannot merge ${source.signature()}`);
40
+ }
41
+ } catch (error) { disposeResources(next); throw error; }
42
+ const previous = this.#rows; this.#rows = next;
43
+ this.releaseRopeArr(); disposeResources(previous);
44
+ }
45
+ extractRow(row: number): PagedKVCache { return this.#rows[row]!.clone(); }
46
+ filterRows(keep: readonly number[]): void {
47
+ const previous = this.#rows;
48
+ this.#rows = keep.map(row => previous[row]!);
49
+ const retained = new Set(keep);
50
+ this.releaseRopeArr();
51
+ disposeResources(previous.filter((_, row) => !retained.has(row)));
52
+ }
53
+
54
+ updateAndFetch(keys: MlxArray, values: MlxArray): [MlxArray, MlxArray] {
55
+ if (this.#rows.length === 1) return this.#rows[0]!.updateAndFetch(keys, values);
56
+ const fetched: Array<{ keys: MlxArray; values: MlxArray }> = [];
57
+ try {
58
+ for (const [row, storage] of this.#rows.entries()) {
59
+ using k = keys.slice([row, 0, 0, 0], [row + 1, ...keys.shape.slice(1)]);
60
+ using v = values.slice([row, 0, 0, 0], [row + 1, ...values.shape.slice(1)]);
61
+ const [nextK, nextV] = storage.updateAndFetch(k, v);
62
+ fetched.push({ keys: nextK, values: nextV });
63
+ }
64
+ const result = mergeKVRows(fetched);
65
+ return [result.keys, result.values];
66
+ } finally { disposeResources(fetched.flatMap(row => [row.keys, row.values])); }
67
+ }
68
+ makeMask(tokens: number, window: number | null): Mask {
69
+ if (this.#rows.length === 1) return this.#rows[0]!.makeMask(tokens, window);
70
+ return { mode: "array", arr: buildBatchedDecodeMask(this.batchSize, tokens,
71
+ this.offset + tokens, this.leftPad, window) };
72
+ }
73
+ state(): MlxArray[] { return this.#rows.flatMap(row => row.state()); }
74
+ isTrimmable(): boolean { return true; }
75
+ trim(tokens: number): void { for (const row of this.#rows) row.trim(tokens); this.releaseRopeArr(); }
76
+ projectedBytes(tokens: number): number {
77
+ const pool = this.#rows.find(row => row.pool)?.pool;
78
+ if (!pool) return 0;
79
+ const bytesPerBlock = (pool.keys.nbytes + pool.values.nbytes) / pool.numBlocks;
80
+ return poolBlocksFor(tokens, this.blockSize) * bytesPerBlock;
81
+ }
82
+ dispose(): void { const rows = this.#rows; this.#rows = []; this.releaseRopeArr(); disposeResources(rows); }
83
+ }