mlx-bun 0.5.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 (640) hide show
  1. package/README.md +1449 -109
  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 +7 -5
  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 +10 -10
  78. package/src/{serve → server}/chat-stage.ts +14 -14
  79. package/src/{client.ts → server/client.ts} +28 -2
  80. package/src/{serve → server}/completion-executor.ts +33 -41
  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 +25 -82
  85. package/src/{serve → server}/http.ts +25 -13
  86. package/src/{serve → server}/inference-request.ts +5 -5
  87. package/src/server/job-routes.ts +20 -0
  88. package/src/server/media-prompt.ts +63 -0
  89. package/src/server/memory-completion-client.ts +46 -0
  90. package/src/server/model-routes.ts +192 -0
  91. package/src/{serve → server}/openai-wire.ts +3 -2
  92. package/src/{serve → server}/pipeline.ts +1 -1
  93. package/src/server/prompt-builder.ts +13 -0
  94. package/src/server/prompt-contracts.ts +17 -0
  95. package/src/server/proxy-routes.ts +187 -0
  96. package/src/server/publishing-routes.ts +41 -0
  97. package/src/{serve → server}/request-plan.ts +6 -8
  98. package/src/{serve → server}/request-prep.ts +47 -50
  99. package/src/{serve → server}/responses-client.ts +30 -13
  100. package/src/{responses.ts → server/responses.ts} +12 -93
  101. package/src/server/routes.ts +205 -0
  102. package/src/server/start.ts +72 -0
  103. package/src/server/status-routes.ts +108 -0
  104. package/src/{serve → server}/text-completion-stage.ts +25 -4
  105. package/src/server/text-prompt.ts +12 -0
  106. package/src/{serve → server}/token-streams.ts +36 -38
  107. package/src/server/worker-routes.ts +311 -0
  108. package/src/storage/paths.ts +61 -0
  109. package/src/web/assets.ts +37 -0
  110. package/src/web/{src → browser}/api.ts +5 -23
  111. package/src/web/browser/chat-host.ts +17 -0
  112. package/src/web/browser/installed-panels.ts +6 -0
  113. package/src/web/browser/main.ts +55 -0
  114. package/src/web/browser/markdown.ts +279 -0
  115. package/src/web/browser/memory-panel.ts +9 -0
  116. package/src/web/browser/palette.ts +64 -0
  117. package/src/web/browser/protocol.ts +13 -0
  118. package/src/web/browser/shell.ts +467 -0
  119. package/src/web/{src → browser}/status.ts +32 -36
  120. package/src/web/build.ts +51 -0
  121. package/src/web/public/app.html +1054 -0
  122. package/src/web/{sw.js → public/sw.js} +4 -3
  123. package/src/web/{vendor → public/vendor}/hljs-theme.css +2 -2
  124. package/src/web/{vendor → public/vendor}/hljs.js +2 -2
  125. package/THIRD_PARTY_LICENSES.md +0 -51
  126. package/docs/README.md +0 -107
  127. package/docs/archive/hlg-sampling.md +0 -350
  128. package/docs/archive/investigations/audio-input-plan.md +0 -356
  129. package/docs/archive/investigations/benchmark-ledger-through-v0.4.0.md +0 -3617
  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/release-notes-v0.4.0.md +0 -45
  174. package/docs/archive/planning/release-notes-v0.5.0.md +0 -37
  175. package/docs/archive/planning/web-ui-pass-plan.md +0 -213
  176. package/docs/archive/planning/website-readme-pass-plan.md +0 -224
  177. package/docs/design/batching.md +0 -2054
  178. package/docs/design/decode-speed-program.md +0 -2404
  179. package/docs/design/dreaming-nightly-pipeline.md +0 -684
  180. package/docs/design/generic-model-support.md +0 -821
  181. package/docs/design/kv-cache.md +0 -993
  182. package/docs/design/orpo-training.md +0 -680
  183. package/docs/design/speculative-decoding.md +0 -2129
  184. package/docs/design/turboquant.md +0 -2225
  185. package/docs/design/unified-engine-frontier-plan.md +0 -1987
  186. package/docs/design/web-chat-redesign.md +0 -695
  187. package/docs/planning/PRODUCT_ROADMAP.md +0 -593
  188. package/docs/planning/ResearchTopics.md +0 -119
  189. package/docs/planning/public-readiness-handoff.md +0 -244
  190. package/docs/planning/public-readiness.md +0 -149
  191. package/docs/planning/video-series.md +0 -421
  192. package/docs/reference/benchmarks.md +0 -3587
  193. package/docs/reference/cli.md +0 -646
  194. package/docs/reference/distribution.md +0 -380
  195. package/docs/reference/environment.md +0 -494
  196. package/docs/reference/glossary.md +0 -39
  197. package/docs/reference/library-api.md +0 -688
  198. package/docs/reference/memory.md +0 -260
  199. package/docs/reference/models.md +0 -346
  200. package/docs/reference/server-api.md +0 -1945
  201. package/docs/reference/server-config.md +0 -973
  202. package/docs/reference/training.md +0 -462
  203. package/docs/reference/troubleshooting.md +0 -158
  204. package/src/admission-plan.ts +0 -18
  205. package/src/audio/audiotoolbox.ts +0 -112
  206. package/src/audio/conformer.ts +0 -655
  207. package/src/audio/decode.ts +0 -133
  208. package/src/audio/features.ts +0 -202
  209. package/src/audio/mic-capture.ts +0 -98
  210. package/src/audio/silero-vad.ts +0 -379
  211. package/src/audio/transcode.ts +0 -67
  212. package/src/audio/whisper-decode.ts +0 -774
  213. package/src/audio/whisper-fast.ts +0 -732
  214. package/src/audio/whisper-format.ts +0 -73
  215. package/src/audio/whisper-mel.ts +0 -185
  216. package/src/audio/whisper-timing.ts +0 -274
  217. package/src/audio/whisper-tokenizer.ts +0 -239
  218. package/src/audio/whisper-transcribe.ts +0 -410
  219. package/src/backends/mlx/assistant-target.ts +0 -46
  220. package/src/backends/mlx/autoregressive-draft-rows.ts +0 -120
  221. package/src/backends/mlx/autoregressive.ts +0 -129
  222. package/src/backends/mlx/batch-group.ts +0 -1874
  223. package/src/backends/mlx/cache-attachment.ts +0 -67
  224. package/src/backends/mlx/cache-layout.ts +0 -31
  225. package/src/backends/mlx/checkpoint-state.ts +0 -53
  226. package/src/backends/mlx/continuation-persistence.ts +0 -107
  227. package/src/backends/mlx/continuation-request.ts +0 -43
  228. package/src/backends/mlx/continuation.ts +0 -77
  229. package/src/backends/mlx/diffusion.ts +0 -39
  230. package/src/backends/mlx/draft-target.ts +0 -32
  231. package/src/backends/mlx/encoder-cache.ts +0 -25
  232. package/src/backends/mlx/evaluation.ts +0 -105
  233. package/src/backends/mlx/fill-append.ts +0 -58
  234. package/src/backends/mlx/fill-group.ts +0 -497
  235. package/src/backends/mlx/full-kv-row-append.ts +0 -32
  236. package/src/backends/mlx/full-kv-row-donor.ts +0 -54
  237. package/src/backends/mlx/full-transitioning-kv-rows.ts +0 -65
  238. package/src/backends/mlx/gateway-binding.ts +0 -174
  239. package/src/backends/mlx/graph.ts +0 -38
  240. package/src/backends/mlx/kv-codec.ts +0 -9
  241. package/src/backends/mlx/kv-maintenance.ts +0 -161
  242. package/src/backends/mlx/kv-tensor-rows.ts +0 -212
  243. package/src/backends/mlx/memory-client.ts +0 -189
  244. package/src/backends/mlx/methods.ts +0 -90
  245. package/src/backends/mlx/mixed-iteration.ts +0 -62
  246. package/src/backends/mlx/model-prompt.ts +0 -178
  247. package/src/backends/mlx/model-serving.ts +0 -80
  248. package/src/backends/mlx/paged-cache-codec.ts +0 -27
  249. package/src/backends/mlx/prefill-cohort.ts +0 -100
  250. package/src/backends/mlx/prefill-policy.ts +0 -26
  251. package/src/backends/mlx/prefill-rows.ts +0 -244
  252. package/src/backends/mlx/prefill.ts +0 -33
  253. package/src/backends/mlx/projected-context-rows.ts +0 -72
  254. package/src/backends/mlx/prompt-input.ts +0 -29
  255. package/src/backends/mlx/qwen-prompt-input.ts +0 -40
  256. package/src/backends/mlx/request-policy.ts +0 -20
  257. package/src/backends/mlx/request-state-policy.ts +0 -54
  258. package/src/backends/mlx/rollback.ts +0 -92
  259. package/src/backends/mlx/row-sampling.ts +0 -49
  260. package/src/backends/mlx/serial-executor.ts +0 -251
  261. package/src/backends/mlx/speculative-group.ts +0 -438
  262. package/src/backends/mlx/speculative-round.ts +0 -161
  263. package/src/backends/mlx/speculative.ts +0 -131
  264. package/src/backends/mlx/state-rows.ts +0 -104
  265. package/src/backends/mlx/state-views.ts +0 -46
  266. package/src/backends/mlx/target-layout-capability.ts +0 -35
  267. package/src/backends/mlx/text-engine.ts +0 -73
  268. package/src/backends/mlx/transitioning-kv-rows.ts +0 -173
  269. package/src/chat-template.ts +0 -382
  270. package/src/cli.ts +0 -3490
  271. package/src/config.ts +0 -399
  272. package/src/contracts/completion.ts +0 -16
  273. package/src/contracts/execution.ts +0 -70
  274. package/src/contracts/generation.ts +0 -103
  275. package/src/contracts/host.ts +0 -7
  276. package/src/contracts/object-cache.ts +0 -8
  277. package/src/contracts/pi-provider.ts +0 -30
  278. package/src/contracts/pi.ts +0 -261
  279. package/src/contracts/prefix-cache.ts +0 -33
  280. package/src/contracts/resources.ts +0 -25
  281. package/src/contracts/sampling.ts +0 -23
  282. package/src/contracts/scheduling.ts +0 -54
  283. package/src/contracts/tsconfig.json +0 -14
  284. package/src/dataset/generators.ts +0 -1087
  285. package/src/dataset/index.ts +0 -12
  286. package/src/dataset/job.ts +0 -81
  287. package/src/dataset/llm.ts +0 -71
  288. package/src/dataset/registry.ts +0 -528
  289. package/src/diffusion/diffusion-generate.ts +0 -507
  290. package/src/download.ts +0 -505
  291. package/src/embed.ts +0 -77
  292. package/src/engine/admission.ts +0 -59
  293. package/src/engine/cancellation.ts +0 -38
  294. package/src/engine/engine.ts +0 -56
  295. package/src/engine/execution-coordinator.ts +0 -70
  296. package/src/engine/execution-plan.ts +0 -64
  297. package/src/engine/execution-tasks.ts +0 -44
  298. package/src/engine/namespaced-cache.ts +0 -13
  299. package/src/engine/resources.ts +0 -53
  300. package/src/engine/scheduler.ts +0 -92
  301. package/src/engine/session.ts +0 -247
  302. package/src/eval/README.md +0 -130
  303. package/src/eval/ifeval.ts +0 -81
  304. package/src/eval/kl-prompts.ts +0 -29
  305. package/src/eval/kl.ts +0 -590
  306. package/src/eval/perplexity.ts +0 -164
  307. package/src/eval/quality-db.ts +0 -92
  308. package/src/eval/reference-logits.ts +0 -207
  309. package/src/eval/runner.ts +0 -124
  310. package/src/eval/score.ts +0 -49
  311. package/src/eval/tasks/bfcl.ts +0 -535
  312. package/src/eval/tasks/gsm8k.ts +0 -98
  313. package/src/eval/tasks/hashhop.ts +0 -139
  314. package/src/eval/tasks/humaneval.ts +0 -275
  315. package/src/eval/tasks/ifeval.ts +0 -525
  316. package/src/eval/tasks/mmlu.ts +0 -157
  317. package/src/eval/ultrafeedback.ts +0 -60
  318. package/src/evaldb.ts +0 -199
  319. package/src/expert-io.ts +0 -393
  320. package/src/expert-offload-build.ts +0 -77
  321. package/src/expert-offload.ts +0 -76
  322. package/src/expert-residency.ts +0 -1134
  323. package/src/expert-usage.ts +0 -415
  324. package/src/fill/echo-index.ts +0 -188
  325. package/src/fill/fill-session.ts +0 -463
  326. package/src/fill/proposal.ts +0 -68
  327. package/src/fill/schema-rows.ts +0 -506
  328. package/src/fill/tool-boundary.ts +0 -103
  329. package/src/fit.ts +0 -249
  330. package/src/generate.ts +0 -1352
  331. package/src/grammar.ts +0 -604
  332. package/src/harness-pi.ts +0 -184
  333. package/src/hf-push.ts +0 -628
  334. package/src/hub-rest.ts +0 -242
  335. package/src/index.ts +0 -63
  336. package/src/inference/denoising.ts +0 -17
  337. package/src/inference/draft-acceptance.ts +0 -41
  338. package/src/inference/draft-output.ts +0 -49
  339. package/src/inference/graph.ts +0 -33
  340. package/src/inference/prefill.ts +0 -45
  341. package/src/inference/rollback.ts +0 -7
  342. package/src/jobs/index.ts +0 -31
  343. package/src/jobs/job-entry.ts +0 -107
  344. package/src/jobs/task-client.ts +0 -19
  345. package/src/jobs/types.ts +0 -2
  346. package/src/kv-scheme.ts +0 -279
  347. package/src/kv-store.ts +0 -1135
  348. package/src/lab/README.md +0 -22
  349. package/src/lab/curve/curve-designer.html +0 -303
  350. package/src/lab/curve/curve-sampler.ts +0 -122
  351. package/src/lab/expert-trace/expert-trace.ts +0 -69
  352. package/src/lab/paged-kv/paged-attention.ts +0 -117
  353. package/src/lab/paged-kv/paged-kv-rows.ts +0 -111
  354. package/src/lab/paged-kv/paged-kv.ts +0 -336
  355. package/src/library.ts +0 -40
  356. package/src/lora.ts +0 -408
  357. package/src/md-modules.d.ts +0 -54
  358. package/src/media-fetch.ts +0 -442
  359. package/src/memory/article.ts +0 -558
  360. package/src/memory/chunk-validation.ts +0 -150
  361. package/src/memory/chunk.ts +0 -318
  362. package/src/memory/cluster.ts +0 -351
  363. package/src/memory/coverage.ts +0 -134
  364. package/src/memory/crosslink.ts +0 -0
  365. package/src/memory/db.ts +0 -253
  366. package/src/memory/entity.ts +0 -203
  367. package/src/memory/gate.ts +0 -187
  368. package/src/memory/history.ts +0 -152
  369. package/src/memory/ingest.ts +0 -335
  370. package/src/memory/model.ts +0 -183
  371. package/src/memory/normalize.ts +0 -671
  372. package/src/memory/parse.ts +0 -22
  373. package/src/memory/pipeline.ts +0 -275
  374. package/src/memory/prompts.ts +0 -65
  375. package/src/memory/query.ts +0 -334
  376. package/src/memory/reconcile.ts +0 -734
  377. package/src/memory/redirect.ts +0 -108
  378. package/src/memory/reindex.ts +0 -325
  379. package/src/memory/resolve.ts +0 -307
  380. package/src/memory/rest.ts +0 -245
  381. package/src/memory/route.ts +0 -272
  382. package/src/memory/schedule.ts +0 -165
  383. package/src/memory/stages.ts +0 -777
  384. package/src/memory/synthesize.ts +0 -1638
  385. package/src/memory/tools.ts +0 -749
  386. package/src/memory/vault.ts +0 -1194
  387. package/src/memory/wikify.ts +0 -1064
  388. package/src/mlx/array.ts +0 -425
  389. package/src/mlx/autograd.ts +0 -304
  390. package/src/mlx/checkpoint.ts +0 -149
  391. package/src/mlx/compile.ts +0 -168
  392. package/src/mlx/custom-vjp.ts +0 -188
  393. package/src/mlx/ffi.ts +0 -531
  394. package/src/mlx/materialize.ts +0 -23
  395. package/src/mlx/metal-kernel.ts +0 -159
  396. package/src/mlx/normalized-argmax.ts +0 -116
  397. package/src/mlx/ops.ts +0 -1108
  398. package/src/mlx/quantized-rows.ts +0 -36
  399. package/src/mlx/token-bitmask.ts +0 -52
  400. package/src/mlx/turboquant-kv-decode.ts +0 -82
  401. package/src/mlx/turboquant-kv-inverse.ts +0 -122
  402. package/src/mlx/turboquant-ops.ts +0 -861
  403. package/src/mlx/turboquant-tables.ts +0 -237
  404. package/src/mmap.ts +0 -90
  405. package/src/model/artifact-identity.ts +0 -73
  406. package/src/model/batched-kv.ts +0 -235
  407. package/src/model/batched-mask.ts +0 -279
  408. package/src/model/batched-quant.ts +0 -212
  409. package/src/model/batched-quantized-kv.ts +0 -144
  410. package/src/model/batched-rotating-quant.ts +0 -353
  411. package/src/model/batched-rotating-state.ts +0 -149
  412. package/src/model/batched-rotating.ts +0 -338
  413. package/src/model/batched-row-storage.ts +0 -179
  414. package/src/model/batched-ssm.ts +0 -52
  415. package/src/model/batched-turboquant-kv.ts +0 -94
  416. package/src/model/compiled-decode.ts +0 -712
  417. package/src/model/decoded-kv-donor.ts +0 -12
  418. package/src/model/delayed-quantized-kv.ts +0 -72
  419. package/src/model/delayed-rotating-quantized-kv.ts +0 -93
  420. package/src/model/delayed-turboquant-kv.ts +0 -57
  421. package/src/model/diffusion-gemma.ts +0 -1026
  422. package/src/model/factory.ts +0 -215
  423. package/src/model/fingerprint.ts +0 -45
  424. package/src/model/flash-attention.ts +0 -694
  425. package/src/model/full-prefill-padding.ts +0 -56
  426. package/src/model/full-prefill-row.ts +0 -105
  427. package/src/model/gemma4-base.ts +0 -2370
  428. package/src/model/gemma4.ts +0 -1395
  429. package/src/model/generated/gemma4-12b.ts +0 -429
  430. package/src/model/generated/gemma4-26b.ts +0 -448
  431. package/src/model/generated/gemma4-e4b.ts +0 -717
  432. package/src/model/generated/index.ts +0 -18
  433. package/src/model/glm52-atlas.ts +0 -511
  434. package/src/model/glm52-cache.ts +0 -792
  435. package/src/model/glm52-config.ts +0 -262
  436. package/src/model/glm52-container.ts +0 -305
  437. package/src/model/glm52-coupling.ts +0 -388
  438. package/src/model/glm52-dsa.ts +0 -606
  439. package/src/model/glm52-expert-layout.ts +0 -253
  440. package/src/model/glm52-layout.ts +0 -152
  441. package/src/model/glm52-memory.ts +0 -500
  442. package/src/model/glm52-mla.ts +0 -792
  443. package/src/model/glm52-moe.ts +0 -223
  444. package/src/model/glm52-pilot.ts +0 -537
  445. package/src/model/glm52-reference.ts +0 -445
  446. package/src/model/glm52-residency.ts +0 -557
  447. package/src/model/glm52-resident-weights.ts +0 -135
  448. package/src/model/glm52-streamed-experts.ts +0 -453
  449. package/src/model/glm52-streamed-metal.ts +0 -610
  450. package/src/model/glm52-weights.ts +0 -503
  451. package/src/model/glm52.ts +0 -1132
  452. package/src/model/implementation.ts +0 -69
  453. package/src/model/kv-attention-view.ts +0 -70
  454. package/src/model/minicpm5.ts +0 -376
  455. package/src/model/profile.ts +0 -468
  456. package/src/model/quantized-append-attention.ts +0 -31
  457. package/src/model/qwen-append.ts +0 -12
  458. package/src/model/qwen-conv.ts +0 -52
  459. package/src/model/qwen3-delta-state.ts +0 -74
  460. package/src/model/qwen3-delta.ts +0 -451
  461. package/src/model/qwen3-moe.ts +0 -404
  462. package/src/model/qwen3-mrope.ts +0 -250
  463. package/src/model/qwen3.ts +0 -277
  464. package/src/model/qwen3_5-checkpoint.ts +0 -164
  465. package/src/model/qwen3_5.ts +0 -881
  466. package/src/model/rotating-kv-donor.ts +0 -35
  467. package/src/model/rotating-kv-layout.ts +0 -262
  468. package/src/model/rotating-row-transaction.ts +0 -35
  469. package/src/model/speculative-rotating-kv.ts +0 -101
  470. package/src/model/ssm-prefill-padding.ts +0 -39
  471. package/src/model/support.ts +0 -110
  472. package/src/model/token-groups.ts +0 -66
  473. package/src/model/trellis-balanced-scatter.ts +0 -51
  474. package/src/model/trellis-linear.ts +0 -672
  475. package/src/model/trellis-shared-m.ts +0 -151
  476. package/src/model/trellis-shared-scatter.ts +0 -144
  477. package/src/model/trellis-splitk-prefill.ts +0 -158
  478. package/src/model/trellis-tiled-prefill.ts +0 -136
  479. package/src/model/trellis-vector-expand.ts +0 -62
  480. package/src/model/trellis-wide-prefill.ts +0 -94
  481. package/src/model/turboquant-codec.ts +0 -89
  482. package/src/model/universal/archs.ts +0 -332
  483. package/src/model/universal/dense.ts +0 -612
  484. package/src/model/universal/modules.ts +0 -170
  485. package/src/model/universal/rope.ts +0 -381
  486. package/src/model/whisper.ts +0 -435
  487. package/src/native/expert_io.c +0 -523
  488. package/src/native/frame_extract.swift +0 -140
  489. package/src/native/mic_capture.swift +0 -92
  490. package/src/native-pack.ts +0 -163
  491. package/src/pi-provider.ts +0 -53
  492. package/src/pi-session.ts +0 -81
  493. package/src/pi-terminal.ts +0 -274
  494. package/src/pi-web.ts +0 -1940
  495. package/src/preflight.ts +0 -158
  496. package/src/prompt-cache.ts +0 -682
  497. package/src/quantize/allocator.ts +0 -627
  498. package/src/quantize/atomic-output.ts +0 -38
  499. package/src/quantize/calibration.ts +0 -181
  500. package/src/quantize/config-writer.ts +0 -145
  501. package/src/quantize/index.ts +0 -84
  502. package/src/quantize/job.ts +0 -162
  503. package/src/quantize/quantizer.ts +0 -666
  504. package/src/quantize/rotate.ts +0 -603
  505. package/src/quantize/safetensors-writer.ts +0 -249
  506. package/src/quantize/sensitivity.ts +0 -216
  507. package/src/quantize/trellis.ts +0 -586
  508. package/src/quantize/weight-transform.ts +0 -292
  509. package/src/registry.ts +0 -602
  510. package/src/runtime-config.ts +0 -77
  511. package/src/safetensors.ts +0 -165
  512. package/src/sampler.ts +0 -1012
  513. package/src/serve/admin-routes.ts +0 -184
  514. package/src/serve/assets/curve-terrain.html +0 -105
  515. package/src/serve/assets/training-inference-map.html +0 -575
  516. package/src/serve/audio-routes.ts +0 -323
  517. package/src/serve/aux-routes.ts +0 -132
  518. package/src/serve/batch-scheduler.ts +0 -8
  519. package/src/serve/checkpoint-identity.ts +0 -64
  520. package/src/serve/generation-gateway.ts +0 -537
  521. package/src/serve/isolate.ts +0 -609
  522. package/src/serve/kv-budget.ts +0 -14
  523. package/src/serve/lab-routes.ts +0 -341
  524. package/src/serve/lane-registry.ts +0 -45
  525. package/src/serve/model-admin-routes.ts +0 -155
  526. package/src/serve/model-binding.ts +0 -49
  527. package/src/serve/model-host.ts +0 -511
  528. package/src/serve/parent-application.ts +0 -56
  529. package/src/serve/prompt-response-trace.ts +0 -161
  530. package/src/serve/response-lease.ts +0 -24
  531. package/src/serve/session-search.ts +0 -326
  532. package/src/serve/static-routes.ts +0 -101
  533. package/src/serve/transcription-server.ts +0 -64
  534. package/src/serve/transcription-service.ts +0 -502
  535. package/src/server.ts +0 -1282
  536. package/src/spec/assistant-rows.ts +0 -93
  537. package/src/spec/assistant-source.ts +0 -126
  538. package/src/spec/cache-identity.ts +0 -22
  539. package/src/spec/deepspec-rows.ts +0 -29
  540. package/src/spec/deepspec-source.ts +0 -145
  541. package/src/spec/dflash-source.ts +0 -149
  542. package/src/spec/drafter.ts +0 -304
  543. package/src/spec/dspark/ab-stats.ts +0 -136
  544. package/src/spec/dspark/calibration.ts +0 -112
  545. package/src/spec/dspark/data-dflash.ts +0 -156
  546. package/src/spec/dspark/data.ts +0 -201
  547. package/src/spec/dspark/deepspec-module.ts +0 -776
  548. package/src/spec/dspark/generate-dflash.ts +0 -245
  549. package/src/spec/dspark/generate.ts +0 -290
  550. package/src/spec/dspark/loader.ts +0 -23
  551. package/src/spec/dspark/loss.ts +0 -213
  552. package/src/spec/dspark/module-dflash.ts +0 -560
  553. package/src/spec/dspark/module.ts +0 -540
  554. package/src/spec/dspark/quantize-drafter.ts +0 -91
  555. package/src/spec/dspark/sample.ts +0 -112
  556. package/src/spec/generate.ts +0 -276
  557. package/src/spec/glm52-mtp-graph.ts +0 -29
  558. package/src/spec/glm52-mtp-rows.ts +0 -132
  559. package/src/spec/glm52-mtp-source.ts +0 -318
  560. package/src/spec/glm52-mtp-state.ts +0 -33
  561. package/src/spec/ngram-source.ts +0 -226
  562. package/src/spec/projected-draft-rows.ts +0 -73
  563. package/src/spec/qwen-mtp-module.ts +0 -171
  564. package/src/spec/qwen-mtp-rows.ts +0 -230
  565. package/src/spec/qwen-mtp-source.ts +0 -386
  566. package/src/spec/qwen-mtp-state.ts +0 -30
  567. package/src/spec/serve-loop.ts +0 -587
  568. package/src/spec/source.ts +0 -228
  569. package/src/spec/two-model-rows.ts +0 -62
  570. package/src/spec/two-model.ts +0 -113
  571. package/src/ssd-cache.ts +0 -431
  572. package/src/ssd-durability.ts +0 -209
  573. package/src/storage/host-buffer.ts +0 -24
  574. package/src/storage/kv-writer.ts +0 -83
  575. package/src/storage/kv-writer.worker.js +0 -264
  576. package/src/storage/retention-policy.ts +0 -37
  577. package/src/tiered-prompt-cache.ts +0 -45
  578. package/src/tokenizer.ts +0 -335
  579. package/src/tool-approvals.ts +0 -131
  580. package/src/tool-call.ts +0 -695
  581. package/src/train/dataset.ts +0 -484
  582. package/src/train/diffusion-lora.ts +0 -216
  583. package/src/train/export.ts +0 -32
  584. package/src/train/flash-cce.ts +0 -1234
  585. package/src/train/forward.ts +0 -266
  586. package/src/train/fuse.ts +0 -243
  587. package/src/train/index.ts +0 -46
  588. package/src/train/job.ts +0 -205
  589. package/src/train/lora-params.ts +0 -254
  590. package/src/train/loss.ts +0 -1591
  591. package/src/train/merge.ts +0 -195
  592. package/src/train/optimizer.ts +0 -183
  593. package/src/train/prefix-shared.ts +0 -490
  594. package/src/train/rank.ts +0 -137
  595. package/src/train/segmented.ts +0 -1828
  596. package/src/train/steel-qmm-header.ts +0 -1461
  597. package/src/train/trainer.ts +0 -1221
  598. package/src/train/watch.ts +0 -542
  599. package/src/vision/diffusion-vision.ts +0 -620
  600. package/src/vision/embedder.ts +0 -176
  601. package/src/vision/preprocess.ts +0 -247
  602. package/src/vision/prompt.ts +0 -409
  603. package/src/vision/qwen3vl-preprocess.ts +0 -409
  604. package/src/vision/qwen3vl-prompt.ts +0 -233
  605. package/src/vision/qwen3vl-tower.ts +0 -566
  606. package/src/vision/siglip.ts +0 -607
  607. package/src/vision/video-frames.ts +0 -148
  608. package/src/web/app.html +0 -2155
  609. package/src/web/app.js +0 -5863
  610. package/src/web/skills/memory/SKILL.md +0 -120
  611. package/src/web/skills/web-research/SKILL.md +0 -46
  612. package/src/web/skills.ts +0 -97
  613. package/src/web/src/adapters-panel.ts +0 -288
  614. package/src/web/src/assistant.ts +0 -451
  615. package/src/web/src/chat.ts +0 -1060
  616. package/src/web/src/composer.ts +0 -962
  617. package/src/web/src/dataset.ts +0 -132
  618. package/src/web/src/finetune.ts +0 -267
  619. package/src/web/src/hub.ts +0 -428
  620. package/src/web/src/main.ts +0 -98
  621. package/src/web/src/markdown.ts +0 -0
  622. package/src/web/src/memory-panel.ts +0 -572
  623. package/src/web/src/model-picker.ts +0 -159
  624. package/src/web/src/palette.ts +0 -346
  625. package/src/web/src/protocol.ts +0 -26
  626. package/src/web/src/quantize.ts +0 -167
  627. package/src/web/src/rag.ts +0 -274
  628. package/src/web/src/sessions.ts +0 -469
  629. package/src/web/src/shell.ts +0 -804
  630. package/src/web/src/ui-catalog.ts +0 -78
  631. package/src/web/src/voice.ts +0 -141
  632. package/src/web/vendor/README.md +0 -61
  633. package/src/web-assets.ts +0 -47
  634. package/src/web-tools.ts +0 -478
  635. package/src/weights.ts +0 -255
  636. /package/src/{serve → server}/completion-sink.ts +0 -0
  637. /package/src/{serve → server}/generated-token-history.ts +0 -0
  638. /package/src/web/{icon.svg → public/icon.svg} +0 -0
  639. /package/src/web/{manifest.webmanifest → public/manifest.webmanifest} +0 -0
  640. /package/src/web/{vendor → public/vendor}/hljs-LICENSE +0 -0
package/src/kv-store.ts DELETED
@@ -1,1135 +0,0 @@
1
- import { type CheckpointAttachment, attachmentBytes, disposeAttachments } from "./backends/mlx/checkpoint-state";
2
- import { cleanupFailure, disposeResources } from "./engine/resources";
3
- // KV-cache persistence: save prompt caches to disk, reload by streamed copy.
4
- // The serialization core of the SSD cold tier (docs/design/kv-cache.md).
5
- //
6
- // File layout (every tensor PAGE-ALIGNED — the Phase 1 corollary: files
7
- // we write can be mmap'd and handed to the GPU without copies):
8
- // [magic "MLXBUNKV2\n"][u32 LE header length][u32 LE dataStart]
9
- // [u64 LE header hash][JSON header][padding]
10
- // [tensor data at 16 KiB-aligned offsets]
11
- // Header: { formatVersion, modelId, configFingerprint, ns, tokenizerHash,
12
- // createdAt, tokens, caches: [{ kind, offset, idx?, maxSize?,
13
- // groupSize?, bits?, kBits?, vBits?, headDim?,
14
- // tensors: [{ off, bytes, shape, dtype, hash }] }] }
15
- //
16
- // v2 over v1: quantized cache kinds (kv_config quantization — v1 could not
17
- // persist it; NOTE bf16 is the serving default since the 2026-07-05 L1
18
- // decision, quantized KV is opt-in), SSM kind (Qwen3.5 hybrid),
19
- // invalidation metadata (configFingerprint covers the kv-quant scheme;
20
- // tokenizerHash guards vocab drift; ns = adapter spec), per-tensor hashes
21
- // (verified opt-in — the hash pass roughly doubles restore reads),
22
- // a header hash (always verified — cheap), and atomic tmp+fsync+rename
23
- // writes. v1 files are not migrated: nothing shipped wrote them (the
24
- // format was test/experiment-only) — they read as "not a v2 file" and the
25
- // SSD tier deletes-and-regenerates.
26
- //
27
- // v3 over v2: fixed-width (zero-padded 16-hex) tensor hashes, which let the
28
- // writer STREAM — header sized up front, tensors materialized/hashed/written
29
- // one at a time, header patched in last (peak host transient = one tensor,
30
- // not the whole entry). Same magic; v2 files read as unsupported and the
31
- // SSD tier deletes-and-regenerates (machine-local, cheap).
32
- //
33
- // Reload (2026-07-07, A7-restore) is a STREAMED COPY — the read-side twin
34
- // of the v3 streaming writer: each tensor is copied out of a read-only mmap
35
- // into an mlx-owned leaf (MlxArray.fromBytesCopy), its clean file pages
36
- // dropped (MADV_DONTNEED) right after, and the mapping unmapped before
37
- // loadKvCache returns. Peak host transient = the live entry + one tensor,
38
- // and NOTHING outlives the call: the old zero-copy wrap (COW mmap +
39
- // fromPointer) required pinning every restore mapping for the PROCESS
40
- // lifetime once the FFI-dtor fix removed the unmap signal (2026-07-06) —
41
- // one leaked full-entry mapping per restore — and the exactly-offset-sized
42
- // restored buffers made the first post-restore decode step concat-copy the
43
- // whole entry again.
44
-
45
- import { openSync, writeSync, readSync, closeSync, fsyncSync, renameSync, rmSync } from "node:fs";
46
- import { pagedCacheCodec } from "./backends/mlx/paged-cache-codec";
47
- import { HostBuffer } from "./storage/host-buffer";
48
- import { readFileSync } from "node:fs";
49
- import { join, dirname } from "node:path";
50
- import { createHash } from "node:crypto";
51
- import { MmapFile, MADV_DONTNEED } from "./mmap";
52
- import { MlxArray } from "./mlx/array";
53
- import { kvWriter, kvReader, type KvWriteRequest } from "./storage/kv-writer";
54
- import type { Dtype } from "./mlx/ffi";
55
- import * as ops from "./mlx/ops";
56
- import {
57
- type Cache, cacheSignature, KVCache, RotatingKVCache,
58
- QuantizedKVCache, RotatingQuantizedKVCache,
59
- TurboQuantKVCache, type TurboQuantTensor,
60
- } from "./model/gemma4-base";
61
- import { Glm52Cache, MLACache } from "./model/glm52-cache";
62
- import { SSMCache } from "./model/qwen3-delta";
63
-
64
- const MAGIC = "MLXBUNKV2\n";
65
- const ALIGN = 16384;
66
- /** magic + u32 headerLen + u32 dataStart + u64 headerHash */
67
- const PREFIX_LEN = MAGIC.length + 4 + 4 + 8;
68
-
69
- // Fixed-width (16 hex chars): the v3 streaming writer sizes the header
70
- // BEFORE materializing any tensor bytes, so hash strings must not change
71
- // the header's byte length when the real values replace the placeholders.
72
- const hash64 = (bytes: Uint8Array): string => Bun.hash(bytes).toString(16).padStart(16, "0");
73
-
74
- export type CacheKind =
75
- | "kv"
76
- | "paged"
77
- | "rotating"
78
- | "qkv"
79
- | "rotating-qkv"
80
- | "ssm"
81
- | "turboquant"
82
- | "mla"
83
- | "mla-dsa"
84
- | "mtp-mla";
85
-
86
- export interface TensorSlot {
87
- blocks?: Array<{ hash: string; bytes: number }>;
88
- off: number;
89
- bytes: number;
90
- shape: number[];
91
- dtype: number;
92
- hash: string;
93
- }
94
-
95
- export interface CacheHeaderEntry {
96
- paged?: { capacityTokens: number; blockSize: number; blockTable: number[]; numBlocks: number;
97
- headDim: number; vHeadDim: number; dtype: Dtype; direct: boolean };
98
- kind: CacheKind;
99
- offset: number;
100
- minimumReusableOffset?: number;
101
- /** rotating variants: ring write index */
102
- idx?: number;
103
- /** rotating variants: window size */
104
- maxSize?: number;
105
- /** quantized variants (qkv/rotating-qkv: mlx affine scheme) */
106
- groupSize?: number;
107
- bits?: number;
108
- /** turboquant: per-side bit widths (reuses the `bits` field's slot class
109
- * but needs both — asymmetric key/value bit widths, unlike qkv's single
110
- * `bits`). `groupSize` is unused for turboquant (fixed at 32, the
111
- * BLOCK_SIZE constant — not a configurable field like qkv's). */
112
- kBits?: number;
113
- vBits?: number;
114
- /** turboquant: head_dim, needed to unpack kIdx/vPacked on restore
115
- * (packed byte width alone doesn't recover the original element count). */
116
- headDim?: number;
117
- /** GLM checkpoint-native compressed-cache geometry. */
118
- kvLoraRank?: number;
119
- ropeHeadDim?: number;
120
- dsaHeadDim?: number;
121
- maxTokens?: number;
122
- /** kv/rotating: [k, v] · qkv/rotating-qkv: [kPacked, kScales, kBiases,
123
- * vPacked, vScales, vBiases] · ssm: [conv, recurrent] · turboquant:
124
- * [kIdx, kScales, kZeros, vPacked, vScales] */
125
- tensors: TensorSlot[];
126
- }
127
-
128
- export interface KvSaveMeta {
129
- attachments?: CheckpointAttachment[];
130
- modelId?: string;
131
- /** configFingerprint(config) — covers every graph-shaping field incl.
132
- * the kv-quant scheme, so a scheme flip invalidates naturally. */
133
- configFingerprint?: string;
134
- /** Adapter namespace (PromptCache ns — adapters joined with "+"). */
135
- ns?: string;
136
- /** sha256 of tokenizer.json: identical ids must mean identical text. */
137
- tokenizerHash?: string;
138
- /** In-flight generation state. The serialized caches cover `tokens`;
139
- * pendingToken was sampled from that state but has not been emitted. */
140
- generationCheckpoint?: {
141
- key: string;
142
- cacheNs: string;
143
- originalPromptTokens: number;
144
- generatedTokens: number;
145
- pendingToken: number;
146
- /** Actual sampler seed, including a server-generated default. Required to
147
- * continue the random stream exactly after a restart. */
148
- seed: number;
149
- /** True when `seed` was part of the client request rather than generated
150
- * by the server. */
151
- seedWasExplicit: boolean;
152
- };
153
- }
154
-
155
- export interface KvFileHeader extends Omit<KvSaveMeta, "attachments"> {
156
- attachments?: Array<Omit<CheckpointAttachment, "tensors"> & { tensors: TensorSlot[] }>;
157
- /** Absent in earlier v3 files, which used mlx-cache-v3. */
158
- codecProvider?: string;
159
- formatVersion: 3 | 4 | 5;
160
- createdAt: number;
161
- tokens: number[];
162
- caches: CacheHeaderEntry[];
163
- }
164
-
165
- const alignUp = (n: number) => Math.ceil(n / ALIGN) * ALIGN;
166
-
167
- /** Snapshot one cache into header entry + LAZY tensor sources (v3 streaming
168
- * writer: sizes/shapes are metadata, bytes materialize ONE tensor at a
169
- * time at write — the old all-blobs-up-front path doubled RSS by the whole
170
- * entry, ~390 MB for a 16k cpm5 entry, on every write-behind). Quantized
171
- * full caches are sliced to the live [.., :offset, :] region (token axis —
172
- * quantization groups run along the FEATURE dim, so a sequence-axis cut
173
- * never splits a group); rotating caches persist the whole ring
174
- * as-laid-out with ringIdx. Slice handles are lazy graph nodes (no GPU
175
- * materialization until rawBytes at write time). */
176
- interface TensorSource { arr: MlxArray; disposeAfter: boolean }
177
-
178
- export interface SnapshotContext {
179
- slots: TensorSlot[];
180
- push(a: MlxArray, disposeAfter: boolean): void;
181
- liveSlice(a: MlxArray, upTo: number): MlxArray;
182
- liveMlaSlice(a: MlxArray, upTo: number): MlxArray;
183
- pushTriple(t: ops.QuantizedTensor, upTo: number | null): void;
184
- }
185
-
186
- export interface CloneContext {
187
- view(a: MlxArray): MlxArray;
188
- liveView(a: MlxArray, upTo: number): MlxArray;
189
- mlaView(a: MlxArray, upTo: number): MlxArray;
190
- tripleView(t: ops.QuantizedTensor, upTo: number | null): ops.QuantizedTensor;
191
- }
192
-
193
- export interface LoadContext {
194
- path: string;
195
- arr(slot: TensorSlot): MlxArray;
196
- grownArr(slot: TensorSlot, offset: number): MlxArray;
197
- triple(slots: TensorSlot[], at: number): ops.QuantizedTensor;
198
- }
199
-
200
- export interface CacheCodec {
201
- matches(cache: Cache): boolean;
202
- snapshot(cache: Cache, context: SnapshotContext): CacheHeaderEntry;
203
- clone(cache: Cache, context: CloneContext): Cache;
204
- load(entry: CacheHeaderEntry, context: LoadContext): Cache;
205
- headerTrimmable(entry: CacheHeaderEntry): boolean;
206
- }
207
-
208
- const requireMlaState = (cache: Glm52Cache, operation: string): void => {
209
- if (!cache.latent || !cache.rope || cache.batchSize === null || cache.offset === 0)
210
- throw new Error(`cannot ${operation} an empty GLM compressed cache`);
211
- if (cache.dsa && (!cache.dsa.data || cache.dsa.offset !== cache.offset))
212
- throw new Error(`cannot ${operation} misaligned GLM DSA state`);
213
- };
214
-
215
- const snapshotMla = (
216
- kind: Extract<CacheKind, "mla" | "mla-dsa" | "mtp-mla">,
217
- cache: Cache,
218
- context: SnapshotContext,
219
- ): CacheHeaderEntry => {
220
- const c = cache as Glm52Cache;
221
- requireMlaState(c, "persist");
222
- context.push(context.liveMlaSlice(c.latent!, c.offset), true);
223
- context.push(context.liveMlaSlice(c.rope!, c.offset), true);
224
- if (c.dsa) context.push(context.liveMlaSlice(c.dsa.data!, c.offset), true);
225
- return {
226
- kind,
227
- offset: c.offset,
228
- kvLoraRank: c.kvLoraRank,
229
- ropeHeadDim: c.ropeHeadDim,
230
- ...(c.dsa ? { dsaHeadDim: c.dsa.headDim } : {}),
231
- maxTokens: c.maxTokens,
232
- tensors: context.slots,
233
- };
234
- };
235
-
236
- const cloneMla = (cache: Cache, context: CloneContext): Cache => {
237
- const c = cache as Glm52Cache;
238
- requireMlaState(c, "clone");
239
- const clone = new MLACache({
240
- kvLoraRank: c.kvLoraRank,
241
- ropeHeadDim: c.ropeHeadDim,
242
- ...(c.dsa ? { dsa: { headDim: c.dsa.headDim } } : {}),
243
- maxTokens: c.maxTokens,
244
- role: c.role,
245
- });
246
- clone.restoreCompressedState(
247
- context.mlaView(c.latent!, c.offset),
248
- context.mlaView(c.rope!, c.offset),
249
- c.dsa ? context.mlaView(c.dsa.data!, c.offset) : null,
250
- c.offset,
251
- );
252
- return clone;
253
- };
254
-
255
- const loadMla = (entry: CacheHeaderEntry, context: LoadContext): Cache => {
256
- const hasDsa = entry.kind === "mla-dsa";
257
- if (entry.kind === "mtp-mla" && entry.dsaHeadDim !== undefined)
258
- throw new Error(`${context.path}: native MTP cache cannot contain DSA state`);
259
- const cache = new MLACache({
260
- kvLoraRank: entry.kvLoraRank!,
261
- ropeHeadDim: entry.ropeHeadDim!,
262
- ...(hasDsa ? { dsa: { headDim: entry.dsaHeadDim! } } : {}),
263
- maxTokens: entry.maxTokens,
264
- role: entry.kind === "mtp-mla" ? "mtp" : "target",
265
- });
266
- cache.restoreCompressedState(
267
- context.arr(entry.tensors[0]!),
268
- context.arr(entry.tensors[1]!),
269
- hasDsa ? context.arr(entry.tensors[2]!) : null,
270
- entry.offset,
271
- );
272
- return cache;
273
- };
274
-
275
- /** One authoritative persistence algebra. Adding a cache storage kind must
276
- * define all four operations here; save/clone/load and SSD eligibility
277
- * therefore cannot grow independent instanceof/switch chains. */
278
- const CACHE_CODECS = {
279
- kv: {
280
- matches: (cache) => cacheSignature(cache) === "kv:plain",
281
- snapshot: (cache, context) => {
282
- const c = cache as KVCache;
283
- if (!c.keys || !c.values) throw new Error("cannot persist an empty cache");
284
- context.push(context.liveSlice(c.keys, c.offset), true);
285
- context.push(context.liveSlice(c.values, c.offset), true);
286
- return { kind: "kv", offset: c.offset, tensors: context.slots };
287
- },
288
- clone: (cache, context) => {
289
- const c = cache as KVCache;
290
- const clone = new KVCache();
291
- if (!c.keys || !c.values) return clone;
292
- clone.restoreState(
293
- context.liveView(c.keys, c.offset),
294
- context.liveView(c.values, c.offset),
295
- c.offset,
296
- );
297
- return clone;
298
- },
299
- load: (entry, context) => {
300
- const cache = new KVCache();
301
- cache.restoreState(
302
- context.grownArr(entry.tensors[0]!, entry.offset),
303
- context.grownArr(entry.tensors[1]!, entry.offset),
304
- entry.offset,
305
- );
306
- return cache;
307
- },
308
- headerTrimmable: () => true,
309
- },
310
- rotating: {
311
- matches: (cache) => cacheSignature(cache) === "kv:rotating-plain",
312
- snapshot: (cache, context) => {
313
- const c = cache as RotatingKVCache;
314
- if (!c.keys || !c.values) throw new Error("cannot persist an empty cache");
315
- context.push(c.keys, false);
316
- context.push(c.values, false);
317
- return { kind: "rotating", offset: c.offset, idx: c.ringIdx,
318
- maxSize: c.maxSize, tensors: context.slots };
319
- },
320
- clone: (cache, context) => {
321
- const c = cache as RotatingKVCache;
322
- const clone = new RotatingKVCache(c.maxSize);
323
- if (!c.keys || !c.values) return clone;
324
- clone.restoreState(context.view(c.keys), context.view(c.values), c.offset, c.ringIdx);
325
- return clone;
326
- },
327
- load: (entry, context) => {
328
- const cache = new RotatingKVCache(entry.maxSize!);
329
- cache.restoreState(
330
- context.arr(entry.tensors[0]!), context.arr(entry.tensors[1]!),
331
- entry.offset, entry.idx!,
332
- );
333
- return cache;
334
- },
335
- headerTrimmable: (entry) => entry.offset < (entry.maxSize ?? 0),
336
- },
337
- qkv: {
338
- matches: (cache) => cacheSignature(cache).startsWith("kv:quant:"),
339
- snapshot: (cache, context) => {
340
- const c = cache as QuantizedKVCache;
341
- if (!c.keys || !c.values) throw new Error("cannot persist an empty cache");
342
- context.pushTriple(c.keys, c.offset);
343
- context.pushTriple(c.values, c.offset);
344
- return { kind: "qkv", offset: c.offset, groupSize: c.groupSize,
345
- bits: c.bits, tensors: context.slots };
346
- },
347
- clone: (cache, context) => {
348
- const c = cache as QuantizedKVCache;
349
- const clone = new QuantizedKVCache(c.groupSize, c.bits);
350
- if (!c.keys || !c.values) return clone;
351
- clone.restoreState(
352
- context.tripleView(c.keys, c.offset),
353
- context.tripleView(c.values, c.offset),
354
- c.offset,
355
- );
356
- return clone;
357
- },
358
- load: (entry, context) => {
359
- const cache = new QuantizedKVCache(entry.groupSize!, entry.bits!);
360
- cache.restoreState(
361
- context.triple(entry.tensors, 0), context.triple(entry.tensors, 3), entry.offset,
362
- );
363
- return cache;
364
- },
365
- headerTrimmable: () => true,
366
- },
367
- "rotating-qkv": {
368
- matches: (cache) => cacheSignature(cache).startsWith("kv:rotating-quant:"),
369
- snapshot: (cache, context) => {
370
- const c = cache as RotatingQuantizedKVCache;
371
- if (!c.keys || !c.values) throw new Error("cannot persist an empty cache");
372
- context.pushTriple(c.keys, null);
373
- context.pushTriple(c.values, null);
374
- return { kind: "rotating-qkv", offset: c.offset, idx: c.ringIdx,
375
- maxSize: c.maxSize, groupSize: c.groupSize, bits: c.bits,
376
- tensors: context.slots };
377
- },
378
- clone: (cache, context) => {
379
- const c = cache as RotatingQuantizedKVCache;
380
- const clone = new RotatingQuantizedKVCache(c.maxSize, c.groupSize, c.bits);
381
- if (!c.keys || !c.values) return clone;
382
- clone.restoreState(
383
- context.tripleView(c.keys, null), context.tripleView(c.values, null),
384
- c.offset, c.ringIdx,
385
- );
386
- return clone;
387
- },
388
- load: (entry, context) => {
389
- const cache = new RotatingQuantizedKVCache(
390
- entry.maxSize!, entry.groupSize!, entry.bits!,
391
- );
392
- cache.restoreState(
393
- context.triple(entry.tensors, 0), context.triple(entry.tensors, 3),
394
- entry.offset, entry.idx!,
395
- );
396
- return cache;
397
- },
398
- headerTrimmable: (entry) => entry.offset < (entry.maxSize ?? 0),
399
- },
400
- ssm: {
401
- matches: (cache) => cacheSignature(cache) === "ssm",
402
- snapshot: (cache, context) => {
403
- const c = cache as SSMCache;
404
- if (!c.conv || !c.recurrent) throw new Error("cannot persist an empty cache");
405
- context.push(c.conv, false);
406
- context.push(c.recurrent, false);
407
- return { kind: "ssm", offset: c.offset, tensors: context.slots };
408
- },
409
- clone: (cache, context) => {
410
- const c = cache as SSMCache;
411
- if (!c.conv || !c.recurrent) throw new Error("cannot clone an empty cache");
412
- const clone = new SSMCache();
413
- clone.conv = context.view(c.conv);
414
- clone.recurrent = context.view(c.recurrent);
415
- clone.offset = c.offset;
416
- return clone;
417
- },
418
- load: (entry, context) => {
419
- const cache = new SSMCache();
420
- cache.conv = context.arr(entry.tensors[0]!);
421
- cache.recurrent = context.arr(entry.tensors[1]!);
422
- cache.offset = entry.offset;
423
- return cache;
424
- },
425
- headerTrimmable: () => false,
426
- },
427
- turboquant: {
428
- matches: (cache) => cacheSignature(cache).startsWith("kv:turboquant:"),
429
- snapshot: (cache, context) => {
430
- const c = cache as TurboQuantKVCache;
431
- const state = c.state();
432
- if (state.length === 0) throw new Error("cannot persist an empty cache");
433
- for (const array of state) context.push(array, true);
434
- return { kind: "turboquant", offset: c.offset, kBits: c.kBits,
435
- vBits: c.vBits, headDim: c.headDim ?? 0, tensors: context.slots };
436
- },
437
- clone: (cache, context) => {
438
- const c = cache as TurboQuantKVCache;
439
- const state = c.state();
440
- if (state.length === 0) throw new Error("cannot clone an empty cache");
441
- try {
442
- const [kIdx, kScales, kZeros, vPacked, vScales] = state;
443
- const clone = new TurboQuantKVCache(c.kBits, c.vBits, c.fusedDecode);
444
- clone.restoreState({
445
- kIdx: context.view(kIdx!),
446
- kScales: context.view(kScales!),
447
- kZeros: context.view(kZeros!),
448
- vPacked: context.view(vPacked!),
449
- vScales: context.view(vScales!),
450
- }, c.offset, c.headDim!);
451
- return clone;
452
- } finally {
453
- for (const array of state) array.dispose();
454
- }
455
- },
456
- load: (entry, context) => {
457
- const cache = new TurboQuantKVCache(entry.kBits!, entry.vBits!);
458
- const tensors: TurboQuantTensor = {
459
- kIdx: context.arr(entry.tensors[0]!),
460
- kScales: context.arr(entry.tensors[1]!),
461
- kZeros: context.arr(entry.tensors[2]!),
462
- vPacked: context.arr(entry.tensors[3]!),
463
- vScales: context.arr(entry.tensors[4]!),
464
- };
465
- cache.restoreState(tensors, entry.offset, entry.headDim!);
466
- return cache;
467
- },
468
- headerTrimmable: () => true,
469
- },
470
- paged: pagedCacheCodec,
471
- mla: {
472
- matches: (cache) => cacheSignature(cache) === "kv:mla:target",
473
- snapshot: (cache, context) => snapshotMla("mla", cache, context),
474
- clone: cloneMla,
475
- load: loadMla,
476
- headerTrimmable: () => true,
477
- },
478
- "mla-dsa": {
479
- matches: (cache) => cacheSignature(cache) === "kv:mla:target:dsa",
480
- snapshot: (cache, context) => snapshotMla("mla-dsa", cache, context),
481
- clone: cloneMla,
482
- load: loadMla,
483
- headerTrimmable: () => true,
484
- },
485
- "mtp-mla": {
486
- matches: (cache) => cacheSignature(cache) === "kv:mla:mtp",
487
- snapshot: (cache, context) => snapshotMla("mtp-mla", cache, context),
488
- clone: cloneMla,
489
- load: loadMla,
490
- headerTrimmable: () => true,
491
- },
492
- } satisfies Record<CacheKind, CacheCodec>;
493
-
494
- /** Codecs belong to the loaded backend binding. The persisted ID is checked
495
- * before allocating state; codecs are engine code, never loaded from a file. */
496
- export interface CacheCodecProvider {
497
- readonly id: string;
498
- forCache(cache: Cache): CacheCodec;
499
- forHeader(entry: CacheHeaderEntry): CacheCodec;
500
- }
501
-
502
- export function createCacheCodecProvider(
503
- id: string, entries: Readonly<Partial<Record<CacheKind, CacheCodec>>>,
504
- ): CacheCodecProvider {
505
- if (!id.trim()) throw new Error("cache codec provider needs an identity");
506
- const table = new Map(Object.entries(entries).map(([kind, codec]) => [kind, Object.freeze({
507
- matches: codec.matches.bind(codec), snapshot: codec.snapshot.bind(codec), clone: codec.clone.bind(codec),
508
- load: codec.load.bind(codec), headerTrimmable: codec.headerTrimmable.bind(codec),
509
- })]));
510
- const candidates = [...table.values()];
511
- const selected = new WeakMap<Cache, CacheCodec>();
512
- return Object.freeze({
513
- id,
514
- forCache(cache: Cache) {
515
- const bound = selected.get(cache);
516
- if (bound) return bound;
517
- const matches = candidates.filter((codec) => codec.matches(cache));
518
- if (matches.length !== 1)
519
- throw new Error(`cache codec provider ${id}: ${matches.length} matches for ${cacheSignature(cache)}`);
520
- selected.set(cache, matches[0]!);
521
- return matches[0]!;
522
- },
523
- forHeader(entry: CacheHeaderEntry) {
524
- const codec = table.get(entry.kind);
525
- if (!codec) throw new Error(`cache codec provider ${id}: unsupported cache kind ${entry.kind}`);
526
- return codec;
527
- },
528
- });
529
- }
530
-
531
- export const legacyCacheCodecs = createCacheCodecProvider("mlx-cache-v3", CACHE_CODECS);
532
-
533
- /** Old TQ files lack the conversion boundary. Keep them reusable at their
534
- * stored offset, but never infer that an earlier precision state is valid. */
535
- export function cacheHeaderMinimumReusableOffset(entry: CacheHeaderEntry): number {
536
- const offset = entry.minimumReusableOffset ?? (entry.kind === "turboquant" ? entry.offset : 0);
537
- if (!Number.isSafeInteger(offset) || offset < 0 || offset > entry.offset)
538
- throw new Error("invalid cache minimum reusable offset");
539
- return offset;
540
- }
541
- export const cacheHeadersMinimumReusableOffset = (caches: readonly CacheHeaderEntry[]): number =>
542
- Math.max(0, ...caches.map(cacheHeaderMinimumReusableOffset));
543
-
544
- export const cacheHeadersTrimmable = (
545
- caches: readonly CacheHeaderEntry[], codecs: CacheCodecProvider = legacyCacheCodecs,
546
- ): boolean => caches.every((entry) => codecs.forHeader(entry).headerTrimmable(entry));
547
-
548
- function snapshotCache(c: Cache, dataOffset: number, codecs: CacheCodecProvider): { entry: CacheHeaderEntry; sources: TensorSource[]; next: number } {
549
- const slots: TensorSlot[] = [];
550
- const sources: TensorSource[] = [];
551
- let cursor = dataOffset;
552
- const HASH_PLACEHOLDER = "0".repeat(16); // fixed-width; patched at write
553
- const push = (a: MlxArray, disposeAfter: boolean): void => {
554
- const off = alignUp(cursor);
555
- slots.push({ off, bytes: a.nbytes, shape: a.shape, dtype: a.dtype, hash: HASH_PLACEHOLDER });
556
- sources.push({ arr: a, disposeAfter });
557
- cursor = off + a.nbytes;
558
- };
559
- const liveSlice = (a: MlxArray, upTo: number): MlxArray => {
560
- const [B, H, , D] = a.shape as [number, number, number, number];
561
- return a.slice([0, 0, 0, 0], [B, H, upTo, D]);
562
- };
563
- const liveMlaSlice = (a: MlxArray, upTo: number): MlxArray => {
564
- const [B, , D] = a.shape as [number, number, number];
565
- return a.slice([0, 0, 0], [B, upTo, D]);
566
- };
567
- const pushTriple = (t: ops.QuantizedTensor, upTo: number | null): void => {
568
- for (const a of [t.packed, t.scales, t.biases]) {
569
- if (upTo === null) push(a, false);
570
- else push(liveSlice(a, upTo), true);
571
- }
572
- };
573
-
574
- try {
575
- const entry = codecs.forCache(c).snapshot(c, { slots, push, liveSlice, liveMlaSlice, pushTriple });
576
- if (c.minimumReusableOffset !== undefined) entry.minimumReusableOffset = c.minimumReusableOffset;
577
- return { entry, sources, next: cursor };
578
- } catch (error) {
579
- for (const source of sources) if (source.disposeAfter) source.arr.dispose();
580
- throw error;
581
- }
582
- }
583
-
584
- /** Persist `caches` (+ the exact token prefix they encode) to `path`.
585
- * ATOMIC: written to `<path>.tmp`, fsync'd, renamed — a crash mid-write
586
- * leaves only a .tmp orphan (ignored + reaped by SsdCacheStore.scan). */
587
- /** Zero-copy in-memory clone of a cache list at its CURRENT state — new
588
- * cache instances holding slice VIEWS of the live tensors (mlx arrays are
589
- * immutable: continued generation replaces the source caches' internal
590
- * arrays, it never mutates the shared buffers, so views are stable).
591
- * Built for the prompt-boundary prompt-cache snapshot (multi-turn agent
592
- * traffic: the reply's decode→encode roundtrip drift makes prompt+gen
593
- * entries untrimmable at context > sliding window — a prompt-only entry
594
- * is ALWAYS an exact prefix of the next turn, immune to drift). Must be
595
- * called while the caches hold EXACTLY the state to snapshot (post-wrap
596
- * rings cannot be rewound later). Same per-kind dispatch as
597
- * snapshotCache/loadKvCache. */
598
- export function cloneKvCaches(caches: Cache[], codecs: CacheCodecProvider = legacyCacheCodecs): Cache[] {
599
- const pending: MlxArray[] = [];
600
- const hold = (array: MlxArray): MlxArray => { pending.push(array); return array; };
601
- const view = (a: MlxArray): MlxArray => hold(a.slice(a.shape.map(() => 0), [...a.shape]));
602
- const liveView = (a: MlxArray, upTo: number): MlxArray => {
603
- const [B, H, , D] = a.shape as [number, number, number, number];
604
- return hold(a.slice([0, 0, 0, 0], [B, H, upTo, D]));
605
- };
606
- const mlaView = (a: MlxArray, upTo: number): MlxArray => {
607
- const [B, , D] = a.shape as [number, number, number];
608
- return hold(a.slice([0, 0, 0], [B, upTo, D]));
609
- };
610
- const tripleView = (t: ops.QuantizedTensor, upTo: number | null): ops.QuantizedTensor => ({
611
- packed: upTo === null ? view(t.packed) : liveView(t.packed, upTo),
612
- scales: upTo === null ? view(t.scales) : liveView(t.scales, upTo),
613
- biases: upTo === null ? view(t.biases) : liveView(t.biases, upTo),
614
- });
615
- const out: Cache[] = [];
616
- try {
617
- for (const c of caches) {
618
- const clone = codecs.forCache(c).clone(c, { view, liveView, mlaView, tripleView });
619
- if (c.minimumReusableOffset !== undefined) clone.minimumReusableOffset = c.minimumReusableOffset;
620
- out.push(clone);
621
- pending.length = 0; // transferred to the returned cache
622
- }
623
- } catch (err) {
624
- cleanupFailure(err, () => disposeResources([...pending, ...out]));
625
- }
626
- return out;
627
- }
628
-
629
- /** Shared file layout and codec planning for synchronous and worker writes. */
630
- function planKvCache(tokens: number[], caches: Cache[], meta: KvSaveMeta, codecs: CacheCodecProvider): { header: KvFileHeader; sources: TensorSource[] } {
631
- // Plan pass: header entries + lazy tensor sources, NO bytes materialized.
632
- const entries: CacheHeaderEntry[] = [];
633
- const sources: TensorSource[] = [];
634
- let dataOffset = 0; // relative; rebased after header is sized
635
- try {
636
- for (const c of caches) {
637
- const s = snapshotCache(c, dataOffset, codecs);
638
- entries.push(s.entry);
639
- sources.push(...s.sources);
640
- dataOffset = s.next;
641
- }
642
-
643
- const { attachments: liveAttachments, ...identity } = meta;
644
- const attachments = liveAttachments?.map((attachment) => ({
645
- schema: attachment.schema,
646
- metadata: { ...attachment.metadata },
647
- tensors: attachment.tensors.map((arr) => {
648
- const slot: TensorSlot = { off: dataOffset, bytes: arr.nbytes,
649
- shape: [...arr.shape], dtype: arr.dtype, hash: "0000000000000000" };
650
- dataOffset = alignUp(dataOffset + slot.bytes);
651
- sources.push({ arr, disposeAfter: false });
652
- return slot;
653
- }),
654
- }));
655
-
656
- // Header size is final NOW: hashes are fixed-width placeholders that get
657
- // patched in place (same byte length) after the data pass computes them.
658
- const header: KvFileHeader = {
659
- formatVersion: attachments?.length ? 4 : 3, createdAt: Date.now(), ...identity,
660
- ...(attachments?.length ? { attachments } : {}), codecProvider: codecs.id, tokens, caches: entries,
661
- };
662
- return { header, sources };
663
- } catch (error) {
664
- for (const source of sources) if (source.disposeAfter) source.arr.dispose();
665
- throw error;
666
- }
667
- }
668
-
669
- function* saveKvCacheSteps(path: string, tokens: number[], caches: Cache[], meta: KvSaveMeta, codecs: CacheCodecProvider): Generator<void, void, void> {
670
- const { header, sources } = planKvCache(tokens, caches, meta, codecs);
671
- const entries = header.caches, attachments = header.attachments;
672
- try {
673
- const headerLen = new TextEncoder().encode(JSON.stringify(header)).length;
674
- const dataStart = alignUp(PREFIX_LEN + headerLen);
675
-
676
- const tmp = `${path}.tmp`;
677
- const fd = openSync(tmp, "w");
678
- try {
679
- // Data pass: materialize → hash → write → drop, ONE tensor at a time.
680
- // The hash+write read a ZERO-COPY view of the contiguous mlx buffer
681
- // (rawBytesView) — never a JS-heap copy. rawBytes() here looked
682
- // "streamed" but each call allocated a dead per-tensor JS-heap copy
683
- // whose reclamation is GC-timing-dependent — up to a whole extra
684
- // entry of heap on a busy server (the A7 residual). Now zero JS
685
- // allocations; the only real transient is the one mlx-side
686
- // contiguous copy (allocator-pooled; none at all for contiguous
687
- // sources — mlx save-transient measured 0 bytes, 2026-07-07). NOTE:
688
- // the hash+write still CPU-touch the live entry's unified-memory
689
- // pages, which makes them VISIBLE to ps RSS — accounting, not an
690
- // allocation (see bench-serve.ts' per-leg RSS note).
691
- let srcIdx = 0;
692
- for (const e of [...entries, ...(attachments ?? [])]) {
693
- for (const slot of e.tensors) {
694
- const src = sources[srcIdx++]!;
695
- const c = ops.contiguous(src.arr);
696
- try {
697
- const bytes = c.rawBytesView(); // evals; aliases the mlx buffer
698
- if (bytes.length !== slot.bytes)
699
- throw new Error(`tensor byte-length drift: planned ${slot.bytes}, got ${bytes.length}`);
700
- slot.hash = hash64(bytes);
701
- writeSync(fd, bytes, 0, bytes.length, dataStart + slot.off);
702
- } finally {
703
- c.dispose(); // the view dies with the buffer — nothing retains it
704
- }
705
- if (src.disposeAfter) { src.arr.dispose(); src.disposeAfter = false; }
706
- yield;
707
- }
708
- }
709
-
710
- // Header pass: real hashes in, byte length unchanged by construction.
711
- const headerJson = new TextEncoder().encode(JSON.stringify(header));
712
- if (headerJson.length !== headerLen)
713
- throw new Error(`header length drift: planned ${headerLen}, got ${headerJson.length}`);
714
- const pre = new Uint8Array(dataStart);
715
- pre.set(new TextEncoder().encode(MAGIC), 0);
716
- const dv = new DataView(pre.buffer);
717
- dv.setUint32(MAGIC.length, headerJson.length, true);
718
- dv.setUint32(MAGIC.length + 4, dataStart, true);
719
- dv.setBigUint64(MAGIC.length + 8, BigInt(Bun.hash(headerJson)), true);
720
- pre.set(headerJson, PREFIX_LEN);
721
- writeSync(fd, pre, 0, pre.length, 0);
722
- fsyncSync(fd);
723
- } catch (err) {
724
- closeSync(fd);
725
- try { rmSync(tmp, { force: true }); } catch {}
726
- throw err;
727
- }
728
- closeSync(fd);
729
- renameSync(tmp, path);
730
- } finally {
731
- // Slices created in the plan pass are ours to free on EVERY path.
732
- for (const s of sources) if (s.disposeAfter) s.arr.dispose();
733
- }
734
- }
735
-
736
- export function saveKvCache(path: string, tokens: number[], caches: Cache[], meta: KvSaveMeta = {}, codecs: CacheCodecProvider = legacyCacheCodecs): void {
737
- for (const _ of saveKvCacheSteps(path, tokens, caches, meta, codecs)) { /* drain */ }
738
- }
739
-
740
- /** Prepare immutable native storage on the owner thread, then pack, hash and
741
- * write it on the CPU worker. The optional runner covers preparation only.
742
- * Callers retain cache and attachment ownership until this promise settles. */
743
- export async function saveKvCacheAsync(
744
- path: string, tokens: number[], caches: Cache[], meta: KvSaveMeta = {},
745
- runStep?: <T>(step: () => T) => Promise<T>, codecs: CacheCodecProvider = legacyCacheCodecs,
746
- storage: Pick<KvWriteRequest, "layout" | "blockBytes" | "segmented"> = {},
747
- ): Promise<void> {
748
- const { header, sources } = planKvCache(tokens, caches, meta, codecs);
749
- try {
750
- // Snapshot preparation belongs to the MLX owner. After publication the
751
- // worker sees only immutable bytes and never evaluates or calls MLX.
752
- const prepare = () => sources.map(source => source.arr.storageView());
753
- const tensors = runStep ? await runStep(prepare) : prepare();
754
- await kvWriter.write({ path, header, tensors, ...storage });
755
- } finally {
756
- for (const source of sources) if (source.disposeAfter) source.arr.dispose();
757
- }
758
- }
759
-
760
- /** Queued immutable snapshot; the queue releases its shared views on settle. */
761
- export interface SpillItem {
762
- attachments?: CheckpointAttachment[];
763
- tokens: number[];
764
- caches: Cache[];
765
- ns: string;
766
- }
767
-
768
- /** Serial snapshot queue with optional capacity shedding. TieredPromptCache
769
- * disables shedding and owns residency itself. Interrupted-generation
770
- * persistence uses the cap for supersedable intervals. Queue ownership ends
771
- * after each write settles; the cache can retain independent shared views. */
772
- type SpillRec = SpillItem & {
773
- bytes: number;
774
- dropped: boolean;
775
- settle: (stored: boolean) => void;
776
- };
777
-
778
- export class SpillQueue {
779
- #queue: SpillRec[] = [];
780
- #bytes = 0;
781
- #dropped = 0;
782
- #failed = 0;
783
- #inFlight: object | null = null;
784
- #chain: Promise<void> = Promise.resolve();
785
-
786
- constructor(
787
- readonly capBytes: number,
788
- /** Byte size of a clone set (prompt-cache's cacheBytes). */
789
- readonly bytesOf: (caches: Cache[]) => number,
790
- /** The asynchronous storage operation. */
791
- readonly store: (item: SpillItem) => Promise<unknown>,
792
- /** Clone disposal — frees the pinned GPU memory. */
793
- readonly disposeClones: (caches: Cache[]) => void,
794
- /** Zero-byte control barriers may need to survive capacity shedding. */
795
- readonly canDrop: (item: SpillItem) => boolean = () => true,
796
- ) {}
797
-
798
- /** Bytes pinned by queued (not yet flushed) clones. */
799
- get pendingBytes(): number { return this.#bytes; }
800
- get pendingCount(): number { return this.#queue.length; }
801
- /** Spills dropped by the cap since start (each = one future cache miss). */
802
- get droppedCount(): number { return this.#dropped; }
803
- /** Store calls that threw or returned false. */
804
- get failedCount(): number { return this.#failed; }
805
-
806
- enqueue(item: SpillItem): Promise<boolean> {
807
- let settle!: (stored: boolean) => void;
808
- const result = new Promise<boolean>((resolve) => { settle = resolve; });
809
- const rec: SpillRec = {
810
- ...item,
811
- bytes: this.bytesOf(item.caches) + attachmentBytes(item.attachments),
812
- dropped: false,
813
- settle,
814
- };
815
- this.#queue.push(rec);
816
- this.#bytes += rec.bytes;
817
- while (this.#bytes > this.capBytes) {
818
- const victim = this.#queue.find(
819
- (p) => !p.dropped && p !== this.#inFlight && p !== rec && this.canDrop(p),
820
- );
821
- if (!victim) break; // only the new item (or in-flight) left — soft cap
822
- this.#drop(victim);
823
- }
824
- this.#chain = this.#chain.then(async () => {
825
- if (rec.dropped) return; // clones already disposed at drop time
826
- this.#inFlight = rec;
827
- try {
828
- const stored = await this.store(rec);
829
- if (stored === false) this.#failed++;
830
- rec.settle(stored !== false);
831
- } catch {
832
- // best-effort tier — the entry simply won't restore
833
- this.#failed++;
834
- rec.settle(false);
835
- } finally {
836
- this.#inFlight = null;
837
- this.#remove(rec);
838
- disposeResources([{ dispose: () => this.disposeClones(rec.caches) },
839
- { dispose: () => disposeAttachments(rec.attachments) }]);
840
- }
841
- });
842
- return result;
843
- }
844
-
845
- /** Cancel queued ownership, excluding the write already in flight. */
846
- cancelWhere(matches: (item: SpillItem) => boolean): void {
847
- for (const rec of [...this.#queue])
848
- if (!rec.dropped && rec !== this.#inFlight && matches(rec)) this.#drop(rec);
849
- }
850
-
851
- /** Settles when everything enqueued so far has flushed or dropped
852
- * (tests and graceful shutdown). */
853
- drain(): Promise<void> { return this.#chain; }
854
-
855
- #drop(rec: SpillRec): void {
856
- rec.dropped = true;
857
- this.#dropped++;
858
- rec.settle(false);
859
- this.#remove(rec);
860
- disposeResources([{ dispose: () => this.disposeClones(rec.caches) },
861
- { dispose: () => disposeAttachments(rec.attachments) }]);
862
- }
863
-
864
- #remove(rec: SpillRec): void {
865
- const i = this.#queue.indexOf(rec);
866
- if (i >= 0) {
867
- this.#queue.splice(i, 1);
868
- this.#bytes -= rec.bytes;
869
- }
870
- }
871
- }
872
-
873
- /** Read only the header (cheap — for prefix matching across many files).
874
- * Always verifies the header hash; throws on any structural mismatch. */
875
- export function readKvHeader(path: string): KvFileHeader & { dataStart: number } {
876
- const fd = openSync(path, "r");
877
- try {
878
- const head = new Uint8Array(PREFIX_LEN);
879
- readSync(fd, head, 0, head.length, 0);
880
- if (new TextDecoder().decode(head.subarray(0, MAGIC.length)) !== MAGIC)
881
- throw new Error(`${path}: not an mlx-bun KV v2 cache file`);
882
- const dv = new DataView(head.buffer);
883
- const len = dv.getUint32(MAGIC.length, true);
884
- const dataStart = dv.getUint32(MAGIC.length + 4, true);
885
- const expectHash = dv.getBigUint64(MAGIC.length + 8, true);
886
- const body = new Uint8Array(len);
887
- readSync(fd, body, 0, len, PREFIX_LEN);
888
- if (BigInt(Bun.hash(body)) !== expectHash)
889
- throw new Error(`${path}: header hash mismatch (truncated or corrupt)`);
890
- const header = JSON.parse(new TextDecoder().decode(body)) as KvFileHeader;
891
- if (header.formatVersion !== 3 && header.formatVersion !== 4 && header.formatVersion !== 5)
892
- throw new Error(`${path}: unsupported formatVersion ${header.formatVersion}`);
893
- return { ...header, dataStart };
894
- } finally {
895
- closeSync(fd);
896
- }
897
- }
898
-
899
- export interface LoadedKvCache {
900
- attachments?: CheckpointAttachment[];
901
- tokens: number[];
902
- header: KvFileHeader;
903
- caches: Cache[];
904
- }
905
-
906
- export interface KvLoadExpect {
907
- /** Reject on metadata mismatch (pass what the server is running). */
908
- modelId?: string;
909
- configFingerprint?: string;
910
- tokenizerHash?: string;
911
- ns?: string;
912
- /** Verify every tensor hash before copying it in (off by default,
913
- * `--ssd-cache-verify` — the hash pass roughly doubles restore reads). */
914
- verify?: boolean;
915
- }
916
-
917
- /** Reject GLM cache/layout drift before opening the tensor mmap. Generic KV
918
- * kinds may legitimately differ from makeCache() after runtime quantization;
919
- * GLM's checkpoint-native cache geometry does not. */
920
- function validateGlm52Prototype(
921
- path: string,
922
- entry: CacheHeaderEntry,
923
- prototype: Cache,
924
- ): void {
925
- const glmKind = entry.kind === "mla" || entry.kind === "mla-dsa" ||
926
- entry.kind === "mtp-mla";
927
- const prototypeSignature = cacheSignature(prototype);
928
- if (!prototypeSignature.startsWith("kv:mla:")) {
929
- if (glmKind)
930
- throw new Error(`${path}: GLM cache kind ${entry.kind} does not match model cache`);
931
- return;
932
- }
933
- const glmPrototype = prototype as Glm52Cache;
934
- const expectedKind: CacheKind = glmPrototype.role === "mtp"
935
- ? "mtp-mla"
936
- : glmPrototype.dsa ? "mla-dsa" : "mla";
937
- if (entry.kind !== expectedKind) {
938
- throw new Error(
939
- `${path}: cache kind ${entry.kind} != model cache kind ${expectedKind}`,
940
- );
941
- }
942
- for (const [field, actual, expected] of [
943
- ["kvLoraRank", entry.kvLoraRank, glmPrototype.kvLoraRank],
944
- ["ropeHeadDim", entry.ropeHeadDim, glmPrototype.ropeHeadDim],
945
- ["dsaHeadDim", entry.dsaHeadDim, glmPrototype.dsa?.headDim],
946
- ["maxTokens", entry.maxTokens, glmPrototype.maxTokens],
947
- ] as const) {
948
- if (actual !== expected) {
949
- throw new Error(
950
- `${path}: ${field} ${String(actual)} != model ${String(expected)}`,
951
- );
952
- }
953
- }
954
- const expectedTensors = glmPrototype.dsa ? 3 : 2;
955
- if (entry.tensors.length !== expectedTensors) {
956
- throw new Error(
957
- `${path}: ${entry.kind} has ${entry.tensors.length} tensors, ` +
958
- `expected ${expectedTensors}`,
959
- );
960
- }
961
- }
962
-
963
- /** Copy-restore a full-attention KV tensor into STEP-rounded capacity with
964
- * ≥1 token of slack (what a live mid-generation cache looks like) so the
965
- * first post-restore updateAndFetch never takes the grow path — the old
966
- * exactly-offset-sized restore made that first step concat-copy the ENTIRE
967
- * entry into a fresh buffer. Zero padding is bit-safe: writes land at
968
- * [offset..) before any read, and attention only reads [:offset+L).
969
- * Evaluated EAGERLY so the host-copy leaf frees before the next tensor
970
- * streams in (bounded transient). Plain KVCache only — rotating rings and
971
- * quantized triples are position-exact layouts. */
972
- function withStepCapacity(a: MlxArray, offset: number): MlxArray {
973
- const [B, H, S, D] = a.shape as [number, number, number, number];
974
- const cap = Math.ceil((offset + 1) / KVCache.STEP) * KVCache.STEP;
975
- if (S >= cap) return a;
976
- // Internals drained on throw (2026-07-07 review: a grown.eval() failure
977
- // orphaned z/grown; `a` stays the CALLER's to free — loadKvCache's
978
- // pending[] holds it).
979
- let z: MlxArray | null = null;
980
- let grown: MlxArray | null = null;
981
- try {
982
- z = ops.zeros([B, H, cap, D], a.dtype);
983
- grown = ops.sliceUpdate(z, a, [0, 0, 0, 0], [B, H, S, D]);
984
- z.dispose();
985
- z = null;
986
- grown.eval();
987
- a.dispose();
988
- const out = grown;
989
- grown = null;
990
- return out;
991
- } finally {
992
- z?.dispose();
993
- grown?.dispose();
994
- }
995
- }
996
-
997
- function validateKvHeader(path: string, header: KvFileHeader, model: { makeCache(): Cache[] },
998
- expect: KvLoadExpect, codecs: CacheCodecProvider): void {
999
- if ((header.codecProvider ?? legacyCacheCodecs.id) !== codecs.id)
1000
- throw new Error(`${path}: cache codec provider mismatch`);
1001
- for (const entry of header.caches) codecs.forHeader(entry);
1002
- for (const key of [
1003
- "modelId",
1004
- "configFingerprint",
1005
- "tokenizerHash",
1006
- "ns",
1007
- ] as const) {
1008
- if (expect[key] !== undefined && header[key] !== expect[key])
1009
- throw new Error(`${path}: ${key} mismatch (file ${header[key]}, expected ${expect[key]})`);
1010
- }
1011
- const proto = model.makeCache();
1012
- try {
1013
- if (header.caches.length !== proto.length) {
1014
- throw new Error(
1015
- `${path}: ${header.caches.length} cached layers but model has ${proto.length}`,
1016
- );
1017
- }
1018
- for (let index = 0; index < proto.length; index++) {
1019
- validateGlm52Prototype(path, header.caches[index]!, proto[index]!);
1020
- }
1021
- } finally {
1022
- for (const c of proto) c.dispose();
1023
- }
1024
-
1025
- }
1026
-
1027
- /** Decode cache layouts independently of the transport supplying arrays. */
1028
- function restoreKvArrays(path: string, header: KvFileHeader, codecs: CacheCodecProvider,
1029
- readArray: (slot: TensorSlot) => MlxArray): LoadedKvCache {
1030
- // Every tensor materialized for the CURRENT entry, drained on any throw
1031
- // (2026-07-07 review: a hash mismatch under --ssd-cache-verify — or any
1032
- // per-tensor failure — orphaned the entry's already-built tensors: the
1033
- // catch below only saw completed caches). Cleared once the entry's cache
1034
- // takes ownership; disposing an array withStepCapacity already consumed
1035
- // is safe (dispose is idempotent).
1036
- const pending: MlxArray[] = [];
1037
- const arr = (slot: TensorSlot): MlxArray => {
1038
- const a = readArray(slot); pending.push(a); return a;
1039
- };
1040
- /** withStepCapacity whose (possibly fresh) result is pending-tracked. */
1041
- const grownArr = (slot: TensorSlot, offset: number): MlxArray => {
1042
- const g = withStepCapacity(arr(slot), offset);
1043
- pending.push(g);
1044
- return g;
1045
- };
1046
- const triple = (slots: TensorSlot[], at: number): ops.QuantizedTensor =>
1047
- ({ packed: arr(slots[at]!), scales: arr(slots[at + 1]!), biases: arr(slots[at + 2]!) });
1048
-
1049
- const caches: Cache[] = [];
1050
- const attachments: CheckpointAttachment[] = [];
1051
- try {
1052
- for (const e of header.caches) {
1053
- const codec = codecs.forHeader(e);
1054
- const minimumOffset = cacheHeaderMinimumReusableOffset(e);
1055
- const cache = codec.load(e, { path, arr, grownArr, triple });
1056
- cache.minimumReusableOffset = minimumOffset;
1057
- caches.push(cache);
1058
- // This entry's tensors are now owned by its cache — stop tracking them
1059
- // (the catch must not double-free through both pending AND caches).
1060
- pending.length = 0;
1061
- }
1062
- for (const attachment of header.attachments ?? []) {
1063
- attachments.push({ schema: attachment.schema, metadata: { ...attachment.metadata },
1064
- tensors: attachment.tensors.map(arr) });
1065
- pending.length = 0;
1066
- }
1067
- } catch (err) {
1068
- disposeAttachments(attachments);
1069
- for (const a of pending) a.dispose(); // the mid-entry orphans
1070
- for (const c of caches) c.dispose();
1071
- throw err;
1072
- }
1073
- return { tokens: header.tokens, header, caches, ...(attachments.length ? { attachments } : {}) };
1074
- }
1075
-
1076
- /** CPU-only asynchronous reads into unpublished allocations; no tensor copy
1077
- * crosses the event loop. Native array construction uses the shared codecs. */
1078
- export async function loadKvCacheAsync(path: string, model: { makeCache(): Cache[] },
1079
- expect: KvLoadExpect = {}, codecs: CacheCodecProvider = legacyCacheCodecs): Promise<LoadedKvCache> {
1080
- const header = readKvHeader(path);
1081
- validateKvHeader(path, header, model, expect, codecs);
1082
- const slots = [...header.caches, ...(header.attachments ?? [])].flatMap(e => e.tensors);
1083
- const buffers = new Map<TensorSlot, HostBuffer>();
1084
- try {
1085
- for (const slot of slots) buffers.set(slot, new HostBuffer(slot.bytes));
1086
- await kvReader.read({ path, header, pointers: slots.map(slot => buffers.get(slot)!.pointer), verify: expect.verify });
1087
- return restoreKvArrays(path, header, codecs, slot =>
1088
- MlxArray.adoptHostBuffer(buffers.get(slot)!, slot.shape, slot.dtype as Dtype));
1089
- } finally { for (const buffer of buffers.values()) buffer.dispose(); }
1090
- }
1091
-
1092
- /** Reload by STREAMED COPY (see the header note): every tensor is copied
1093
- * into an mlx-owned leaf; the mapping is read-only, its pages dropped
1094
- * per-tensor, and unmapped before returning — the caches own their bytes
1095
- * outright (no dtor contract, no pinned mapping, nothing to retain).
1096
- * `model` is anything with makeCache() — the entry count is validated
1097
- * against the DONOR cache list (model.layers.length was wrong for
1098
- * KV-shared models like e4b, whose makeCache() returns donors only). */
1099
- export function loadKvCache(
1100
- path: string,
1101
- model: { makeCache(): Cache[] },
1102
- expect: KvLoadExpect = {}, codecs: CacheCodecProvider = legacyCacheCodecs,
1103
- ): LoadedKvCache {
1104
- const header = readKvHeader(path);
1105
- validateKvHeader(path, header, model, expect, codecs);
1106
- if (header.formatVersion === 5) {
1107
- return restoreKvArrays(path, header, codecs, slot => {
1108
- const bytes = new Uint8Array(slot.bytes);
1109
- let offset = 0;
1110
- for (const block of slot.blocks ?? []) {
1111
- if (!/^[a-f0-9]{64}$/.test(block.hash) || block.bytes <= 0 || offset + block.bytes > bytes.length)
1112
- throw new Error(`${path}: invalid KV block reference`);
1113
- const part = readFileSync(join(dirname(path), "blocks", block.hash));
1114
- if (part.length !== block.bytes || (expect.verify && createHash("sha256").update(part).digest("hex") !== block.hash))
1115
- throw new Error(`${path}: KV block mismatch`);
1116
- bytes.set(part, offset); offset += part.length;
1117
- }
1118
- if (offset !== bytes.length)
1119
- throw new Error(`${path}: KV tensor mismatch`);
1120
- return MlxArray.fromBytesCopy(bytes, slot.shape, slot.dtype as Dtype);
1121
- });
1122
- }
1123
- const mmap = MmapFile.open(path, "ro"), dataStart = header.dataStart;
1124
- try {
1125
- return restoreKvArrays(path, header, codecs, slot => {
1126
- const view = mmap.view(dataStart + slot.off, slot.bytes);
1127
- if (expect.verify && hash64(view) !== slot.hash)
1128
- throw new Error(`${path}: tensor hash mismatch at offset ${slot.off}`);
1129
- const array = MlxArray.fromBytesCopy(view, slot.shape, slot.dtype as Dtype);
1130
- const len = Math.min(alignUp(slot.bytes), mmap.size - (dataStart + slot.off));
1131
- mmap.advise(dataStart + slot.off, len, MADV_DONTNEED);
1132
- return array;
1133
- });
1134
- } finally { mmap.unmap(); }
1135
- }