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