mlx-bun 0.2.0 → 0.3.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 (364) hide show
  1. package/README.md +71 -588
  2. package/docs/README.md +91 -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/{design → archive/investigations}/optimization_plan.md +1 -1
  8. package/docs/{design → archive/investigations}/pre-colibri-stabilization.md +5 -5
  9. package/docs/{design → archive/investigations}/the-dreaming-handoff.md +2 -2
  10. package/docs/design/batching.md +569 -0
  11. package/docs/design/decode-speed-program.md +3178 -169
  12. package/docs/design/dreaming-nightly-pipeline.md +675 -282
  13. package/docs/design/generic-model-support.md +579 -334
  14. package/docs/design/kv-cache.md +680 -0
  15. package/docs/design/orpo-training.md +673 -1239
  16. package/docs/design/speculative-decoding.md +1627 -0
  17. package/docs/design/turboquant.md +2102 -0
  18. package/docs/design/unified-engine-frontier-plan.md +1831 -727
  19. package/docs/design/web-chat-redesign.md +666 -1083
  20. package/docs/planning/PRODUCT_ROADMAP.md +34 -1
  21. package/docs/planning/release-notes-v0.3.0.md +35 -0
  22. package/docs/planning/video-series.md +2 -2
  23. package/docs/reference/benchmarks.md +1162 -0
  24. package/docs/reference/cli.md +394 -159
  25. package/docs/reference/distribution.md +321 -182
  26. package/docs/reference/environment.md +348 -0
  27. package/docs/reference/glossary.md +39 -0
  28. package/docs/reference/library-api.md +566 -168
  29. package/docs/reference/memory.md +227 -64
  30. package/docs/reference/models.md +104 -6
  31. package/docs/reference/server-api.md +886 -425
  32. package/docs/reference/server-config.md +638 -330
  33. package/docs/reference/training.md +393 -254
  34. package/docs/reference/troubleshooting.md +166 -0
  35. package/package.json +9 -4
  36. package/src/audio/decode.ts +2 -2
  37. package/src/audio/features.ts +2 -2
  38. package/src/backends/mlx/autoregressive.ts +108 -0
  39. package/src/backends/mlx/batch-group.ts +1646 -0
  40. package/src/backends/mlx/diffusion.ts +39 -0
  41. package/src/backends/mlx/draft-target.ts +47 -0
  42. package/src/backends/mlx/evaluation.ts +105 -0
  43. package/src/backends/mlx/fill-append.ts +45 -0
  44. package/src/backends/mlx/gateway-binding.ts +71 -0
  45. package/src/backends/mlx/graph.ts +38 -0
  46. package/src/backends/mlx/memory-client.ts +189 -0
  47. package/src/backends/mlx/memory-guard.ts +40 -0
  48. package/src/backends/mlx/methods.ts +90 -0
  49. package/src/backends/mlx/model-prompt.ts +164 -0
  50. package/src/backends/mlx/model-serving.ts +79 -0
  51. package/src/backends/mlx/prefill.ts +33 -0
  52. package/src/backends/mlx/request-policy.ts +20 -0
  53. package/src/backends/mlx/rollback.ts +51 -0
  54. package/src/backends/mlx/serial-executor.ts +273 -0
  55. package/src/backends/mlx/speculative.ts +114 -0
  56. package/src/backends/mlx/state-views.ts +26 -0
  57. package/src/backends/mlx/text-engine.ts +73 -0
  58. package/src/cli.ts +58 -25
  59. package/src/client.ts +66 -0
  60. package/src/config.ts +21 -4
  61. package/src/contracts/completion.ts +16 -0
  62. package/src/contracts/execution.ts +49 -0
  63. package/src/contracts/generation.ts +103 -0
  64. package/src/contracts/host.ts +7 -0
  65. package/src/contracts/jobs.ts +66 -0
  66. package/src/contracts/pi-provider.ts +30 -0
  67. package/src/contracts/pi.ts +259 -0
  68. package/src/contracts/resources.ts +18 -0
  69. package/src/contracts/scheduling.ts +27 -0
  70. package/src/contracts/tsconfig.json +14 -0
  71. package/src/diffusion/diffusion-generate.ts +218 -153
  72. package/src/embed.ts +1 -1
  73. package/src/engine/admission.ts +59 -0
  74. package/src/engine/cancellation.ts +38 -0
  75. package/src/engine/engine.ts +56 -0
  76. package/src/engine/execution-coordinator.ts +70 -0
  77. package/src/engine/execution-plan.ts +51 -0
  78. package/src/engine/resources.ts +42 -0
  79. package/src/engine/scheduler.ts +67 -0
  80. package/src/engine/session.ts +247 -0
  81. package/src/eval/README.md +44 -1
  82. package/src/eval/kl.ts +236 -3
  83. package/src/eval/reference-logits.ts +207 -0
  84. package/src/eval/runner.ts +8 -96
  85. package/src/fill/echo-index.ts +188 -0
  86. package/src/fill/fill-session.ts +452 -0
  87. package/src/fill/proposal.ts +68 -0
  88. package/src/fill/schema-rows.ts +506 -0
  89. package/src/fill/tool-boundary.ts +103 -0
  90. package/src/fit.ts +1 -1
  91. package/src/generate.ts +656 -268
  92. package/src/grammar.ts +12 -7
  93. package/src/harness-pi.ts +14 -33
  94. package/src/hub-rest.ts +4 -4
  95. package/src/index.ts +7 -0
  96. package/src/inference/denoising.ts +17 -0
  97. package/src/inference/graph.ts +33 -0
  98. package/src/inference/prefill.ts +39 -0
  99. package/src/inference/rollback.ts +7 -0
  100. package/src/jobs/db.ts +4 -0
  101. package/src/jobs/index.ts +2 -0
  102. package/src/jobs/job-entry.ts +12 -12
  103. package/src/jobs/runner.ts +98 -13
  104. package/src/jobs/task-client.ts +19 -0
  105. package/src/jobs/types.ts +2 -58
  106. package/src/kv-store.ts +109 -49
  107. package/src/lab/README.md +18 -0
  108. package/src/{assets → lab/curve}/curve-designer.html +2 -2
  109. package/src/{curve-sampler.ts → lab/curve/curve-sampler.ts} +2 -2
  110. package/src/{expert-trace.ts → lab/expert-trace/expert-trace.ts} +5 -5
  111. package/src/{model → lab/paged-kv}/paged-kv.ts +9 -6
  112. package/src/library.ts +40 -0
  113. package/src/lora.ts +18 -0
  114. package/src/media-fetch.ts +1 -1
  115. package/src/memory/article.ts +1 -1
  116. package/src/memory/chunk.ts +1 -1
  117. package/src/memory/model.ts +30 -169
  118. package/src/memory/prompts.ts +1 -1
  119. package/src/memory/rest.ts +1 -1
  120. package/src/memory/schedule.ts +1 -1
  121. package/src/memory/stages.ts +4 -1
  122. package/src/memory/vault.ts +1 -3
  123. package/src/mlx/array.ts +9 -10
  124. package/src/mlx/compile.ts +12 -2
  125. package/src/mlx/ffi.ts +58 -11
  126. package/src/mlx/materialize.ts +23 -0
  127. package/src/mlx/metal-kernel.ts +10 -11
  128. package/src/mlx/ops.ts +33 -13
  129. package/src/mlx/quantized-rows.ts +36 -0
  130. package/src/mlx/token-bitmask.ts +52 -0
  131. package/src/mlx/turboquant-kv-decode.ts +82 -0
  132. package/src/mlx/turboquant-kv-inverse.ts +122 -0
  133. package/src/mlx/turboquant-ops.ts +33 -3
  134. package/src/mlx/turboquant-tables.ts +2 -2
  135. package/src/model/batched-mask.ts +8 -2
  136. package/src/model/batched-quant.ts +1 -1
  137. package/src/model/batched-rotating-quant.ts +2 -2
  138. package/src/model/batched-rotating.ts +1 -1
  139. package/src/model/compiled-decode.ts +1 -1
  140. package/src/model/diffusion-gemma.ts +8 -8
  141. package/src/model/factory.ts +76 -33
  142. package/src/model/fingerprint.ts +1 -1
  143. package/src/model/flash-attention.ts +3 -3
  144. package/src/model/gemma4-base.ts +26 -9
  145. package/src/model/gemma4.ts +6 -6
  146. package/src/model/generated/gemma4-12b.ts +2 -2
  147. package/src/model/generated/gemma4-26b.ts +2 -2
  148. package/src/model/generated/gemma4-e4b.ts +2 -2
  149. package/src/model/glm52-dsa.ts +13 -9
  150. package/src/model/glm52-memory.ts +1 -1
  151. package/src/model/implementation.ts +69 -0
  152. package/src/model/minicpm5.ts +6 -8
  153. package/src/model/profile.ts +12 -2
  154. package/src/model/qwen-append.ts +12 -0
  155. package/src/model/qwen-conv.ts +52 -0
  156. package/src/model/qwen3_5-checkpoint.ts +164 -0
  157. package/src/model/qwen3_5.ts +140 -56
  158. package/src/model/support.ts +1 -1
  159. package/src/model/trellis-balanced-scatter.ts +51 -0
  160. package/src/model/trellis-linear.ts +672 -0
  161. package/src/model/trellis-shared-m.ts +151 -0
  162. package/src/model/trellis-shared-scatter.ts +144 -0
  163. package/src/model/trellis-splitk-prefill.ts +158 -0
  164. package/src/model/trellis-tiled-prefill.ts +136 -0
  165. package/src/model/trellis-vector-expand.ts +62 -0
  166. package/src/model/trellis-wide-prefill.ts +94 -0
  167. package/src/model/universal/dense.ts +1 -1
  168. package/src/model/universal/modules.ts +8 -1
  169. package/src/native-pack.ts +6 -5
  170. package/src/pi-provider.ts +6 -64
  171. package/src/pi-web.ts +8 -315
  172. package/src/prompt-cache.ts +96 -53
  173. package/src/quantize/allocator.ts +1 -1
  174. package/src/quantize/config-writer.ts +10 -3
  175. package/src/quantize/rotate.ts +4 -4
  176. package/src/quantize/trellis.ts +586 -0
  177. package/src/responses.ts +28 -1
  178. package/src/runtime-config.ts +18 -4
  179. package/src/sampler.ts +4 -4
  180. package/src/serve/batch-scheduler.ts +8 -1671
  181. package/src/serve/chat-request.ts +391 -0
  182. package/src/serve/chat-stage.ts +195 -0
  183. package/src/serve/checkpoint-identity.ts +64 -0
  184. package/src/serve/completion-executor.ts +15 -7
  185. package/src/serve/discovery-routes.ts +12 -13
  186. package/src/serve/generation-gateway.ts +154 -193
  187. package/src/serve/http.ts +159 -0
  188. package/src/serve/inference-request.ts +79 -0
  189. package/src/serve/isolate.ts +297 -105
  190. package/src/serve/lab-routes.ts +3 -0
  191. package/src/serve/lane-registry.ts +2 -1
  192. package/src/serve/model-admin-routes.ts +7 -5
  193. package/src/serve/model-binding.ts +43 -0
  194. package/src/serve/model-host.ts +512 -0
  195. package/src/serve/openai-wire.ts +103 -0
  196. package/src/serve/parent-application.ts +56 -0
  197. package/src/serve/pipeline.ts +33 -0
  198. package/src/serve/preparation.ts +33 -0
  199. package/src/serve/request-plan.ts +19 -15
  200. package/src/serve/request-prep.ts +346 -0
  201. package/src/serve/response-lease.ts +24 -0
  202. package/src/serve/responses-client.ts +69 -0
  203. package/src/serve/session-completion-engine.ts +110 -0
  204. package/src/serve/session-search.ts +1 -1
  205. package/src/serve/static-routes.ts +5 -5
  206. package/src/serve/text-completion-stage.ts +104 -0
  207. package/src/serve/token-streams.ts +519 -0
  208. package/src/server.ts +317 -2649
  209. package/src/spec/assistant-source.ts +10 -44
  210. package/src/spec/deepspec-source.ts +6 -6
  211. package/src/spec/dflash-source.ts +8 -8
  212. package/src/spec/dspark/ab-stats.ts +3 -3
  213. package/src/spec/dspark/calibration.ts +2 -2
  214. package/src/spec/dspark/deepspec-module.ts +2 -2
  215. package/src/spec/dspark/generate-dflash.ts +2 -2
  216. package/src/spec/dspark/generate.ts +2 -2
  217. package/src/spec/dspark/loader.ts +1 -1
  218. package/src/spec/dspark/module-dflash.ts +4 -3
  219. package/src/spec/dspark/module.ts +3 -3
  220. package/src/spec/dspark/quantize-drafter.ts +1 -1
  221. package/src/spec/generate.ts +1 -1
  222. package/src/spec/glm52-mtp-source.ts +1 -1
  223. package/src/spec/ngram-source.ts +1 -1
  224. package/src/spec/prefix-state.ts +45 -0
  225. package/src/spec/qwen-mtp-source.ts +166 -69
  226. package/src/spec/serve-loop.ts +154 -156
  227. package/src/spec/source.ts +42 -10
  228. package/src/spec/two-model.ts +1 -1
  229. package/src/ssd-cache.ts +80 -12
  230. package/src/ssd-durability.ts +25 -13
  231. package/src/tool-call.ts +51 -14
  232. package/src/train/flash-cce.ts +2 -2
  233. package/src/train/forward.ts +6 -0
  234. package/src/train/loss.ts +1 -1
  235. package/src/train/prefix-shared.ts +5 -1
  236. package/src/train/segmented.ts +5 -3
  237. package/src/train/trainer.ts +6 -6
  238. package/src/vision/prompt.ts +1 -1
  239. package/src/vision/qwen3vl-tower.ts +52 -10
  240. package/src/web/app.html +1 -1
  241. package/src/web/app.js +5 -3
  242. package/src/web/src/adapters-panel.ts +3 -3
  243. package/src/web/src/assistant.ts +2 -36
  244. package/src/web/src/chat.ts +4 -4
  245. package/src/web/src/composer.ts +4 -4
  246. package/src/web/src/hub.ts +1 -1
  247. package/src/web/src/markdown.ts +0 -0
  248. package/src/web/src/memory-panel.ts +1 -1
  249. package/src/web/src/model-picker.ts +1 -1
  250. package/src/web/src/palette.ts +1 -1
  251. package/src/web/src/protocol.ts +3 -39
  252. package/src/web/src/sessions.ts +1 -1
  253. package/src/web/src/ui-catalog.ts +4 -18
  254. package/src/web-assets.ts +47 -0
  255. package/src/weights.ts +55 -9
  256. package/docs/archive/investigations/curve-runs/bisector-route.json +0 -98
  257. package/docs/archive/investigations/curve-runs/bisector-trace.json +0 -1393
  258. package/docs/archive/investigations/curve-runs/curve-terrain.json +0 -1179
  259. package/docs/archive/investigations/curve-runs/curve-terrain.svg +0 -42
  260. package/docs/archive/investigations/hlg-ab.html +0 -34
  261. package/docs/archive/investigations/hlg-explorer.html +0 -238
  262. package/docs/archive/investigations/hlg-figs/cliff.svg +0 -1
  263. package/docs/archive/investigations/hlg-figs/cross.svg +0 -1
  264. package/docs/archive/investigations/hlg-figs/edges.svg +0 -1
  265. package/docs/archive/investigations/hlg-figs/k20-grids.svg +0 -1
  266. package/docs/archive/investigations/hlg-figs/k20-peaks.svg +0 -1
  267. package/docs/archive/investigations/hlg-figs/lw-curve.svg +0 -1
  268. package/docs/archive/investigations/hlg-figs/terrain.svg +0 -1
  269. package/docs/archive/investigations/hlg-report.html +0 -262
  270. package/docs/archive/investigations/hlg-runs/hlg-ab.json +0 -149
  271. package/docs/archive/investigations/hlg-runs/hlg-blind-prompts.md +0 -87
  272. package/docs/archive/investigations/hlg-runs/hlg-blind.json +0 -107
  273. package/docs/archive/investigations/hlg-runs/hlg-cliff.json +0 -197
  274. package/docs/archive/investigations/hlg-runs/hlg-cross.json +0 -505
  275. package/docs/archive/investigations/hlg-runs/hlg-edge.json +0 -388
  276. package/docs/archive/investigations/hlg-runs/hlg-eval1.json +0 -25
  277. package/docs/archive/investigations/hlg-runs/hlg-frontier.json +0 -86
  278. package/docs/archive/investigations/hlg-runs/hlg-grid.json +0 -242
  279. package/docs/archive/investigations/hlg-runs/hlg-grid2.json +0 -343
  280. package/docs/archive/investigations/hlg-runs/hlg-lw-coarse.json +0 -110
  281. package/docs/archive/investigations/hlg-runs/hlg-map.json +0 -383
  282. package/docs/archive/investigations/hlg-runs/run0-original.log +0 -159
  283. package/docs/archive/investigations/hlg-runs/runA-recommended.log +0 -131
  284. package/docs/archive/investigations/hlg-runs/runAA-cliff-cpm5.log +0 -26
  285. package/docs/archive/investigations/hlg-runs/runAB-grid2-e4b.log +0 -18
  286. package/docs/archive/investigations/hlg-runs/runAC-peak-e4b-partial.log +0 -25
  287. package/docs/archive/investigations/hlg-runs/runAD-ab-samples.log +0 -8
  288. package/docs/archive/investigations/hlg-runs/runAE-edge-e4b.log +0 -51
  289. package/docs/archive/investigations/hlg-runs/runAF-logit-calibration.log +0 -10
  290. package/docs/archive/investigations/hlg-runs/runAG-eval-config2.log +0 -16
  291. package/docs/archive/investigations/hlg-runs/runB-gain-variants.log +0 -192
  292. package/docs/archive/investigations/hlg-runs/runC-pivots.log +0 -162
  293. package/docs/archive/investigations/hlg-runs/runD-oetf.log +0 -132
  294. package/docs/archive/investigations/hlg-runs/runE-eotf.log +0 -162
  295. package/docs/archive/investigations/hlg-runs/runF-shaper-working.log +0 -86
  296. package/docs/archive/investigations/hlg-runs/runG-shaper-sweep.log +0 -101
  297. package/docs/archive/investigations/hlg-runs/runH-wsweep.log +0 -176
  298. package/docs/archive/investigations/hlg-runs/runI-asweep.log +0 -176
  299. package/docs/archive/investigations/hlg-runs/runJ-knobmap.log +0 -49
  300. package/docs/archive/investigations/hlg-runs/runK-frontier.log +0 -19
  301. package/docs/archive/investigations/hlg-runs/runL-widemap.log +0 -65
  302. package/docs/archive/investigations/hlg-runs/runM-frontier-emb.log +0 -15
  303. package/docs/archive/investigations/hlg-runs/runN-verify.log +0 -116
  304. package/docs/archive/investigations/hlg-runs/runO-frontier-seed5000.log +0 -15
  305. package/docs/archive/investigations/hlg-runs/runP-grid-AxSm.log +0 -15
  306. package/docs/archive/investigations/hlg-runs/runQ-grid-tgW.log +0 -15
  307. package/docs/archive/investigations/hlg-runs/runR-blind-transcript.log +0 -79
  308. package/docs/archive/investigations/hlg-runs/runS-cross-coherence.log +0 -33
  309. package/docs/archive/investigations/hlg-runs/runT-cross-cpm5.log +0 -33
  310. package/docs/archive/investigations/hlg-runs/runU-cross-12b.log +0 -33
  311. package/docs/archive/investigations/hlg-runs/runV-lw-e4b.log +0 -22
  312. package/docs/archive/investigations/hlg-runs/runW-lw-12b.log +0 -22
  313. package/docs/archive/investigations/hlg-runs/runX-lw-cpm5.log +0 -22
  314. package/docs/archive/investigations/hlg-runs/runY-cliff-e4b.log +0 -26
  315. package/docs/archive/investigations/hlg-runs/runZ-cliff-12b.log +0 -26
  316. package/docs/archive/mlx-bun-lab-report.html +0 -275
  317. package/docs/archive/wwdc-mlx-bun.html +0 -848
  318. package/docs/design/adapters-end-to-end.md +0 -205
  319. package/docs/design/batching-perf-path.md +0 -255
  320. package/docs/design/batching-v2-plan.md +0 -529
  321. package/docs/design/bucketing-stage.md +0 -197
  322. package/docs/design/compat-cli-surface-design.md +0 -230
  323. package/docs/design/docs-reorg-plan.md +0 -165
  324. package/docs/design/dspark-serving-program.md +0 -371
  325. package/docs/design/dspark-speculative-decoding.md +0 -318
  326. package/docs/design/grammar-spec-batching-integration.md +0 -301
  327. package/docs/design/memory-inference-path.md +0 -422
  328. package/docs/design/memory-synthesis.md +0 -287
  329. package/docs/design/memory-system.md +0 -237
  330. package/docs/design/mlx-lm-serving-execution-seam.md +0 -81
  331. package/docs/design/mlx-lm-tool-parity-plan.md +0 -545
  332. package/docs/design/omlx-adoption-map.md +0 -98
  333. package/docs/design/orpo-dynamic-lambda.md +0 -220
  334. package/docs/design/orpo-future-enhancements.md +0 -340
  335. package/docs/design/paged-kv-cache.md +0 -214
  336. package/docs/design/parallel-slots.md +0 -567
  337. package/docs/design/parity-tier-dag.md +0 -161
  338. package/docs/design/repo-cleanup-plan.md +0 -231
  339. package/docs/design/runtime-isolation.md +0 -263
  340. package/docs/design/segmented-backward-training.md +0 -543
  341. package/docs/design/spec-decode-larger-targets.md +0 -229
  342. package/docs/design/ssd-kv-cold-tier.md +0 -415
  343. package/docs/design/structured-output.md +0 -484
  344. package/docs/design/tauri-desktop-app.md +0 -127
  345. package/docs/design/the-dreaming-master-plan.md +0 -445
  346. package/docs/design/turboquant-kv.md +0 -235
  347. package/docs/design/turboquant-weights.md +0 -467
  348. package/docs/design/w4a16-compute-precision-spike.md +0 -232
  349. package/docs/design/web-chat-beat-matrix.md +0 -315
  350. package/docs/design/write-pipeline-entity.md +0 -468
  351. package/docs/planning/IDEAS.md +0 -69
  352. package/docs/reference/embedding.md +0 -197
  353. package/docs/reference/features-matrix.md +0 -101
  354. package/docs/reference/orpo-quickstart.md +0 -135
  355. package/src/flags.ts +0 -11
  356. /package/docs/{design → archive/investigations}/colibri-glm52-port.md +0 -0
  357. /package/docs/{planning → archive/planning}/chunk-task-roadmap.md +0 -0
  358. /package/docs/{planning → archive/planning}/curve-sampler-research-plan.md +0 -0
  359. /package/docs/{planning → archive/planning}/memory-docs-and-dag-plan.md +0 -0
  360. /package/docs/{planning → archive/planning}/pi-integration-review.md +0 -0
  361. /package/docs/{planning → archive/planning}/release-notes-v0.2.0.md +0 -0
  362. /package/src/{memory-plan.ts → admission-plan.ts} +0 -0
  363. /package/{docs/archive/investigations → src/serve/assets}/curve-terrain.html +0 -0
  364. /package/{docs/dag → src/serve/assets}/training-inference-map.html +0 -0
package/src/ssd-cache.ts CHANGED
@@ -1,4 +1,4 @@
1
- // SSD cold tier for the prompt/KV cache (docs/design/ssd-kv-cold-tier.md).
1
+ // SSD cold tier for the prompt/KV cache (docs/design/kv-cache.md).
2
2
  //
3
3
  // Files ARE the database: no sidecar index. Layout
4
4
  // <dir>/<configFingerprint>/<nsHash>/<uuid>.mlxkv
@@ -22,7 +22,7 @@ import { randomUUID } from "node:crypto";
22
22
  import { commonPrefixLength } from "./prompt-cache";
23
23
  import {
24
24
  saveKvCache, saveKvCacheAsync, loadKvCache, readKvHeader,
25
- cacheHeadersTrimmable,
25
+ cacheHeadersTrimmable, legacyCacheCodecs, type CacheCodecProvider,
26
26
  type KvSaveMeta, type KvLoadExpect, type LoadedKvCache,
27
27
  } from "./kv-store";
28
28
  import type { Cache } from "./model/gemma4-base";
@@ -39,9 +39,11 @@ export interface SsdIndexEntry {
39
39
  * over a shorter usable one (the 2026-07-06 restart-0 defect: the
40
40
  * [prompt+gen] file always won, always got rejected after restore). */
41
41
  trimmable: boolean;
42
+ generationCheckpoint?: NonNullable<KvSaveMeta["generationCheckpoint"]>;
42
43
  }
43
44
 
44
45
  export interface SsdStoreOptions {
46
+ codecs?: CacheCodecProvider;
45
47
  dir: string;
46
48
  maxBytes: number;
47
49
  /** Server identity — written into every file, enforced on scan/load. */
@@ -57,6 +59,7 @@ const nsHash = (ns: string): string =>
57
59
 
58
60
  export class SsdCacheStore {
59
61
  readonly #opts: SsdStoreOptions;
62
+ readonly #codecs: CacheCodecProvider;
60
63
  readonly #root: string; // <dir>/<configFingerprint>
61
64
  #index: SsdIndexEntry[] = [];
62
65
  #warnedWriteFailure = false;
@@ -64,6 +67,7 @@ export class SsdCacheStore {
64
67
 
65
68
  constructor(opts: SsdStoreOptions) {
66
69
  this.#opts = opts;
70
+ this.#codecs = opts.codecs ?? legacyCacheCodecs;
67
71
  this.#root = join(opts.dir, opts.configFingerprint);
68
72
  }
69
73
 
@@ -121,10 +125,14 @@ export class SsdCacheStore {
121
125
  h.tokenizerHash !== this.#opts.tokenizerHash
122
126
  )
123
127
  throw new Error("metadata mismatch");
128
+ if ((h.codecProvider ?? legacyCacheCodecs.id) !== this.#codecs.id) continue;
124
129
  const st = statSync(path);
125
130
  this.#index.push({
126
131
  path, ns: h.ns ?? "", tokens: h.tokens, bytes: st.size, mtimeMs: st.mtimeMs,
127
- trimmable: cacheHeadersTrimmable(h.caches),
132
+ trimmable: cacheHeadersTrimmable(h.caches, this.#codecs),
133
+ ...(h.generationCheckpoint
134
+ ? { generationCheckpoint: h.generationCheckpoint }
135
+ : {}),
128
136
  });
129
137
  } catch {
130
138
  try { rmSync(path, { force: true }); } catch {}
@@ -171,7 +179,7 @@ export class SsdCacheStore {
171
179
  ns: entry.ns,
172
180
  verify: this.#opts.verify,
173
181
  };
174
- const loaded = loadKvCache(entry.path, model, expect);
182
+ const loaded = loadKvCache(entry.path, model, expect, this.#codecs);
175
183
  const now = new Date();
176
184
  try { utimesSync(entry.path, now, now); entry.mtimeMs = now.getTime(); } catch {}
177
185
  this.stats.restores++;
@@ -198,7 +206,7 @@ export class SsdCacheStore {
198
206
  const path = join(dir, `${randomUUID()}.mlxkv`);
199
207
  try {
200
208
  mkdirSync(dir, { recursive: true });
201
- saveKvCache(path, tokens, caches, this.#meta(ns));
209
+ saveKvCache(path, tokens, caches, this.#meta(ns), this.#codecs);
202
210
  return this.#indexStored(path, tokens, caches, ns);
203
211
  } catch (err) {
204
212
  return this.#storeFailed(path, err);
@@ -210,21 +218,75 @@ export class SsdCacheStore {
210
218
  * yields the event loop between tensors so serving interleaves.
211
219
  * Caller passes zero-copy CLONES it owns (a consistent snapshot no
212
220
  * 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> {
221
+ * `runStep` owns every per-tensor step (see saveKvCacheAsync) — the
222
+ * server passes the gateway's exclusive runner so a request cannot start
223
+ * between an idle check and the blocking MLX readback. */
224
+ async storeAsync(
225
+ tokens: number[], caches: Cache[], ns = "",
226
+ runStep?: <T>(step: () => T) => Promise<T>,
227
+ ): Promise<boolean> {
217
228
  const dir = join(this.#root, nsHash(ns));
218
229
  const path = join(dir, `${randomUUID()}.mlxkv`);
219
230
  try {
220
231
  mkdirSync(dir, { recursive: true });
221
- await saveKvCacheAsync(path, tokens, caches, this.#meta(ns), waitTurn);
232
+ await saveKvCacheAsync(path, tokens, caches, this.#meta(ns), runStep, this.#codecs);
222
233
  return this.#indexStored(path, tokens, caches, ns);
223
234
  } catch (err) {
224
235
  return this.#storeFailed(path, err);
225
236
  }
226
237
  }
227
238
 
239
+ /** Longest durable in-flight generation for an identical request. The
240
+ * checkpoint namespace is separate from the ordinary prompt-cache tier,
241
+ * so incomplete assistant output can never seed an unrelated prefill. */
242
+ findGenerationCheckpoint(
243
+ prompt: number[], key: string, cacheNs = "",
244
+ ): SsdIndexEntry | null {
245
+ let best: SsdIndexEntry | null = null;
246
+ for (const entry of this.#index) {
247
+ const checkpoint = entry.generationCheckpoint;
248
+ if (!checkpoint || checkpoint.key !== key || checkpoint.cacheNs !== cacheNs)
249
+ continue;
250
+ if (checkpoint.originalPromptTokens !== prompt.length) continue;
251
+ if (entry.tokens.length <= prompt.length) continue;
252
+ if (commonPrefixLength(entry.tokens, prompt) !== prompt.length) continue;
253
+ if (!best || entry.tokens.length > best.tokens.length) best = entry;
254
+ }
255
+ return best;
256
+ }
257
+
258
+ /** Persist one in-flight generation atomically. A completed newer
259
+ * checkpoint supersedes older checkpoints for the same request only after
260
+ * its rename succeeds, so a crash during a write leaves the prior point. */
261
+ async storeGenerationCheckpoint(
262
+ tokens: number[], caches: Cache[], checkpoint: NonNullable<KvSaveMeta["generationCheckpoint"]>,
263
+ ): Promise<boolean> {
264
+ const ns = `__generation_checkpoint__:${checkpoint.key}`;
265
+ const dir = join(this.#root, nsHash(ns));
266
+ const path = join(dir, `${randomUUID()}.mlxkv`);
267
+ try {
268
+ mkdirSync(dir, { recursive: true });
269
+ await saveKvCacheAsync(path, tokens, caches, {
270
+ ...this.#meta(ns), generationCheckpoint: checkpoint,
271
+ }, undefined, this.#codecs);
272
+ const stored = this.#indexStored(path, tokens, caches, ns, checkpoint);
273
+ if (stored) {
274
+ for (const entry of [...this.#index]) {
275
+ if (entry.path === path) continue;
276
+ if (entry.generationCheckpoint?.key === checkpoint.key) this.remove(entry.path);
277
+ }
278
+ }
279
+ return stored;
280
+ } catch (err) {
281
+ return this.#storeFailed(path, err);
282
+ }
283
+ }
284
+
285
+ removeGenerationCheckpoints(key: string): void {
286
+ for (const entry of [...this.#index])
287
+ if (entry.generationCheckpoint?.key === key) this.remove(entry.path);
288
+ }
289
+
228
290
  #meta(ns: string): KvSaveMeta {
229
291
  return {
230
292
  modelId: this.#opts.modelId,
@@ -234,7 +296,10 @@ export class SsdCacheStore {
234
296
  };
235
297
  }
236
298
 
237
- #indexStored(path: string, tokens: number[], caches: Cache[], ns: string): boolean {
299
+ #indexStored(
300
+ path: string, tokens: number[], caches: Cache[], ns: string,
301
+ generationCheckpoint?: NonNullable<KvSaveMeta["generationCheckpoint"]>,
302
+ ): boolean {
238
303
  const st = statSync(path);
239
304
  if (st.size > this.#opts.maxBytes) {
240
305
  rmSync(path, { force: true });
@@ -260,7 +325,10 @@ export class SsdCacheStore {
260
325
  if (commonPrefixLength(e.tokens, tokens) === e.tokens.length) this.remove(e.path);
261
326
  }
262
327
  }
263
- this.#index.push({ path, ns, tokens, bytes: st.size, mtimeMs: Date.now(), trimmable });
328
+ this.#index.push({
329
+ path, ns, tokens, bytes: st.size, mtimeMs: Date.now(), trimmable,
330
+ ...(generationCheckpoint ? { generationCheckpoint } : {}),
331
+ });
264
332
  this.stats.spills++;
265
333
  this.evictToCap();
266
334
  return true;
@@ -115,9 +115,9 @@ export class SsdDurabilityCoordinator {
115
115
  this.#attempts.set(key, task);
116
116
  try {
117
117
  const outcome = await task;
118
- if (outcome !== "failed" && this.#dirty.get(key) === rec)
118
+ if (outcome === "stored" && this.#dirty.get(key) === rec)
119
119
  this.#dirty.delete(key);
120
- if (outcome === "failed" && !force && this.#dirty.get(key) === rec)
120
+ if (outcome !== "stored" && !force && this.#dirty.get(key) === rec)
121
121
  this.#arm(key, this.busyRetryMs);
122
122
  return outcome;
123
123
  } finally {
@@ -129,6 +129,7 @@ export class SsdDurabilityCoordinator {
129
129
  let snap: SpillItem | null = null;
130
130
  try {
131
131
  snap = await this.gateway.runExclusive(async () => {
132
+ if (this.isAlreadyDurable(rec.tokens, rec.ns)) return null;
132
133
  const entry = this.promptCache.findExact(rec.tokens, rec.ns);
133
134
  if (!entry) return null;
134
135
  return {
@@ -150,7 +151,7 @@ export class SsdDurabilityCoordinator {
150
151
  this.#timers.clear();
151
152
 
152
153
  let flushedSnapshots = 0;
153
- let missingSnapshots = 0;
154
+ const missing = new Map<string, DirtySnapshot>();
154
155
  const droppedBefore = this.spillQueue.droppedCount;
155
156
  const failedBefore = this.spillQueue.failedCount;
156
157
 
@@ -160,22 +161,33 @@ export class SsdDurabilityCoordinator {
160
161
 
161
162
  // Flush one entry at a time. This prevents the queue cap from dropping a
162
163
  // boundary snapshot while a large final snapshot is already in flight.
164
+ // Attempt each record version once. Missing state stays dirty, so a
165
+ // second flush cannot report success without a real durable snapshot.
166
+ const attempted = new Set<DirtySnapshot>();
163
167
  while (this.#dirty.size > 0) {
164
- const keys = [...this.#dirty.keys()];
165
- let progressed = false;
166
- for (const key of keys) {
167
- if (!this.#dirty.has(key)) continue;
168
+ const records = [...this.#dirty.entries()].filter(([, rec]) => !attempted.has(rec));
169
+ if (records.length === 0) break;
170
+ for (const [key, rec] of records) {
171
+ if (this.#dirty.get(key) !== rec) continue;
172
+ attempted.add(rec);
168
173
  const outcome = await this.#attempt(key, true);
169
174
  await this.spillQueue.drain();
170
175
  if (outcome === "stored") {
171
176
  flushedSnapshots++;
172
- progressed = true;
173
177
  } else if (outcome === "missing") {
174
- missingSnapshots++;
175
- progressed = true;
178
+ missing.set(key, rec);
176
179
  }
177
180
  }
178
- if (!progressed) break;
181
+ }
182
+
183
+ // A later trimmable snapshot may now cover a superseded RAM ancestor.
184
+ // Reconcile against committed SSD coverage after all writes settle;
185
+ // never clear a missing prefix merely because another write succeeded.
186
+ for (const [key, rec] of missing) {
187
+ if (!this.isAlreadyDurable(rec.tokens, rec.ns)) continue;
188
+ if (this.#dirty.get(key) === rec) this.#dirty.delete(key);
189
+ missing.delete(key);
190
+ flushedSnapshots++;
179
191
  }
180
192
 
181
193
  const stats = this.stats;
@@ -186,9 +198,9 @@ export class SsdDurabilityCoordinator {
186
198
  stats.pendingSpills === 0 &&
187
199
  stats.droppedSpills === droppedBefore &&
188
200
  stats.failedSpills === failedBefore &&
189
- missingSnapshots === 0,
201
+ missing.size === 0,
190
202
  flushedSnapshots,
191
- missingSnapshots,
203
+ missingSnapshots: missing.size,
192
204
  elapsedMs: performance.now() - started,
193
205
  };
194
206
  }
package/src/tool-call.ts CHANGED
@@ -161,7 +161,7 @@ export function parseToolCalls(text: string): ParsedToolCall[] {
161
161
 
162
162
  const TOOL_CALL_BLOCK_RE = /<tool_call>\s*([\s\S]*?)\s*<\/tool_call>/gi;
163
163
  const XML_FUNCTION_EQUALS_RE = /^\s*<function=([^>\s]+)>\s*([\s\S]*?)\s*<\/function>\s*$/i;
164
- const XML_PARAMETER_EQUALS_RE = /<parameter=([^>\s]+)>\s*([\s\S]*?)\s*<\/parameter>/gi;
164
+ const XML_PARAMETER_EQUALS_RE = /<parameter=([^>\s]+)>([\s\S]*?)<\/parameter>/gi;
165
165
  const GLM52_ARG_KEY_RE = /<arg_key>/i;
166
166
  const GLM52_ARG_PAIR_RE = /<arg_key>\s*([\s\S]*?)\s*<\/arg_key>\s*<arg_value>\s*([\s\S]*?)\s*<\/arg_value>/gi;
167
167
  const XML_FUNCTION_ATTR_RE = /<function\s+name=["']([^"']+)["']\s*>\s*([\s\S]*?)\s*<\/function>/gi;
@@ -208,11 +208,9 @@ function toolParameterSchema(
208
208
  return null;
209
209
  }
210
210
 
211
- /** Oracle `_decode_tool_parameter_value`: string-typed params stay raw
212
- * text (a path like "2025" must not become a number); everything else
213
- * is JSON-decoded when possible. */
214
- function decodeToolValue(value: string, schema: Record<string, unknown> | null): unknown {
215
- const text = decodeXml(value);
211
+ /** Decode a value after its caller removes protocol-specific delimiters.
212
+ * String-typed params stay raw; everything else is JSON-decoded when possible. */
213
+ function decodeToolValue(text: string, schema: Record<string, unknown> | null): unknown {
216
214
  if (!text) return "";
217
215
  const types = schemaTypeNames(schema);
218
216
  if (types.size && [...types].every((t) => t === "string" || t === "null")) return text;
@@ -546,23 +544,59 @@ function parseJsonToolCall(block: string): ParsedToolCall | null {
546
544
  return repairs.length ? { ...call, repaired: true, repairs } : call;
547
545
  }
548
546
 
549
- function parseXmlEqualsToolCall(block: string, tools: ToolSpec[]): ParsedToolCall | null {
547
+ function parseXmlEqualsToolCall(block: string, tools: ToolSpec[], strict = false): ParsedToolCall | null {
550
548
  const match = XML_FUNCTION_EQUALS_RE.exec(block);
551
549
  XML_FUNCTION_EQUALS_RE.lastIndex = 0;
552
550
  if (!match) return null;
551
+ if (strict && (match.index !== 0 || match[0].length !== block.length)) return null;
553
552
  const name = match[1]!.trim();
554
553
  const body = match[2]!;
555
554
  const args: Record<string, unknown> = {};
555
+ let cursor = 0;
556
556
  for (const param of body.matchAll(XML_PARAMETER_EQUALS_RE)) {
557
+ if (strict && body.slice(cursor, param.index).trim()) return null;
557
558
  const key = param[1]!.trim();
558
559
  if (!key) throw new Error(`tool '${name}' contains an empty parameter name`);
559
- args[key] = decodeToolValue(param[2]!, toolParameterSchema(tools, name, key));
560
+ if (strict && Object.hasOwn(args, key)) return null;
561
+ // Qwen's format carries raw text, not XML entity-encoded text. Match
562
+ // mlx_lm.tool_parsers.qwen3_coder: remove one framing newline at each
563
+ // end, preserving indentation, literal entities and JSON string escapes.
564
+ const value = param[2]!.replace(/^\n|\n$/g, "");
565
+ args[key] = decodeToolValue(value, toolParameterSchema(tools, name, key));
566
+ cursor = param.index + param[0].length;
560
567
  }
561
568
  XML_PARAMETER_EQUALS_RE.lastIndex = 0;
569
+ if (strict && body.slice(cursor).trim()) return null;
562
570
  return { name, arguments: args };
563
571
  }
564
572
 
565
- function parseGlm52ToolCall(block: string, tools: ToolSpec[]): ParsedToolCall | null {
573
+ /** One complete envelope, with full consumption and no syntax repair. Used
574
+ * to establish structural fill boundaries, never to verify model predictions. */
575
+ export function parseStrictToolCall(text: string, tools: ToolSpec[]): ParsedToolCall | null {
576
+ const open = "<tool_call>", close = "</tool_call>";
577
+ if (!text.startsWith(open) || !text.endsWith(close)) return null;
578
+ const body = text.slice(open.length, -close.length).trim();
579
+ if (body.includes(open) || body.includes(close)) return null;
580
+ try {
581
+ let call: ParsedToolCall | null;
582
+ if (body.startsWith("{")) {
583
+ // JSON.parse must succeed before the existing payload interpretation.
584
+ JSON.parse(body);
585
+ call = parseJsonToolCall(body);
586
+ } else {
587
+ call = parseXmlEqualsToolCall(body, tools, true) ?? parseGlm52ToolCall(body, tools, true);
588
+ }
589
+ if (!call || call.repaired || !tools.some(tool => toolSpecName(tool) === call.name)) return null;
590
+ return call;
591
+ } catch { return null; }
592
+ }
593
+
594
+ /** A textual argument delimiter is complete only after its closing bracket. */
595
+ export function hasCompleteToolValueDelimiter(text: string): boolean {
596
+ return /^(?:<\/parameter>|<\/arg_value>)/.test(text.trimStart());
597
+ }
598
+
599
+ function parseGlm52ToolCall(block: string, tools: ToolSpec[], strict = false): ParsedToolCall | null {
566
600
  const firstArg = block.search(GLM52_ARG_KEY_RE);
567
601
  const name = (firstArg < 0 ? block : block.slice(0, firstArg)).trim();
568
602
  if (!name || !/^[^\s<>]+$/.test(name)) return null;
@@ -576,7 +610,8 @@ function parseGlm52ToolCall(block: string, tools: ToolSpec[]): ParsedToolCall |
576
610
  if (body.slice(cursor, pair.index).trim()) return null;
577
611
  const key = pair[1]!.trim();
578
612
  if (!key) throw new Error(`tool '${name}' contains an empty argument name`);
579
- args[key] = decodeToolValue(pair[2]!, toolParameterSchema(tools, name, key));
613
+ if (strict && Object.hasOwn(args, key)) return null;
614
+ args[key] = decodeToolValue(decodeXml(pair[2]!), toolParameterSchema(tools, name, key));
580
615
  cursor = pair.index + pair[0].length;
581
616
  matched = true;
582
617
  }
@@ -594,7 +629,7 @@ function parseXmlAttrToolCalls(text: string, tools: ToolSpec[]): ParsedToolCall[
594
629
  for (const param of body.matchAll(XML_PARAM_ATTR_RE)) {
595
630
  const key = param[1]!.trim();
596
631
  if (!key) throw new Error(`tool '${name}' contains an empty parameter name`);
597
- args[key] = decodeToolValue(param[2]!, toolParameterSchema(tools, name, key));
632
+ args[key] = decodeToolValue(decodeXml(param[2]!), toolParameterSchema(tools, name, key));
598
633
  }
599
634
  XML_PARAM_ATTR_RE.lastIndex = 0;
600
635
  calls.push({ name, arguments: args });
@@ -616,9 +651,11 @@ export function parseGeneratedToolCalls(text: string, tools: ToolSpec[]): Parsed
616
651
  const calls: ParsedToolCall[] = [];
617
652
  for (const block of text.matchAll(TOOL_CALL_BLOCK_RE)) {
618
653
  const body = block[1]!.trim();
619
- const parsed = parseJsonToolCall(body) ??
620
- parseXmlEqualsToolCall(body, tools) ??
621
- parseGlm52ToolCall(body, tools);
654
+ // A valid Qwen parameter may itself contain JSON. Dispatch by the outer
655
+ // format before JSON repair can mistake that value for the tool envelope.
656
+ const parsed = /^<function=/i.test(body)
657
+ ? parseXmlEqualsToolCall(body, tools)
658
+ : parseJsonToolCall(body) ?? parseGlm52ToolCall(body, tools);
622
659
  if (!parsed) throw new Error("unsupported tool_call payload format");
623
660
  calls.push(parsed);
624
661
  }
@@ -728,7 +728,7 @@ const BWD_SG_SOURCE = String.raw`
728
728
  // (flipped 2026-07-02 — the kernel-perf-review backlog #1 flip). The old
729
729
  // 0.66%→2.7% dh-accuracy scare was on RANDOM targets (flat softmax, the filter's
730
730
  // worst case — see PLAN.md "filter-on-real-data"); the real-data gate
731
- // (scripts/experiments/flash-cce-filter-realdata.ts) measured, on REAL chunk-ORPO
731
+ // (flash-cce-filter-realdata.ts (deleted 2026-08-23; git history)) measured, on REAL chunk-ORPO
732
732
  // data (M1 Max, 2026-07-02): CPM5 eps 1e-5 = 0.343% dh for 1.41×; e4b = 0.158% dh
733
733
  // for 1.70× — and the teacher-forced full-logits fidelity shows the filter's added
734
734
  // error is far below the flash-vs-full fp-reassociation scatter that already
@@ -781,7 +781,7 @@ function bwdSgKernel(dimTiles: number, filterEps: string, blockEps: string): Met
781
781
  // accumulated into D's frags LANE-LOCALLY (frag += frag — no get_coord math, the
782
782
  // per-lane element mapping is identical between temp and D). dh atomic-added
783
783
  // across the NBLK vocab-block programs. Validated dh ~1e-4 rel vs autograd.
784
- // (Design + parity: scripts/experiments/steel-bwd-htile-test.ts.)
784
+ // (Design + parity: steel-bwd-htile-test.ts (deleted 2026-08-23; git history).)
785
785
  // PHASE-2 DEQUANT: the Wd[32,HTw] staging uses MLX's vectorized+fused
786
786
  // QuantizedBlockLoader (the SAME loader the forward uses), NOT a manual scalar
787
787
  // dequant. The wrinkle: phase-2's GEMM contracts over vocab (K=32) and outputs H
@@ -31,6 +31,8 @@ import type { RuntimeModel } from "../model/factory";
31
31
  * backward recompute, and a stateful (appending) cache would corrupt on the
32
32
  * second run. */
33
33
  export class TrainingCache implements Cache {
34
+ /** Training-only adapter; never admitted, merged, or persisted. */
35
+ signature(): string { return "train:training"; }
34
36
  offset = 0;
35
37
  updateAndFetch(k: MlxArray, v: MlxArray): [MlxArray, MlxArray] {
36
38
  // Return fresh views: the caller disposes its input k/v right after (its
@@ -67,6 +69,8 @@ export class TrainingCache implements Cache {
67
69
  * threw on the missing conv/recurrent/advance surface (the recorded
68
70
  * "mlx-bun perplexity cannot score qwen3_5" gap). */
69
71
  export class TrainingSSMCache implements Cache {
72
+ /** Training-only adapter; never admitted, merged, or persisted. */
73
+ signature(): string { return "train:training-ssm"; }
70
74
  offset = 0;
71
75
  specRound: null = null;
72
76
  get conv(): MlxArray | null { return null; }
@@ -192,6 +196,8 @@ export function buildBatchedPadMask(
192
196
  * forwardLayers only disposes the mask it pulled from makeMask, so makeMask
193
197
  * hands back a fresh view each call and keeps the canonical copy itself. */
194
198
  class BatchedMaskCache implements Cache {
199
+ /** Training-only adapter; never admitted, merged, or persisted. */
200
+ signature(): string { return "train:batched-mask"; }
195
201
  constructor(
196
202
  private readonly inner: Cache,
197
203
  private readonly window: number | null,
package/src/train/loss.ts CHANGED
@@ -1190,7 +1190,7 @@ export function fusedRespLogpMean(
1190
1190
  // [chunk,vocabBlock]. Otherwise the whole-vocab analytic head below.
1191
1191
  const vb = vocabBlock > 0 && vocabBlock < V ? vocabBlock : 0;
1192
1192
  // Auto-dispatch by M (kernel backlog #3; measured 2026-07-02 on the M1 Max,
1193
- // scripts/experiments/head-dispatch-sweep.ts): the fused QM head is FASTER
1193
+ // head-dispatch-sweep.ts (deleted 2026-08-23; git history)): the fused QM head is FASTER
1194
1194
  // at short M (e4b M=64 1.6×, M=512 1.13×; CPM5 faster at EVERY M) — the
1195
1195
  // flash head's win is residency, plus time at long M on e4b (M≥2048 after
1196
1196
  // the 2026-07-02 filter/blockMax flip; 8K: 10.7 vs 13.2 s). So a
@@ -11,7 +11,7 @@
11
11
  // prompt dominates (our chunk/document fine-tunes), ~0 when the response does.
12
12
  //
13
13
  // Bit-exact with the two-forward path (validated in
14
- // scripts/experiments/prefix-shared-parity.ts): each predicted token uses a hidden
14
+ // prefix-shared-parity.ts (deleted 2026-08-23; git history)): each predicted token uses a hidden
15
15
  // conditioned on exactly the same context + RoPE positions —
16
16
  // chosen[k] from H[P-1+k] (k=0..Rc-1; H[P-1] = prompt's last)
17
17
  // rejected[k] from H[P-1] (k=0) / H[P+Rc+k-1] (k≥1)
@@ -62,6 +62,8 @@ export function blockSparseMask(P: number, Rc: number, Rr: number): MlxArray {
62
62
  * TrainingCache) but `makeMask` returns the block-sparse mask. One per layer;
63
63
  * MiniCPM5's runLayerRange calls makeMask once and disposes the returned arr. */
64
64
  export class PrefixSharedCache implements Cache {
65
+ /** Training-only adapter; never admitted, merged, or persisted. */
66
+ signature(): string { return "train:prefix-shared"; }
65
67
  offset = 0;
66
68
  constructor(
67
69
  private readonly P: number, private readonly Rc: number, private readonly Rr: number,
@@ -371,6 +373,8 @@ export function blockSparsePrefixMaskGemma(P: number, Rc: number, Rr: number, wi
371
373
  * type serves both (the window arg selects the sliding term). One per DONOR
372
374
  * layer (sharers reuse donors' fetched KV). */
373
375
  class Gemma4PrefixSharedCache implements Cache {
376
+ /** Training-only adapter; never admitted, merged, or persisted. */
377
+ signature(): string { return "train:gemma4prefix-shared"; }
374
378
  offset = 0;
375
379
  constructor(private readonly P: number, private readonly Rc: number, private readonly Rr: number) {}
376
380
  updateAndFetch(k: MlxArray, v: MlxArray): [MlxArray, MlxArray] {
@@ -2,7 +2,7 @@
2
2
  // segment-by-segment so only ONE segment's activations are ever live, instead
3
3
  // of holding every layer's recompute activations at once (the spike that makes
4
4
  // naive gradient checkpointing crash at long context). Design + proof:
5
- // docs/design/segmented-backward-training.md (mechanism validated on a toy in
5
+ // docs/design/orpo-training.md (mechanism validated on a toy in
6
6
  // scripts/ckpt-mem-test.ts, SEG mode: 0.000% grad error, peak below per-layer
7
7
  // checkpointing; bit-exact on MiniCPM5 under flash attention).
8
8
  //
@@ -24,7 +24,7 @@
24
24
  // `value_and_grad(sum(stop_grad(dh) (.) segment_forward(...)))` is numerically
25
25
  // identical but LEAKS ~one activation buffer per segment per step at the mlx
26
26
  // level (measured; not GC/cache/synchronize/reuse reclaimable — see
27
- // docs/design/segmented-backward-training.md §9.4). mlx_vjp takes the cotangent
27
+ // docs/design/orpo-training.md §9.4). mlx_vjp takes the cotangent
28
28
  // directly, needs no surrogate, and does not leak.
29
29
  //
30
30
  // IMPORTANT lifetime fact: mlx `eval` does NOT detach — an eval'd array retains
@@ -130,7 +130,7 @@ export function planSegmentsBySize(nLayers: number, segSize: number): [number, n
130
130
  // segment_size 1 measures 14.59 GB @8K (+3% step time) vs 17.5 @seg2. Below
131
131
  // that needs the [M,V]-bounded SFT head (backlog #8, ~3 GB head-vjp spike)
132
132
  // and/or an O(L)-memory attention backward. Evidence:
133
- // scripts/experiments/seg-isolation-smoke.ts + MLX_BUN_SEG_MEM_LOG=1 probes.
133
+ // seg-isolation-smoke.ts (deleted 2026-08-23; git history) + MLX_BUN_SEG_MEM_LOG=1 probes.
134
134
 
135
135
  /** Detach `a` into a graph-free LEAF holding its exact (evaluated) bytes, then
136
136
  * dispose `a`. mlx retains the upstream graph after `eval`, so a segment
@@ -1517,6 +1517,8 @@ export class SegmentedBackwardOrpoGemma4 {
1517
1517
  * and null for full layers; the window arg selects the sliding term. One per DONOR
1518
1518
  * layer (sharers reuse donors' fetched KV inside runLayerRange). */
1519
1519
  class Gemma4PrefixSharedSegCache implements Cache {
1520
+ /** Training-only adapter; never admitted, merged, or persisted. */
1521
+ signature(): string { return "train:gemma4-prefix-shared-seg"; }
1520
1522
  offset = 0;
1521
1523
  constructor(private readonly P: number, private readonly Rc: number, private readonly Rr: number) {}
1522
1524
  updateAndFetch(k: MlxArray, v: MlxArray): [MlxArray, MlxArray] {
@@ -82,7 +82,7 @@ export interface TrainConfig {
82
82
  * beats per-layer checkpointing at long context, where naive checkpointing
83
83
  * holds every layer's recompute activations at once and crashes. B=1 SFT
84
84
  * only (the responseOnlyCe path); mutually exclusive with gradCheckpoint.
85
- * Numerically identical to off. See docs/design/segmented-backward-training.md. */
85
+ * Numerically identical to off. See docs/design/orpo-training.md. */
86
86
  segmentSize: number;
87
87
  /** Keep ALL eval-step checkpoints (full mountable adapters under
88
88
  * checkpoints/step-<NNNNN>-val<loss>/) + write metrics.json. Off by default
@@ -440,12 +440,12 @@ export async function trainLora(
440
440
 
441
441
  // ---------------------------------------------------------------------------
442
442
  // Segment planning: uniform planSegmentsBySize only. A full-attention
443
- // ISOLATION planner (kernel-review backlog #2 / segmented-backward-training.md
443
+ // ISOLATION planner (kernel-review backlog #2 / docs/design/orpo-training.md
444
444
  // §5) was built and A/B-measured 2026-07-02: ZERO peak win — mlx's sdpa
445
445
  // backward is O(L²) for EVERY layer (~3.5 GB/layer @8K e4b), sliding included,
446
446
  // so the worst segment is set by layer count alone and segment_size is the
447
447
  // whole knob (seg1 = 14.59 GB @8K, +3% step time — fits the 24 GB M4 Pro).
448
- // Evidence: scripts/experiments/seg-isolation-smoke.ts; note in segmented.ts.
448
+ // Evidence: seg-isolation-smoke.ts (deleted 2026-08-23; git history); note in segmented.ts.
449
449
  // ---------------------------------------------------------------------------
450
450
 
451
451
  // ---------------------------------------------------------------------------
@@ -473,7 +473,7 @@ async function sftLoop(
473
473
  // Segmented backward: stream the SFT backward segment-by-segment (only one
474
474
  // segment's activations live at a time). Phase A is MiniCPM5 SFT B=1 only;
475
475
  // it replaces the single value_and_grad below and is mutually exclusive with
476
- // gradient checkpointing (see docs/design/segmented-backward-training.md).
476
+ // gradient checkpointing (see docs/design/orpo-training.md).
477
477
  const useSegmented = cfg.segmentSize > 0;
478
478
  let segmented: SegmentedBackward | SegmentedBackwardGemma4 | null = null;
479
479
  if (useSegmented) {
@@ -532,13 +532,13 @@ async function sftLoop(
532
532
  // hand-rolled flash kernel is an opt-in (MLX_BUN_TRAIN_ATTN=flash) O(L)-memory
533
533
  // path for memory-bound long context — now correct (its dK-transpose and
534
534
  // dQ-causal-barrier bugs were fixed; FD-validated in
535
- // scripts/experiments/flash-fd-check.ts), though ~30× slower than ops.sdpa.
535
+ // flash-fd-check.ts (deleted 2026-08-23; git history)), though ~30× slower than ops.sdpa.
536
536
  // Cleared in finally.
537
537
  //
538
538
  // GEMMA GUARD (same convention as the launcher's env sanitization in
539
539
  // cli.ts / job.ts, but enforced at the trainer): e4b on this path SIGTRAPed
540
540
  // (uncatchable native crash) at multi-K sequence lengths (>=2K, reproduced in
541
- // scripts/experiments/segmented-grad-test-e4b.ts; docs/reference/training.md)
541
+ // segmented-grad-test-e4b.ts (deleted 2026-08-23; git history); docs/reference/training.md)
542
542
  // and has NOT been re-validated at that scale since the two kernel fixes —
543
543
  // the regression tests stop at T<=256. cli.ts even defaults e4b seq to 8192,
544
544
  // so a stale `export MLX_BUN_TRAIN_ATTN=flash` from a MiniCPM5 experiment
@@ -8,7 +8,7 @@
8
8
  // n = min(ceil(duration_ms/40), 750) (processing_gemma4.py) — embed the full
9
9
  // sequence, then overwrite the soft-token rows with the towers' features.
10
10
  //
11
- // Attention semantics (audio-input-plan.md §3.3, resolved vs the oracle):
11
+ // Attention semantics (docs/design/generic-model-support.md §3.3, resolved vs the oracle):
12
12
  // - image runs attend bidirectionally among themselves (bidirMask), BUT
13
13
  // any audio token in the prompt disables the vision overlay entirely —
14
14
  // mixed image+audio prompts run fully causal (bidirMask = null);