mlx-bun 0.4.0 → 1.0.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 (601) hide show
  1. package/README.md +1453 -88
  2. package/THIRD_PARTY_NOTICES.md +882 -0
  3. package/bin/mlx-bun.convert.mjs +4 -0
  4. package/bin/mlx-bun.fuse.mjs +4 -0
  5. package/bin/mlx-bun.generate.mjs +4 -0
  6. package/bin/mlx-bun.lora.mjs +4 -0
  7. package/bin/mlx-bun.mjs +13 -21
  8. package/bin/mlx-bun.server.mjs +4 -0
  9. package/bin/mlx-bun.upload.mjs +4 -0
  10. package/dist/web/app.js +9584 -0
  11. package/package.json +50 -53
  12. package/src/cli/ane-install.ts +76 -0
  13. package/src/cli/args.ts +179 -0
  14. package/src/cli/engine-entry.ts +68 -0
  15. package/src/cli/inference.ts +214 -0
  16. package/src/cli/job-entry.ts +97 -0
  17. package/src/cli/library-host.ts +117 -0
  18. package/src/cli/main.ts +57 -0
  19. package/src/cli/memory-engine.ts +163 -0
  20. package/src/cli/memory.ts +36 -0
  21. package/src/cli/mlx-lm-aliases.ts +309 -0
  22. package/src/cli/model-choice.ts +33 -0
  23. package/src/cli/model-selection.ts +84 -0
  24. package/src/cli/module-host.ts +11 -0
  25. package/src/cli/module-verbs.ts +50 -0
  26. package/src/cli/numerical-policy.ts +12 -0
  27. package/src/cli/perplexity.ts +89 -0
  28. package/src/cli/serve-host.ts +423 -0
  29. package/src/cli/serve-isolated.ts +217 -0
  30. package/src/cli/serve-options.ts +106 -0
  31. package/src/cli/serve-state.ts +205 -0
  32. package/src/cli/serve.ts +500 -0
  33. package/src/cli/served-model-host.ts +102 -0
  34. package/src/cli/server-entry.ts +148 -0
  35. package/src/cli/serving-unit.ts +177 -0
  36. package/src/{tui.ts → cli/terminal.ts} +13 -8
  37. package/src/cli/worker-adapters.ts +34 -0
  38. package/src/cli/worker-entry.ts +272 -0
  39. package/src/cli/worker-unit.ts +179 -0
  40. package/src/engine/adapter-operation.ts +26 -0
  41. package/src/engine/cache-services.ts +114 -0
  42. package/src/engine/completion.ts +39 -0
  43. package/src/engine/generation-gateway.ts +358 -0
  44. package/src/engine/index.ts +59 -0
  45. package/src/engine/kv-budget.ts +120 -0
  46. package/src/engine/media-preparation.ts +232 -0
  47. package/src/engine/model-binding.ts +24 -0
  48. package/src/engine/model-host.ts +513 -0
  49. package/src/engine/model-serving.ts +89 -0
  50. package/src/{serve → engine}/preparation.ts +10 -8
  51. package/src/{serve → engine}/session-completion-engine.ts +6 -6
  52. package/src/engine/telemetry.ts +109 -0
  53. package/src/hub/downloads.ts +116 -0
  54. package/src/jobs/db.ts +16 -7
  55. package/src/jobs/events.ts +29 -0
  56. package/src/jobs/executable.ts +5 -0
  57. package/src/jobs/host.ts +51 -0
  58. package/src/jobs/in-process.ts +55 -0
  59. package/src/{contracts/jobs.ts → jobs/protocol.ts} +7 -9
  60. package/src/jobs/runner.ts +150 -231
  61. package/src/jobs/service.ts +75 -0
  62. package/src/jobs/sse.ts +59 -23
  63. package/src/jobs/worker-memory.ts +46 -0
  64. package/src/jobs/worker-process.ts +163 -0
  65. package/src/jobs/worker-supervisor.ts +258 -0
  66. package/src/modules.ts +58 -0
  67. package/src/publishing/catalog-hub.ts +18 -0
  68. package/src/publishing/credentials.ts +31 -0
  69. package/src/publishing/upload.ts +37 -0
  70. package/src/residency/leased-adapters.ts +21 -0
  71. package/src/residency/local-records.ts +16 -0
  72. package/src/residency/model-residency.ts +405 -0
  73. package/src/residency/record-index.ts +28 -0
  74. package/src/residency/resident-estimate.ts +50 -0
  75. package/src/{anthropic.ts → server/anthropic.ts} +3 -81
  76. package/src/server/cache-routes.ts +36 -0
  77. package/src/{serve → server}/chat-request.ts +12 -10
  78. package/src/{serve → server}/chat-stage.ts +27 -22
  79. package/src/{client.ts → server/client.ts} +28 -2
  80. package/src/{serve → server}/completion-executor.ts +42 -42
  81. package/src/server/decision-body.ts +55 -0
  82. package/src/server/decision-routes.ts +134 -0
  83. package/src/server/decision-wire.ts +228 -0
  84. package/src/{serve → server}/discovery-routes.ts +42 -83
  85. package/src/server/generated-token-history.ts +57 -0
  86. package/src/{serve → server}/http.ts +25 -13
  87. package/src/{serve → server}/inference-request.ts +5 -5
  88. package/src/server/job-routes.ts +20 -0
  89. package/src/server/media-prompt.ts +63 -0
  90. package/src/server/memory-completion-client.ts +46 -0
  91. package/src/server/model-routes.ts +192 -0
  92. package/src/{serve → server}/openai-wire.ts +3 -2
  93. package/src/{serve → server}/pipeline.ts +1 -1
  94. package/src/server/prompt-builder.ts +13 -0
  95. package/src/server/prompt-contracts.ts +17 -0
  96. package/src/server/proxy-routes.ts +187 -0
  97. package/src/server/publishing-routes.ts +41 -0
  98. package/src/{serve → server}/request-plan.ts +8 -8
  99. package/src/{serve → server}/request-prep.ts +82 -68
  100. package/src/{serve → server}/responses-client.ts +30 -13
  101. package/src/{responses.ts → server/responses.ts} +12 -93
  102. package/src/server/routes.ts +205 -0
  103. package/src/server/start.ts +72 -0
  104. package/src/server/status-routes.ts +108 -0
  105. package/src/{serve → server}/text-completion-stage.ts +25 -4
  106. package/src/server/text-prompt.ts +12 -0
  107. package/src/{serve → server}/token-streams.ts +36 -38
  108. package/src/server/worker-routes.ts +311 -0
  109. package/src/storage/paths.ts +61 -0
  110. package/src/web/assets.ts +37 -0
  111. package/src/web/{src → browser}/api.ts +5 -23
  112. package/src/web/browser/chat-host.ts +17 -0
  113. package/src/web/browser/installed-panels.ts +6 -0
  114. package/src/web/browser/main.ts +55 -0
  115. package/src/web/browser/markdown.ts +279 -0
  116. package/src/web/browser/memory-panel.ts +9 -0
  117. package/src/web/browser/palette.ts +64 -0
  118. package/src/web/browser/protocol.ts +13 -0
  119. package/src/web/browser/shell.ts +467 -0
  120. package/src/web/{src → browser}/status.ts +32 -36
  121. package/src/web/build.ts +51 -0
  122. package/src/web/public/app.html +1054 -0
  123. package/src/web/{sw.js → public/sw.js} +4 -3
  124. package/src/web/{vendor → public/vendor}/hljs-theme.css +2 -2
  125. package/src/web/{vendor → public/vendor}/hljs.js +2 -2
  126. package/THIRD_PARTY_LICENSES.md +0 -51
  127. package/docs/README.md +0 -94
  128. package/docs/archive/hlg-sampling.md +0 -350
  129. package/docs/archive/investigations/audio-input-plan.md +0 -356
  130. package/docs/archive/investigations/chunk-finetune-experiment.md +0 -84
  131. package/docs/archive/investigations/colibri-glm52-port.md +0 -1486
  132. package/docs/archive/investigations/colibri-oracle-pin.md +0 -1284
  133. package/docs/archive/investigations/curve-bisector-routing.md +0 -123
  134. package/docs/archive/investigations/decode-roofline-lookagain.md +0 -348
  135. package/docs/archive/investigations/diffusion-gemma-port.md +0 -602
  136. package/docs/archive/investigations/dspark-handoff.md +0 -224
  137. package/docs/archive/investigations/expert-offload-single-user-moe.md +0 -292
  138. package/docs/archive/investigations/faithful-l1-consolidation.md +0 -310
  139. package/docs/archive/investigations/hlg-sampling-investigation.md +0 -970
  140. package/docs/archive/investigations/indexshare-performance-spike.md +0 -357
  141. package/docs/archive/investigations/kernel-perf-review-2026-07.md +0 -171
  142. package/docs/archive/investigations/lab-build-journal.md +0 -157
  143. package/docs/archive/investigations/minicpm5-decode-megakernel.md +0 -489
  144. package/docs/archive/investigations/openjarvis-fit.md +0 -198
  145. package/docs/archive/investigations/openwiki-evaluation.md +0 -249
  146. package/docs/archive/investigations/optimization_plan.md +0 -346
  147. package/docs/archive/investigations/orpo-base-uf-experiment-and-directions.md +0 -120
  148. package/docs/archive/investigations/orpo-flash-cce-pin-leak.md +0 -167
  149. package/docs/archive/investigations/orpo-uf-testing-handoff.md +0 -126
  150. package/docs/archive/investigations/pareto-specialized-runtime-findings.md +0 -92
  151. package/docs/archive/investigations/pi-builtin-investigation.md +0 -308
  152. package/docs/archive/investigations/pre-colibri-stabilization.md +0 -329
  153. package/docs/archive/investigations/qwen38-27b-campaign-log.md +0 -1092
  154. package/docs/archive/investigations/segmented-backward-handoff.md +0 -148
  155. package/docs/archive/investigations/starter-model-port-handoff.md +0 -342
  156. package/docs/archive/investigations/steel-flash-cce-handoff.md +0 -489
  157. package/docs/archive/investigations/the-dreaming-handoff.md +0 -141
  158. package/docs/archive/investigations/trainer-validation-experiment.md +0 -173
  159. package/docs/archive/planning/chat-ui-vision-memory-first.md +0 -199
  160. package/docs/archive/planning/chat-ui-vision.md +0 -50
  161. package/docs/archive/planning/chunk-task-roadmap.md +0 -77
  162. package/docs/archive/planning/curve-sampler-research-plan.md +0 -331
  163. package/docs/archive/planning/journal.md +0 -316
  164. package/docs/archive/planning/memory-docs-and-dag-plan.md +0 -194
  165. package/docs/archive/planning/pi-integration-review.md +0 -178
  166. package/docs/archive/planning/release-notes-v0.0.10.md +0 -57
  167. package/docs/archive/planning/release-notes-v0.0.11.md +0 -239
  168. package/docs/archive/planning/release-notes-v0.0.12.md +0 -80
  169. package/docs/archive/planning/release-notes-v0.0.13.md +0 -77
  170. package/docs/archive/planning/release-notes-v0.0.9.md +0 -122
  171. package/docs/archive/planning/release-notes-v0.2.0.md +0 -49
  172. package/docs/archive/planning/release-notes-v0.3.0.md +0 -35
  173. package/docs/archive/planning/web-ui-pass-plan.md +0 -213
  174. package/docs/archive/planning/website-readme-pass-plan.md +0 -224
  175. package/docs/design/batching.md +0 -1841
  176. package/docs/design/decode-speed-program.md +0 -2349
  177. package/docs/design/dreaming-nightly-pipeline.md +0 -684
  178. package/docs/design/generic-model-support.md +0 -705
  179. package/docs/design/kv-cache.md +0 -824
  180. package/docs/design/orpo-training.md +0 -680
  181. package/docs/design/speculative-decoding.md +0 -1990
  182. package/docs/design/turboquant.md +0 -2212
  183. package/docs/design/unified-engine-frontier-plan.md +0 -1880
  184. package/docs/design/web-chat-redesign.md +0 -695
  185. package/docs/planning/PRODUCT_ROADMAP.md +0 -594
  186. package/docs/planning/ResearchTopics.md +0 -119
  187. package/docs/planning/release-notes-v0.4.0.md +0 -45
  188. package/docs/planning/video-series.md +0 -421
  189. package/docs/reference/benchmarks.md +0 -3613
  190. package/docs/reference/cli.md +0 -585
  191. package/docs/reference/distribution.md +0 -355
  192. package/docs/reference/environment.md +0 -398
  193. package/docs/reference/glossary.md +0 -39
  194. package/docs/reference/library-api.md +0 -674
  195. package/docs/reference/memory.md +0 -260
  196. package/docs/reference/models.md +0 -334
  197. package/docs/reference/server-api.md +0 -1775
  198. package/docs/reference/server-config.md +0 -846
  199. package/docs/reference/training.md +0 -462
  200. package/docs/reference/troubleshooting.md +0 -158
  201. package/src/admission-plan.ts +0 -18
  202. package/src/audio/conformer.ts +0 -647
  203. package/src/audio/decode.ts +0 -133
  204. package/src/audio/features.ts +0 -202
  205. package/src/audio/transcode.ts +0 -67
  206. package/src/backends/mlx/assistant-target.ts +0 -46
  207. package/src/backends/mlx/autoregressive-draft-rows.ts +0 -120
  208. package/src/backends/mlx/autoregressive.ts +0 -108
  209. package/src/backends/mlx/batch-group.ts +0 -1790
  210. package/src/backends/mlx/cache-attachment.ts +0 -67
  211. package/src/backends/mlx/cache-layout.ts +0 -31
  212. package/src/backends/mlx/checkpoint-state.ts +0 -53
  213. package/src/backends/mlx/continuation-persistence.ts +0 -106
  214. package/src/backends/mlx/continuation-request.ts +0 -43
  215. package/src/backends/mlx/continuation.ts +0 -77
  216. package/src/backends/mlx/diffusion.ts +0 -39
  217. package/src/backends/mlx/draft-target.ts +0 -32
  218. package/src/backends/mlx/evaluation.ts +0 -105
  219. package/src/backends/mlx/fill-append.ts +0 -45
  220. package/src/backends/mlx/full-kv-row-append.ts +0 -32
  221. package/src/backends/mlx/full-kv-row-donor.ts +0 -54
  222. package/src/backends/mlx/full-transitioning-kv-rows.ts +0 -65
  223. package/src/backends/mlx/gateway-binding.ts +0 -130
  224. package/src/backends/mlx/graph.ts +0 -38
  225. package/src/backends/mlx/kv-codec.ts +0 -9
  226. package/src/backends/mlx/kv-maintenance.ts +0 -133
  227. package/src/backends/mlx/kv-tensor-rows.ts +0 -212
  228. package/src/backends/mlx/memory-client.ts +0 -189
  229. package/src/backends/mlx/methods.ts +0 -90
  230. package/src/backends/mlx/model-prompt.ts +0 -164
  231. package/src/backends/mlx/model-serving.ts +0 -79
  232. package/src/backends/mlx/prefill-cohort.ts +0 -89
  233. package/src/backends/mlx/prefill-rows.ts +0 -200
  234. package/src/backends/mlx/prefill.ts +0 -33
  235. package/src/backends/mlx/projected-context-rows.ts +0 -72
  236. package/src/backends/mlx/request-policy.ts +0 -20
  237. package/src/backends/mlx/request-state-policy.ts +0 -41
  238. package/src/backends/mlx/rollback.ts +0 -89
  239. package/src/backends/mlx/row-sampling.ts +0 -49
  240. package/src/backends/mlx/serial-executor.ts +0 -242
  241. package/src/backends/mlx/speculative-group.ts +0 -359
  242. package/src/backends/mlx/speculative-round.ts +0 -161
  243. package/src/backends/mlx/speculative.ts +0 -114
  244. package/src/backends/mlx/state-rows.ts +0 -104
  245. package/src/backends/mlx/state-views.ts +0 -46
  246. package/src/backends/mlx/target-layout-capability.ts +0 -35
  247. package/src/backends/mlx/text-engine.ts +0 -73
  248. package/src/backends/mlx/transitioning-kv-rows.ts +0 -165
  249. package/src/chat-template.ts +0 -382
  250. package/src/cli.ts +0 -3168
  251. package/src/config.ts +0 -399
  252. package/src/contracts/completion.ts +0 -16
  253. package/src/contracts/execution.ts +0 -60
  254. package/src/contracts/generation.ts +0 -103
  255. package/src/contracts/host.ts +0 -7
  256. package/src/contracts/pi-provider.ts +0 -30
  257. package/src/contracts/pi.ts +0 -259
  258. package/src/contracts/prefix-cache.ts +0 -23
  259. package/src/contracts/resources.ts +0 -25
  260. package/src/contracts/sampling.ts +0 -23
  261. package/src/contracts/scheduling.ts +0 -45
  262. package/src/contracts/tsconfig.json +0 -14
  263. package/src/dataset/generators.ts +0 -1087
  264. package/src/dataset/index.ts +0 -12
  265. package/src/dataset/job.ts +0 -81
  266. package/src/dataset/llm.ts +0 -71
  267. package/src/dataset/registry.ts +0 -528
  268. package/src/diffusion/diffusion-generate.ts +0 -507
  269. package/src/download.ts +0 -505
  270. package/src/embed.ts +0 -77
  271. package/src/engine/admission.ts +0 -59
  272. package/src/engine/cancellation.ts +0 -38
  273. package/src/engine/engine.ts +0 -56
  274. package/src/engine/execution-coordinator.ts +0 -70
  275. package/src/engine/execution-plan.ts +0 -53
  276. package/src/engine/resources.ts +0 -53
  277. package/src/engine/scheduler.ts +0 -72
  278. package/src/engine/session.ts +0 -247
  279. package/src/eval/README.md +0 -130
  280. package/src/eval/ifeval.ts +0 -81
  281. package/src/eval/kl-prompts.ts +0 -29
  282. package/src/eval/kl.ts +0 -590
  283. package/src/eval/perplexity.ts +0 -164
  284. package/src/eval/quality-db.ts +0 -92
  285. package/src/eval/reference-logits.ts +0 -207
  286. package/src/eval/runner.ts +0 -124
  287. package/src/eval/score.ts +0 -49
  288. package/src/eval/tasks/bfcl.ts +0 -535
  289. package/src/eval/tasks/gsm8k.ts +0 -98
  290. package/src/eval/tasks/hashhop.ts +0 -139
  291. package/src/eval/tasks/humaneval.ts +0 -275
  292. package/src/eval/tasks/ifeval.ts +0 -525
  293. package/src/eval/tasks/mmlu.ts +0 -157
  294. package/src/eval/ultrafeedback.ts +0 -60
  295. package/src/evaldb.ts +0 -199
  296. package/src/expert-io.ts +0 -393
  297. package/src/expert-offload-build.ts +0 -77
  298. package/src/expert-offload.ts +0 -76
  299. package/src/expert-residency.ts +0 -1134
  300. package/src/expert-usage.ts +0 -415
  301. package/src/fill/echo-index.ts +0 -188
  302. package/src/fill/fill-session.ts +0 -452
  303. package/src/fill/proposal.ts +0 -68
  304. package/src/fill/schema-rows.ts +0 -506
  305. package/src/fill/tool-boundary.ts +0 -103
  306. package/src/fit.ts +0 -249
  307. package/src/generate.ts +0 -1346
  308. package/src/grammar.ts +0 -591
  309. package/src/harness-pi.ts +0 -184
  310. package/src/hf-push.ts +0 -628
  311. package/src/hub-rest.ts +0 -242
  312. package/src/index.ts +0 -63
  313. package/src/inference/denoising.ts +0 -17
  314. package/src/inference/draft-acceptance.ts +0 -41
  315. package/src/inference/draft-output.ts +0 -49
  316. package/src/inference/graph.ts +0 -33
  317. package/src/inference/prefill.ts +0 -39
  318. package/src/inference/rollback.ts +0 -7
  319. package/src/jobs/index.ts +0 -31
  320. package/src/jobs/job-entry.ts +0 -107
  321. package/src/jobs/task-client.ts +0 -19
  322. package/src/jobs/types.ts +0 -2
  323. package/src/kv-scheme.ts +0 -257
  324. package/src/kv-store.ts +0 -1099
  325. package/src/lab/README.md +0 -18
  326. package/src/lab/curve/curve-designer.html +0 -303
  327. package/src/lab/curve/curve-sampler.ts +0 -122
  328. package/src/lab/expert-trace/expert-trace.ts +0 -69
  329. package/src/lab/paged-kv/paged-kv-rows.ts +0 -83
  330. package/src/lab/paged-kv/paged-kv.ts +0 -270
  331. package/src/library.ts +0 -40
  332. package/src/lora.ts +0 -408
  333. package/src/md-modules.d.ts +0 -49
  334. package/src/media-fetch.ts +0 -442
  335. package/src/memory/article.ts +0 -558
  336. package/src/memory/chunk-validation.ts +0 -150
  337. package/src/memory/chunk.ts +0 -318
  338. package/src/memory/cluster.ts +0 -351
  339. package/src/memory/coverage.ts +0 -134
  340. package/src/memory/crosslink.ts +0 -0
  341. package/src/memory/db.ts +0 -253
  342. package/src/memory/entity.ts +0 -203
  343. package/src/memory/gate.ts +0 -187
  344. package/src/memory/history.ts +0 -152
  345. package/src/memory/ingest.ts +0 -335
  346. package/src/memory/model.ts +0 -183
  347. package/src/memory/normalize.ts +0 -671
  348. package/src/memory/parse.ts +0 -22
  349. package/src/memory/pipeline.ts +0 -275
  350. package/src/memory/prompts.ts +0 -65
  351. package/src/memory/query.ts +0 -334
  352. package/src/memory/reconcile.ts +0 -734
  353. package/src/memory/redirect.ts +0 -108
  354. package/src/memory/reindex.ts +0 -325
  355. package/src/memory/resolve.ts +0 -307
  356. package/src/memory/rest.ts +0 -245
  357. package/src/memory/route.ts +0 -272
  358. package/src/memory/schedule.ts +0 -165
  359. package/src/memory/stages.ts +0 -777
  360. package/src/memory/synthesize.ts +0 -1638
  361. package/src/memory/tools.ts +0 -749
  362. package/src/memory/vault.ts +0 -1194
  363. package/src/memory/wikify.ts +0 -1064
  364. package/src/mlx/array.ts +0 -388
  365. package/src/mlx/autograd.ts +0 -304
  366. package/src/mlx/checkpoint.ts +0 -149
  367. package/src/mlx/compile.ts +0 -168
  368. package/src/mlx/custom-vjp.ts +0 -188
  369. package/src/mlx/ffi.ts +0 -512
  370. package/src/mlx/materialize.ts +0 -23
  371. package/src/mlx/metal-kernel.ts +0 -159
  372. package/src/mlx/ops.ts +0 -1021
  373. package/src/mlx/quantized-rows.ts +0 -36
  374. package/src/mlx/token-bitmask.ts +0 -52
  375. package/src/mlx/turboquant-kv-decode.ts +0 -82
  376. package/src/mlx/turboquant-kv-inverse.ts +0 -122
  377. package/src/mlx/turboquant-ops.ts +0 -861
  378. package/src/mlx/turboquant-tables.ts +0 -237
  379. package/src/mmap.ts +0 -90
  380. package/src/model/artifact-identity.ts +0 -73
  381. package/src/model/batched-kv.ts +0 -235
  382. package/src/model/batched-mask.ts +0 -279
  383. package/src/model/batched-quant.ts +0 -212
  384. package/src/model/batched-quantized-kv.ts +0 -144
  385. package/src/model/batched-rotating-quant.ts +0 -353
  386. package/src/model/batched-rotating-state.ts +0 -149
  387. package/src/model/batched-rotating.ts +0 -338
  388. package/src/model/batched-row-storage.ts +0 -179
  389. package/src/model/batched-ssm.ts +0 -52
  390. package/src/model/batched-turboquant-kv.ts +0 -93
  391. package/src/model/compiled-decode.ts +0 -712
  392. package/src/model/decoded-kv-donor.ts +0 -12
  393. package/src/model/delayed-quantized-kv.ts +0 -72
  394. package/src/model/delayed-rotating-quantized-kv.ts +0 -93
  395. package/src/model/delayed-turboquant-kv.ts +0 -55
  396. package/src/model/diffusion-gemma.ts +0 -1026
  397. package/src/model/factory.ts +0 -189
  398. package/src/model/fingerprint.ts +0 -33
  399. package/src/model/flash-attention.ts +0 -694
  400. package/src/model/full-prefill-padding.ts +0 -56
  401. package/src/model/full-prefill-row.ts +0 -105
  402. package/src/model/gemma4-base.ts +0 -2351
  403. package/src/model/gemma4.ts +0 -1315
  404. package/src/model/generated/gemma4-12b.ts +0 -429
  405. package/src/model/generated/gemma4-26b.ts +0 -448
  406. package/src/model/generated/gemma4-e4b.ts +0 -717
  407. package/src/model/generated/index.ts +0 -18
  408. package/src/model/glm52-atlas.ts +0 -511
  409. package/src/model/glm52-cache.ts +0 -792
  410. package/src/model/glm52-config.ts +0 -262
  411. package/src/model/glm52-container.ts +0 -305
  412. package/src/model/glm52-coupling.ts +0 -388
  413. package/src/model/glm52-dsa.ts +0 -606
  414. package/src/model/glm52-expert-layout.ts +0 -253
  415. package/src/model/glm52-layout.ts +0 -152
  416. package/src/model/glm52-memory.ts +0 -500
  417. package/src/model/glm52-mla.ts +0 -792
  418. package/src/model/glm52-moe.ts +0 -223
  419. package/src/model/glm52-pilot.ts +0 -537
  420. package/src/model/glm52-reference.ts +0 -445
  421. package/src/model/glm52-residency.ts +0 -557
  422. package/src/model/glm52-resident-weights.ts +0 -135
  423. package/src/model/glm52-streamed-experts.ts +0 -453
  424. package/src/model/glm52-streamed-metal.ts +0 -610
  425. package/src/model/glm52-weights.ts +0 -503
  426. package/src/model/glm52.ts +0 -1132
  427. package/src/model/implementation.ts +0 -69
  428. package/src/model/kv-attention-view.ts +0 -67
  429. package/src/model/minicpm5.ts +0 -376
  430. package/src/model/profile.ts +0 -443
  431. package/src/model/qwen-append.ts +0 -12
  432. package/src/model/qwen-conv.ts +0 -52
  433. package/src/model/qwen3-delta-state.ts +0 -74
  434. package/src/model/qwen3-delta.ts +0 -451
  435. package/src/model/qwen3-moe.ts +0 -404
  436. package/src/model/qwen3-mrope.ts +0 -252
  437. package/src/model/qwen3.ts +0 -277
  438. package/src/model/qwen3_5-checkpoint.ts +0 -164
  439. package/src/model/qwen3_5.ts +0 -819
  440. package/src/model/rotating-kv-donor.ts +0 -35
  441. package/src/model/rotating-kv-layout.ts +0 -262
  442. package/src/model/rotating-row-transaction.ts +0 -35
  443. package/src/model/speculative-rotating-kv.ts +0 -101
  444. package/src/model/ssm-prefill-padding.ts +0 -39
  445. package/src/model/support.ts +0 -102
  446. package/src/model/trellis-balanced-scatter.ts +0 -51
  447. package/src/model/trellis-linear.ts +0 -672
  448. package/src/model/trellis-shared-m.ts +0 -151
  449. package/src/model/trellis-shared-scatter.ts +0 -144
  450. package/src/model/trellis-splitk-prefill.ts +0 -158
  451. package/src/model/trellis-tiled-prefill.ts +0 -136
  452. package/src/model/trellis-vector-expand.ts +0 -62
  453. package/src/model/trellis-wide-prefill.ts +0 -94
  454. package/src/model/turboquant-codec.ts +0 -83
  455. package/src/model/universal/archs.ts +0 -332
  456. package/src/model/universal/dense.ts +0 -612
  457. package/src/model/universal/modules.ts +0 -170
  458. package/src/model/universal/rope.ts +0 -381
  459. package/src/native/expert_io.c +0 -523
  460. package/src/native/frame_extract.swift +0 -140
  461. package/src/native-pack.ts +0 -162
  462. package/src/pi-provider.ts +0 -53
  463. package/src/pi-session.ts +0 -81
  464. package/src/pi-terminal.ts +0 -274
  465. package/src/pi-web.ts +0 -1932
  466. package/src/preflight.ts +0 -158
  467. package/src/prompt-cache.ts +0 -460
  468. package/src/quantize/allocator.ts +0 -627
  469. package/src/quantize/atomic-output.ts +0 -38
  470. package/src/quantize/calibration.ts +0 -181
  471. package/src/quantize/config-writer.ts +0 -145
  472. package/src/quantize/index.ts +0 -84
  473. package/src/quantize/job.ts +0 -162
  474. package/src/quantize/quantizer.ts +0 -666
  475. package/src/quantize/rotate.ts +0 -603
  476. package/src/quantize/safetensors-writer.ts +0 -249
  477. package/src/quantize/sensitivity.ts +0 -216
  478. package/src/quantize/trellis.ts +0 -586
  479. package/src/quantize/weight-transform.ts +0 -292
  480. package/src/registry.ts +0 -602
  481. package/src/runtime-config.ts +0 -81
  482. package/src/safetensors.ts +0 -158
  483. package/src/sampler.ts +0 -1011
  484. package/src/serve/admin-routes.ts +0 -184
  485. package/src/serve/assets/curve-terrain.html +0 -105
  486. package/src/serve/assets/training-inference-map.html +0 -575
  487. package/src/serve/aux-routes.ts +0 -132
  488. package/src/serve/batch-scheduler.ts +0 -8
  489. package/src/serve/checkpoint-identity.ts +0 -64
  490. package/src/serve/generation-gateway.ts +0 -496
  491. package/src/serve/isolate.ts +0 -609
  492. package/src/serve/kv-budget.ts +0 -14
  493. package/src/serve/lab-routes.ts +0 -341
  494. package/src/serve/lane-registry.ts +0 -45
  495. package/src/serve/model-admin-routes.ts +0 -155
  496. package/src/serve/model-binding.ts +0 -43
  497. package/src/serve/model-host.ts +0 -511
  498. package/src/serve/parent-application.ts +0 -56
  499. package/src/serve/prompt-response-trace.ts +0 -147
  500. package/src/serve/response-lease.ts +0 -24
  501. package/src/serve/session-search.ts +0 -326
  502. package/src/serve/static-routes.ts +0 -101
  503. package/src/server.ts +0 -1314
  504. package/src/spec/assistant-rows.ts +0 -92
  505. package/src/spec/assistant-source.ts +0 -126
  506. package/src/spec/cache-identity.ts +0 -22
  507. package/src/spec/deepspec-rows.ts +0 -29
  508. package/src/spec/deepspec-source.ts +0 -145
  509. package/src/spec/dflash-source.ts +0 -149
  510. package/src/spec/drafter.ts +0 -304
  511. package/src/spec/dspark/ab-stats.ts +0 -136
  512. package/src/spec/dspark/calibration.ts +0 -112
  513. package/src/spec/dspark/data-dflash.ts +0 -156
  514. package/src/spec/dspark/data.ts +0 -201
  515. package/src/spec/dspark/deepspec-module.ts +0 -776
  516. package/src/spec/dspark/generate-dflash.ts +0 -245
  517. package/src/spec/dspark/generate.ts +0 -290
  518. package/src/spec/dspark/loader.ts +0 -23
  519. package/src/spec/dspark/loss.ts +0 -213
  520. package/src/spec/dspark/module-dflash.ts +0 -560
  521. package/src/spec/dspark/module.ts +0 -540
  522. package/src/spec/dspark/quantize-drafter.ts +0 -91
  523. package/src/spec/dspark/sample.ts +0 -112
  524. package/src/spec/generate.ts +0 -276
  525. package/src/spec/glm52-mtp-graph.ts +0 -29
  526. package/src/spec/glm52-mtp-rows.ts +0 -132
  527. package/src/spec/glm52-mtp-source.ts +0 -317
  528. package/src/spec/glm52-mtp-state.ts +0 -33
  529. package/src/spec/ngram-source.ts +0 -204
  530. package/src/spec/projected-draft-rows.ts +0 -73
  531. package/src/spec/qwen-mtp-module.ts +0 -171
  532. package/src/spec/qwen-mtp-rows.ts +0 -219
  533. package/src/spec/qwen-mtp-source.ts +0 -383
  534. package/src/spec/qwen-mtp-state.ts +0 -30
  535. package/src/spec/serve-loop.ts +0 -585
  536. package/src/spec/source.ts +0 -215
  537. package/src/spec/two-model-rows.ts +0 -61
  538. package/src/spec/two-model.ts +0 -113
  539. package/src/ssd-cache.ts +0 -372
  540. package/src/ssd-durability.ts +0 -211
  541. package/src/tokenizer.ts +0 -335
  542. package/src/tool-approvals.ts +0 -131
  543. package/src/tool-call.ts +0 -695
  544. package/src/train/dataset.ts +0 -484
  545. package/src/train/diffusion-lora.ts +0 -216
  546. package/src/train/export.ts +0 -32
  547. package/src/train/flash-cce.ts +0 -1234
  548. package/src/train/forward.ts +0 -266
  549. package/src/train/fuse.ts +0 -243
  550. package/src/train/index.ts +0 -46
  551. package/src/train/job.ts +0 -205
  552. package/src/train/lora-params.ts +0 -254
  553. package/src/train/loss.ts +0 -1591
  554. package/src/train/merge.ts +0 -195
  555. package/src/train/optimizer.ts +0 -183
  556. package/src/train/prefix-shared.ts +0 -490
  557. package/src/train/rank.ts +0 -137
  558. package/src/train/segmented.ts +0 -1828
  559. package/src/train/steel-qmm-header.ts +0 -1461
  560. package/src/train/trainer.ts +0 -1221
  561. package/src/train/watch.ts +0 -542
  562. package/src/vision/diffusion-vision.ts +0 -620
  563. package/src/vision/embedder.ts +0 -168
  564. package/src/vision/preprocess.ts +0 -247
  565. package/src/vision/prompt.ts +0 -373
  566. package/src/vision/qwen3vl-preprocess.ts +0 -409
  567. package/src/vision/qwen3vl-prompt.ts +0 -196
  568. package/src/vision/qwen3vl-tower.ts +0 -555
  569. package/src/vision/siglip.ts +0 -599
  570. package/src/vision/video-frames.ts +0 -148
  571. package/src/web/app.html +0 -2143
  572. package/src/web/app.js +0 -5703
  573. package/src/web/skills/memory/SKILL.md +0 -120
  574. package/src/web/skills/web-research/SKILL.md +0 -46
  575. package/src/web/skills.ts +0 -97
  576. package/src/web/src/adapters-panel.ts +0 -288
  577. package/src/web/src/assistant.ts +0 -451
  578. package/src/web/src/chat.ts +0 -1057
  579. package/src/web/src/composer.ts +0 -959
  580. package/src/web/src/dataset.ts +0 -132
  581. package/src/web/src/finetune.ts +0 -267
  582. package/src/web/src/hub.ts +0 -428
  583. package/src/web/src/main.ts +0 -98
  584. package/src/web/src/markdown.ts +0 -0
  585. package/src/web/src/memory-panel.ts +0 -572
  586. package/src/web/src/model-picker.ts +0 -159
  587. package/src/web/src/palette.ts +0 -346
  588. package/src/web/src/protocol.ts +0 -26
  589. package/src/web/src/quantize.ts +0 -167
  590. package/src/web/src/rag.ts +0 -274
  591. package/src/web/src/sessions.ts +0 -469
  592. package/src/web/src/shell.ts +0 -804
  593. package/src/web/src/ui-catalog.ts +0 -78
  594. package/src/web/vendor/README.md +0 -61
  595. package/src/web-assets.ts +0 -47
  596. package/src/web-tools.ts +0 -478
  597. package/src/weights.ts +0 -255
  598. /package/src/{serve → server}/completion-sink.ts +0 -0
  599. /package/src/web/{icon.svg → public/icon.svg} +0 -0
  600. /package/src/web/{manifest.webmanifest → public/manifest.webmanifest} +0 -0
  601. /package/src/web/{vendor → public/vendor}/hljs-LICENSE +0 -0
@@ -1,824 +0,0 @@
1
- ---
2
- status: landed
3
- axis: ON
4
- canonical-for: kv-cache
5
- plan-anchor: "Phase 18 — Concurrent / batched serving (slots) + parallel load benchmark `[~]` (2026-06-13)"
6
- last-verified: 2026-09-09
7
- ---
8
-
9
- # KV cache — residency and layout
10
-
11
- The one doc for where KV lives and how it is arranged: the cache classes
12
- and their `signature()` capability model, the RAM prompt cache
13
- (`PromptCache.take()`), the SSD cold tier and its durability boundary, the
14
- optional paged block allocator, and how KV *schemes* plug into all of it.
15
- The TurboQuant codec itself is documented in
16
- [docs/design/turboquant.md](./turboquant.md); this doc only covers how its cache
17
- class participates in residency.
18
-
19
- Sources consolidated here: `docs/design/kv-cache.md`, `docs/design/kv-cache.md`, and
20
- the cache-integration parts of `docs/design/turboquant.md`. Status and changelog
21
- prose lives in PLAN.md; this file keeps mechanism, invariants, decisions,
22
- and open items.
23
-
24
- Code: `src/model/gemma4-base.ts` (Cache interface + the K/V cache family),
25
- `src/model/batched-*.ts`, `src/lab/paged-kv/paged-kv.ts`, `src/prompt-cache.ts`,
26
- `src/kv-store.ts`, `src/ssd-cache.ts`, `src/ssd-durability.ts`,
27
- `src/kv-scheme.ts`, `src/generate.ts` (`maybeQuantizeKv` / `maybePageKv`),
28
- `src/server.ts` (wiring, `/stats`, `POST /admin/cache/flush`),
29
- `src/serve/generation-gateway.ts`, `src/serve/batch-scheduler.ts`.
30
-
31
- ## 1. Layering
32
-
33
- Residency is a three-tier stack, each tier keyed by the exact token prefix
34
- its KV encodes:
35
-
36
- | tier | owner | unit | key |
37
- |---|---|---|---|
38
- | live caches | one generation (serial lane or a batch row) | `Cache[]`, one per layer | — |
39
- | RAM prompt cache | `PromptCache` (`src/prompt-cache.ts`) | whole prefix entry (tokens + `Cache[]`) | tokens + adapter ns |
40
- | SSD cold tier | `SsdCacheStore` (`src/ssd-cache.ts`) over `src/kv-store.ts` files | one `.mlxkv` file per entry | tokens + ns + model/scheme/tokenizer fingerprint |
41
-
42
- The prompt cache tiers over the SSD store *inside* `take()` (structural
43
- `ColdTier` interface), so every consumer (serial lane, batch scheduler
44
- joiners, `/admin/drain`) gets both tiers through the same `take()`/`put()`
45
- calls. Requests and evictions enqueue persistence work. The writer performs
46
- it when the engine is idle; RAM reuse does not enqueue an extra write or wait
47
- for the queue. Explicit flushes await the queued work. The opt-in `--generation-checkpoint N` path transfers owned snapshots at coarse decode boundaries and queues atomic
48
- persistence without awaiting disk in decode (section 5.7). Paged KV
49
- (section 6) is a *layout* choice for the live tier and, in v1, bypasses the
50
- other two.
51
-
52
- ## 2. Cache classes and the signature/capability model
53
-
54
- ### 2.1 The `Cache` contract (`gemma4-base.ts`)
55
-
56
- Every cache-owning layer holds one `Cache`:
57
-
58
- - `updateAndFetch(k, v)` appends and returns the attended window;
59
- `makeMask(N, windowSize)`; `state()` (arrays to eval at a prefill chunk
60
- boundary); `dispose()`.
61
- - `isTrimmable()` / `trim(n, bypass?)` — whether the last `n` tokens can be
62
- dropped. This single predicate drives prefix reuse: an entry longer than
63
- the matched prefix is usable only if every cache can trim.
64
- - `signature?()` — stable storage identity ("`kv:plain`",
65
- "`kv:quant:<bits>:<group>`", "`kv:rotating-plain`",
66
- "`kv:rotating-quant:<bits>:<group>`", "`kv:turboquant:<k>:<v>`", "`ssm`",
67
- "`kv:mla:target`", "`kv:mla:target:dsa`", "`kv:mla:mtp`"). Persistence
68
- codecs (`kv-store.ts` `CACHE_CODECS`) and the scheduler's layer-kind map
69
- dispatch on it. `cacheSignature(cache)` returns `"unknown"` when the
70
- method is absent.
71
- - `bytesPerToken?()` — physical growth per token per row (0 for recurrent
72
- state); `stateNeedsDispose` (TurboQuant's `state()` returns fresh views
73
- the caller must dispose); `ropeOffsetArr` (per-row RoPE positions for
74
- batched/compiled paths); `specRound*` (speculative rollback for
75
- untrimmable recurrent caches).
76
-
77
- Capability interfaces sit beside the signature: `RowBatchCache`
78
- (`batchSize`/`filterRows`/`extractRow`) and `BatchableCache` (caches that
79
- own their own dynamic-row batching — GLM's MLA/DSA state). Type guards
80
- (`isPlainKvCache`, `isRotatingPlainCache`, …) are signature-based;
81
- `isRowBatchCache`/`isBatchableCache` are structural.
82
-
83
- ### 2.2 The family
84
-
85
- | class | file | signature | trimmable | notes |
86
- |---|---|---|---|---|
87
- | `KVCache` | gemma4-base.ts | `kv:plain` | yes | mlx-lm port; grows in `STEP = 256` slabs, in-place `sliceUpdate` |
88
- | `QuantizedKVCache` | gemma4-base.ts | `kv:quant:b:g` | yes | affine triples (packed/scales/biases), groups along head_dim |
89
- | `RotatingKVCache` | gemma4-base.ts | `kv:rotating-plain` | **only while `offset < maxSize`** (pre-wrap) | sliding-window ring |
90
- | `RotatingQuantizedKVCache` | gemma4-base.ts | `kv:rotating-quant:b:g` | pre-wrap | quantized ring |
91
- | `TurboQuantKVCache` | gemma4-base.ts | `kv:turboquant:k:v` | yes | not a `KVCache` subclass — deliberately fails every `instanceof` gate (monolith fallback, solo-only); see docs/design/turboquant.md |
92
- | `SSMCache` | qwen3-delta.ts | `ssm` | **no** (recurrent) | `bytesPerToken()` = 0; per-row `offsets` in the batch lane |
93
- | `Glm52Cache` / `MLACache` | glm52-cache.ts | `kv:mla:*` | yes | `BatchableCache`; compressed latent + rope (+ DSA index) |
94
- | `PagedKVCache` | paged-kv.ts | **none** | yes | block pool + gather; section 6 |
95
- | `BatchedRotatingCache` | batched-rotating.ts | `kv:rotating-plain` | pre-wrap | `RowBatchCache`; batched ring, host-side scalar ring state |
96
- | `BatchedRotatingQuantCache` | batched-rotating-quant.ts | inherited (`extends RotatingQuantizedKVCache`) | pre-wrap | batched quantized ring |
97
- | `BatchedDecodeMaskCache` | batched-mask.ts | **none** | delegates | per-step wrapper over a B-generic inner (padding mask + per-row RoPE) |
98
- | `BatchedQuantDecodeMaskCache` | batched-quant.ts | inherited (`extends QuantizedKVCache`) | delegates | quantized twin of the wrapper; subclassing keeps generated forwards' `instanceof` guards happy |
99
- | compiled-decode `Trace*` | compiled-decode.ts | inherited | — | trace adapters over the four compiled-supported classes |
100
- | training caches (`TrainingCache`, `TrainingSSMCache`, `BatchedMaskCache`, `PrefixSharedCache`, `Gemma4PrefixSharedCache`, `Gemma4PrefixSharedSegCache`) | src/train/* | **none** | — | never admitted, cloned, or persisted; the interface comment explicitly allows omission here |
101
-
102
- Invariant: **quantization groups run along head_dim, never the token
103
- axis**, so token-axis slicing (trim, `cloneKvCaches` live views, batch row
104
- surgery, persistence slicing to `offset`) is byte-safe for every quantized
105
- kind. Every cache kind persisted by kv-store has a `restoreState(...)`.
106
-
107
- ### 2.3 Batching gates
108
-
109
- Two independent checks decide whether a model/scheme can batch
110
- (`generation-gateway.ts`):
111
-
112
- - `#modelCachesBatchable()` — every fresh `makeCache()` entry is a
113
- `KVCache`, `RotatingKVCache`, `BatchableCache`, or (unless
114
- `MLX_BUN_BATCH_SSM=0`) `SSMCache`. Novel classes (TurboQuant, paged) fail
115
- this by construction.
116
- - `#kvBatchable()` — `KvScheme.batchable(config, canConvert)`: per-layer
117
- `affine-config` and uniform affine KV4/KV8 batch when their cache-owning
118
- layers can convert. TurboQuant still requires merged-row layout support;
119
- a scheme-less gateway must never silently
120
- drop quantization (`kv-scheme.ts` makes the capability probe mandatory
121
- for quantized schemes).
122
-
123
- The scheduler then maps each layer to a kind ("owned-batch" / "rot" /
124
- "ssm" / "full") from the prototype caches, and joins/filters/extracts by
125
- that kind.
126
-
127
- ### 2.4 Known design gap: optional `signature()` with an `"unknown"` fallback
128
-
129
- `Cache.signature` is optional and `cacheSignature()` substitutes
130
- `"unknown"`. That makes an unsigned class *route* rather than *fail*: any
131
- signature-based conjunct silently evaluates false. This is exactly how the
132
- 2026-08-22 agg×4 regression happened (PLAN.md "agg×4 regression —
133
- root-caused and fixed", commit `443f333`; the BatchedRotatingCache fix
134
- PRs #42/#43): the scheduler's rot-merge required
135
- `isRowBatchCache(prev) && isRotatingPlainCache(prev)`, `BatchedRotatingCache`
136
- had no `signature()`, the running batch's ring was dropped from the join,
137
- and the next full-B decode step crashed in the grow-path concatenate. The
138
- quantized twin escaped only because it inherits a real signature.
139
-
140
- What holds today (verified in src, 2026-08-23):
141
-
142
- - `BatchedRotatingCache` now returns `"kv:rotating-plain"`
143
- (`batched-rotating.ts`), and the scheduler's rot branch routes by
144
- capability alone (`isRowBatchCache`). The comment at
145
- `batch-scheduler.ts` ~line 1007 still says the class has *no* override —
146
- that comment is stale; the guard order it describes (capability first)
147
- is still the rule.
148
- - Still unsigned: `PagedKVCache` and `BatchedDecodeMaskCache` in the
149
- serving tree, plus the six training-only caches listed above. Neither
150
- serving class is currently reachable by a signature-dispatched path
151
- (paged bypasses prompt-cache/kv-store/batching; the mask wrapper is
152
- rebuilt per step and the scheduler dispatches on the *inner* caches),
153
- so today the gap is latent, not live.
154
- - Rule going forward (PLAN.md lesson): **"unknown" is a bug, not a
155
- route.** Any new `Cache` that can reach the scheduler, `cloneKvCaches`,
156
- or kv-store must declare a signature; kv-store already throws on an
157
- unknown signature rather than guessing. Making `signature()` required on
158
- the interface (with training adapters returning an explicit
159
- `"training"`) is the open hardening item.
160
-
161
- ## 3. KV schemes — how they plug into the cache (`src/kv-scheme.ts`)
162
-
163
- `KvScheme` is the immutable declaration of *what bytes a token costs and
164
- which cache class holds it*: kind `bf16` | `affine-uniform` |
165
- `affine-config` | `turbo`, resolved once at server start by
166
- `resolveKvScheme` (TurboQuant wins if set; `--kv-quant off|<bits>|config`
167
- otherwise; a missing `kv_config.json` under `config` is bf16 or an error
168
- per caller). Its roles in residency:
169
-
170
- - **Conversion chokepoint.** `maybeQuantizeKv` (`generate.ts`) converts
171
- populated plain/rotating caches in place after the first prefill
172
- (`offset === 0` caches are skipped — the optiq mixed-KV convention; the
173
- affine start threshold is `quantizedKvStart`, which `resolveKvScheme`
174
- pins to 0 for every server scheme — the `generate.ts` fallback of 5000
175
- for uniform bits only reaches library callers who pass `kvBits` without
176
- a threshold) and evaluates layer by layer so the bf16 source frees
177
- before the next layer converts. TurboQuant converts
178
- full-attention `KVCache` layers only; rotating layers stay bf16 with a
179
- one-time warning.
180
- - **Byte accounting.** `kvBytesAt`/`kvGeometry` bill per layer type (full
181
- / sliding capped at the window / linear state) with
182
- `bits/8 + 4/groupSize` bytes per affine element. `fitOptions` bills
183
- TurboQuant as bf16 on purpose (conservative until its packed layout
184
- exposes a stable projector). The scheduler's KV-budget admission uses
185
- the same seam, and `BatchableCache.projectedBytes` for owned-batch
186
- caches.
187
- - **Cache compatibility key.** `cacheKey` (`bf16` | `kv<bits>` | `config`
188
- | `turbo-k<k>v<v>`) is folded into the SSD store's fingerprint
189
- (`${configFingerprint(config)}-${schemeKey}`, `server.ts`), so restored
190
- files always match what serving would have produced. `configFingerprint`
191
- itself covers the per-layer `kv_config.json` scheme but not CLI-only
192
- levers (uniform bits, TurboQuant) — the scheme key is the anti-collision
193
- mechanism for those.
194
- - **Batch placement.** `batchable()` as in 2.3.
195
-
196
- Uniform 4/8-bit affine KV is bit-exact L1 (unfused SDPA == mlx-lm
197
- `base.py`); the per-layer config scheme is the L2/optiq composition, and
198
- TurboQuant is a third, oracle-backed codec (vllm-metal). The scheme axis
199
- (which bits where) is orthogonal to the residency axis (which tier holds
200
- them); nothing in this doc changes numerics.
201
-
202
- ## 4. RAM tier — `PromptCache` (`src/prompt-cache.ts`)
203
-
204
- Byte-capped LRU of whole prefix entries (`--prompt-cache <GB>`, default
205
- 8 GB; `0` disables). The mlx-lm lesson that motivated it — a count-capped
206
- cache of multi-GB entries is an OOM footgun — still stands even though
207
- upstream is byte-capped now.
208
-
209
- **`take(prompt, ns)` — prefix sharing.** Finds the entry with the longest
210
- usable common prefix (usable = common prefix capped at `prompt.length - 1`,
211
- so at least one token is forwarded; an entry longer than the match must be
212
- trimmable). Every RAM hit lends zero-copy views (`cloneKvCaches` and
213
- `cloneAttachments`) with a ref-counted backing share; the donor remains in
214
- place. This includes exact-boundary recurrent and method state. Only
215
- trimmable state may be shortened. Decode extends its own views without
216
- changing the retained checkpoint.
217
-
218
- SSD is an eventually consistent persistence layer. RAM reuse never writes
219
- to SSD or waits for durability. Background writes retain immutable snapshot
220
- views and yield between tensor writes; explicit flush and graceful shutdown
221
- wait for persistence. A pending or failed write does not prevent RAM reuse.
222
- The existing byte cap controls retained donors and LRU eviction.
223
-
224
- The native sharing gate checks logical checkpoint bytes after repeated decode
225
- and after SSD restore. It passes on both development Macs; the M1 long-prefix
226
- case also preserves three independent 512-token continuations. The queued
227
- writer test retains snapshot state after both donor and borrower disposal,
228
- and explicit flush remains pending until the write completes.
229
-
230
- **Tier order inside `take()`.** The cold tier is consulted with
231
- `find(prompt, ns)` (index-only, no I/O) and wins only with a *strictly*
232
- longer usable prefix; then `restore(handle)` materializes it and the
233
- divergent tail is trimmed. A cold entry that would need a trim but is
234
- untrimmable (wrapped ring, SSM) is disposed loudly and the RAM candidate
235
- (or a fresh prefill) serves. `hits`/`misses` count RAM candidacy only.
236
-
237
- **`put(tokens, caches, ns, retain)` — supersession + eviction.** Exact
238
- duplicates (same tokens) are replaced regardless of trimmability. Strict
239
- prefix-ancestors in the same ns are superseded without spill — but only
240
- when the *new* entry is fully trimmable; an untrimmable new entry (wrapped
241
- ring) can only serve exact-length matches, so shorter ancestors (the
242
- prompt-boundary snapshot) must survive. Then LRU-evict until under cap,
243
- spilling each evictee to the cold tier first. An entry larger than the cap
244
- is spilled and disposed, never stored. `onPut` fires after every
245
- successful put — the server hangs the write-behind scheduler there so both
246
- lanes' entries persist.
247
-
248
- **Prompt-boundary snapshot.** Serial lane (`server.ts`) and batch
249
- scheduler (`snapshotAt`) both clone-and-put a trim-free *prompt-only*
250
- entry at the template probe's stable boundary during a substantial cold
251
- prefill. Rationale: the prompt+generation entry put at completion is
252
- untrimmable past the sliding window (wrapped rings) and under mid-group
253
- quantization, so any decode→encode drift in the reply the client sends
254
- back would turn the next turn into a total miss; a prompt-only entry is
255
- always an exact prefix of the next rendering.
256
-
257
- **Namespaces.** `ns` = the adapter spec (`adapters.join("+")`, `""` = base).
258
- KV computed under one adapter never seeds another's prefill.
259
-
260
- **Bypasses.** Media (vision/audio) requests skip the cache (soft tokens are
261
- identical placeholder ids → false hits), as do paged-KV requests (v1) and
262
- the speculative lane (v1). Merged batch rows are not re-put on finish;
263
- never-merged lone rows put their adopted caches back zero-copy, and
264
- merged rows are extracted per row (`#extractAndPut`) before `filter()`
265
- mutates the batched inners.
266
-
267
- **Idle demotion (`demoteIdle`).** Entries unused past a threshold spill to
268
- the cold tier and free their GPU arrays; the prefix stays reachable via
269
- restore. No-op without a cold tier (demotion with nowhere to demote to is
270
- data loss). `/admin/drain` (unix-socket engine children only) is
271
- `demoteIdle(0)` under the gateway lock — the model pool's lossless
272
- pre-eviction step.
273
-
274
- **Spill contract (`SpillSink`).** Preferred `spillOwned`: the cache hands
275
- the sink zero-copy clones + copied tokens made *before* the donor is
276
- disposed, and the sink owns their disposal on every settle path. A sink
277
- that throws synchronously has not taken ownership; the spill degrades to
278
- a no-op and eviction never unwinds. `spillSync` (live entry, synchronous)
279
- remains for tests and simple embedders.
280
-
281
- Known numerics property (not a residency defect): continuing from a
282
- restored or reused prefix produces the same output as the RAM tier's own
283
- cache hit, but a bf16 prefix-reuse continuation can differ from a fresh
284
- full prefill (GEMV-vs-GEMM reduction order). Both tiers share it.
285
-
286
- ## 5. SSD cold tier
287
-
288
- ### 5.1 Decisions
289
-
290
- - **D1 — Whole-prefix-entry spill, not paged blocks.** Single-user serving
291
- with a prefix-entry cache that grows per conversation. Per-block hashing
292
- and save-queue bookkeeping during decode is where oMLX's measured ~20%
293
- steady-state tax came from; ours does zero work on the token loop
294
- (prefix tokens are the key). Partial-prefix reuse works without paging:
295
- restore + `trim()`. The economics that justify the tier are
296
- SSD-vs-recompute, not SSD-vs-RAM: at 12B prefill rates a 30k agent
297
- context is minutes of GPU compute against a ~1 s NVMe restore.
298
- - **D2 — One store, two tiers.** The cold tier is bound *into*
299
- `PromptCache` (section 4); restart persistence comes from a debounced
300
- write-behind snapshot after each put, not only from spill-on-evict.
301
- - **D3 — Own page-aligned format, not safetensors.** `src/kv-store.ts`
302
- writes `MLXBUNKV2\n`-magic files with a streaming writer: ordinary KV
303
- retains format v3; checkpoints with method companion state use v4.
304
- The header is sized up front with fixed-width tensor
305
- hashes, tensors materialized/hashed/written one at a time at 16 KiB
306
- aligned offsets, header patched last (peak host transient = one
307
- tensor). Header carries `formatVersion`, `modelId`,
308
- `configFingerprint`, `ns`, `tokenizerHash`, `createdAt`, `tokens`, and
309
- per-cache `{kind, offset, idx?, maxSize?, groupSize?, bits?, kBits?,
310
- vBits?, headDim?, GLM geometry, tensors[]}`. Kinds: `kv`, `rotating`,
311
- `qkv`, `rotating-qkv`, `ssm`, `turboquant`, `mla`, `mla-dsa`, `mtp-mla`.
312
- Quantized buffers are sliced to `offset` (no 256-step slack persisted).
313
- Header hash always verified; per-tensor hashes verified only under
314
- `--ssd-cache-verify` (eager reads roughly double restore cost). Writes
315
- are atomic (tmp + fsync + rename). Older v1/v2 files read as
316
- unsupported and are deleted-and-regenerated (machine-local, cheap).
317
- - **D4 — Files are the database.**
318
- `<dir>/<configFingerprint>/<nsHash>/<uuid>.mlxkv`. Startup recovery is a
319
- header-only scan of *our* fingerprint dir into an in-memory
320
- `SsdIndexEntry[]` (`{path, ns, tokens, bytes, mtimeMs, trimmable}`);
321
- foreign fingerprint dirs are ignored, never deleted. LRU = file mtime
322
- (`utimes` on hit); the byte cap is enforced at write time by oldest-mtime
323
- eviction; corrupt headers and metadata mismatches are unlinked; `.tmp`
324
- orphans reaped; disk-full is a warn-once soft-fail. Every failure path
325
- degrades to "no hit" / "not stored" — the tier can never take serving
326
- down.
327
- - **Numerical compatibility.** The serving fingerprint also includes a hash
328
- of `ModelServingBinding.stateCompatibility`. The MLX backend supplies its
329
- actual runtime version and GPU architecture at binding creation. The same
330
- artifact produces different cache tensors in the recorded 0.31.2/0.32.2
331
- RTN4 comparisons, so graph geometry and KV format alone cannot qualify
332
- automatic reuse. A runtime/architecture change selects another directory;
333
- previous snapshots remain intact. The binding owns this identity, keeping
334
- backend details out of request parsing and portable model configuration.
335
- A replacement-binding restart regression verifies matching reuse, isolation
336
- and recovery of the original snapshot. Evidence:
337
- `reports/qwen38-rd/mlx-upgrade-0.32.2/cross-runtime-cache-numerics-review.json`.
338
- The real RTN4 HTTP gate also passes all eight responses across four
339
- internal-SSD server starts. Its first requests reuse 0/352/0/352 tokens
340
- under default/default/alternate/default identities; every second request
341
- reuses 352. The alternate tag changes metadata only. All response content,
342
- tool calls and usage counts other than cached tokens match. Exit allocation
343
- includes a retained prompt cache with different fresh/restored capacity,
344
- so this is not a post-disposal memory comparison. Evidence:
345
- `reports/qwen38-rd/runtime-state-http-review.json`.
346
- - **D5 — Flags and interactions.** `--ssd-cache <dir>` (off unless set),
347
- `--ssd-cache-max <GB>` (optional; `0` or unset = unlimited), `--ssd-cache-verify`,
348
- `--ssd-demote-idle <sec>` (default 300 with the tier on; `0` disables).
349
- Requires the RAM tier (`--prompt-cache 0` + `--ssd-cache` is a startup
350
- error). Sub-flags without `--ssd-cache` warn and are ignored.
351
- `--memory-budget` is unchanged (admission already assumes worst-case
352
- bf16). `--paged-kv` warns that the tier sees nothing (section 6).
353
- - **D6 — In-flight resume is explicit.** `--generation-checkpoint <tokens>`
354
- requires `--ssd-cache` and supports eligible shared ordinary execution. It
355
- is off by default. Captured states queue for SSD persistence; an explicit
356
- flush establishes durability of queued work (section 5.7). An identical request resumes; a changed
357
- prompt or sampling policy starts normally. Ordinary adapter requests use the
358
- same continuation hooks under their existing execution context. The checkpoint
359
- identity includes the ordered adapter IDs and content-revision namespace; a
360
- base-model request or replacement adapter cannot restore that checkpoint.
361
-
362
- ### 5.2 Restore is a streamed copy (supersedes the original zero-copy plan)
363
-
364
- The first design restored by COW mmap + `fromPointer` and unmapped in the
365
- entry's `retain`. Two things killed it: the 2026-07-06 rule that mlx
366
- buffer destructors must never call into JS removed the unmap signal, so
367
- every restore became a process-lifetime mapping leak; and exactly-sized
368
- restored buffers made the first post-restore decode step concat-copy the
369
- whole entry anyway. `loadKvCache` now copies each tensor out of a
370
- read-only mmap into an mlx-owned leaf (`fromBytesCopy`), drops the clean
371
- file pages (`MADV_DONTNEED`; offsets are 16 KiB = arm64 page size),
372
- unmaps before returning, and lands plain-KV tensors in STEP-rounded
373
- capacity with slack so the first step updates in place. Peak host
374
- transient = live entry + one tensor; nothing outlives the call. The
375
- `retain` thunk stays in the entry contract as a no-op so callers' dispose
376
- ordering is unchanged (the server's `coldTier.restore` returns
377
- `() => {}`). Comments in `prompt-cache.ts` that still say "zero-copy
378
- mmap" describe the superseded mechanism.
379
-
380
- The write side is the mirror: hashing and writing from a zero-copy view
381
- of the contiguous mlx buffer (`rawBytesView`), no JS-heap copy at any
382
- point.
383
-
384
- ### 5.3 `find()` returns partial matches, gated on trimmability
385
-
386
- `SsdCacheStore.find` returns the longest usable prefix (capped at
387
- `prompt.length - 1`) and *skips* entries that would need a trim but are
388
- untrimmable (`trimmable` is derived from the header kinds + state at
389
- store/scan time via `cacheHeadersTrimmable`). Before this gate the big
390
- untrimmable `[prompt+gen]` file always outranked the usable boundary
391
- snapshot, got restored, and was thrown away — the 2026-07-06 "restart
392
- restores 0 tokens" defect and its silent restore-then-re-prefill cost.
393
- Store-side supersession mirrors `PromptCache.put`: exact duplicates
394
- replaced; prefix-ancestors superseded only when the new entry is
395
- trimmable.
396
-
397
- ### 5.4 Write-behind scheduling contract: own each tensor step
398
-
399
- Every per-tensor flush step is real GPU-stream and JS-thread work
400
- (`ops.contiguous` on the decode stream, a synchronous eval for
401
- `rawBytesView`, a synchronous multi-MB `writeSync`). Interleaving those
402
- between decode tokens taxed decode at long context, so:
403
-
404
- - `saveKvCacheAsync` / `SsdCacheStore.storeAsync` accept a per-step runner.
405
- The server passes `gateway.runWhenIdle`, so the idle decision and the
406
- blocking MLX readback are one atomic engine turn. Background work waits
407
- without registering a serial waiter or draining active batches. The earlier
408
- `gateway.onIdle()` check had a check/use race: a request could begin after
409
- the check and overlap the next tensor sync. A request arriving mid-flush
410
- can run between tensor writes. Neither execution lane drains pending
411
- durability work before generation.
412
- - `GenerationGateway.busy` covers both lanes (serial mutex held/awaited or
413
- batch rows active/pending).
414
- - `MLX_BUN_SSD_WRITEBEHIND=0` disables write-behind snapshots entirely
415
- (kill switch + paired-A/B lever; restart survival then degrades to
416
- spill-on-evict).
417
-
418
- Explicit flush and shutdown drain outstanding writes outside the generation
419
- lock because the writer needs that same lock. Ordinary requests do not.
420
-
421
- The RAM cache's configured byte cap is only an upper bound. Serial serving
422
- checks MLX's live allocation count against the smaller of admission's usable
423
- memory and Metal's recommended working set, reserving prefill workspace and
424
- the prompt's KV footprint for growth/copy-on-write overlap. Pressure relief
425
- persists LRU entries synchronously before disposing them, avoiding deferred
426
- spill clones that would keep the memory resident. The selected request cache
427
- remains owned by the request; older entries remain reusable from SSD. Qwen
428
- checks between prefill layers, and serial decode checks every 256 tokens.
429
- If no reclaimable entries remain, the guard raises a request error instead
430
- of intentionally proceeding over that budget. In-flight checkpoints survive.
431
-
432
- ### 5.5 Bounded retention: `SpillQueue` (`kv-store.ts`)
433
-
434
- All three producers — eviction spills, idle demotions, write-behind
435
- snapshots — go through one serial, byte-capped queue. Pending clones pin
436
- their entries' GPU buffers until the generation-locked flush gets a turn; without
437
- a cap, sustained traffic (gate starved, evictions ongoing) made resident
438
- memory = prompt-cache cap + every queued clone. Policy: cap default 2 GiB
439
- (`MLX_BUN_SSD_SPILL_QUEUE_GB`; `0` keeps only the newest + in-flight
440
- clone); over cap the *oldest* not-in-flight item drops and its clones are
441
- disposed immediately; the item just enqueued is never its own victim
442
- (soft cap). A dropped spill is a future cache miss, never a wrong result.
443
- Clones are disposed on every settle path (fulfil, reject, drop). The
444
- snapshot timer never holds batch admission: while rows are active it
445
- re-arms instead of grabbing the exclusive (entries are immutable; a late
446
- snapshot is equally valid).
447
-
448
- ### 5.6 Durability boundary (`src/ssd-durability.ts`)
449
-
450
- The 2026-08-22 finding: a debounce timer (1 s), a busy re-arm (5 s), and a
451
- serial queue meant a SIGTERM shortly after a long request could outrun
452
- the write — the server implied durability it did not have.
453
- `SsdDurabilityCoordinator` turns puts into a state machine:
454
-
455
- - `schedule(tokens, ns)` records a *dirty* key that includes the exact
456
- token sequence (two same-ns, same-length conversations cannot cancel
457
- each other) and arms the debounce.
458
- - An attempt checks SSD coverage under `gateway.runWhenIdle` before cloning.
459
- Otherwise it snapshots the RAM entry (`findExact` + `cloneKvCaches`) and
460
- enqueues it; a busy gateway re-arms;
461
- a dropped or failed store leaves the key dirty so it is retryable.
462
- A snapshot that vanished from RAM is "stored" only if the SSD index
463
- already covers the prefix (`hasDurablePrefix`), else "missing" and still
464
- dirty. A second flush cannot clear that failure without durable coverage.
465
- - `flush()` cancels timers, awaits in-flight attempts and `drain()`, then
466
- forces each dirty record version once, one at a time (so the queue cap
467
- cannot drop a boundary snapshot while a large final snapshot is in flight).
468
- A replacement scheduled during a write receives its own attempt. After
469
- all writes settle, the coordinator checks missing ancestors against the
470
- committed SSD index again: a later trimmable descendant may now cover a
471
- superseded RAM prefix. Uncovered or foreign-namespace prefixes stay dirty;
472
- this adds no duplicate snapshot write. Result:
473
- `durable` is true only when nothing is pending, nothing dropped or
474
- failed during this flush, and no missing prefix remains uncovered.
475
-
476
- Surfaces: `POST /admin/cache/flush` returns 200 at the boundary (503
477
- otherwise) with pending/dropped/failed/entry-count/longest-prefix
478
- evidence; `mlx-bun serve` handles SIGINT/SIGTERM by stopping admission,
479
- draining active requests, and awaiting the same flush, bounded by
480
- `MLX_BUN_SHUTDOWN_TIMEOUT_MS` (default 120000). `/stats.ssd_cache` exposes
481
- `entries`, `bytes`, `max_bytes`, `restores`, `spills`, `restore_ms_last`,
482
- `demotions`, `pending_snapshots` (includes debounce/re-arm timers),
483
- `pending_spills`, `pending_spill_bytes`, `dropped_spills`,
484
- `failed_spills`, `longest_durable_prefix_tokens`. The standard restart
485
- benchmark calls the endpoint instead of sleeping.
486
-
487
- ### 5.7 In-flight generation checkpoints
488
-
489
- At every configured token interval, `generate()` reaches a boundary where
490
- the live caches cover every emitted token and the next token has already been
491
- sampled but not emitted. The SSD entry stores both pieces atomically:
492
-
493
- - the normal token list and cache tensors encode the emitted prefix;
494
- - `generationCheckpoint` in the KV header records the request key, original
495
- prompt length, emitted-token count, adapter namespace, and pending token.
496
-
497
- Saving the pending token is what makes the mechanism work for Qwen hybrid
498
- models: recurrent `SSMCache` state cannot trim one token to recompute logits.
499
- After restart, an identical request restores the full cache, replays the saved
500
- tokens through the normal completion sink so the client receives one ordinary
501
- assistant response, then continues directly from the pending token with the
502
- original sampler step and history. The request key includes rendered prompt
503
- IDs and every sampling/KV policy field. Grammar, token-fill, media, paged KV,
504
- and logprob requests are not checkpointed in v1 because they carry additional
505
- state or response data that cannot yet be reconstructed by token replay.
506
-
507
- Ordinary continuation adds an optional request port to
508
- that same ordinary driver. Checkpoint lookup, keys and persistence are shared
509
- with the compatibility executor; the numerical driver supplies owned row
510
- snapshots at an emitted-prefix/pending-token boundary. Restored rows complete
511
- preparation without a target forward, before merging cold prefill rows, and
512
- recover sampler seed, history and sample index. The server binding qualifies
513
- ordinary requests through supported row-cache layouts, including recurrent Qwen
514
- and full-attention Llama/MiniCPM as well as Gemma4. It transfers owned row
515
- snapshots to a bounded persistence coordinator without awaiting disk in decode.
516
- Tensor serialization and file maintenance use the existing idle gate. Completion
517
- invalidates reuse immediately and queues cleanup after any in-flight write;
518
- new attempts cannot be erased by older cleanup. Cancellation retains eventual
519
- persistence, and explicit flush reports failed or pending durability. A hard
520
- process exit may lose queued intervals. Plain greedy retains the ordinary
521
- vectorized sampler when no history-dependent processors are active. Supported KV layouts retain their existing affine, per-layer and full-attention
522
- TurboQuant conversion policy. Requests remain without
523
- media, grammar, fill, paging, logprobs or speculative methods. Native tests
524
- compare interrupted/restored runs at the same batch geometry; arbitrary
525
- cohort changes are not an exact numerical identity guarantee. The HTTP fixture
526
- checks serial B1, shared B1 and shared B4 with identical seeded requests and
527
- held arrivals, and inspects the generated checkpoint before restart.
528
- When checkpointing is enabled, the existing total spill queue budget is split
529
- between prompt spills and continuation snapshots. Flush duration and pending/failed
530
- statistics cover both queues.
531
-
532
- Only the newest successful checkpoint for a request is retained. The old file
533
- remains valid until the new file's fsync+rename completes. Queue backpressure
534
- can leave more than one interval undurable; a crash loses work since the latest
535
- successful write. Normal completion immediately invalidates the checkpoint and
536
- queues its disk removal.
537
- Client cancellation throws through the serial token callback and preserves the
538
- last checkpoint, so terminating a long-lived client does not erase its restart
539
- point.
540
-
541
- Fresh-model compiled continuation preserves the device sampler's uint32 pending
542
- register. The Gemma model materializes its computed FP32 RoPE frequencies once
543
- before compiled graph capture: a restored request may skip the prefill that
544
- normally evaluates those constants. Capturing their unevaluated construction
545
- graph can change arithmetic on M1. The forced-token continuation regression
546
- compares a warmed model and a fresh model with cloned state, including every
547
- active cache byte and full logits across delayed KV conversion. Live delayed
548
- row layouts publish compact state through their extraction interface before
549
- persistence; they do not need storage codecs for the live wrapper itself.
550
-
551
- ### Method companion state
552
-
553
- `PrefixCache<State, Attachment>` is the shared consumer contract for ordinary
554
- and batched execution. It has no scheduling or storage-tier operations. The
555
- MLX binding supplies target cache layers plus optional `CheckpointAttachment`
556
- records containing a method schema, scalar metadata and tensor snapshots.
557
- The method owns their meaning and alignment; the shared cache owns retention.
558
- Method and drafter compatibility must be part of the namespace before a
559
- producer publishes these records. Storage does not interpret draft state as
560
- extra target-model layers.
561
-
562
- RAM accounting, eviction, idle demotion and the bounded SSD spill queue include
563
- companion tensors. Checkpoints containing them require an exact prefix
564
- boundary and preserve shorter fallback entries. Write-behind captures companion
565
- views together with target-cache views under the existing exclusive boundary.
566
- The writer persists them in the same atomic file and the same per-tensor write
567
- loop. Restore copies and verifies them through the existing tensor reader.
568
- Ordinary files retain v3 compatibility; older readers reject v4 files rather
569
- than restore an incomplete checkpoint.
570
-
571
- The storage implementation is in the working tree. Synthetic gates cover RAM
572
- ownership, divergent-prefix fallback, SSD eviction, fresh-process restore,
573
- corruption, queue drops/failures and durability on both development Macs.
574
- Qwen MTP now uses this interface for prefill snapshots; its private store and
575
- separate budget option are removed. `DraftSource.checkpoint` supplies the draft
576
- KV and pending true hidden row. The namespace includes a SHA-256 of draft
577
- configuration and shard bytes, resolved at provider load, plus the mounted
578
- adapter revision and KV policy. Model loading resolves that digest through
579
- `artifactIdentity`, independently of inference and scheduling. A memo key includes
580
- the configuration seed, sorted shard names, resolved paths, device/inode, size,
581
- and nanosecond modification/change times. Unchanged revisions reuse the exact
582
- existing SHA-256; a miss reads every shard byte. Same-size writes with restored
583
- mtime and changed symlink targets invalidate the memo. Rechecking revisions after
584
- hashing prevents persisting a digest under a revision that changed during reading.
585
-
586
- `ArtifactIdentityStore` owns memo retention and persistence. Its file adapter uses
587
- `~/.cache/mlx-bun/artifact-identities/`, keeps RAM hits and queues atomic file writes
588
- without awaiting them in model loading. Missing, malformed or unwritable memo
589
- storage does not prevent computing the identity. It stores only digests, not
590
- weights, prompts or KV. Qwen MTP, standalone, assistant, DeepSpec and DSpark use
591
- the same identity service; existing namespaces and SSD entries remain compatible.
592
- Tests cover byte identity, no weight reads after a fresh-store memo hit, file
593
- mutation, configuration changes, symlink replacement and write failure.
594
-
595
- Native bf16/KV4 gates on M1 Max and M4 Pro
596
- preserve live target/companion state hashes, emitted IDs and acceptance decisions
597
- through repeated RAM reuse and SSD cache/provider restart. SSD allocation
598
- padding is excluded from the live-state comparison. Generated-output acceptance
599
- is described below; long-conversation timing remains R17 work.
600
-
601
- The unreleased shared method now publishes completed row state before
602
- retirement through the same `PrefixCache.put` port. It records only inputs
603
- retained by committed target/draft rounds; an unprocessed correction or bonus
604
- token is absent from the key. The target row layout and draft provider supply
605
- their snapshots independently. Failed and cancelled consumers publish no
606
- generated checkpoint. Persistence continues through the existing queue.
607
-
608
- M1 and M4 native bf16, affine KV4 and fused TurboQuant K8/V3 tests cover four active
609
- rows, early retirement, exact sampled-ID coverage, unchanged retained bytes
610
- after sibling and follow-up work, shorter-prefix selection after an edit, and
611
- matching RAM/SSD continuation and acceptance. Existing paired-prefill tests
612
- also pass. A second native gate records actual sampled IDs for an echo tool
613
- turn with thinking off/on, passes them through the production output parsers,
614
- and renders the subsequent tool-result request with the same chat template.
615
- Both published checkpoints are exact token prefixes of that next request.
616
- A production HTTP gate now passes on both Macs for bf16, KV4 and fused K8/V3, with
617
- thinking off/on. The next tool-result request reuses generated content in RAM.
618
- After a durable flush, a separate server process restores the first turn's
619
- SSD state and reproduces the same response, logprobs and acceptance. The test
620
- copies only the first turn's flushed files before running the RAM continuation,
621
- so no later prefill checkpoint can explain the restart hit. All six cases
622
- pass, including the actual wire `reasoning` alias and template options.
623
- The five-file change is adopted alongside the captured prefill-policy fix.
624
- Native acceptance of that final composition, long saved Kanban replay,
625
- pressure and complete next-turn timing remain open.
626
- It does not resolve the older serial prototype's bf16 comparison with fresh
627
- full prefill, whose operation shapes differ from a generated history.
628
-
629
- ### 5.8 Invalidation
630
-
631
- Compatibility key = `configFingerprint(config)` + `KvScheme.cacheKey` +
632
- tokenizer hash (`Bun.hash(tokenizer.json)`) + adapter ns; all enforced on
633
- scan and load (`KvLoadExpect`). Chat-template drift degrades to a shorter
634
- matched prefix (safe). GLM compressed caches additionally validate
635
- geometry (`kvLoraRank`, `ropeHeadDim`, `dsaHeadDim`, `maxTokens`) against
636
- the model's prototype cache before opening the tensor mmap.
637
-
638
- ### 5.9 Measured (kept with labels)
639
-
640
- - 2026-07-02, M1 Max, MiniCPM5, 13.7k-token prefix, `kv_config` quant:
641
- restart TTFT 12,083 ms (full prefill) → 236 ms restored; steady-state
642
- decode 192.0 vs 192.3 tok/s (flag off/on). Restored + trimmed
643
- continuation identical to the RAM tier's cache-hit output.
644
- - 2026-07-07, streamed-copy restore, 512 MB synthetic entry: mlx peak
645
- during restore + first step = live entry + one tensor (552 vs 520 MB
646
- active); `vmmap` shows no `.mlxkv` mapping survives; 12B real-model cold
647
- cache-load → first token 277 ms.
648
- - 2026-07-07, e4b 9.5k-token entry: repeat decode 37.9 tok/s with the
649
- flush overlapping vs 47.1 cold; flat 44–46 across three samples after
650
- the idle gate.
651
- - 2026-08-22, unified e4b, dirty machine (correctness evidence only):
652
- 4,026-token prompt flushed in 408 ms with `durable=true`, then 4,025
653
- cached tokens restored after SIGTERM + respawn.
654
-
655
- ### 5.9 Open items
656
-
657
- - Full clean 16k e4b / 12B / Qwen3.8 restart matrix (batch 8 and
658
- `--batch 1`, `cached_tokens ≥ prompt_tokens - 1`) is the acceptance run
659
- for the durability fix; only the dirty-machine smoke has run.
660
- - P4 hardening extras never closed: kill-during-write crash test; a
661
- `THIRD_PARTY_LICENSES.md` note if any oMLX-derived code (not just ideas)
662
- ever lands.
663
- - Block-granular dedup for multi-user shared prefixes stays out of scope
664
- (D1); revisit only with the paged follow-ups in 6.4.
665
- - Stale comments: `prompt-cache.ts` still describes restore as zero-copy
666
- mmap (5.2).
667
-
668
- ## 6. Optional paged KV (`src/lab/paged-kv/paged-kv.ts`, `--paged-kv`)
669
-
670
- Default off; with the flag unset no paged code executes and the plain
671
- `KVCache`/`RotatingKVCache` path is byte-identical.
672
-
673
- ### 6.1 What it buys here, honestly
674
-
675
- vLLM's PagedAttention solves contiguous worst-case per-sequence
676
- reservation and allocator fragmentation for a multi-tenant server.
677
- mlx-bun is one process on unified memory with byte-budget admission
678
- already shipped, so what transfers is: (a) padded-batch waste removal —
679
- today's batched buffer width tracks the longest live row
680
- (`mergeKVRows`/`extendKVRows`), so a short row cohabiting with a long one
681
- pays the long one's KV; and (b) block-level copy-on-write prefix sharing.
682
- Persistent pools now stay separate per request under the shared executor.
683
- Attention still gathers and pads a temporary batch tensor; block-level prefix
684
- sharing and direct paged attention remain open. At batch one, the gather adds
685
- a copy. Paging remains optional and does not imply a speed improvement.
686
-
687
- This is neither of the two paging rejections already on record:
688
- paged KV as a *prompt-cache substitute* (rejected 2026-07-07 — `take()`'s
689
- zero-copy clones already share physically) and paged *blocks* as the SSD
690
- spill granularity (D1). It is the rung-3 allocation abstraction from
691
- docs/design/batching.md.
692
-
693
- No external oracle exists (mlx-lm's `cache.py` has no paged cache); the
694
- gate is mlx-bun's own plain `KVCache`, valid because the claim is storage
695
- equivalence (same bytes, different arrangement), so the bar is bit-exact
696
- (tol 0): `tests/paged-kv.test.ts` (model-free: block-boundary crossing,
697
- free-list reuse after trim, typed exhaustion, gathered fetch vs a plain
698
- reference, dispose) and `tests/paged-kv-parity.test.ts` (weights-gated
699
- greedy trajectory paged-on vs paged-off).
700
-
701
- ### 6.2 Mechanism
702
-
703
- - **`BlockPool`** — per-layer arena: K and V pool tensors
704
- `[numBlocks, H_kv, blockSize, headDim]` (V may differ in head dim),
705
- `ops.zeros`-allocated so they are mlx-owned end to end (no host-pointer
706
- alignment/dtor hazards). LIFO free list; `alloc()` throws the typed
707
- `PagedPoolExhausted`; writes are `ops.sliceUpdate` at
708
- `[block, 0, within, 0]` — the incoming `[1,H,l,D]` piece matches the
709
- destination slice directly.
710
- - **`PagedKVCache implements Cache`** — standalone (not a `KVCache`
711
- subclass). The cache-layout binding supplies `PagedKvRows` for dynamic
712
- membership, masks and per-row positions. `updateAndFetch` splits the
713
- incoming `L` along block boundaries, allocates tail blocks as reached,
714
- then gathers occupied blocks with `ops.takeAxis(pool, blockTable, 0)` →
715
- transpose `[1,0,2,3]` → reshape `[1,H,nb·bs,D]` → slice to `offset`. Only
716
- existing bound ops — no `mlx_gather` FFI binding was needed. `makeMask`
717
- is `KVCache.makeMask`'s logic; `trim(n)` rewinds `offset` and frees
718
- now-unoccupied tail blocks (stale bytes past `offset` are never read —
719
- the `KVCache` padding invariant, block-shaped). Block table is a
720
- host-side `number[]`; all intermediates are bound and disposed.
721
- - Pool lazily allocates on the first write (head count/dims/dtype from
722
- the first k/v pair) and is sized once from `capacityTokens` =
723
- prompt + `max_tokens`, so exhaustion is unreachable absent an
724
- accounting bug — it exists as a tripwire, never silent truncation.
725
- - **Wiring** — `maybePageKv(cache, options, capacityTokens)`
726
- (`backends/mlx/request-state-policy.ts`, re-exported by `generate.ts`)
727
- mirrors `maybeQuantizeKv`'s post-construction in-place
728
- swap but runs *once before prefill*: paging changes layout, not
729
- arithmetic, so there is no "convert when populated" trigger. Only fresh
730
- (`offset === 0`) plain `KVCache` entries are replaced; rotating layers
731
- keep their scheme — mixed paged-full + rotating-sliding is the supported
732
- shape. Default block size 256 = `KVCache.STEP`, so v1's growth
733
- granularity is a permutation of today's into reusable slots, not a new
734
- tuning axis.
735
-
736
- ### 6.3 Gates (explicit refusals, never silent downgrades)
737
-
738
- - Startup (`server.ts`): `--paged-kv` with any `--kv-quant`
739
- (affine or TurboQuant), `--draft-model`, or a non-`gemma4*` model exits
740
- with a clear message; `--paged-kv-block-size` must be a positive
741
- integer; `--ssd-cache` alongside it warns that the tier sees nothing.
742
- Paging preserves the selected batch size. Env `MLX_BUN_PAGED_KV=1`
743
- is equivalent to the flag.
744
- - Request scope: media (vision/audio) and LoRA-adapter requests strip the
745
- flag and run the plain cache path (v1 non-goal cells, never a 400). One
746
- effective `pagedKv` value per request keeps the strip and the
747
- prompt-cache bypass coherent.
748
- - Both execution modes: paged requests skip `take`/`put` and the boundary
749
- snapshot; caches are disposed on completion. Spec eligibility also
750
- excludes `options.pagedKv` (belt on top of the startup refusal).
751
- - Compiled decode: `CompiledDecode.supports()` excludes `PagedKVCache`
752
- automatically (not one of the four supported classes) — a
753
- data-dependent block-list length is the shape shapeless replay already
754
- broke on.
755
- - Shared execution: `PagedKvRows` owns independent block pools and immutable
756
- row snapshots. A request-state policy creates the storage and supplies no
757
- reusable-prefix store. Its opaque compatibility key keeps incompatible
758
- layouts in separate cohorts without making scheduling inspect paging flags.
759
-
760
- ### 6.4 Follow-ups (dependency order) and open items
761
-
762
- 1. Direct paged attention and block sharing — row integration now uses the
763
- shared layout interface with independent per-request pools. Attention still
764
- gathers padded contiguous inputs. Measure block-level shared arenas and
765
- direct reads before replacing this compatibility implementation. Tracked under the plan anchor (Phase 18
766
- S3+).
767
- 2. Block-level CoW prefix sharing — refcounted block table, fork on
768
- divergent write; extends `PromptCache`'s entry-level ref-counting down
769
- to blocks.
770
- 3. Quantized paged blocks — dtype-parametric block descriptor sharing one
771
- block-index space.
772
- 4. Fused paged-attention Metal kernel (optiq's `sdpa_2pass_paged` is the
773
- port source) — kills the per-step gather copy.
774
- - Perf disclosure: paged-on vs paged-off decode/prefill on the quiet
775
- reference machine has not been recorded in `docs/reference/benchmarks.md`
776
- (expected: a small decode regression at batch=1, to be reported plainly).
777
- - `PagedKVCache` has no `signature()` (2.4) and no kv-store codec; both
778
- are prerequisites for any prompt-cache or SSD integration.
779
-
780
- ## History
781
-
782
- - 2026-06-14 — `BatchedRotatingCache` (sliding-window batching) landed.
783
- - 2026-07-02 — SSD cold tier P1–P3 landed: kv-store v2 (all cache kinds,
784
- invalidation metadata, atomic writes), `SsdCacheStore`, tier
785
- integration, flags.
786
- - 2026-07-04 — prompt-boundary snapshot (multi-turn agent reuse fix).
787
- - 2026-07-05 — non-consuming prefix sharing in `take()`; Layer-0 tiering
788
- moved inside `PromptCache.take()` (both lanes); idle demotion
789
- (`--ssd-demote-idle`).
790
- - 2026-07-06 — TurboQuant v1 cache class + kv-store `turboquant` kind;
791
- RAM cap default 2 GB → 8 GB; non-blocking `spillOwned` spills; `find()`
792
- trimmability gate (restart-0 defect).
793
- - 2026-07-07 — streamed-copy restore + zero-copy write view (A7 RSS);
794
- idle-gated write-behind (`MLX_BUN_SSD_WRITEBEHIND`); bounded
795
- `SpillQueue` (`MLX_BUN_SSD_SPILL_QUEUE_GB`); admission-safe snapshot
796
- timer.
797
- - Phase 21 (closed 2026-08-17) — GLM `mla` / `mla-dsa` / `mtp-mla`
798
- kv-store kinds.
799
- - 2026-08-21 — `KvScheme` seam: immutable scheme drives conversion,
800
- admission billing, SSD key, and batch placement.
801
- - 2026-08-22 — agg×4 regression root-caused to the unsigned
802
- `BatchedRotatingCache` (capability-only routing fix, signature added);
803
- `SsdDurabilityCoordinator`, `POST /admin/cache/flush`, graceful
804
- SIGINT/SIGTERM flush.
805
- - Paged KV v1 (serial, Gemma4, bf16, gather-then-SDPA) landed with
806
- bit-exact gates; flag rows in cli.md / server-config.md (landing date
807
- not recorded in the source doc, which still reads "in progress").
808
-
809
- ## Minimum reusable offsets
810
-
811
- Physical trimmability does not prove that an older prefix has a valid
812
- numerical representation. Caches may expose `minimumReusableOffset` after
813
- an irreversible transition, such as delayed TurboQuant conversion. Prefix
814
- selection requires the matched offset to meet every layer's bound. A
815
- trimmable descendant supersedes an ancestor only when it can serve that
816
- ancestor's offset; otherwise the earlier donor remains.
817
-
818
- Cloning and per-row extraction preserve this state metadata. New SSD
819
- headers record it, and indexing, durable-prefix checks and restore use the
820
- same rule as RAM. Legacy TurboQuant headers without a recorded boundary
821
- are conservatively reusable from their full stored offset only. Other
822
- legacy cache kinds retain their existing interpretation. This changes
823
- reuse eligibility, not scheduling, quantization arithmetic or memory
824
- admission policy.