@mlx-bun/inference 0.0.0-stage → 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 (467) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1475 -2
  3. package/THIRD_PARTY_NOTICES.md +3404 -0
  4. package/dist/native/libmlx_bun_ane.dylib +0 -0
  5. package/dist/native/libmlx_bun_expert_io.dylib +0 -0
  6. package/dist/native/mlx-bun-frame-extract +0 -0
  7. package/examples/fit-model.ts +25 -0
  8. package/examples/qwen3-forward.ts +42 -0
  9. package/examples/qwen3-generate.ts +28 -0
  10. package/examples/trellis-expand.ts +23 -0
  11. package/package.json +129 -4
  12. package/src/adapters/available.ts +110 -0
  13. package/src/adapters/index.ts +3 -0
  14. package/src/adapters/manager.ts +316 -0
  15. package/src/artifacts/auxiliary-files.ts +45 -0
  16. package/src/artifacts/config-dialects.ts +94 -0
  17. package/src/artifacts/config.ts +341 -0
  18. package/src/artifacts/draft-vocabulary.ts +30 -0
  19. package/src/artifacts/expert-offload-build.ts +75 -0
  20. package/src/artifacts/expert-offload.ts +89 -0
  21. package/src/artifacts/experts/index.ts +3 -0
  22. package/src/artifacts/experts/io.ts +386 -0
  23. package/src/artifacts/experts/residency.ts +1134 -0
  24. package/src/artifacts/experts/usage.ts +415 -0
  25. package/src/artifacts/fingerprint.ts +44 -0
  26. package/src/artifacts/glm52/container.ts +305 -0
  27. package/src/artifacts/glm52/expert-layout.ts +249 -0
  28. package/src/artifacts/glm52/index.ts +7 -0
  29. package/src/artifacts/glm52/kernel-layout.ts +124 -0
  30. package/src/artifacts/glm52/layout.ts +152 -0
  31. package/src/artifacts/glm52/memory.ts +500 -0
  32. package/src/artifacts/glm52/resident-weights.ts +135 -0
  33. package/src/artifacts/glm52/weights.ts +503 -0
  34. package/src/artifacts/glm52-config.ts +262 -0
  35. package/src/artifacts/identity.ts +94 -0
  36. package/src/artifacts/index.ts +15 -0
  37. package/src/artifacts/mmap.ts +90 -0
  38. package/src/artifacts/safetensors-writer.ts +249 -0
  39. package/src/artifacts/safetensors.ts +164 -0
  40. package/src/artifacts/turboquant-fold.ts +42 -0
  41. package/src/artifacts/weight-audit.ts +43 -0
  42. package/src/artifacts/weights.ts +255 -0
  43. package/src/contracts/index.ts +2 -0
  44. package/src/contracts/mlx/attention.ts +8 -0
  45. package/src/contracts/mlx/cache.ts +510 -0
  46. package/src/contracts/mlx/checkpoint.ts +10 -0
  47. package/src/contracts/mlx/draft-checkpoint.ts +14 -0
  48. package/src/contracts/mlx/draft-projection.ts +27 -0
  49. package/src/contracts/mlx/draft-target.ts +84 -0
  50. package/src/contracts/mlx/drafter.ts +176 -0
  51. package/src/contracts/mlx/forward-work.ts +23 -0
  52. package/src/contracts/mlx/glm52-weights.ts +25 -0
  53. package/src/contracts/mlx/graph.ts +309 -0
  54. package/src/contracts/mlx/head.ts +6 -0
  55. package/src/contracts/mlx/index.ts +15 -0
  56. package/src/contracts/mlx/kv-codec.ts +9 -0
  57. package/src/contracts/mlx/media.ts +113 -0
  58. package/src/contracts/mlx/positions.ts +16 -0
  59. package/src/contracts/mlx/row-table.ts +2 -0
  60. package/src/contracts/mlx/trainable.ts +103 -0
  61. package/src/contracts/mlx/whisper.ts +96 -0
  62. package/src/contracts/portable/composition.ts +106 -0
  63. package/src/contracts/portable/denoising.ts +17 -0
  64. package/src/contracts/portable/execution.ts +79 -0
  65. package/src/contracts/portable/generation.ts +103 -0
  66. package/src/contracts/portable/graph.ts +80 -0
  67. package/src/contracts/portable/index.ts +15 -0
  68. package/src/contracts/portable/memory-plan.ts +26 -0
  69. package/src/contracts/portable/object-cache.ts +8 -0
  70. package/src/contracts/portable/prefill.ts +63 -0
  71. package/src/contracts/portable/prefix-cache.ts +33 -0
  72. package/src/contracts/portable/prompt.ts +2 -0
  73. package/src/contracts/portable/resources.ts +25 -0
  74. package/src/contracts/portable/rollback.ts +7 -0
  75. package/src/contracts/portable/sampling.ts +44 -0
  76. package/src/contracts/portable/scheduling.ts +54 -0
  77. package/src/contracts/portable/token-delivery.ts +4 -0
  78. package/src/contracts/portable/tsconfig.json +14 -0
  79. package/src/embeddings/index.ts +1 -0
  80. package/src/embeddings/text.ts +85 -0
  81. package/src/execution/admission.ts +59 -0
  82. package/src/execution/batch-group.ts +1240 -0
  83. package/src/execution/batch-types.ts +225 -0
  84. package/src/execution/cancellation.ts +38 -0
  85. package/src/execution/continuation-request.ts +45 -0
  86. package/src/execution/continuation-types.ts +10 -0
  87. package/src/execution/coordinator.ts +70 -0
  88. package/src/execution/denoising-group.ts +180 -0
  89. package/src/execution/engine.ts +56 -0
  90. package/src/execution/fill-group.ts +505 -0
  91. package/src/execution/fit.ts +184 -0
  92. package/src/execution/gateway-binding.ts +178 -0
  93. package/src/execution/grammar-group.ts +224 -0
  94. package/src/execution/index.ts +18 -0
  95. package/src/execution/methods.ts +75 -0
  96. package/src/execution/mixed-iteration.ts +96 -0
  97. package/src/execution/plan.ts +65 -0
  98. package/src/execution/prefill-cohort.ts +100 -0
  99. package/src/execution/prefill-rows.ts +263 -0
  100. package/src/execution/request-policy.ts +19 -0
  101. package/src/execution/row-sampling.ts +49 -0
  102. package/src/execution/scheduler.ts +92 -0
  103. package/src/execution/session.ts +247 -0
  104. package/src/execution/speculative-group.ts +797 -0
  105. package/src/execution/tasks.ts +44 -0
  106. package/src/generation/autoregressive.ts +840 -0
  107. package/src/generation/bindings/autoregressive.ts +104 -0
  108. package/src/generation/bindings/denoising.ts +60 -0
  109. package/src/generation/bindings/prefill-policy.ts +36 -0
  110. package/src/generation/checkpoint-identity.ts +60 -0
  111. package/src/generation/denoising.ts +85 -0
  112. package/src/generation/diffusion.ts +583 -0
  113. package/src/generation/fill/echo-index.ts +188 -0
  114. package/src/generation/fill/index.ts +3 -0
  115. package/src/generation/fill/proposal.ts +69 -0
  116. package/src/generation/fill/schema-rows.ts +506 -0
  117. package/src/generation/fill/session.ts +464 -0
  118. package/src/generation/fill/tool-boundary.ts +103 -0
  119. package/src/generation/fill-append.ts +58 -0
  120. package/src/generation/generate.ts +44 -0
  121. package/src/generation/grammar-step.ts +39 -0
  122. package/src/generation/index.ts +14 -0
  123. package/src/generation/phase-dispatch.ts +115 -0
  124. package/src/generation/prefill.ts +33 -0
  125. package/src/generation/result.ts +49 -0
  126. package/src/generation/scopes.ts +157 -0
  127. package/src/generation/speculative/acceptance.ts +41 -0
  128. package/src/generation/speculative/adaptive-draft-gate.ts +166 -0
  129. package/src/generation/speculative/bindings/assistant-rows.ts +102 -0
  130. package/src/generation/speculative/bindings/autoregressive-rows.ts +124 -0
  131. package/src/generation/speculative/bindings/binding.ts +106 -0
  132. package/src/generation/speculative/bindings/deepspec-rows.ts +33 -0
  133. package/src/generation/speculative/bindings/draft-target.ts +10 -0
  134. package/src/generation/speculative/bindings/glm52-mtp-rows.ts +128 -0
  135. package/src/generation/speculative/bindings/projected-context-rows.ts +78 -0
  136. package/src/generation/speculative/bindings/projected-draft-rows.ts +95 -0
  137. package/src/generation/speculative/bindings/qwen-mtp-rows.ts +336 -0
  138. package/src/generation/speculative/bindings/two-model-rows.ts +63 -0
  139. package/src/generation/speculative/cache-identity.ts +16 -0
  140. package/src/generation/speculative/draft-kind.ts +149 -0
  141. package/src/generation/speculative/draft-policy.ts +41 -0
  142. package/src/generation/speculative/draft-registry.ts +118 -0
  143. package/src/generation/speculative/draft-vocab.ts +110 -0
  144. package/src/generation/speculative/index.ts +17 -0
  145. package/src/generation/speculative/output.ts +49 -0
  146. package/src/generation/speculative/round.ts +307 -0
  147. package/src/generation/speculative/source.ts +144 -0
  148. package/src/generation/speculative/sources/assistant-source.ts +64 -0
  149. package/src/generation/speculative/sources/deepspec-source.ts +70 -0
  150. package/src/generation/speculative/sources/dflash-source.ts +84 -0
  151. package/src/generation/speculative/sources/dflash2-source.ts +86 -0
  152. package/src/generation/speculative/sources/glm52-mtp-source.ts +66 -0
  153. package/src/generation/speculative/sources/ngram-source.ts +165 -0
  154. package/src/generation/speculative/sources/qwen-mtp-source.ts +154 -0
  155. package/src/generation/speculative/sources/two-model.ts +75 -0
  156. package/src/generation/types.ts +194 -0
  157. package/src/index.ts +20 -0
  158. package/src/input/audio/audiotoolbox.ts +112 -0
  159. package/src/input/audio/decode.ts +133 -0
  160. package/src/input/audio/features.ts +202 -0
  161. package/src/input/audio/index.ts +6 -0
  162. package/src/input/audio/transcode.ts +67 -0
  163. package/src/input/audio/whisper-mel.ts +185 -0
  164. package/src/input/audio/whisper-tokenizer.ts +238 -0
  165. package/src/input/chat-template.ts +389 -0
  166. package/src/input/index.ts +6 -0
  167. package/src/input/media-fetch.ts +439 -0
  168. package/src/input/packed-mixed.ts +43 -0
  169. package/src/input/special-tokens.ts +75 -0
  170. package/src/input/token-groups.ts +49 -0
  171. package/src/input/tokenizer.ts +336 -0
  172. package/src/input/tool-call.ts +689 -0
  173. package/src/input/vision/index.ts +3 -0
  174. package/src/input/vision/preprocess.ts +247 -0
  175. package/src/input/vision/prompt.ts +398 -0
  176. package/src/input/vision/qwen3vl-preprocess.ts +409 -0
  177. package/src/input/vision/qwen3vl-prompt.ts +237 -0
  178. package/src/input/vision/video-frames.ts +100 -0
  179. package/src/kernels/ane/fill.ts +101 -0
  180. package/src/kernels/ane/linear.ts +416 -0
  181. package/src/kernels/attention/flash.ts +694 -0
  182. package/src/kernels/attention/kvq4-decode.ts +155 -0
  183. package/src/kernels/attention/masks.ts +40 -0
  184. package/src/kernels/attention/multi-query.ts +193 -0
  185. package/src/kernels/attention/paged.ts +130 -0
  186. package/src/kernels/delta/gated.ts +306 -0
  187. package/src/kernels/delta/index.ts +3 -0
  188. package/src/kernels/delta/silu-rms-heads.ts +69 -0
  189. package/src/kernels/delta/state.ts +74 -0
  190. package/src/kernels/gated-rms-norm.ts +64 -0
  191. package/src/kernels/glm52/layout.ts +37 -0
  192. package/src/kernels/glm52/streamed-experts.ts +360 -0
  193. package/src/kernels/logits.ts +38 -0
  194. package/src/kernels/quantization/affine-row.ts +154 -0
  195. package/src/kernels/quantization/affine-rows.ts +36 -0
  196. package/src/kernels/quantization/affine-split-mma.ts +144 -0
  197. package/src/kernels/quantization/affine3-mma.ts +117 -0
  198. package/src/kernels/quantization/affine3-rows.ts +103 -0
  199. package/src/kernels/quantization/affine4-mma.ts +113 -0
  200. package/src/kernels/quantization/affine4-rows.ts +102 -0
  201. package/src/kernels/residual-rms-norm.ts +65 -0
  202. package/src/kernels/sampling/index.ts +2 -0
  203. package/src/kernels/sampling/normalized-argmax.ts +116 -0
  204. package/src/kernels/sampling/token-bitmask.ts +52 -0
  205. package/src/kernels/softcap.ts +35 -0
  206. package/src/kernels/trellis/codebook.ts +123 -0
  207. package/src/kernels/trellis/down-codebook.ts +184 -0
  208. package/src/kernels/trellis/down-factored.ts +180 -0
  209. package/src/kernels/trellis/down-k3-interleaved-factored.ts +146 -0
  210. package/src/kernels/trellis/down-mma-wide.ts +195 -0
  211. package/src/kernels/trellis/down-mma.ts +145 -0
  212. package/src/kernels/trellis/expand.ts +42 -0
  213. package/src/kernels/trellis/gate-up-codebook-mma.ts +141 -0
  214. package/src/kernels/trellis/gate-up-codebook.ts +137 -0
  215. package/src/kernels/trellis/gate-up-factored.ts +197 -0
  216. package/src/kernels/trellis/gate-up-mma-wide.ts +147 -0
  217. package/src/kernels/trellis/gate-up-mma.ts +148 -0
  218. package/src/kernels/trellis/gate-up.ts +248 -0
  219. package/src/kernels/trellis/geometry.ts +22 -0
  220. package/src/kernels/trellis/index.ts +103 -0
  221. package/src/kernels/trellis/launch.ts +12 -0
  222. package/src/kernels/trellis/mixed-gate-up-factored.ts +119 -0
  223. package/src/kernels/trellis/mixed-gate-up.ts +146 -0
  224. package/src/kernels/trellis/qualified.ts +34 -0
  225. package/src/kernels/trellis/reduce.ts +150 -0
  226. package/src/kernels/trellis/scatter.ts +407 -0
  227. package/src/kernels/trellis/splitk-prefill.ts +152 -0
  228. package/src/kernels/trellis/tiled-prefill.ts +131 -0
  229. package/src/kernels/trellis/vector-expand.ts +58 -0
  230. package/src/kernels/trellis/wide-prefill.ts +89 -0
  231. package/src/kernels/turboquant/decode.ts +82 -0
  232. package/src/kernels/turboquant/index.ts +5 -0
  233. package/src/kernels/turboquant/inverse.ts +122 -0
  234. package/src/kernels/turboquant/ops.ts +860 -0
  235. package/src/kernels/turboquant/signs.ts +130 -0
  236. package/src/kernels/turboquant/tables.ts +124 -0
  237. package/src/layers/affine-verify-linear.ts +75 -0
  238. package/src/layers/ane-prefill-split.ts +283 -0
  239. package/src/layers/compiled-normalization.ts +41 -0
  240. package/src/layers/delta-heads.ts +23 -0
  241. package/src/layers/dense-embedding.ts +29 -0
  242. package/src/layers/dense-linear.ts +32 -0
  243. package/src/layers/dequantized-linear.ts +72 -0
  244. package/src/layers/folded-attention.ts +42 -0
  245. package/src/layers/geglu.ts +130 -0
  246. package/src/layers/helpers.ts +7 -0
  247. package/src/layers/index.ts +36 -0
  248. package/src/layers/kv4-decode-attention.ts +15 -0
  249. package/src/layers/layer-norm.ts +24 -0
  250. package/src/layers/loading.ts +92 -0
  251. package/src/layers/lora.ts +150 -0
  252. package/src/layers/normalization.ts +9 -0
  253. package/src/layers/prefix-layout.ts +33 -0
  254. package/src/layers/quantized-append-attention.ts +32 -0
  255. package/src/layers/quantized-attention.ts +353 -0
  256. package/src/layers/quantized-embedding.ts +39 -0
  257. package/src/layers/quantized-linear.ts +137 -0
  258. package/src/layers/quantized-switch-linear.ts +37 -0
  259. package/src/layers/qwen-append.ts +12 -0
  260. package/src/layers/qwen-mrope.ts +238 -0
  261. package/src/layers/rope-2d.ts +110 -0
  262. package/src/layers/rope.ts +389 -0
  263. package/src/layers/swiglu.ts +23 -0
  264. package/src/layers/trellis-codebook-down.ts +52 -0
  265. package/src/layers/trellis-codebook-gate-up.ts +42 -0
  266. package/src/layers/trellis-down.ts +117 -0
  267. package/src/layers/trellis-gate-up.ts +71 -0
  268. package/src/layers/trellis-linear.ts +211 -0
  269. package/src/layers/trellis-prompt.ts +102 -0
  270. package/src/layers/trellis-reduce-gate-up.ts +73 -0
  271. package/src/layers/trellis-scatter-down.ts +98 -0
  272. package/src/models/ane-install.ts +74 -0
  273. package/src/models/audio/conformer.ts +656 -0
  274. package/src/models/cache-composition.ts +44 -0
  275. package/src/models/capabilities.ts +89 -0
  276. package/src/models/chat-template.ts +10 -0
  277. package/src/models/composition.ts +107 -0
  278. package/src/models/diffusion-gemma/experts.ts +59 -0
  279. package/src/models/diffusion-gemma/family.ts +21 -0
  280. package/src/models/diffusion-gemma/model.ts +900 -0
  281. package/src/models/diffusion-gemma/vision.ts +549 -0
  282. package/src/models/drafter-loaders.ts +49 -0
  283. package/src/models/drafters.ts +39 -0
  284. package/src/models/factory.ts +351 -0
  285. package/src/models/families.ts +70 -0
  286. package/src/models/family.ts +125 -0
  287. package/src/models/gemma4/assistant-target.ts +46 -0
  288. package/src/models/gemma4/assistant.ts +327 -0
  289. package/src/models/gemma4/blocks.ts +755 -0
  290. package/src/models/gemma4/compiled-decode.ts +452 -0
  291. package/src/models/gemma4/family.ts +39 -0
  292. package/src/models/gemma4/gemma4-12b.ts +677 -0
  293. package/src/models/gemma4/gemma4-26b.ts +544 -0
  294. package/src/models/gemma4/gemma4-e4b.ts +599 -0
  295. package/src/models/gemma4/graphs.ts +147 -0
  296. package/src/models/gemma4/media.ts +88 -0
  297. package/src/models/gemma4/model.ts +860 -0
  298. package/src/models/gemma4/serving-blocks.ts +72 -0
  299. package/src/models/gemma4/trainable.ts +186 -0
  300. package/src/models/gemma4/written-blocks.ts +232 -0
  301. package/src/models/gemma4/written-forward.ts +159 -0
  302. package/src/models/gemma4/written-input.ts +29 -0
  303. package/src/models/glm52/atlas.ts +507 -0
  304. package/src/models/glm52/coupling.ts +388 -0
  305. package/src/models/glm52/dsa.ts +601 -0
  306. package/src/models/glm52/family.ts +20 -0
  307. package/src/models/glm52/mla.ts +812 -0
  308. package/src/models/glm52/model.ts +1127 -0
  309. package/src/models/glm52/moe.ts +223 -0
  310. package/src/models/glm52/mtp.ts +60 -0
  311. package/src/models/glm52/pilot.ts +537 -0
  312. package/src/models/glm52/reference.ts +445 -0
  313. package/src/models/glm52/residency.ts +557 -0
  314. package/src/models/glm52/streamed-experts.ts +447 -0
  315. package/src/models/graph.ts +59 -0
  316. package/src/models/implementation.ts +230 -0
  317. package/src/models/index.ts +15 -0
  318. package/src/models/media-input.ts +15 -0
  319. package/src/models/memory-plan.ts +39 -0
  320. package/src/models/minicpm5/family.ts +29 -0
  321. package/src/models/minicpm5/model.ts +394 -0
  322. package/src/models/minicpm5/trainable.ts +90 -0
  323. package/src/models/profile.ts +337 -0
  324. package/src/models/qwen/blocks.ts +658 -0
  325. package/src/models/qwen/checkpoint.ts +190 -0
  326. package/src/models/qwen/families.ts +57 -0
  327. package/src/models/qwen/media-input.ts +161 -0
  328. package/src/models/qwen/mtp.ts +101 -0
  329. package/src/models/qwen/qwen3-moe.ts +355 -0
  330. package/src/models/qwen/qwen3.ts +323 -0
  331. package/src/models/qwen/qwen38-27b-trellis-m1max.ts +1532 -0
  332. package/src/models/qwen/qwen38-27b-trellis-m4pro.ts +1092 -0
  333. package/src/models/qwen/qwen3_5.ts +433 -0
  334. package/src/models/qwen/written-layers.ts +285 -0
  335. package/src/models/row-table.ts +1 -0
  336. package/src/models/runtime.ts +73 -0
  337. package/src/models/speculative/deepspec-artifact.ts +10 -0
  338. package/src/models/speculative/deepspec.ts +744 -0
  339. package/src/models/speculative/dflash.ts +553 -0
  340. package/src/models/speculative/dflash2-artifact.ts +9 -0
  341. package/src/models/speculative/dflash2-operations.ts +1 -0
  342. package/src/models/speculative/dflash2.ts +576 -0
  343. package/src/models/speculative/loader.ts +29 -0
  344. package/src/models/support.ts +79 -0
  345. package/src/models/universal/archs.ts +365 -0
  346. package/src/models/universal/blocks.ts +479 -0
  347. package/src/models/universal/dense.ts +255 -0
  348. package/src/models/universal/family.ts +17 -0
  349. package/src/models/vision/qwen3vl.ts +566 -0
  350. package/src/models/vision/siglip.ts +520 -0
  351. package/src/models/vision/unified.ts +176 -0
  352. package/src/models/whisper/family.ts +19 -0
  353. package/src/models/whisper/fast.ts +734 -0
  354. package/src/models/whisper/model.ts +430 -0
  355. package/src/runtime/compiled-trace.ts +16 -0
  356. package/src/runtime/config.ts +77 -0
  357. package/src/runtime/expert-trace.ts +71 -0
  358. package/src/runtime/graph-forward.ts +65 -0
  359. package/src/runtime/integers.ts +6 -0
  360. package/src/runtime/native.ts +16 -0
  361. package/src/runtime/resources.ts +53 -0
  362. package/src/runtime/row-operations.ts +42 -0
  363. package/src/runtime/trace.ts +161 -0
  364. package/src/sampling/curve.ts +122 -0
  365. package/src/sampling/draft.ts +55 -0
  366. package/src/sampling/extras.ts +45 -0
  367. package/src/sampling/filters.ts +134 -0
  368. package/src/sampling/grammar.ts +630 -0
  369. package/src/sampling/hlg.ts +290 -0
  370. package/src/sampling/index.ts +9 -0
  371. package/src/sampling/processors.ts +142 -0
  372. package/src/sampling/sampler.ts +120 -0
  373. package/src/sampling/step.ts +343 -0
  374. package/src/sampling/types.ts +161 -0
  375. package/src/sampling/window-filters.ts +46 -0
  376. package/src/scoring/decisions.ts +96 -0
  377. package/src/scoring/full-sequence.ts +103 -0
  378. package/src/scoring/index.ts +9 -0
  379. package/src/scoring/kl.ts +29 -0
  380. package/src/scoring/perplexity.ts +165 -0
  381. package/src/scoring/response-logps.ts +65 -0
  382. package/src/state/affine-attention.ts +180 -0
  383. package/src/state/attachment.ts +68 -0
  384. package/src/state/attention-read.ts +215 -0
  385. package/src/state/batched-kv.ts +294 -0
  386. package/src/state/batched-mask.ts +445 -0
  387. package/src/state/batched-quant.ts +389 -0
  388. package/src/state/batched-quantized-kv.ts +209 -0
  389. package/src/state/batched-rotating-quant.ts +457 -0
  390. package/src/state/batched-rotating-state.ts +149 -0
  391. package/src/state/batched-rotating.ts +430 -0
  392. package/src/state/batched-row-storage.ts +179 -0
  393. package/src/state/batched-ssm.ts +54 -0
  394. package/src/state/batched-turboquant-kv.ts +144 -0
  395. package/src/state/bf16-first-kv.ts +663 -0
  396. package/src/state/cache-service.ts +592 -0
  397. package/src/state/capabilities.ts +65 -0
  398. package/src/state/checkpoint.ts +46 -0
  399. package/src/state/continuation-persistence.ts +126 -0
  400. package/src/state/decode-step-inputs.ts +31 -0
  401. package/src/state/decoded-kv-donor.ts +12 -0
  402. package/src/state/delayed-quantized-kv.ts +141 -0
  403. package/src/state/delayed-rotating-quantized-kv.ts +178 -0
  404. package/src/state/delayed-turboquant-kv.ts +123 -0
  405. package/src/state/durability.ts +214 -0
  406. package/src/state/encoder-cache.ts +26 -0
  407. package/src/state/full-kv-row-append.ts +33 -0
  408. package/src/state/full-kv-row-donor.ts +54 -0
  409. package/src/state/full-prefill-padding.ts +56 -0
  410. package/src/state/full-prefill-row.ts +112 -0
  411. package/src/state/full-transitioning-kv-rows.ts +70 -0
  412. package/src/state/glm52-cache.ts +783 -0
  413. package/src/state/grouped-heads-kv4-cache.ts +26 -0
  414. package/src/state/index.ts +38 -0
  415. package/src/state/kv-attention-view.ts +77 -0
  416. package/src/state/kv-budget.ts +14 -0
  417. package/src/state/kv-scheme.ts +315 -0
  418. package/src/state/kv-storage.ts +57 -0
  419. package/src/state/kv-tensor-rows.ts +212 -0
  420. package/src/state/kv.ts +324 -0
  421. package/src/state/kv4-head256-cache.ts +46 -0
  422. package/src/state/latent-legos.ts +895 -0
  423. package/src/state/layout.ts +277 -0
  424. package/src/state/leases.ts +26 -0
  425. package/src/state/paged/cache.ts +689 -0
  426. package/src/state/paged/codec.ts +36 -0
  427. package/src/state/paged/rows.ts +223 -0
  428. package/src/state/persistence-types.ts +173 -0
  429. package/src/state/persistence-worker.ts +83 -0
  430. package/src/state/persistence.ts +1036 -0
  431. package/src/state/persistence.worker.d.ts +3 -0
  432. package/src/state/persistence.worker.js +264 -0
  433. package/src/state/prefix-cache.ts +685 -0
  434. package/src/state/quantized-kv.ts +369 -0
  435. package/src/state/quantized-tensor.ts +26 -0
  436. package/src/state/request-policy.ts +48 -0
  437. package/src/state/retention-policy.ts +37 -0
  438. package/src/state/rollback.ts +131 -0
  439. package/src/state/rotating-kv-donor.ts +35 -0
  440. package/src/state/rotating-kv-layout.ts +341 -0
  441. package/src/state/rotating-kv.ts +529 -0
  442. package/src/state/rotating-quantized-kv.ts +616 -0
  443. package/src/state/rotating-row-transaction.ts +35 -0
  444. package/src/state/rows.ts +104 -0
  445. package/src/state/softcap-attention.ts +118 -0
  446. package/src/state/speculative/glm52-mtp-state.ts +38 -0
  447. package/src/state/speculative/qwen-mtp-state.ts +34 -0
  448. package/src/state/speculative-rotating-kv.ts +113 -0
  449. package/src/state/ssd-cache.ts +434 -0
  450. package/src/state/ssm-prefill-padding.ts +39 -0
  451. package/src/state/ssm.ts +422 -0
  452. package/src/state/target-layout.ts +7 -0
  453. package/src/state/tiered-prefix-cache.ts +45 -0
  454. package/src/state/training-cache.ts +300 -0
  455. package/src/state/transitioning-kv-rows.ts +267 -0
  456. package/src/state/turboquant-attention.ts +97 -0
  457. package/src/state/turboquant-codec.ts +90 -0
  458. package/src/state/turboquant-kv.ts +442 -0
  459. package/src/state/views.ts +26 -0
  460. package/src/transcription/index.ts +3 -0
  461. package/src/transcription/silero-vad.ts +379 -0
  462. package/src/transcription/whisper/decode.ts +773 -0
  463. package/src/transcription/whisper/format.ts +74 -0
  464. package/src/transcription/whisper/index.ts +4 -0
  465. package/src/transcription/whisper/timing.ts +274 -0
  466. package/src/transcription/whisper/transcribe.ts +394 -0
  467. package/src/transcription/whisper/types.ts +22 -0
@@ -0,0 +1,592 @@
1
+ // The composition's cache service: one per loaded model, constructed with its
2
+ // budget. It owns the state every request runs on: fresh caches (paged when the
3
+ // composition pages), prefix reuse from RAM and saved state, SSD offload,
4
+ // eviction, the KV budget requests are admitted under, and the generation
5
+ // checkpoints of replayable requests. The scheduler asks for a request's state
6
+ // (`acquire`: the caches plus the prompt tokens they already cover, 0 for a
7
+ // miss), offers back state it produced (`publish`, `checkpoint`) and ends the
8
+ // request (`release`). It never learns what is resident, what was evicted, or
9
+ // where (RAM, SSD); what the service holds is in its own `report`.
10
+
11
+ import { join } from "node:path";
12
+ import type { Cache } from "../contracts/mlx/cache";
13
+ import type { CheckpointAttachment } from "../contracts/mlx/checkpoint";
14
+ import { BF16_KV, type KvStorageScheme, type PagedKvComposition } from "../contracts/portable/composition";
15
+ import type { ObjectCache } from "../contracts/portable/object-cache";
16
+ import type { ModelConfig } from "../artifacts/config";
17
+ import { runtimeConfig, withRuntimeConfig, type RuntimeConfig } from "../runtime/config";
18
+ import { disposeResources } from "../runtime/resources";
19
+ import { ContinuationPersistence, type ContinuationAttempt, type ContinuationState,
20
+ type ContinuationStore } from "./continuation-persistence";
21
+ import type { DurabilityFlushResult, DurabilitySnapshotStats } from "./durability";
22
+ import { batchRowKvBytes } from "./kv-budget";
23
+ import { kvSchemeOf, type KvScheme } from "./kv-scheme";
24
+ import { rowAdmission } from "./layout";
25
+ import { cloneKvCaches, composeAtLayer, legacyCacheCodecs } from "./persistence";
26
+ import type { CacheCodecProvider } from "./persistence-types";
27
+ import { PromptCache, type ColdTier } from "./prefix-cache";
28
+ import { maybePageKv, pagedPrefixNamespace } from "./request-policy";
29
+ import type { RetentionPolicy } from "./retention-policy";
30
+ import { SsdCacheStore, type SsdIndexEntry, type SsdStoreOptions } from "./ssd-cache";
31
+ import { TieredPromptCache } from "./tiered-prefix-cache";
32
+
33
+ export { type ContinuationState } from "./continuation-persistence";
34
+
35
+ /** The loaded graph the service builds request state for: its fresh caches,
36
+ * the config its KV is projected from, the composition's KV scheme and paging,
37
+ * and the snapshot form of caches it composes beyond the built-in codecs
38
+ * (GLM-5.2's latent legos), where it declares one. */
39
+ export interface CacheServiceGraph {
40
+ makeCache(): Cache[];
41
+ readonly config: ModelConfig;
42
+ readonly composition?: { readonly kv: KvStorageScheme; readonly pagedKv: PagedKvComposition | null } | null;
43
+ readonly stateCodecs?: CacheCodecProvider;
44
+ }
45
+
46
+ /** What a replayable generation gives the service to checkpoint it. */
47
+ export interface ContinuationRequest {
48
+ /** The checkpoint key of this generation in the state namespace `namespace`
49
+ * of a store whose identity is `identity`. */
50
+ key(namespace: string, identity: unknown): string;
51
+ /** The sampler seed the generation starts from, and whether the client chose it. */
52
+ readonly seed: number;
53
+ readonly seedWasExplicit: boolean;
54
+ }
55
+
56
+ /** One request as the cache sees it: fields of the request the scheduler admits.
57
+ * The request object identifies it from `admit` to `release`. */
58
+ export interface StateRequest {
59
+ readonly promptIds: number[];
60
+ /** Tokens the request may generate: with the prompt, its KV projection and page capacity. */
61
+ readonly maxTokens: number;
62
+ readonly cacheSessionId?: string;
63
+ /** False for a request whose prefix has no identity (prepared media without
64
+ * one): it takes fresh state, reuses none, and its state is not kept. */
65
+ readonly reusesPrefix?: boolean;
66
+ /** A replayable generation: the service checkpoints it and resumes it. */
67
+ readonly continuation?: ContinuationRequest;
68
+ }
69
+
70
+ /** A request's state, owned by the caller. */
71
+ export interface AcquiredState {
72
+ /** Restored, a reused prefix (composed as the graph's caches), or fresh. */
73
+ readonly caches: Cache[];
74
+ /** Prompt tokens `caches` already cover; 0 is a miss. */
75
+ readonly reusedTokens: number;
76
+ /** Method state stored with a reused prefix. */
77
+ readonly attachments?: CheckpointAttachment[];
78
+ /** Releases the backing of reused state; run it after disposing `caches`. */
79
+ readonly retain?: () => void;
80
+ /** A resumed generation: `caches` cover its prompt and generated tokens. */
81
+ readonly continuation?: ContinuationState;
82
+ }
83
+
84
+ /** State a request offers back for reuse, covering the tokens it is published with. */
85
+ export interface PublishedState {
86
+ readonly caches: Cache[];
87
+ readonly attachments?: CheckpointAttachment[];
88
+ readonly retain?: () => void;
89
+ }
90
+
91
+ /**
92
+ * The scheduler's port to the composition's cache service. `acquire` and
93
+ * `release` bracket a request; between them the scheduler offers state it
94
+ * produced (`publish` at prefix boundaries and when a row finishes,
95
+ * `checkpoint` for a replayable generation) and marks execution boundaries.
96
+ * Admission under the KV budget runs before `acquire`. Nothing here says what
97
+ * is resident or where.
98
+ */
99
+ export interface RequestStateCache {
100
+ /** The snapshot form of the composition's caches: how the scheduler copies
101
+ * live serial state it publishes. */
102
+ readonly codecs: CacheCodecProvider;
103
+ /** Admit `request` beside the requests already holding state: true admits it
104
+ * (it holds its projected KV bytes until `release`), false means it waits.
105
+ * Throws for a request that can never be admitted: one past the caches' own
106
+ * position limit, or one over the KV budget alone. */
107
+ admit(request: StateRequest): boolean;
108
+ /** Whether `admit` would admit `request` now; holds nothing. */
109
+ fits(request: StateRequest): boolean;
110
+ /** `request`'s state: its resumed generation, else the longest reusable prefix
111
+ * of its prompt in `namespace` (its adapters, prepared media, method), else
112
+ * fresh state. Ownership passes to the caller. */
113
+ acquire(request: StateRequest, namespace: string): AcquiredState;
114
+ /** Whether state `request` publishes may be kept: the scheduler skips
115
+ * copying state the cache would drop. */
116
+ keeps(request: StateRequest): boolean;
117
+ /** Offer state covering `tokens` for reuse in `namespace`; the cache keeps or
118
+ * drops it by its own policy. Ownership transfers on return; on a throw it
119
+ * stays with the caller. */
120
+ publish(request: StateRequest, namespace: string, tokens: number[], state: PublishedState): void;
121
+ /** Save a resumable point of `request`'s generation (owned caches; the cache
122
+ * disposes them whatever happens). */
123
+ checkpoint(request: StateRequest, state: Omit<ContinuationState, "seed">): void;
124
+ /** `request` is done: its KV bytes return to the budget; a completed
125
+ * generation's checkpoints are removed, an interrupted one keeps them. */
126
+ release(request: StateRequest, completed: boolean): void;
127
+ /** An execution boundary passed: the cache may give optional state back to
128
+ * the allocator. Never changes a running request. */
129
+ boundary(): void;
130
+ }
131
+
132
+ /** What the service holds, as it reports itself (`/stats`, telemetry, residency). */
133
+ export interface KvCacheReport {
134
+ /** Reusable prefixes held in RAM. */
135
+ readonly prefixes: {
136
+ readonly entries: number; readonly bytes: number; readonly maxBytes: number;
137
+ readonly hits: number; readonly misses: number; readonly sessionHits: number; readonly sessionMisses: number;
138
+ readonly prefixScans: number; readonly objectHits: number; readonly objectMisses: number;
139
+ readonly objectRestores: number; readonly demotions: number;
140
+ };
141
+ /** Saved state on disk; null without it. */
142
+ readonly saved: {
143
+ readonly entries: number; readonly bytes: number; readonly maxBytes: number;
144
+ readonly restores: number; readonly spills: number; readonly restoreMsLast: number;
145
+ readonly longestDurablePrefixTokens: number;
146
+ } | null;
147
+ /** Writes not yet durable. */
148
+ readonly pending: DurabilitySnapshotStats;
149
+ /** Projected KV bytes of the admitted requests, and the budget they are admitted under. */
150
+ readonly kv: { readonly projectedBytes: number; readonly budgetBytes: number | null };
151
+ }
152
+
153
+ /** The parts a service composes. `createKvCacheService` builds them from the
154
+ * serve flags; CPU tests and replacement runtimes may supply their own. */
155
+ export interface KvCacheParts {
156
+ /** Reusable prefixes, tiered over saved state when there is any. Null: the
157
+ * service reuses nothing and keeps nothing. */
158
+ readonly prefixes?: PromptCache | null;
159
+ /** Saved state the prefixes tier over; reported, never reached by callers. */
160
+ readonly saved?: SsdCacheStore | null;
161
+ /** Generation checkpoints, written every `everyTokens` generated tokens. */
162
+ readonly continuations?: { readonly store: ContinuationStore; readonly persistence: ContinuationPersistence;
163
+ readonly everyTokens: number } | null;
164
+ }
165
+
166
+ export interface KvCacheSettings {
167
+ /** Aggregate projected KV bytes the admitted requests may hold (`--kv-budget`); absent: unlimited. */
168
+ readonly kvBudgetBytes?: number;
169
+ /** The snapshot form of the composition's caches (default the graph's
170
+ * `stateCodecs`, else the built-in codecs). */
171
+ readonly codecs?: CacheCodecProvider;
172
+ /** Artifact, implementation, state ABI and codec identity captured at load: part of every checkpoint key. */
173
+ readonly identity?: unknown;
174
+ /** The scope paged state is built in. */
175
+ readonly runtime?: RuntimeConfig;
176
+ /** Demote RAM entries idle this long to saved state (milliseconds), on `schedule`'s timer. */
177
+ readonly idleDemotion?: { readonly afterMs: number; schedule(run: () => void, intervalMs: number): () => void };
178
+ /** Runs once after `close` cleared the state (the saved-state budget's release). */
179
+ readonly closed?: () => void;
180
+ }
181
+
182
+ interface Lease {
183
+ /** Admitted under the KV budget: the request holds its projected bytes until `release`. */
184
+ admitted: boolean;
185
+ /** Its projected KV bytes, once something asked. */
186
+ bytes?: number;
187
+ continuation?: { key: string; namespace: string; attempt: ContinuationAttempt; seed: number };
188
+ }
189
+
190
+ const NO_PENDING: DurabilitySnapshotStats = Object.freeze({ pendingSnapshots: 0, pendingSpills: 0,
191
+ pendingSpillBytes: 0, droppedSpills: 0, failedSpills: 0 });
192
+
193
+ /** The composition's cache service (see the file header). */
194
+ export class KvCacheService implements RequestStateCache {
195
+ readonly codecs: CacheCodecProvider;
196
+ readonly #graph: CacheServiceGraph;
197
+ readonly #kv: KvStorageScheme;
198
+ readonly #paged: PagedKvComposition | null;
199
+ readonly #runtime: RuntimeConfig;
200
+ readonly #prefixes: PromptCache | null;
201
+ readonly #saved: SsdCacheStore | null;
202
+ readonly #continuations: NonNullable<KvCacheParts["continuations"]> | null;
203
+ readonly #identity: unknown;
204
+ readonly #kvBudget: number | undefined;
205
+ readonly #scheme: KvScheme;
206
+ #rowAdmission: ReturnType<typeof rowAdmission> | undefined;
207
+ readonly #leases = new Map<StateRequest, Lease>();
208
+ readonly #closed: (() => void) | undefined;
209
+ #stopIdle: (() => void) | undefined;
210
+ #closing: Promise<DurabilityFlushResult> | undefined;
211
+
212
+ constructor(graph: CacheServiceGraph, parts: KvCacheParts = {}, settings: KvCacheSettings = {}) {
213
+ this.#graph = graph;
214
+ this.#kv = graph.composition?.kv ?? BF16_KV;
215
+ this.#paged = graph.composition?.pagedKv ?? null;
216
+ this.#runtime = settings.runtime ?? runtimeConfig();
217
+ this.#prefixes = parts.prefixes ?? null;
218
+ this.#saved = parts.saved ?? null;
219
+ this.#continuations = parts.continuations ?? null;
220
+ this.codecs = settings.codecs ?? graph.stateCodecs ?? legacyCacheCodecs;
221
+ this.#identity = settings.identity;
222
+ this.#kvBudget = settings.kvBudgetBytes;
223
+ this.#scheme = kvSchemeOf(this.#kv);
224
+ this.#closed = settings.closed;
225
+ const idle = settings.idleDemotion, prefixes = this.#prefixes;
226
+ if (idle && prefixes && idle.afterMs > 0)
227
+ this.#stopIdle = idle.schedule(() => prefixes.demoteIdle(idle.afterMs), Math.max(30_000, idle.afterMs / 4));
228
+ }
229
+
230
+ /** Generation checkpoints every this many tokens; undefined: the service keeps none. */
231
+ get checkpointEveryTokens(): number | undefined { return this.#continuations?.everyTokens; }
232
+
233
+ /** The state namespace of a request whose own namespace is `namespace`: the composition's paging over it. */
234
+ #namespace(namespace: string): string {
235
+ return this.#paged ? pagedPrefixNamespace(this.#kv, this.#paged, namespace) : namespace;
236
+ }
237
+
238
+ /** Storage that projects its own rows (a layout such as GLM-5.2's compressed
239
+ * state) bills a request by its projection; the rest by the KV scheme. Read
240
+ * from the graph's fresh caches at the first admission. */
241
+ get #admission(): ReturnType<typeof rowAdmission> {
242
+ if (this.#rowAdmission === undefined) {
243
+ const proto = this.#graph.makeCache(); // fresh caches hold no buffers
244
+ try { this.#rowAdmission = rowAdmission(proto); } finally { disposeResources(proto); }
245
+ }
246
+ return this.#rowAdmission;
247
+ }
248
+
249
+ #rowBytes(request: StateRequest): number {
250
+ const admission = this.#admission;
251
+ return admission
252
+ ? admission.bytes(request.promptIds.length + request.maxTokens)
253
+ : batchRowKvBytes(this.#graph.config, request.promptIds.length, request.maxTokens, this.#scheme);
254
+ }
255
+
256
+ /** Projected KV of every admitted request (its full prompt and completion). */
257
+ get #projectedBytes(): number {
258
+ let total = 0;
259
+ for (const [request, lease] of this.#leases) if (lease.admitted) total += lease.bytes ??= this.#rowBytes(request);
260
+ return total;
261
+ }
262
+
263
+ admit(request: StateRequest): boolean {
264
+ const requested = request.promptIds.length + request.maxTokens;
265
+ const limit = this.#admission?.maxTokens ?? Infinity;
266
+ if (requested > limit)
267
+ throw new RangeError(`context limit: prompt ${request.promptIds.length} + max_tokens ${request.maxTokens} exceeds ${limit}`);
268
+ let need: number | undefined;
269
+ if (this.#kvBudget !== undefined) {
270
+ need = this.#rowBytes(request);
271
+ const alone = ![...this.#leases.values()].some(lease => lease.admitted);
272
+ if (need > this.#kvBudget && alone)
273
+ throw new Error(`kv budget: request needs ~${(need / 1e9).toFixed(2)} GB KV ` +
274
+ `(prompt ${request.promptIds.length} + max_tokens ${request.maxTokens}), ` +
275
+ `over --kv-budget ${(this.#kvBudget / 1e9).toFixed(2)} GB — lower max_tokens or raise the budget`);
276
+ if (this.#projectedBytes + need > this.#kvBudget) return false;
277
+ }
278
+ const lease = this.#lease(request);
279
+ lease.admitted = true; lease.bytes = need;
280
+ return true;
281
+ }
282
+
283
+ fits(request: StateRequest): boolean {
284
+ return this.#kvBudget === undefined || this.#projectedBytes + this.#rowBytes(request) <= this.#kvBudget;
285
+ }
286
+
287
+ #lease(request: StateRequest): Lease {
288
+ let lease = this.#leases.get(request);
289
+ if (!lease) this.#leases.set(request, lease = { admitted: false });
290
+ return lease;
291
+ }
292
+
293
+ keeps(request: StateRequest): boolean {
294
+ return this.#prefixes !== null && request.reusesPrefix !== false;
295
+ }
296
+
297
+ acquire(request: StateRequest, namespace: string): AcquiredState {
298
+ const lease = this.#lease(request);
299
+ const stateNamespace = this.#namespace(namespace);
300
+ if (request.continuation) {
301
+ const resumed = this.#resume(request, request.continuation, lease, stateNamespace);
302
+ if (resumed) return { caches: resumed.caches, reusedTokens: request.promptIds.length, continuation: resumed };
303
+ }
304
+ if (this.keeps(request)) {
305
+ const hit = this.#prefixes!.take(request.promptIds, stateNamespace, request.cacheSessionId);
306
+ if (hit) return { caches: hit.caches, reusedTokens: hit.tokens.length,
307
+ ...(hit.attachments ? { attachments: hit.attachments } : {}), ...(hit.retain ? { retain: hit.retain } : {}) };
308
+ }
309
+ return { caches: this.#fresh(request), reusedTokens: 0 };
310
+ }
311
+
312
+ /** Fresh state of the composition: the graph's caches, paged when it pages. */
313
+ #fresh(request: StateRequest): Cache[] {
314
+ const paged = this.#paged;
315
+ if (!paged) return this.#graph.makeCache();
316
+ return withRuntimeConfig(this.#runtime, () => {
317
+ const caches = this.#graph.makeCache();
318
+ try { maybePageKv(caches, this.#kv, paged, request.promptIds.length + request.maxTokens); return caches; }
319
+ catch (error) { disposeResources(caches); throw error; }
320
+ });
321
+ }
322
+
323
+ /** The generation checkpoint `request` resumes from, or null. Opens the
324
+ * request's checkpoint writes either way. */
325
+ #resume(request: StateRequest, continuation: ContinuationRequest, lease: Lease, namespace: string): ContinuationState | null {
326
+ const continuations = this.#continuations;
327
+ if (!continuations) throw new Error("qualified continuation requires bound persistence services");
328
+ const key = continuation.key(namespace, this.#identity);
329
+ const run = lease.continuation = { key, namespace, attempt: continuations.persistence.begin(key), seed: continuation.seed };
330
+ if (!continuations.persistence.canRestore(key)) return null;
331
+ const prompt = request.promptIds;
332
+ const entry = continuations.store.findGenerationCheckpoint(prompt, key, namespace);
333
+ // A restored entry takes the caches the graph composes.
334
+ const restored = entry ? continuations.store.restore(entry, this.#graph) : null;
335
+ if (!restored) return null;
336
+ const metadata = restored.header.generationCheckpoint;
337
+ if (!metadata || metadata.originalPromptTokens !== prompt.length ||
338
+ metadata.generatedTokens !== restored.tokens.length - prompt.length) {
339
+ disposeResources(restored.caches);
340
+ throw new Error("generation checkpoint has inconsistent continuation metadata");
341
+ }
342
+ run.seed = metadata.seed;
343
+ return { caches: restored.caches, cacheTokens: restored.tokens,
344
+ generatedTokens: metadata.generatedTokens, pendingToken: metadata.pendingToken, seed: run.seed };
345
+ }
346
+
347
+ publish(request: StateRequest, namespace: string, tokens: number[], state: PublishedState): void {
348
+ if (!this.keeps(request)) throw new Error("this cache keeps no state for the request");
349
+ this.#prefixes!.put(tokens, state.caches, this.#namespace(namespace), state.retain, state.attachments, request.cacheSessionId);
350
+ }
351
+
352
+ checkpoint(request: StateRequest, state: Omit<ContinuationState, "seed">): void {
353
+ const run = this.#leases.get(request)?.continuation;
354
+ if (!run || !this.#continuations) {
355
+ disposeResources(state.caches);
356
+ throw new Error("owned continuation requires persistence coordinator");
357
+ }
358
+ this.#continuations.persistence.enqueue(run.attempt, state, { key: run.key, cacheNs: run.namespace,
359
+ originalPromptTokens: request.promptIds.length, generatedTokens: state.generatedTokens,
360
+ pendingToken: state.pendingToken, seed: run.seed, seedWasExplicit: request.continuation?.seedWasExplicit === true });
361
+ }
362
+
363
+ release(request: StateRequest, completed: boolean): void {
364
+ const lease = this.#leases.get(request);
365
+ if (!lease) return;
366
+ this.#leases.delete(request);
367
+ const run = lease.continuation, persistence = this.#continuations?.persistence;
368
+ if (run && persistence) {
369
+ if (completed) persistence.complete(run.attempt);
370
+ else persistence.release(run.attempt);
371
+ }
372
+ }
373
+
374
+ boundary(): void { this.#prefixes?.reclaim(); }
375
+
376
+ /** Start reading `promptIds`' saved prefix in `namespace` before the request
377
+ * is admitted. The returned release ends the request's interest. */
378
+ async prefetch(promptIds: number[], namespace: string, sessionId?: string): Promise<() => void> {
379
+ return await this.#prefixes?.prefetch(promptIds, this.#namespace(namespace), sessionId) ?? (() => {});
380
+ }
381
+
382
+ /** The longest prefix of `promptIds` held in RAM under `namespace`, which is
383
+ * looked up as given (not under the composition's paging). */
384
+ peekPrefixLen(promptIds: number[], namespace: string): number {
385
+ return this.#prefixes?.peekPrefixLen(promptIds, namespace) ?? 0;
386
+ }
387
+
388
+ /** Exact tensor objects (encoder outputs) under the same budget and storage; absent without a prefix store. */
389
+ get objects(): ObjectCache<CheckpointAttachment[]> | undefined { return this.#prefixes?.objects; }
390
+
391
+ /** Drop a session's affinity without deleting reusable state. */
392
+ closeSession(sessionId: string): void { this.#prefixes?.closeSession(sessionId); }
393
+
394
+ /** Observes every prefix the cache stores, with its tokens. */
395
+ set onPut(observe: ((tokens: number[], namespace: string) => void) | null) {
396
+ if (this.#prefixes) this.#prefixes.onPut = observe;
397
+ }
398
+
399
+ /** The token prefixes saved state holds (header metadata only). */
400
+ savedPrefixes(): Iterable<readonly number[]> { return this.#saved?.tokenPrefixes() ?? []; }
401
+
402
+ report(): KvCacheReport {
403
+ const prefixes = this.#prefixes, saved = this.#saved;
404
+ return {
405
+ prefixes: {
406
+ entries: prefixes?.size ?? 0, bytes: prefixes?.totalBytes ?? 0, maxBytes: prefixes?.maxBytes ?? 0,
407
+ hits: prefixes?.hits ?? 0, misses: prefixes?.misses ?? 0,
408
+ sessionHits: prefixes?.sessionHits ?? 0, sessionMisses: prefixes?.sessionMisses ?? 0,
409
+ prefixScans: prefixes?.prefixScans ?? 0, objectHits: prefixes?.objectHits ?? 0,
410
+ objectMisses: prefixes?.objectMisses ?? 0, objectRestores: prefixes?.objectRestores ?? 0,
411
+ demotions: prefixes?.demotions ?? 0,
412
+ },
413
+ saved: saved ? { entries: saved.entries, bytes: saved.totalBytes, maxBytes: saved.maxBytes,
414
+ restores: saved.stats.restores, spills: saved.stats.spills, restoreMsLast: saved.stats.restoreMsLast,
415
+ longestDurablePrefixTokens: saved.longestDurablePrefixTokens } : null,
416
+ pending: this.stats(),
417
+ kv: { projectedBytes: this.#projectedBytes, budgetBytes: this.#kvBudget ?? null },
418
+ };
419
+ }
420
+
421
+ /** Pending persistence: prefix snapshots and spills, and checkpoint writes. */
422
+ stats(): DurabilitySnapshotStats {
423
+ const tiered = this.#prefixes instanceof TieredPromptCache ? this.#prefixes : null;
424
+ const current = tiered?.durability.stats ?? NO_PENDING;
425
+ const pending = this.#continuations?.persistence.stats;
426
+ return { ...current, pendingSpills: current.pendingSpills + (pending?.pendingCount ?? 0),
427
+ pendingSpillBytes: current.pendingSpillBytes + (pending?.pendingBytes ?? 0),
428
+ droppedSpills: current.droppedSpills + (pending?.dropped ?? 0), failedSpills: current.failedSpills + (pending?.failed ?? 0) };
429
+ }
430
+
431
+ /** Make every kept prefix and checkpoint durable, as far as storage allows. */
432
+ async flush(): Promise<DurabilityFlushResult> {
433
+ const started = performance.now();
434
+ const tiered = this.#prefixes instanceof TieredPromptCache ? this.#prefixes : null;
435
+ const errors: unknown[] = [];
436
+ let checkpointResult: Awaited<ReturnType<ContinuationPersistence["flush"]>> | undefined;
437
+ let result: DurabilityFlushResult | undefined;
438
+ try { checkpointResult = await this.#continuations?.persistence.flush(); } catch (error) { errors.push(error); }
439
+ try { result = await tiered?.durability.flush(); } catch (error) { errors.push(error); }
440
+ // Also drain after a failed coordinator flush before releasing RAM views.
441
+ try { await tiered?.spillQueue.drain(); } catch (error) { errors.push(error); }
442
+ if (errors.length === 1) throw errors[0];
443
+ if (errors.length) throw new AggregateError(errors, "cache flush failed");
444
+ const current = this.stats();
445
+ return { ...current, durable: (result?.durable ?? current.pendingSpills === 0) &&
446
+ (checkpointResult?.durable ?? true) && current.pendingSpills === 0,
447
+ flushedSnapshots: result?.flushedSnapshots ?? 0, missingSnapshots: result?.missingSnapshots ?? 0,
448
+ elapsedMs: performance.now() - started };
449
+ }
450
+
451
+ /** Stop idle demotion ahead of draining requests; the state stays. */
452
+ stopIdleDemotion(): void { const stop = this.#stopIdle; this.#stopIdle = undefined; stop?.(); }
453
+
454
+ /** After every request drained: flush, then clear the state even when
455
+ * persistence fails (`durable: false` reports it). Idempotent. */
456
+ close(): Promise<DurabilityFlushResult> {
457
+ return this.#closing ??= (async () => {
458
+ let result: DurabilityFlushResult | undefined; const errors: unknown[] = [];
459
+ try { this.stopIdleDemotion(); } catch (error) { errors.push(error); }
460
+ try { result = await this.flush(); } catch (error) { errors.push(error); }
461
+ try { this.#prefixes?.clear(); } catch (error) { errors.push(error); }
462
+ try { this.#closed?.(); } catch (error) { errors.push(error); }
463
+ if (errors.length === 1) throw errors[0];
464
+ if (errors.length) throw new AggregateError(errors, "cache cleanup failed");
465
+ return result!;
466
+ })();
467
+ }
468
+ }
469
+
470
+ /** The saved-state root's one byte budget (`--ssd-cache-max`), shared by every
471
+ * store under it: a live store counts against it and enforces the room it is lent. */
472
+ export interface SavedStateBudget {
473
+ readonly maxBytes: number;
474
+ attach(member: { readonly dir: string; bytes(): number }): { limit(): number; detach(): void };
475
+ /** Evict what idle directories hold until the root fits. */
476
+ reclaim(): unknown;
477
+ }
478
+
479
+ /** Saved state under a directory the caller owns. */
480
+ export interface SavedStateOptions {
481
+ /** The saved-state root; this store's directory is `<dir>/<configFingerprint>/`. */
482
+ readonly dir: string;
483
+ /** Written into every file and enforced on scan and load. */
484
+ readonly identity: Pick<SsdStoreOptions, "configFingerprint" | "modelId" | "tokenizerHash">;
485
+ readonly budget: SavedStateBudget;
486
+ /** Verify every tensor hash on load (`--ssd-cache-verify`). */
487
+ readonly verify?: boolean;
488
+ readonly storage?: SsdStoreOptions["storage"];
489
+ /** Write each kept prefix to saved state as it is kept (default true). */
490
+ readonly writeBehind?: boolean;
491
+ /** Read a saved prefix ahead of admission (default true). */
492
+ readonly prefetch?: boolean;
493
+ /** Demote RAM entries idle this long (milliseconds) to saved state; 0 never. */
494
+ readonly demoteIdleMs?: number;
495
+ readonly schedule?: (run: () => void, intervalMs: number) => () => void;
496
+ /** Generation checkpoints every this many generated tokens; absent: none. */
497
+ readonly checkpointEveryTokens?: number;
498
+ /** Bytes of checkpoint snapshots the writer may queue. */
499
+ readonly checkpointQueueBytes?: number;
500
+ }
501
+
502
+ export interface KvCacheServiceOptions extends Pick<KvCacheSettings, "kvBudgetBytes" | "codecs" | "identity" | "runtime"> {
503
+ /** RAM bytes of reusable prefixes (`--prompt-cache`), 0 keeping none; absent: no prefix store. */
504
+ readonly promptCacheBytes?: number;
505
+ readonly retention?: RetentionPolicy;
506
+ /** Whether live memory is over what optional state may use, given the bytes
507
+ * the prefix store holds or has queued for writing. */
508
+ readonly pressure?: (heldBytes: number) => boolean;
509
+ readonly saved?: SavedStateOptions;
510
+ }
511
+
512
+ const defaultSchedule = (run: () => void, intervalMs: number) => {
513
+ const timer = setInterval(run, intervalMs); timer.unref(); return () => clearInterval(timer);
514
+ };
515
+
516
+ /** The service the serve flags compose for `graph`: RAM prefixes under
517
+ * `promptCacheBytes`, tiered over saved state when `saved` names a directory,
518
+ * with generation checkpoints when it names an interval. The caller closes it
519
+ * after every request drained and before disposing the graph. */
520
+ export function createKvCacheService(graph: CacheServiceGraph, options: KvCacheServiceOptions = {}): KvCacheService {
521
+ const codecs = options.codecs ?? graph.stateCodecs ?? legacyCacheCodecs;
522
+ // A reused prefix comes back as the graph's composed caches, which convert as
523
+ // the caches that stored it would have.
524
+ const clone = (caches: Cache[]): Cache[] => {
525
+ const cloned = cloneKvCaches(caches, codecs);
526
+ const prototypes = graph.makeCache();
527
+ try { return cloned.map((cache, layer) => composeAtLayer(prototypes[layer]!, cache)); }
528
+ finally { disposeResources(prototypes); }
529
+ };
530
+ const savedOptions = options.saved;
531
+ let saved: SsdCacheStore | null = null;
532
+ let member: ReturnType<SavedStateBudget["attach"]> | undefined;
533
+ let prefixes: PromptCache | null = null;
534
+ try {
535
+ if (savedOptions) {
536
+ const budget = savedOptions.budget;
537
+ const created = saved = new SsdCacheStore({ codecs, dir: savedOptions.dir, maxBytes: budget.maxBytes,
538
+ limit: () => member?.limit() ?? budget.maxBytes, ...savedOptions.identity,
539
+ verify: savedOptions.verify, storage: savedOptions.storage });
540
+ member = budget.attach({ dir: join(savedOptions.dir, savedOptions.identity.configFingerprint), bytes: () => created.totalBytes });
541
+ saved.scan();
542
+ // A lowered budget, or another process's leftovers, takes effect at once; this store's directory is live and untouched.
543
+ budget.reclaim();
544
+ }
545
+ const store = saved;
546
+ if (options.promptCacheBytes !== undefined) {
547
+ const cold: ColdTier | null = store ? {
548
+ find: (tokens, ns) => { const hit = store.find(tokens, ns); return hit ? { prefixLen: hit.prefixLen, handle: hit.entry } : null; },
549
+ findExact: (tokens, ns) => { const hit = store.findExact(tokens, ns); return hit ? { prefixLen: hit.prefixLen, handle: hit.entry } : null; },
550
+ restoreObjectAsync: async handle => {
551
+ const loaded = await store.restoreAsync(handle as SsdIndexEntry, { makeCache: () => [] });
552
+ return loaded ? { ...loaded, retain() {} } : null;
553
+ },
554
+ restore: handle => {
555
+ const loaded = store.restore(handle as SsdIndexEntry, graph);
556
+ return loaded ? { ...loaded, retain() {} } : null;
557
+ },
558
+ ...(savedOptions?.prefetch !== false ? {
559
+ restoreAsync: async (handle: unknown) => {
560
+ const loaded = await store.restoreAsync(handle as SsdIndexEntry, graph);
561
+ return loaded ? { ...loaded, retain() {} } : null;
562
+ },
563
+ } : {}),
564
+ store: (tokens, caches, ns, attachments) => store.hasDurablePrefix(tokens, ns) || store.store(tokens, caches, ns, attachments),
565
+ } : null;
566
+ const tiered = store && cold ? new TieredPromptCache(options.promptCacheBytes, store, cold, clone,
567
+ savedOptions?.writeBehind !== false) : null;
568
+ const created = prefixes = tiered ?? new PromptCache(options.promptCacheBytes, null, null, clone);
569
+ if (options.retention) created.retention = options.retention;
570
+ const pressure = options.pressure;
571
+ if (pressure) created.pressure = { overBudget: () =>
572
+ pressure(Math.max(created.totalBytes, tiered?.spillQueue.pendingBytes ?? 0)) };
573
+ }
574
+ const every = savedOptions?.checkpointEveryTokens;
575
+ const continuations = store && every ? { store, everyTokens: every,
576
+ persistence: new ContinuationPersistence(store, { maxBytes: savedOptions!.checkpointQueueBytes ?? 2 * 1024 ** 3 }) } : null;
577
+ const budget = savedOptions?.budget;
578
+ return new KvCacheService(graph, { prefixes, saved, continuations }, {
579
+ kvBudgetBytes: options.kvBudgetBytes, codecs, identity: options.identity, runtime: options.runtime,
580
+ ...(store && savedOptions ? { idleDemotion: { afterMs: savedOptions.demoteIdleMs ?? 300_000,
581
+ schedule: savedOptions.schedule ?? defaultSchedule } } : {}),
582
+ // The directory is idle from here: its files count against the root's budget and may be evicted.
583
+ ...(member ? { closed: () => { member!.detach(); budget!.reclaim(); } } : {}),
584
+ });
585
+ } catch (error) {
586
+ const errors: unknown[] = [error];
587
+ try { prefixes?.clear(); } catch (cleanup) { errors.push(cleanup); }
588
+ try { member?.detach(); } catch (cleanup) { errors.push(cleanup); }
589
+ if (errors.length > 1) throw new AggregateError(errors, "cache construction and cleanup failed");
590
+ throw error;
591
+ }
592
+ }
@@ -0,0 +1,65 @@
1
+ // What the state modules ask of a cache's representation. Every caller is in
2
+ // `state/`: the planner, the scheduler and the methods read none of it.
3
+ import type { BatchableCache,Cache,RowBatchCache } from "../contracts/mlx/cache";
4
+ import { KVCache } from "./kv";
5
+ import { QuantizedKVCache } from "./quantized-kv";
6
+ import { RotatingKVCache } from "./rotating-kv";
7
+ import { RotatingQuantizedKVCache } from "./rotating-quantized-kv";
8
+
9
+ export function cacheSignature(cache: Cache | undefined): string {
10
+ return cache ? cache.signature() : "unknown";
11
+ }
12
+
13
+ /** The storage a cache holds: a composed lego's (`Cache.composed`), else the
14
+ * cache itself. What the request state reads to learn which layers a fresh
15
+ * cache list pages. */
16
+ export function composedStorageOf(cache: Cache): Cache {
17
+ return cache.composed ? cache.composed.storage() : cache;
18
+ }
19
+
20
+ export function isRowBatchCache(cache: Cache): cache is RowBatchCache {
21
+ const candidate = cache as Partial<RowBatchCache>;
22
+ return typeof candidate.batchSize === "number" &&
23
+ typeof candidate.filterRows === "function" &&
24
+ typeof candidate.extractRow === "function";
25
+ }
26
+
27
+ /** Recurrent state (gated DeltaNet): fixed-size, never trimmed. */
28
+ export function isRecurrentCache(cache: Cache | undefined): boolean {
29
+ return cacheSignature(cache) === "ssm";
30
+ }
31
+
32
+ export function isPlainKvCache(cache: Cache | undefined): cache is KVCache {
33
+ return cacheSignature(cache) === "kv:plain";
34
+ }
35
+
36
+ export function isQuantizedKvCache(cache: Cache | undefined): cache is QuantizedKVCache {
37
+ return cacheSignature(cache).startsWith("kv:quant:");
38
+ }
39
+
40
+ export function isRotatingPlainCache(cache: Cache | undefined): cache is RotatingKVCache {
41
+ return cacheSignature(cache) === "kv:rotating-plain";
42
+ }
43
+
44
+ export function isTurboQuantKvCache(cache: Cache | undefined): boolean {
45
+ return cacheSignature(cache).startsWith("kv:turboquant:");
46
+ }
47
+
48
+ export function isRotatingQuantizedCache(
49
+ cache: Cache | undefined,
50
+ ): cache is RotatingQuantizedKVCache {
51
+ return cacheSignature(cache).startsWith("kv:rotating-quant:");
52
+ }
53
+
54
+ export function isBatchableCache(cache: Cache): cache is BatchableCache {
55
+ const candidate = cache as Partial<BatchableCache>;
56
+ return (
57
+ typeof candidate.makeEmptyBatch === "function" &&
58
+ typeof candidate.mergeRows === "function" &&
59
+ typeof candidate.extractRow === "function" &&
60
+ typeof candidate.filterRows === "function" &&
61
+ typeof candidate.projectedBytes === "function" &&
62
+ Array.isArray(candidate.rowOffsets) &&
63
+ Array.isArray(candidate.leftPad)
64
+ );
65
+ }