mlx-bun 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (364) hide show
  1. package/README.md +71 -588
  2. package/docs/README.md +91 -0
  3. package/docs/{design → archive/investigations}/audio-input-plan.md +4 -4
  4. package/docs/{design → archive/investigations}/diffusion-gemma-port.md +3 -3
  5. package/docs/{design → archive/investigations}/faithful-l1-consolidation.md +4 -4
  6. package/docs/{design → archive/investigations}/minicpm5-decode-megakernel.md +11 -11
  7. package/docs/{design → archive/investigations}/optimization_plan.md +1 -1
  8. package/docs/{design → archive/investigations}/pre-colibri-stabilization.md +5 -5
  9. package/docs/{design → archive/investigations}/the-dreaming-handoff.md +2 -2
  10. package/docs/design/batching.md +569 -0
  11. package/docs/design/decode-speed-program.md +3178 -169
  12. package/docs/design/dreaming-nightly-pipeline.md +675 -282
  13. package/docs/design/generic-model-support.md +579 -334
  14. package/docs/design/kv-cache.md +680 -0
  15. package/docs/design/orpo-training.md +673 -1239
  16. package/docs/design/speculative-decoding.md +1627 -0
  17. package/docs/design/turboquant.md +2102 -0
  18. package/docs/design/unified-engine-frontier-plan.md +1831 -727
  19. package/docs/design/web-chat-redesign.md +666 -1083
  20. package/docs/planning/PRODUCT_ROADMAP.md +34 -1
  21. package/docs/planning/release-notes-v0.3.0.md +35 -0
  22. package/docs/planning/video-series.md +2 -2
  23. package/docs/reference/benchmarks.md +1162 -0
  24. package/docs/reference/cli.md +394 -159
  25. package/docs/reference/distribution.md +321 -182
  26. package/docs/reference/environment.md +348 -0
  27. package/docs/reference/glossary.md +39 -0
  28. package/docs/reference/library-api.md +566 -168
  29. package/docs/reference/memory.md +227 -64
  30. package/docs/reference/models.md +104 -6
  31. package/docs/reference/server-api.md +886 -425
  32. package/docs/reference/server-config.md +638 -330
  33. package/docs/reference/training.md +393 -254
  34. package/docs/reference/troubleshooting.md +166 -0
  35. package/package.json +9 -4
  36. package/src/audio/decode.ts +2 -2
  37. package/src/audio/features.ts +2 -2
  38. package/src/backends/mlx/autoregressive.ts +108 -0
  39. package/src/backends/mlx/batch-group.ts +1646 -0
  40. package/src/backends/mlx/diffusion.ts +39 -0
  41. package/src/backends/mlx/draft-target.ts +47 -0
  42. package/src/backends/mlx/evaluation.ts +105 -0
  43. package/src/backends/mlx/fill-append.ts +45 -0
  44. package/src/backends/mlx/gateway-binding.ts +71 -0
  45. package/src/backends/mlx/graph.ts +38 -0
  46. package/src/backends/mlx/memory-client.ts +189 -0
  47. package/src/backends/mlx/memory-guard.ts +40 -0
  48. package/src/backends/mlx/methods.ts +90 -0
  49. package/src/backends/mlx/model-prompt.ts +164 -0
  50. package/src/backends/mlx/model-serving.ts +79 -0
  51. package/src/backends/mlx/prefill.ts +33 -0
  52. package/src/backends/mlx/request-policy.ts +20 -0
  53. package/src/backends/mlx/rollback.ts +51 -0
  54. package/src/backends/mlx/serial-executor.ts +273 -0
  55. package/src/backends/mlx/speculative.ts +114 -0
  56. package/src/backends/mlx/state-views.ts +26 -0
  57. package/src/backends/mlx/text-engine.ts +73 -0
  58. package/src/cli.ts +58 -25
  59. package/src/client.ts +66 -0
  60. package/src/config.ts +21 -4
  61. package/src/contracts/completion.ts +16 -0
  62. package/src/contracts/execution.ts +49 -0
  63. package/src/contracts/generation.ts +103 -0
  64. package/src/contracts/host.ts +7 -0
  65. package/src/contracts/jobs.ts +66 -0
  66. package/src/contracts/pi-provider.ts +30 -0
  67. package/src/contracts/pi.ts +259 -0
  68. package/src/contracts/resources.ts +18 -0
  69. package/src/contracts/scheduling.ts +27 -0
  70. package/src/contracts/tsconfig.json +14 -0
  71. package/src/diffusion/diffusion-generate.ts +218 -153
  72. package/src/embed.ts +1 -1
  73. package/src/engine/admission.ts +59 -0
  74. package/src/engine/cancellation.ts +38 -0
  75. package/src/engine/engine.ts +56 -0
  76. package/src/engine/execution-coordinator.ts +70 -0
  77. package/src/engine/execution-plan.ts +51 -0
  78. package/src/engine/resources.ts +42 -0
  79. package/src/engine/scheduler.ts +67 -0
  80. package/src/engine/session.ts +247 -0
  81. package/src/eval/README.md +44 -1
  82. package/src/eval/kl.ts +236 -3
  83. package/src/eval/reference-logits.ts +207 -0
  84. package/src/eval/runner.ts +8 -96
  85. package/src/fill/echo-index.ts +188 -0
  86. package/src/fill/fill-session.ts +452 -0
  87. package/src/fill/proposal.ts +68 -0
  88. package/src/fill/schema-rows.ts +506 -0
  89. package/src/fill/tool-boundary.ts +103 -0
  90. package/src/fit.ts +1 -1
  91. package/src/generate.ts +656 -268
  92. package/src/grammar.ts +12 -7
  93. package/src/harness-pi.ts +14 -33
  94. package/src/hub-rest.ts +4 -4
  95. package/src/index.ts +7 -0
  96. package/src/inference/denoising.ts +17 -0
  97. package/src/inference/graph.ts +33 -0
  98. package/src/inference/prefill.ts +39 -0
  99. package/src/inference/rollback.ts +7 -0
  100. package/src/jobs/db.ts +4 -0
  101. package/src/jobs/index.ts +2 -0
  102. package/src/jobs/job-entry.ts +12 -12
  103. package/src/jobs/runner.ts +98 -13
  104. package/src/jobs/task-client.ts +19 -0
  105. package/src/jobs/types.ts +2 -58
  106. package/src/kv-store.ts +109 -49
  107. package/src/lab/README.md +18 -0
  108. package/src/{assets → lab/curve}/curve-designer.html +2 -2
  109. package/src/{curve-sampler.ts → lab/curve/curve-sampler.ts} +2 -2
  110. package/src/{expert-trace.ts → lab/expert-trace/expert-trace.ts} +5 -5
  111. package/src/{model → lab/paged-kv}/paged-kv.ts +9 -6
  112. package/src/library.ts +40 -0
  113. package/src/lora.ts +18 -0
  114. package/src/media-fetch.ts +1 -1
  115. package/src/memory/article.ts +1 -1
  116. package/src/memory/chunk.ts +1 -1
  117. package/src/memory/model.ts +30 -169
  118. package/src/memory/prompts.ts +1 -1
  119. package/src/memory/rest.ts +1 -1
  120. package/src/memory/schedule.ts +1 -1
  121. package/src/memory/stages.ts +4 -1
  122. package/src/memory/vault.ts +1 -3
  123. package/src/mlx/array.ts +9 -10
  124. package/src/mlx/compile.ts +12 -2
  125. package/src/mlx/ffi.ts +58 -11
  126. package/src/mlx/materialize.ts +23 -0
  127. package/src/mlx/metal-kernel.ts +10 -11
  128. package/src/mlx/ops.ts +33 -13
  129. package/src/mlx/quantized-rows.ts +36 -0
  130. package/src/mlx/token-bitmask.ts +52 -0
  131. package/src/mlx/turboquant-kv-decode.ts +82 -0
  132. package/src/mlx/turboquant-kv-inverse.ts +122 -0
  133. package/src/mlx/turboquant-ops.ts +33 -3
  134. package/src/mlx/turboquant-tables.ts +2 -2
  135. package/src/model/batched-mask.ts +8 -2
  136. package/src/model/batched-quant.ts +1 -1
  137. package/src/model/batched-rotating-quant.ts +2 -2
  138. package/src/model/batched-rotating.ts +1 -1
  139. package/src/model/compiled-decode.ts +1 -1
  140. package/src/model/diffusion-gemma.ts +8 -8
  141. package/src/model/factory.ts +76 -33
  142. package/src/model/fingerprint.ts +1 -1
  143. package/src/model/flash-attention.ts +3 -3
  144. package/src/model/gemma4-base.ts +26 -9
  145. package/src/model/gemma4.ts +6 -6
  146. package/src/model/generated/gemma4-12b.ts +2 -2
  147. package/src/model/generated/gemma4-26b.ts +2 -2
  148. package/src/model/generated/gemma4-e4b.ts +2 -2
  149. package/src/model/glm52-dsa.ts +13 -9
  150. package/src/model/glm52-memory.ts +1 -1
  151. package/src/model/implementation.ts +69 -0
  152. package/src/model/minicpm5.ts +6 -8
  153. package/src/model/profile.ts +12 -2
  154. package/src/model/qwen-append.ts +12 -0
  155. package/src/model/qwen-conv.ts +52 -0
  156. package/src/model/qwen3_5-checkpoint.ts +164 -0
  157. package/src/model/qwen3_5.ts +140 -56
  158. package/src/model/support.ts +1 -1
  159. package/src/model/trellis-balanced-scatter.ts +51 -0
  160. package/src/model/trellis-linear.ts +672 -0
  161. package/src/model/trellis-shared-m.ts +151 -0
  162. package/src/model/trellis-shared-scatter.ts +144 -0
  163. package/src/model/trellis-splitk-prefill.ts +158 -0
  164. package/src/model/trellis-tiled-prefill.ts +136 -0
  165. package/src/model/trellis-vector-expand.ts +62 -0
  166. package/src/model/trellis-wide-prefill.ts +94 -0
  167. package/src/model/universal/dense.ts +1 -1
  168. package/src/model/universal/modules.ts +8 -1
  169. package/src/native-pack.ts +6 -5
  170. package/src/pi-provider.ts +6 -64
  171. package/src/pi-web.ts +8 -315
  172. package/src/prompt-cache.ts +96 -53
  173. package/src/quantize/allocator.ts +1 -1
  174. package/src/quantize/config-writer.ts +10 -3
  175. package/src/quantize/rotate.ts +4 -4
  176. package/src/quantize/trellis.ts +586 -0
  177. package/src/responses.ts +28 -1
  178. package/src/runtime-config.ts +18 -4
  179. package/src/sampler.ts +4 -4
  180. package/src/serve/batch-scheduler.ts +8 -1671
  181. package/src/serve/chat-request.ts +391 -0
  182. package/src/serve/chat-stage.ts +195 -0
  183. package/src/serve/checkpoint-identity.ts +64 -0
  184. package/src/serve/completion-executor.ts +15 -7
  185. package/src/serve/discovery-routes.ts +12 -13
  186. package/src/serve/generation-gateway.ts +154 -193
  187. package/src/serve/http.ts +159 -0
  188. package/src/serve/inference-request.ts +79 -0
  189. package/src/serve/isolate.ts +297 -105
  190. package/src/serve/lab-routes.ts +3 -0
  191. package/src/serve/lane-registry.ts +2 -1
  192. package/src/serve/model-admin-routes.ts +7 -5
  193. package/src/serve/model-binding.ts +43 -0
  194. package/src/serve/model-host.ts +512 -0
  195. package/src/serve/openai-wire.ts +103 -0
  196. package/src/serve/parent-application.ts +56 -0
  197. package/src/serve/pipeline.ts +33 -0
  198. package/src/serve/preparation.ts +33 -0
  199. package/src/serve/request-plan.ts +19 -15
  200. package/src/serve/request-prep.ts +346 -0
  201. package/src/serve/response-lease.ts +24 -0
  202. package/src/serve/responses-client.ts +69 -0
  203. package/src/serve/session-completion-engine.ts +110 -0
  204. package/src/serve/session-search.ts +1 -1
  205. package/src/serve/static-routes.ts +5 -5
  206. package/src/serve/text-completion-stage.ts +104 -0
  207. package/src/serve/token-streams.ts +519 -0
  208. package/src/server.ts +317 -2649
  209. package/src/spec/assistant-source.ts +10 -44
  210. package/src/spec/deepspec-source.ts +6 -6
  211. package/src/spec/dflash-source.ts +8 -8
  212. package/src/spec/dspark/ab-stats.ts +3 -3
  213. package/src/spec/dspark/calibration.ts +2 -2
  214. package/src/spec/dspark/deepspec-module.ts +2 -2
  215. package/src/spec/dspark/generate-dflash.ts +2 -2
  216. package/src/spec/dspark/generate.ts +2 -2
  217. package/src/spec/dspark/loader.ts +1 -1
  218. package/src/spec/dspark/module-dflash.ts +4 -3
  219. package/src/spec/dspark/module.ts +3 -3
  220. package/src/spec/dspark/quantize-drafter.ts +1 -1
  221. package/src/spec/generate.ts +1 -1
  222. package/src/spec/glm52-mtp-source.ts +1 -1
  223. package/src/spec/ngram-source.ts +1 -1
  224. package/src/spec/prefix-state.ts +45 -0
  225. package/src/spec/qwen-mtp-source.ts +166 -69
  226. package/src/spec/serve-loop.ts +154 -156
  227. package/src/spec/source.ts +42 -10
  228. package/src/spec/two-model.ts +1 -1
  229. package/src/ssd-cache.ts +80 -12
  230. package/src/ssd-durability.ts +25 -13
  231. package/src/tool-call.ts +51 -14
  232. package/src/train/flash-cce.ts +2 -2
  233. package/src/train/forward.ts +6 -0
  234. package/src/train/loss.ts +1 -1
  235. package/src/train/prefix-shared.ts +5 -1
  236. package/src/train/segmented.ts +5 -3
  237. package/src/train/trainer.ts +6 -6
  238. package/src/vision/prompt.ts +1 -1
  239. package/src/vision/qwen3vl-tower.ts +52 -10
  240. package/src/web/app.html +1 -1
  241. package/src/web/app.js +5 -3
  242. package/src/web/src/adapters-panel.ts +3 -3
  243. package/src/web/src/assistant.ts +2 -36
  244. package/src/web/src/chat.ts +4 -4
  245. package/src/web/src/composer.ts +4 -4
  246. package/src/web/src/hub.ts +1 -1
  247. package/src/web/src/markdown.ts +0 -0
  248. package/src/web/src/memory-panel.ts +1 -1
  249. package/src/web/src/model-picker.ts +1 -1
  250. package/src/web/src/palette.ts +1 -1
  251. package/src/web/src/protocol.ts +3 -39
  252. package/src/web/src/sessions.ts +1 -1
  253. package/src/web/src/ui-catalog.ts +4 -18
  254. package/src/web-assets.ts +47 -0
  255. package/src/weights.ts +55 -9
  256. package/docs/archive/investigations/curve-runs/bisector-route.json +0 -98
  257. package/docs/archive/investigations/curve-runs/bisector-trace.json +0 -1393
  258. package/docs/archive/investigations/curve-runs/curve-terrain.json +0 -1179
  259. package/docs/archive/investigations/curve-runs/curve-terrain.svg +0 -42
  260. package/docs/archive/investigations/hlg-ab.html +0 -34
  261. package/docs/archive/investigations/hlg-explorer.html +0 -238
  262. package/docs/archive/investigations/hlg-figs/cliff.svg +0 -1
  263. package/docs/archive/investigations/hlg-figs/cross.svg +0 -1
  264. package/docs/archive/investigations/hlg-figs/edges.svg +0 -1
  265. package/docs/archive/investigations/hlg-figs/k20-grids.svg +0 -1
  266. package/docs/archive/investigations/hlg-figs/k20-peaks.svg +0 -1
  267. package/docs/archive/investigations/hlg-figs/lw-curve.svg +0 -1
  268. package/docs/archive/investigations/hlg-figs/terrain.svg +0 -1
  269. package/docs/archive/investigations/hlg-report.html +0 -262
  270. package/docs/archive/investigations/hlg-runs/hlg-ab.json +0 -149
  271. package/docs/archive/investigations/hlg-runs/hlg-blind-prompts.md +0 -87
  272. package/docs/archive/investigations/hlg-runs/hlg-blind.json +0 -107
  273. package/docs/archive/investigations/hlg-runs/hlg-cliff.json +0 -197
  274. package/docs/archive/investigations/hlg-runs/hlg-cross.json +0 -505
  275. package/docs/archive/investigations/hlg-runs/hlg-edge.json +0 -388
  276. package/docs/archive/investigations/hlg-runs/hlg-eval1.json +0 -25
  277. package/docs/archive/investigations/hlg-runs/hlg-frontier.json +0 -86
  278. package/docs/archive/investigations/hlg-runs/hlg-grid.json +0 -242
  279. package/docs/archive/investigations/hlg-runs/hlg-grid2.json +0 -343
  280. package/docs/archive/investigations/hlg-runs/hlg-lw-coarse.json +0 -110
  281. package/docs/archive/investigations/hlg-runs/hlg-map.json +0 -383
  282. package/docs/archive/investigations/hlg-runs/run0-original.log +0 -159
  283. package/docs/archive/investigations/hlg-runs/runA-recommended.log +0 -131
  284. package/docs/archive/investigations/hlg-runs/runAA-cliff-cpm5.log +0 -26
  285. package/docs/archive/investigations/hlg-runs/runAB-grid2-e4b.log +0 -18
  286. package/docs/archive/investigations/hlg-runs/runAC-peak-e4b-partial.log +0 -25
  287. package/docs/archive/investigations/hlg-runs/runAD-ab-samples.log +0 -8
  288. package/docs/archive/investigations/hlg-runs/runAE-edge-e4b.log +0 -51
  289. package/docs/archive/investigations/hlg-runs/runAF-logit-calibration.log +0 -10
  290. package/docs/archive/investigations/hlg-runs/runAG-eval-config2.log +0 -16
  291. package/docs/archive/investigations/hlg-runs/runB-gain-variants.log +0 -192
  292. package/docs/archive/investigations/hlg-runs/runC-pivots.log +0 -162
  293. package/docs/archive/investigations/hlg-runs/runD-oetf.log +0 -132
  294. package/docs/archive/investigations/hlg-runs/runE-eotf.log +0 -162
  295. package/docs/archive/investigations/hlg-runs/runF-shaper-working.log +0 -86
  296. package/docs/archive/investigations/hlg-runs/runG-shaper-sweep.log +0 -101
  297. package/docs/archive/investigations/hlg-runs/runH-wsweep.log +0 -176
  298. package/docs/archive/investigations/hlg-runs/runI-asweep.log +0 -176
  299. package/docs/archive/investigations/hlg-runs/runJ-knobmap.log +0 -49
  300. package/docs/archive/investigations/hlg-runs/runK-frontier.log +0 -19
  301. package/docs/archive/investigations/hlg-runs/runL-widemap.log +0 -65
  302. package/docs/archive/investigations/hlg-runs/runM-frontier-emb.log +0 -15
  303. package/docs/archive/investigations/hlg-runs/runN-verify.log +0 -116
  304. package/docs/archive/investigations/hlg-runs/runO-frontier-seed5000.log +0 -15
  305. package/docs/archive/investigations/hlg-runs/runP-grid-AxSm.log +0 -15
  306. package/docs/archive/investigations/hlg-runs/runQ-grid-tgW.log +0 -15
  307. package/docs/archive/investigations/hlg-runs/runR-blind-transcript.log +0 -79
  308. package/docs/archive/investigations/hlg-runs/runS-cross-coherence.log +0 -33
  309. package/docs/archive/investigations/hlg-runs/runT-cross-cpm5.log +0 -33
  310. package/docs/archive/investigations/hlg-runs/runU-cross-12b.log +0 -33
  311. package/docs/archive/investigations/hlg-runs/runV-lw-e4b.log +0 -22
  312. package/docs/archive/investigations/hlg-runs/runW-lw-12b.log +0 -22
  313. package/docs/archive/investigations/hlg-runs/runX-lw-cpm5.log +0 -22
  314. package/docs/archive/investigations/hlg-runs/runY-cliff-e4b.log +0 -26
  315. package/docs/archive/investigations/hlg-runs/runZ-cliff-12b.log +0 -26
  316. package/docs/archive/mlx-bun-lab-report.html +0 -275
  317. package/docs/archive/wwdc-mlx-bun.html +0 -848
  318. package/docs/design/adapters-end-to-end.md +0 -205
  319. package/docs/design/batching-perf-path.md +0 -255
  320. package/docs/design/batching-v2-plan.md +0 -529
  321. package/docs/design/bucketing-stage.md +0 -197
  322. package/docs/design/compat-cli-surface-design.md +0 -230
  323. package/docs/design/docs-reorg-plan.md +0 -165
  324. package/docs/design/dspark-serving-program.md +0 -371
  325. package/docs/design/dspark-speculative-decoding.md +0 -318
  326. package/docs/design/grammar-spec-batching-integration.md +0 -301
  327. package/docs/design/memory-inference-path.md +0 -422
  328. package/docs/design/memory-synthesis.md +0 -287
  329. package/docs/design/memory-system.md +0 -237
  330. package/docs/design/mlx-lm-serving-execution-seam.md +0 -81
  331. package/docs/design/mlx-lm-tool-parity-plan.md +0 -545
  332. package/docs/design/omlx-adoption-map.md +0 -98
  333. package/docs/design/orpo-dynamic-lambda.md +0 -220
  334. package/docs/design/orpo-future-enhancements.md +0 -340
  335. package/docs/design/paged-kv-cache.md +0 -214
  336. package/docs/design/parallel-slots.md +0 -567
  337. package/docs/design/parity-tier-dag.md +0 -161
  338. package/docs/design/repo-cleanup-plan.md +0 -231
  339. package/docs/design/runtime-isolation.md +0 -263
  340. package/docs/design/segmented-backward-training.md +0 -543
  341. package/docs/design/spec-decode-larger-targets.md +0 -229
  342. package/docs/design/ssd-kv-cold-tier.md +0 -415
  343. package/docs/design/structured-output.md +0 -484
  344. package/docs/design/tauri-desktop-app.md +0 -127
  345. package/docs/design/the-dreaming-master-plan.md +0 -445
  346. package/docs/design/turboquant-kv.md +0 -235
  347. package/docs/design/turboquant-weights.md +0 -467
  348. package/docs/design/w4a16-compute-precision-spike.md +0 -232
  349. package/docs/design/web-chat-beat-matrix.md +0 -315
  350. package/docs/design/write-pipeline-entity.md +0 -468
  351. package/docs/planning/IDEAS.md +0 -69
  352. package/docs/reference/embedding.md +0 -197
  353. package/docs/reference/features-matrix.md +0 -101
  354. package/docs/reference/orpo-quickstart.md +0 -135
  355. package/src/flags.ts +0 -11
  356. /package/docs/{design → archive/investigations}/colibri-glm52-port.md +0 -0
  357. /package/docs/{planning → archive/planning}/chunk-task-roadmap.md +0 -0
  358. /package/docs/{planning → archive/planning}/curve-sampler-research-plan.md +0 -0
  359. /package/docs/{planning → archive/planning}/memory-docs-and-dag-plan.md +0 -0
  360. /package/docs/{planning → archive/planning}/pi-integration-review.md +0 -0
  361. /package/docs/{planning → archive/planning}/release-notes-v0.2.0.md +0 -0
  362. /package/src/{memory-plan.ts → admission-plan.ts} +0 -0
  363. /package/{docs/archive/investigations → src/serve/assets}/curve-terrain.html +0 -0
  364. /package/{docs/dag → src/serve/assets}/training-inference-map.html +0 -0
@@ -1,3 +1,5 @@
1
+ import { leaseCacheState } from "./backends/mlx/state-views";
2
+ import { withResource, cleanupFailure, disposeResources, ownResource } from "./engine/resources";
1
3
  // Byte-capped LRU prompt cache — the RAM tier of the Layer-0 KV store.
2
4
  //
3
5
  // The mlx-lm lesson (PLAN.md): a count-capped cache of multi-GB KV
@@ -5,13 +7,14 @@
5
7
  // per entry) and evicts least-recently-used until under the cap.
6
8
  //
7
9
  // Usage pattern (single generation queue, so take/put is race-free):
8
- // const hit = cache.take(promptIds); // longest-prefix match → CLONES
10
+ // const hit = cache.take(promptIds); // longest-prefix match
9
11
  // generate(model, promptIds, { cache: hit?.caches ?? fresh })
10
12
  // cache.put([...promptIds, ...generated], caches, ns, hit?.retain);
11
- // take() is NON-CONSUMING (prefix sharing): it serves zero-copy clones and
12
- // leaves the donor entry in place — N agents sharing a system prompt all
13
- // clone from one donor, one prefill; put() supersedes same-ns
14
- // prefix-ancestors (when the new entry is trimmable) so a conversation
13
+ // take() is NON-CONSUMING for trimmable caches: it serves zero-copy clones
14
+ // and leaves the donor in place, so N agents can share one prefill.
15
+ // Qwen hybrid entries containing recurrent SSM state transfer ownership on
16
+ // an exact hit, avoiding an extra resident recurrent snapshot. put() supersedes
17
+ // same-ns prefix-ancestors (when the new entry is trimmable) so a conversation
15
18
  // stays one entry, not one per turn.
16
19
  //
17
20
  // TIERING (Layer 0, unified-engine plan): when a ColdTier is attached,
@@ -55,8 +58,9 @@ function makeSharedRetain(retain: (() => void) | undefined): { acquire(): () =>
55
58
 
56
59
  /** The cold (SSD) tier the RAM cache tiers over. Structural — this module
57
60
  * never imports ssd-cache (which imports us); the server binds
58
- * SsdCacheStore + the model into this shape (src/server.ts). All methods
59
- * must be failure-proof: any error degrades to "no hit"/"not stored". */
61
+ * SsdCacheStore + the model into this shape (src/server.ts). Lookup and
62
+ * restore failures return null; store returns false on failure. Operations
63
+ * that require persistence retain their donor if storing fails. */
60
64
  export interface ColdTier {
61
65
  /** Longest stored usable prefix for prompt/ns — index-only, no I/O.
62
66
  * `handle` is the tier's opaque entry token, passed back to restore. */
@@ -65,7 +69,9 @@ export interface ColdTier {
65
69
  * pages fault in lazily). `retain` must run after the caches are
66
70
  * disposed (it unmaps the backing file). Null on any failure. */
67
71
  restore(handle: unknown): { tokens: number[]; caches: Cache[]; retain: () => void } | null;
68
- store(tokens: number[], caches: Cache[], ns: string): void;
72
+ /** Borrow live caches and finish persistence before returning. A false
73
+ * result prevents a required demotion or recurrent ownership transfer. */
74
+ store(tokens: number[], caches: Cache[], ns: string): boolean | void;
69
75
  }
70
76
 
71
77
  /** Spill sink for eviction/demotion (write-behind, 2026-07-06).
@@ -105,14 +111,7 @@ export interface PromptCacheEntry {
105
111
  export function cacheBytes(caches: Cache[]): number {
106
112
  let total = 0;
107
113
  for (const c of caches) {
108
- const state = c.state();
109
- for (const a of state) total += a.nbytes;
110
- // Most Cache kinds' state() returns their own live-owned arrays (safe
111
- // to just read), but TurboQuantKVCache allocates fresh trimmed slice
112
- // views per call (kv-store.ts snapshotCache/cloneKvCaches contract) —
113
- // those must be disposed here or they leak (see generate.ts
114
- // evalCacheState for the same hazard on the prefill path).
115
- if (c.stateNeedsDispose) for (const a of state) a.dispose();
114
+ total += withResource(leaseCacheState(c), (state) => state.reduce((bytes, a) => bytes + a.nbytes, 0));
116
115
  }
117
116
  return total;
118
117
  }
@@ -202,8 +201,8 @@ export class PromptCache {
202
201
  if (spill) try { spill(entry); } catch {}
203
202
  }
204
203
  }
205
- for (const c of entry.caches) c.dispose();
206
- entry.retain?.();
204
+ disposeResources(entry.caches);
205
+ entry.retain?.(); // backing release requires successful cache disposal
207
206
  }
208
207
 
209
208
  get totalBytes(): number {
@@ -214,14 +213,36 @@ export class PromptCache {
214
213
  return this.#entries.length;
215
214
  }
216
215
 
216
+ /** Release LRU entries until the caller's live-memory check is satisfied.
217
+ * Unlike background eviction, pressure relief must finish the SSD write
218
+ * and release the buffers now, not enqueue clones that keep them alive.
219
+ * Call under the engine lock, after taking the request's reusable prefix. */
220
+ relievePressure(overBudget: () => boolean): number {
221
+ let count = 0;
222
+ while (this.#entries.length && overBudget()) {
223
+ let oldest = 0;
224
+ for (let i = 1; i < this.#entries.length; i++)
225
+ if (this.#entries[i]!.lastUsed < this.#entries[oldest]!.lastUsed) oldest = i;
226
+ const { entry } = this.#entries[oldest]!;
227
+ // Keep the entry resident if persistence throws. A failed SSD write
228
+ // must not be presented as a successful demotion.
229
+ if (this.#cold) {
230
+ if (this.#cold.store(entry.tokens, entry.caches, entry.ns) === false) break;
231
+ }
232
+ else this.#spillSync?.(entry);
233
+ this.#entries.splice(oldest, 1);
234
+ this.#disposeEntry(entry, false);
235
+ this.demotions++;
236
+ count++;
237
+ }
238
+ return count;
239
+ }
240
+
217
241
  /** Find the entry with the longest usable common prefix of `prompt`
218
- * across BOTH tiers and serve it NON-CONSUMINGLY (prefix sharing,
219
- * 2026-07-05): the caller receives zero-copy CLONES trimmed to the
220
- * matched prefix (plus a ref-counted retain share); the donor entry
221
- * stays in the cache, ready for the next agent/session with the same
222
- * prefix. The caller owns the returned caches — dispose them or put()
223
- * an extended entry (which supersedes prefix-ancestors), honoring
224
- * `retain` either way.
242
+ * across BOTH tiers. Trimmable RAM entries are served non-consumingly as
243
+ * zero-copy clones; exact hits on Qwen hybrid entries containing recurrent
244
+ * SSM state transfer the donor itself. The caller owns the returned caches —
245
+ * dispose them or put() an extended entry, honoring `retain` either way.
225
246
  *
226
247
  * Usable prefix = common prefix capped at prompt.length - 1 (at least
227
248
  * one token must be forwarded to produce logits). Entries longer than
@@ -258,27 +279,22 @@ export class PromptCache {
258
279
  if (hit && hit.prefixLen > bestLen) {
259
280
  const loaded = this.#cold.restore(hit.handle);
260
281
  if (loaded) {
261
- const trimNeeded = loaded.tokens.length - hit.prefixLen;
262
- if (trimNeeded > 0 && !loaded.caches.every((c) => c.isTrimmable())) {
263
- // Divergent tail on an untrimmable kind: this file can't seed
264
- // the prompt — fall back to the RAM candidate. Loud: this used
265
- // to be the silent 84 s "restore-then-re-prefill" path (the
266
- // whole cold tier degrading with zero observables, 2026-07-06).
267
- console.warn(
268
- `[prompt-cache] cold entry unusable: ${loaded.tokens.length} tokens, ` +
269
- `needs trim ${trimNeeded} but untrimmable (wrapped ring/SSM) — re-prefilling`,
270
- );
271
- for (const c of loaded.caches) c.dispose();
272
- loaded.retain();
273
- } else {
274
- if (trimNeeded > 0) for (const c of loaded.caches) c.trim(trimNeeded);
275
- return {
276
- tokens: loaded.tokens.slice(0, hit.prefixLen),
277
- caches: loaded.caches,
278
- ns,
279
- retain: loaded.retain,
280
- };
281
- }
282
+ const owner = ownResource(loaded, (entry) =>
283
+ this.#disposeEntry({ ...entry, ns }, false));
284
+ const result = withResource(owner, (entry) => {
285
+ const trimNeeded = entry.tokens.length - hit.prefixLen;
286
+ if (trimNeeded > 0 && !entry.caches.every((cache) => cache.isTrimmable())) {
287
+ console.warn(
288
+ `[prompt-cache] cold entry unusable: ${entry.tokens.length} tokens, ` +
289
+ `needs trim ${trimNeeded} but untrimmable (wrapped ring/SSM) — re-prefilling`,
290
+ );
291
+ return null;
292
+ }
293
+ if (trimNeeded > 0) for (const cache of entry.caches) cache.trim(trimNeeded);
294
+ const tokens = entry.tokens.slice(0, hit.prefixLen);
295
+ return { ...owner.transfer(), tokens, ns };
296
+ });
297
+ if (result) return result;
282
298
  }
283
299
  }
284
300
  }
@@ -296,9 +312,25 @@ export class PromptCache {
296
312
  const rec = this.#entries[bestIdx]!;
297
313
  rec.lastUsed = ++this.#clock;
298
314
  rec.lastUsedMs = Date.now();
315
+ // Qwen recurrent caches are untrimmable. Transfer the exact-boundary
316
+ // entry instead of retaining a second recurrent snapshot in RAM.
317
+ // This reduced memory pressure in the recorded multi-turn reproduction.
318
+ // Save the boundary before consumption: the extended recurrent state
319
+ // cannot be trimmed back, and a delayed writer can no longer find it
320
+ // in RAM after ownership moves. An already durable boundary is cheap.
321
+ if (rec.entry.caches.some((c) => c.signature() === "ssm")) {
322
+ if (this.#cold?.store(rec.entry.tokens, rec.entry.caches, ns) === false)
323
+ throw new Error("prompt-cache: failed to persist recurrent boundary before transfer");
324
+ this.#entries.splice(bestIdx, 1);
325
+ return rec.entry;
326
+ }
299
327
  const clones = this.#clone(rec.entry.caches);
300
328
  const trimNeeded = rec.entry.tokens.length - bestLen;
301
- if (trimNeeded > 0) for (const c of clones) c.trim(trimNeeded);
329
+ try {
330
+ if (trimNeeded > 0) for (const c of clones) c.trim(trimNeeded);
331
+ } catch (error) {
332
+ cleanupFailure(error, () => disposeResources(clones));
333
+ }
302
334
  return {
303
335
  tokens: rec.entry.tokens.slice(0, bestLen),
304
336
  caches: clones,
@@ -337,10 +369,20 @@ export class PromptCache {
337
369
  * (spilling each to the cold tier first, when one is attached). If the
338
370
  * entry itself exceeds the cap it is spilled + disposed, not stored. */
339
371
  put(tokens: number[], caches: Cache[], ns = "", retain?: () => void): void {
372
+ // All cache-provided validation runs before ownership is adopted.
340
373
  const bytes = cacheBytes(caches);
374
+ const trimmable = caches.every((cache) => cache.isTrimmable());
375
+ const discard = (entry: PromptCacheEntry, spill: boolean) => {
376
+ try { this.#disposeEntry(entry, spill); }
377
+ catch (error) {
378
+ // The new entry is already owned here. Throwing back to its previous
379
+ // owner would invite disposal of state still held by this store.
380
+ console.warn(`[prompt-cache] entry cleanup failed: ${error}`);
381
+ }
382
+ };
341
383
  const entry: PromptCacheEntry = { tokens, caches, ns, retain };
342
384
  if (bytes > this.maxBytes) {
343
- this.#disposeEntry(entry, true);
385
+ discard(entry, true);
344
386
  return;
345
387
  }
346
388
  // The donor's retain becomes ref-counted so take()'s clones can hold
@@ -359,7 +401,7 @@ export class PromptCache {
359
401
  if (old.entry.tokens.length !== tokens.length) continue;
360
402
  if (commonPrefixLength(old.entry.tokens, tokens) !== tokens.length) continue;
361
403
  this.#entries = this.#entries.filter((r) => r !== old);
362
- this.#disposeEntry(old.entry, false);
404
+ discard(old.entry, false);
363
405
  }
364
406
  // SUPERSEDE strict prefix-ancestors (the SSD store's rule, brought to
365
407
  // RAM — prefix sharing would otherwise grow an entry per turn): same-ns
@@ -370,13 +412,13 @@ export class PromptCache {
370
412
  // can only serve exact-length matches, so shorter ancestors — e.g.
371
413
  // the prompt-boundary snapshot that makes drifted multi-turn agents
372
414
  // hit — must survive.
373
- if (caches.every((c) => c.isTrimmable())) {
415
+ if (trimmable) {
374
416
  for (const old of [...this.#entries]) {
375
417
  if (old === rec || old.entry.ns !== ns) continue;
376
418
  if (old.entry.tokens.length >= tokens.length) continue;
377
419
  if (commonPrefixLength(old.entry.tokens, tokens) !== old.entry.tokens.length) continue;
378
420
  this.#entries = this.#entries.filter((r) => r !== old);
379
- this.#disposeEntry(old.entry, false);
421
+ discard(old.entry, false);
380
422
  }
381
423
  }
382
424
  while (this.totalBytes > this.maxBytes && this.#entries.length > 1) {
@@ -384,7 +426,7 @@ export class PromptCache {
384
426
  for (let i = 1; i < this.#entries.length; i++)
385
427
  if (this.#entries[i]!.lastUsed < this.#entries[lruIdx]!.lastUsed) lruIdx = i;
386
428
  const [evicted] = this.#entries.splice(lruIdx, 1);
387
- this.#disposeEntry(evicted!.entry, true);
429
+ discard(evicted!.entry, true);
388
430
  }
389
431
  try { this.onPut?.(tokens, ns); } catch {}
390
432
  }
@@ -413,7 +455,8 @@ export class PromptCache {
413
455
  }
414
456
 
415
457
  clear(): void {
416
- for (const e of this.#entries) this.#disposeEntry(e.entry, false);
458
+ const entries = this.#entries;
417
459
  this.#entries = [];
460
+ disposeResources(entries.map(({ entry }) => ({ dispose: () => this.#disposeEntry(entry, false) })));
418
461
  }
419
462
  }
@@ -11,7 +11,7 @@
11
11
  // - NaN-sensitivity handling (treat as maximally sensitive)
12
12
  // - post-greedy block-run guard
13
13
  //
14
- // Verified bit-for-bit against the Python via tests/quantize-allocator.test.ts.
14
+ // Verified bit-for-bit against the Python via tests/parity/quantize-allocator.test.ts.
15
15
  //
16
16
  // The latency-aware variants (`optimize_latency_aware`,
17
17
  // `optimize_for_latency_budget`) are intentionally NOT ported — they pull in
@@ -13,7 +13,9 @@ import { existsSync } from "node:fs";
13
13
  import { join } from "node:path";
14
14
 
15
15
  /** A per-module quantization override. `false` means "left unquantized". */
16
- export type PerLayerEntry = { bits: number; groupSize: number } | false;
16
+ export type PerLayerEntry =
17
+ | { bits: number; groupSize: number; mode?: string; trellis?: { L: number; code: string; axis: 0 | 1 } }
18
+ | false;
17
19
 
18
20
  /** The default quantization definition for the model. */
19
21
  export interface QuantDef {
@@ -28,7 +30,7 @@ export interface QuantizationBlock {
28
30
  bits: number;
29
31
  mode: string;
30
32
  // plus per-module entries: { [modulePath]: {bits, group_size} | false }
31
- [modulePath: string]: number | string | { bits: number; group_size: number } | false;
33
+ [modulePath: string]: number | string | { bits: number; group_size: number; mode?: string; trellis?: { L: number; code: string; axis: 0 | 1 } } | false;
32
34
  }
33
35
 
34
36
  /**
@@ -47,7 +49,12 @@ export function buildQuantizationBlock(
47
49
  mode: def.mode ?? "affine",
48
50
  };
49
51
  for (const [path, entry] of perLayer) {
50
- block[path] = entry === false ? false : { bits: entry.bits, group_size: entry.groupSize };
52
+ if (entry === false) { block[path] = false; continue; }
53
+ const v: { bits: number; group_size: number; mode?: string; trellis?: { L: number; code: string; axis: 0 | 1 } } =
54
+ { bits: entry.bits, group_size: entry.groupSize };
55
+ if (entry.mode) v.mode = entry.mode;
56
+ if (entry.trellis) v.trellis = entry.trellis;
57
+ block[path] = v;
51
58
  }
52
59
  return block;
53
60
  }
@@ -3,7 +3,7 @@
3
3
  // Folds an orthogonal rotation offline into producer/consumer weight pairs so
4
4
  // weights are quantized in a basis where outlier channels are smeared into
5
5
  // near-Gaussian marginals. Recipe, fold table, and the decided deviations from
6
- // the QuaRot/SpinQuant references live in docs/design/turboquant-weights.md —
6
+ // the QuaRot/SpinQuant references live in docs/design/turboquant.md —
7
7
  // notably: γ-fold FIRST (R1 only commutes with gain-free RMSNorm), SpinQuant's
8
8
  // fully-offline per-head R2 (NOT QuaRot's, which needs a runtime op), NO R4
9
9
  // half-fold on down_proj's input, NO embedding mean-centering (not an exact
@@ -47,7 +47,7 @@ function assertPow2(n: number, what: string): void {
47
47
  if (n < 2 || (n & (n - 1)) !== 0)
48
48
  throw new Error(
49
49
  `rotation fold: ${what}=${n} is not a power of two — the Kronecker ` +
50
- `Hadamard path is not implemented (docs/design/turboquant-weights.md)`,
50
+ `Hadamard path is not implemented (docs/design/turboquant.md)`,
51
51
  );
52
52
  }
53
53
 
@@ -323,7 +323,7 @@ export function foldLlamaWeights(
323
323
  // elementwise in head space) does not commute with a per-head rotation, so R2
324
324
  // is architecturally off for this family — recorded in the design doc.
325
325
  //
326
- // Residual corridors (docs/design/turboquant-weights.md, W1 map):
326
+ // Residual corridors (docs/design/turboquant.md, W1 map):
327
327
  // readers (@R1 input dim, γ premultiplied): q/k/v_proj,
328
328
  // linear_attn.in_proj_{qkv,z,b,a}, mlp.gate/up_proj, lm_head
329
329
  // writers (R1ᵀ output dim): self_attn.o_proj, linear_attn.out_proj,
@@ -370,7 +370,7 @@ export interface QwenFoldPlan {
370
370
  * FoldOp — pure name analysis, no arrays. R1-ONLY (+γ): the attention output
371
371
  * gate (`o_proj(out · σ(gate))`, elementwise in head space) does not commute
372
372
  * with a per-head rotation, so R2 is architecturally off for this family.
373
- * Corridor map: docs/design/turboquant-weights.md §W1.
373
+ * Corridor map: docs/design/turboquant.md §W1.
374
374
  */
375
375
  export function planQwen35Fold(names: readonly string[], prefix = "language_model."): QwenFoldPlan {
376
376
  const P = prefix;