@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,689 @@
1
+ // Paged KV storage behind the common cache and attention interfaces.
2
+ // Full-attention pools store bf16, affine (any bit width the composition
3
+ // names, per layer) or TurboQuant pages. The established
4
+ // gather + SDPA arm is the numerical control; direct Metal reads are Lab.
5
+ // Published views and SSD snapshots own immutable buffers independently of
6
+ // appends, row membership and RAM eviction.
7
+
8
+ import { disposeResources } from "../../runtime/resources";
9
+ import { pagedAttentionView } from "../../kernels/attention/paged";
10
+ import type { KvStorageScheme, PagedKvComposition } from "../../contracts/portable/composition";
11
+ import { MlxArray } from "@mlx-bun/mlx/array";
12
+ import { Dtype } from "@mlx-bun/mlx/ffi";
13
+ import * as ops from "@mlx-bun/mlx/ops";
14
+ import { bidirMask, createCausalMask } from "../../kernels/attention/masks";
15
+ import {
16
+ type AttentionRead, type BidirectionalAttentionCache, type BlockAttentionCache, type CommittedAttentionCache,
17
+ type CompiledDecodeCache, type DecodeSlot, type DecodeStepInputs, type DecodeStepPlan, type DecodeTrace,
18
+ type EncoderAttentionCache, type KvAttentionView, type KvDonorRows, type Mask,
19
+ } from "../../contracts/mlx/cache";
20
+ import { AttentionMasks, causalLease, encoderWindowMask, sdpaRead, unmaskedLease, withLease, type MaskLease } from "../attention-read";
21
+ import * as tq from "../../kernels/turboquant/ops";
22
+ import { TurboQuantCodec, type TurboQuantTensor } from "../turboquant-codec";
23
+ import { turboQuantDecodeSlot } from "../turboquant-kv";
24
+ import type { TurboQuantKernels } from "../turboquant-attention";
25
+
26
+ /** Typed pool-exhaustion error: a generation outgrew its pool. Sizing from
27
+ * prompt+maxTokens at construction makes this unreachable in practice;
28
+ * it exists so an accounting bug is a clear throw, never silent
29
+ * truncation or an allocator OOM (the byte-capped-not-count-capped
30
+ * lesson). */
31
+ export class PagedPoolExhausted extends Error {
32
+ constructor(numBlocks: number) {
33
+ super(`paged KV pool exhausted (${numBlocks} blocks) — capacity accounting bug`);
34
+ this.name = "PagedPoolExhausted";
35
+ }
36
+ }
37
+
38
+ /** Affine pages: the bit width and group size every page of a layer stores. */
39
+ export interface PagedQuantization { bits: number; groupSize: number }
40
+
41
+ /** TurboQuant pages: the codec's key and value bit widths, its decode policy,
42
+ * and the read the layer's graph composed (`TurboQuantKernels`: dense or
43
+ * deferred values). */
44
+ export interface PagedTurboQuant {
45
+ readonly kBits: number;
46
+ readonly vBits: number;
47
+ readonly fusedDecode: boolean;
48
+ readonly kernels: TurboQuantKernels;
49
+ }
50
+
51
+ /** Per-layer block arena, mlx-owned (ops.zeros — no host-pointer
52
+ * alignment/dtor hazards), every plane shaped [numBlocks, H, blockSize, width].
53
+ * bf16 pages: `keys` and `values`. Affine pages: packed `keys` and `values`
54
+ * with their scales and biases. TurboQuant pages: the codec's five codes, key
55
+ * indices in `keys`, key scales and zero points in `keyScales` and
56
+ * `keyBiases`, value indices in `values`, value scales in `valueScales`.
57
+ * alloc()/free() manage physical slot indices; the owning PagedKVCache
58
+ * writes/gathers through the pool. */
59
+
60
+ export class BlockPool {
61
+ keyScales?: MlxArray; keyBiases?: MlxArray;
62
+ valueScales?: MlxArray; valueBiases?: MlxArray;
63
+ readonly headDim: number;
64
+ readonly vHeadDim: number;
65
+ readonly dtype: Dtype;
66
+ readonly quantization?: PagedQuantization;
67
+ /** TurboQuant pages' bit widths. */
68
+ readonly turbo?: { readonly kBits: number; readonly vBits: number; readonly fusedDecode: boolean };
69
+ readonly #codec?: TurboQuantCodec;
70
+ keys: MlxArray;
71
+ values: MlxArray;
72
+ numBlocks: number;
73
+ readonly blockSize: number;
74
+ #free: number[];
75
+
76
+ constructor(opts: {
77
+ numBlocks: number;
78
+ blockSize: number;
79
+ numKvHeads: number;
80
+ headDim: number;
81
+ /** V head dim when it differs from K's (attention_k_eq_v models don't
82
+ * reach v1's gemma4 scope, but the pool stays shape-honest). */
83
+ vHeadDim?: number;
84
+ dtype: Dtype;
85
+ quantization?: PagedQuantization;
86
+ turbo?: { readonly kBits: number; readonly vBits: number; readonly fusedDecode: boolean };
87
+ }, source?: BlockPool) {
88
+ this.headDim = opts.headDim; this.vHeadDim = opts.vHeadDim ?? opts.headDim;
89
+ this.dtype = opts.dtype; this.quantization = opts.quantization;
90
+ if (opts.turbo) {
91
+ this.turbo = Object.freeze({ kBits: opts.turbo.kBits, vBits: opts.turbo.vBits, fusedDecode: opts.turbo.fusedDecode });
92
+ this.#codec = new TurboQuantCodec(opts.turbo.kBits, opts.turbo.vBits, opts.turbo.fusedDecode);
93
+ }
94
+ this.numBlocks = opts.numBlocks;
95
+ this.blockSize = opts.blockSize;
96
+ const shape = (width: number) => [opts.numBlocks, opts.numKvHeads, opts.blockSize, width];
97
+ if (source) {
98
+ this.keys = ops.contiguous(source.keys); this.values = ops.contiguous(source.values);
99
+ const copy = (array?: MlxArray) => array ? ops.contiguous(array) : undefined;
100
+ this.keyScales = copy(source.keyScales); this.keyBiases = copy(source.keyBiases);
101
+ this.valueScales = copy(source.valueScales); this.valueBiases = copy(source.valueBiases);
102
+ } else if (this.turbo) {
103
+ // The codec's layout (TurboQuantKVCache's): index planes bit-packed below
104
+ // eight bits, one fp16 scale (and key zero point) per 32 positions of a head.
105
+ const { kBits, vBits } = this.turbo, vD = this.vHeadDim, groups = (dim: number) => dim / tq.TURBOQUANT_BLOCK_SIZE;
106
+ this.keys = ops.zeros(shape(tq.packedDim(opts.headDim, kBits)), kBits === 8 ? Dtype.int8 : Dtype.uint8);
107
+ this.keyScales = ops.zeros(shape(groups(opts.headDim)), Dtype.float16);
108
+ this.keyBiases = ops.zeros(shape(groups(opts.headDim)), Dtype.float16);
109
+ this.values = ops.zeros(shape(tq.packedDim(vD, vBits)), Dtype.uint8);
110
+ this.valueScales = ops.zeros(shape(groups(vD)), Dtype.float16);
111
+ } else {
112
+ this.keys = ops.zeros(shape(opts.headDim), opts.dtype);
113
+ this.values = ops.zeros(shape(this.vHeadDim), opts.dtype);
114
+ }
115
+ if (!source && this.quantization) {
116
+ const { bits, groupSize } = this.quantization;
117
+ const k = ops.quantize(this.keys, groupSize, bits), v = ops.quantize(this.values, groupSize, bits);
118
+ this.keys.dispose(); this.values.dispose();
119
+ this.keys = k.packed; this.keyScales = k.scales; this.keyBiases = k.biases;
120
+ this.values = v.packed; this.valueScales = v.scales; this.valueBiases = v.biases;
121
+ }
122
+ // LIFO free list, low indices first — deterministic layout for tests.
123
+ this.#free = source ? [...source.#free] : Array.from({ length: opts.numBlocks }, (_, i) => opts.numBlocks - 1 - i);
124
+ }
125
+
126
+ /** Independent ownership and allocation metadata over immutable MLX buffers.
127
+ * Functional writes detach storage while another snapshot retains it. */
128
+ clone(): BlockPool {
129
+ return new BlockPool({ numBlocks: this.numBlocks, blockSize: this.blockSize,
130
+ numKvHeads: this.keys.shape[1]!, headDim: this.headDim,
131
+ vHeadDim: this.vHeadDim, dtype: this.dtype, quantization: this.quantization, turbo: this.turbo }, this);
132
+ }
133
+
134
+ restoreArrays(arrays: MlxArray[], occupied: readonly number[]): void {
135
+ disposeResources(this.arrays());
136
+ [this.keys, this.values] = [arrays[0]!, arrays[1]!];
137
+ [this.keyScales, this.keyBiases, this.valueScales, this.valueBiases] = arrays.slice(2);
138
+ const used = new Set(occupied);
139
+ this.#free = Array.from({ length: this.numBlocks }, (_, i) => this.numBlocks - 1 - i).filter(i => !used.has(i));
140
+ }
141
+
142
+ grow(blocks: number): void {
143
+ if (blocks <= this.numBlocks) return;
144
+ const grow = (array: MlxArray) => {
145
+ using zeros = ops.zeros([blocks - this.numBlocks, ...array.shape.slice(1)], array.dtype);
146
+ const next = ops.concatAxis([array, zeros], 0); array.dispose(); return next;
147
+ };
148
+ this.keys = grow(this.keys); this.values = grow(this.values);
149
+ if (this.keyScales) this.keyScales = grow(this.keyScales);
150
+ if (this.keyBiases) this.keyBiases = grow(this.keyBiases);
151
+ if (this.valueScales) this.valueScales = grow(this.valueScales);
152
+ if (this.valueBiases) this.valueBiases = grow(this.valueBiases);
153
+ for (let i = blocks - 1; i >= this.numBlocks; i--) this.#free.push(i);
154
+ this.numBlocks = blocks;
155
+ }
156
+
157
+ get freeBlocks(): number {
158
+ return this.#free.length;
159
+ }
160
+
161
+ alloc(): number {
162
+ const idx = this.#free.pop();
163
+ if (idx === undefined) throw new PagedPoolExhausted(this.numBlocks);
164
+ return idx;
165
+ }
166
+
167
+ free(physIdx: number): void {
168
+ this.#free.push(physIdx);
169
+ }
170
+
171
+ /** Write `k`/`v` ([1, H, l, D], l ≤ blockSize − within) into physical
172
+ * block `physIdx` starting at row `within`. Functional slice_update:
173
+ * the pool tensors are single-referenced here, so mlx donates the
174
+ * buffer and the write is in-place in the steady state. */
175
+ writeBlock(physIdx: number, k: MlxArray, v: MlxArray, within: number): void {
176
+ if (this.#codec) {
177
+ const enc = this.#codec.encode(k, v);
178
+ const replace = (target: MlxArray, input: MlxArray): MlxArray => {
179
+ const result = ops.sliceUpdate(target, input, [physIdx, 0, within, 0],
180
+ [physIdx + 1, input.shape[1]!, within + input.shape[2]!, input.shape[3]!]);
181
+ target.dispose(); return result;
182
+ };
183
+ try {
184
+ this.keys = replace(this.keys, enc.kIdx); this.keyScales = replace(this.keyScales!, enc.kScales);
185
+ this.keyBiases = replace(this.keyBiases!, enc.kZeros);
186
+ this.values = replace(this.values, enc.vPacked); this.valueScales = replace(this.valueScales!, enc.vScales);
187
+ } finally { disposeResources([enc.kIdx, enc.kScales, enc.kZeros, enc.vPacked, enc.vScales]); }
188
+ return;
189
+ }
190
+ if (this.quantization) {
191
+ const { bits, groupSize } = this.quantization;
192
+ const kq = ops.quantize(k, groupSize, bits), vq = ops.quantize(v, groupSize, bits);
193
+ const replace = (target: MlxArray, input: MlxArray): MlxArray => {
194
+ const result = ops.sliceUpdate(target, input, [physIdx, 0, within, 0],
195
+ [physIdx + 1, input.shape[1]!, within + input.shape[2]!, input.shape[3]!]);
196
+ target.dispose(); return result;
197
+ };
198
+ try {
199
+ this.keys = replace(this.keys, kq.packed); this.keyScales = replace(this.keyScales!, kq.scales); this.keyBiases = replace(this.keyBiases!, kq.biases);
200
+ this.values = replace(this.values, vq.packed); this.valueScales = replace(this.valueScales!, vq.scales); this.valueBiases = replace(this.valueBiases!, vq.biases);
201
+ } finally { disposeResources([kq.packed, kq.scales, kq.biases, vq.packed, vq.scales, vq.biases]); }
202
+ return;
203
+ }
204
+ const l = k.shape[2]!;
205
+ const [, H, , kD] = this.keys.shape as [number, number, number, number];
206
+ const vD = this.values.shape[3]!;
207
+ const k2 = ops.sliceUpdate(
208
+ this.keys, k, [physIdx, 0, within, 0], [physIdx + 1, H, within + l, kD]);
209
+ this.keys.dispose();
210
+ this.keys = k2;
211
+ const v2 = ops.sliceUpdate(
212
+ this.values, v, [physIdx, 0, within, 0], [physIdx + 1, H, within + l, vD]);
213
+ this.values.dispose();
214
+ this.values = v2;
215
+ }
216
+
217
+ /** Gather `blockTable`'s blocks in logical order into one contiguous
218
+ * [1, H, nb·blockSize, D] pair (the fetch copy). Caller owns both. */
219
+ gather(blockTable: number[]): [MlxArray, MlxArray] {
220
+ if (this.#codec) throw new Error("TurboQuant pages gather through decode (gatherDecoded)");
221
+ const idx = MlxArray.fromInt32(Int32Array.from(blockTable), [blockTable.length]);
222
+ const pick = (pool: MlxArray): MlxArray => pickBlocks(pool, idx);
223
+ const read = (data: MlxArray, scales?: MlxArray, biases?: MlxArray) => {
224
+ const packed = pick(data);
225
+ if (!this.quantization) return packed;
226
+ using owned = packed; using s = pick(scales!), b = pick(biases!);
227
+ return ops.dequantize(owned, s, b, { ...this.quantization, mode: "affine" });
228
+ };
229
+ const k = read(this.keys, this.keyScales, this.keyBiases);
230
+ const v = read(this.values, this.valueScales, this.valueBiases);
231
+ idx.dispose();
232
+ return [k, v];
233
+ }
234
+
235
+ /** TurboQuant pages: write encoded positions (taking ownership) into
236
+ * block `physIdx` from row `within`. */
237
+ writeCodes(physIdx: number, codes: TurboQuantTensor, within: number): void {
238
+ const replace = (target: MlxArray, input: MlxArray): MlxArray => {
239
+ const result = ops.sliceUpdate(target, input, [physIdx, 0, within, 0],
240
+ [physIdx + 1, input.shape[1]!, within + input.shape[2]!, input.shape[3]!]);
241
+ target.dispose(); return result;
242
+ };
243
+ try {
244
+ this.keys = replace(this.keys, codes.kIdx); this.keyScales = replace(this.keyScales!, codes.kScales);
245
+ this.keyBiases = replace(this.keyBiases!, codes.kZeros);
246
+ this.values = replace(this.values, codes.vPacked); this.valueScales = replace(this.valueScales!, codes.vScales);
247
+ } finally { disposeResources([codes.kIdx, codes.kScales, codes.kZeros, codes.vPacked, codes.vScales]); }
248
+ }
249
+
250
+ /** TurboQuant pages: the first `length` positions of `blockTable`'s blocks,
251
+ * decoded by the codec with values in their own domain or left rotated
252
+ * (`TurboQuantKVCache`'s decode, per position). Caller owns both. */
253
+ gatherDecoded(blockTable: number[], length: number, rotated: boolean): [MlxArray, MlxArray] {
254
+ if (!this.#codec) throw new Error("only TurboQuant pages decode");
255
+ using idx = MlxArray.fromInt32(Int32Array.from(blockTable), [blockTable.length]);
256
+ const codes: TurboQuantTensor = {
257
+ kIdx: pickBlocks(this.keys, idx), kScales: pickBlocks(this.keyScales!, idx), kZeros: pickBlocks(this.keyBiases!, idx),
258
+ vPacked: pickBlocks(this.values, idx), vScales: pickBlocks(this.valueScales!, idx),
259
+ };
260
+ try { return this.#codec.decode(codes, length, this.headDim, rotated); }
261
+ finally { disposeResources([codes.kIdx, codes.kScales, codes.kZeros, codes.vPacked, codes.vScales]); }
262
+ }
263
+
264
+ arrays(): MlxArray[] {
265
+ return [this.keys, this.values, this.keyScales, this.keyBiases, this.valueScales, this.valueBiases].filter((a): a is MlxArray => !!a);
266
+ }
267
+ dispose(): void {
268
+ disposeResources([this.keyScales, this.keyBiases, this.valueScales, this.valueBiases].filter((a): a is MlxArray => !!a));
269
+ this.keys.dispose();
270
+ this.values.dispose();
271
+ this.#free = [];
272
+ }
273
+ }
274
+
275
+ /** One plane's blocks `idx` in logical order, [1, H, blocks·blockSize, width]
276
+ * (the fetch copy). Caller owns it. */
277
+ function pickBlocks(pool: MlxArray, idx: MlxArray): MlxArray {
278
+ const took = ops.takeAxis(pool, idx, 0); // [nb, H, bs, D]
279
+ const [nb, H, bs, D] = took.shape as [number, number, number, number];
280
+ const t = ops.transposeAxes(took, [1, 0, 2, 3]); // [H, nb, bs, D] (view)
281
+ took.dispose();
282
+ const out = ops.reshape(t, [1, H, nb * bs, D]); // materializes seq order
283
+ t.dispose();
284
+ return out;
285
+ }
286
+
287
+ /** Whether the direct paged kernel reads this page format: bf16 pages, and
288
+ * affine pages whose codes never straddle a 32-bit word. TurboQuant pages and
289
+ * other widths read gathered. */
290
+ function directReadable(quantization: PagedQuantization | undefined, turbo: PagedTurboQuant | undefined): boolean {
291
+ return !turbo && (!quantization || 32 % quantization.bits === 0);
292
+ }
293
+
294
+ /** Whether the paged lego the composition's paging `paged` and KV scheme `kv`
295
+ * name has a compiled decode form: gathered pages do (a concat slot), and so
296
+ * do direct pages the kernel does not read (TurboQuant, affine widths that
297
+ * straddle a 32-bit word), which read gathered. Direct pages the kernel reads
298
+ * have none: the kernel's partitions follow the context length, which a
299
+ * shapeless recorded graph cannot hold, and the lego reads one way, so a
300
+ * composition with them decodes eagerly (`resolveComposition`). Per-layer
301
+ * affine schemes leave their unlisted layers bf16, which the kernel reads. */
302
+ export function pagedDecodeCompiles(paged: PagedKvComposition, kv: KvStorageScheme): boolean {
303
+ if (!paged.direct) return true;
304
+ return kv.kind === "turbo" || (kv.kind === "affine" && !directReadable({ bits: kv.bits, groupSize: kv.groupSize }, undefined));
305
+ }
306
+
307
+ /** Cache implementation over a BlockPool. Same values as a plain KVCache
308
+ * at every step (parity-gated); only the physical arrangement differs.
309
+ * The pool allocates lazily on the first write (KVCache's lazy-alloc
310
+ * shape): head count / head dims / dtype come from the first k/v pair,
311
+ * so the wiring (maybePageKv) needs no per-model shape plumbing.
312
+ *
313
+ * Reads: each owns an immutable view of the blocks as of its append
314
+ * (`pagedAttentionView`). Decode attends its one query, and a window its
315
+ * queries under the causal mask, with the read `direct` (the composition's
316
+ * `pagedKv.direct`) fixes at construction: the direct Metal read from the
317
+ * blocks for at most eight queries, or the gathered blocks through the stock
318
+ * fused SDPA (decode with no mask, a window with the causal mask). A window of
319
+ * more than eight queries gathers under either.
320
+ *
321
+ * The other reads are `KVCache`'s over the gathered blocks, so bf16 pages read
322
+ * bit for bit as the bf16 lego they replace: a committed span is the window
323
+ * read (as `KVCache` qualifies it); the bidirectional and encoder windows
324
+ * append, gather and attend under `KVCache`'s masks; a block read attends the
325
+ * gathered positions and the block's own keys with no mask; the donor rows are
326
+ * the gathered positions. Affine pages gather dequantized.
327
+ *
328
+ * TurboQuant pages read as `TurboQuantKVCache` reads its storage: the
329
+ * window decoded per position by the codec, in the domain the layer's kernel
330
+ * set reads (`PagedTurboQuant.kernels`), attended by that set (no mask for
331
+ * decode, the causal mask for a window); the other reads take the window
332
+ * decoded to the values' own domain. The direct read takes bf16 pages and
333
+ * affine widths that divide 32 bits; other pages read gathered whatever the
334
+ * composition says.
335
+ *
336
+ * Compiled decode, gathered pages: a concat slot. The stored positions are
337
+ * gathered (and dequantized) outside the recorded graph, as the decode read
338
+ * gathers them; in it the new position is stored as the pages store it
339
+ * (quantized and dequantized again on affine pages) and joined to them, and
340
+ * the fused SDPA reads the joined window with no mask, the gathered decode
341
+ * read. The row leaves the graph as it was appended; the write after the step
342
+ * appends it to the pages. Direct pages have no compiled form
343
+ * (`pagedDecodeCompiles`): their read is a custom kernel whose partitions
344
+ * follow the context length, which a shapeless recorded graph cannot hold, so
345
+ * their composition decodes eagerly and this cache refuses a compiled slot. */
346
+ export class PagedKVCache implements CommittedAttentionCache, BidirectionalAttentionCache, EncoderAttentionCache, BlockAttentionCache, CompiledDecodeCache {
347
+ /** Distinct storage kind; the row adapter preserves its block pools. */
348
+ signature(): string { return "kv:paged"; }
349
+ /** Matches KVCache.STEP: v1's growth granularity is a permutation of
350
+ * today's 256-token step into fixed reusable slots, not a new tuning
351
+ * axis (--paged-kv-block-size overrides for experiments). */
352
+ static readonly DEFAULT_BLOCK_SIZE = 256;
353
+ /** Set only by compiled-decode trace adapters (see Cache); a real cache
354
+ * leaves it unset. */
355
+ readonly ropeOffsetArr?: MlxArray;
356
+ offset = 0;
357
+ /** Logical block order → physical pool slot. */
358
+ blockTable: number[] = [];
359
+ pool: BlockPool | null = null;
360
+
361
+ /** The direct Metal read, where the page format takes it (see the class). */
362
+ readonly direct: boolean;
363
+
364
+ constructor(
365
+ /** Tokens this cache must be able to hold (prompt + maxTokens). */
366
+ readonly capacityTokens: number,
367
+ readonly blockSize: number,
368
+ direct = false,
369
+ /** Affine pages; absent with `turbo` absent: bf16 pages. */
370
+ readonly quantization?: PagedQuantization,
371
+ /** The masks the bidirectional and encoder reads lease. */
372
+ readonly masks = new AttentionMasks(),
373
+ /** TurboQuant pages. */
374
+ readonly turbo?: PagedTurboQuant,
375
+ ) {
376
+ if (quantization && turbo) throw new Error("pages store one format: affine or TurboQuant");
377
+ this.direct = direct && directReadable(quantization, turbo);
378
+ }
379
+
380
+ clone(): PagedKVCache {
381
+ const copy = new PagedKVCache(this.capacityTokens, this.blockSize, this.direct, this.quantization, this.masks, this.turbo);
382
+ copy.offset = this.offset;
383
+ copy.blockTable = [...this.blockTable];
384
+ copy.pool = this.pool?.clone() ?? null;
385
+ return copy;
386
+ }
387
+
388
+ get #blockSize(): number {
389
+ return this.blockSize;
390
+ }
391
+
392
+ /** Blocks needed to hold `n` tokens. */
393
+ #blocksFor(n: number): number {
394
+ return Math.ceil(n / this.#blockSize);
395
+ }
396
+
397
+ appendDecode(k: MlxArray, v: MlxArray): AttentionRead {
398
+ this.append(k, v);
399
+ if (this.turbo) return this.#turboRead(unmaskedLease);
400
+ return pagedRead(pagedAttentionView(this.pool!, [...this.blockTable], this.offset, this.direct), { mode: "", arr: null });
401
+ }
402
+
403
+ appendWindow(k: MlxArray, v: MlxArray): AttentionRead {
404
+ this.append(k, v);
405
+ if (this.turbo) return this.#turboRead(causalLease);
406
+ return pagedRead(pagedAttentionView(this.pool!, [...this.blockTable], this.offset, this.direct), { mode: "causal", arr: null });
407
+ }
408
+
409
+ /** TurboQuant pages: the layer's kernel set over the window it decodes. */
410
+ #turboRead(mask: MaskLease): AttentionRead {
411
+ const table = [...this.blockTable], length = this.offset, pool = this.pool!;
412
+ return this.turbo!.kernels.read({
413
+ decoded: () => pool.gatherDecoded(table, length, false),
414
+ rotated: () => {
415
+ const [keys, values] = pool.gatherDecoded(table, length, true);
416
+ return { keys, values, restore: tq.unrotateValues };
417
+ },
418
+ }, mask);
419
+ }
420
+
421
+ /** The window read: `KVCache`'s committed read, over the same positions. */
422
+ appendCommitted(k: MlxArray, v: MlxArray): AttentionRead {
423
+ return this.appendWindow(k, v);
424
+ }
425
+
426
+ /** `KVCache`'s vision prefill read: the causal mask OR'd with the image
427
+ * block overlay, over the gathered first window. */
428
+ appendBidirectional(k: MlxArray, v: MlxArray, bidirectional: MlxArray): AttentionRead {
429
+ const L = k.shape[2]!;
430
+ const mask = this.masks.lease("bidirectional:none", `${L}`, () => bidirMask(L, null, bidirectional), bidirectional);
431
+ return withLease(mask, held => {
432
+ const [keys, values] = this.appendAndGather(k, v);
433
+ return sdpaRead(keys, values, held);
434
+ });
435
+ }
436
+
437
+ /** `KVCache`'s encoder read: the encoder window's mask over the gathered
438
+ * first window. */
439
+ appendEncoderWindow(k: MlxArray, v: MlxArray, bidirectional: MlxArray): AttentionRead {
440
+ return withLease(encoderWindowMask(this.masks, k.shape[2]!, bidirectional), mask => {
441
+ const [keys, values] = this.appendAndGather(k, v);
442
+ return sdpaRead(keys, values, mask);
443
+ });
444
+ }
445
+
446
+ /** `KVCache`'s block read: the gathered positions in order, then the block's
447
+ * keys and values, with no mask. Nothing is appended. */
448
+ readBlock(k: MlxArray, v: MlxArray): AttentionRead {
449
+ const [storedK, storedV] = this.#gathered();
450
+ let keys: MlxArray, values: MlxArray;
451
+ try {
452
+ keys = ops.concatAxis([storedK, k], 2);
453
+ try { values = ops.concatAxis([storedV, v], 2); }
454
+ catch (error) { keys.dispose(); throw error; }
455
+ } finally { storedK.dispose(); storedV.dispose(); }
456
+ return sdpaRead(keys, values, unmaskedLease);
457
+ }
458
+
459
+ /** The gathered positions, as `KVCache` hands its donor rows out. */
460
+ captureDonorRows(): KvDonorRows {
461
+ const [keys, values] = this.#gathered();
462
+ return { keys, values, offsets: [this.offset], starts: [0], ends: [this.offset] };
463
+ }
464
+
465
+ get attentionState(): this | undefined { return this.direct || this.quantization ? this : undefined; }
466
+ appendAndFetch(k: MlxArray, v: MlxArray): KvAttentionView {
467
+ this.append(k, v);
468
+ return pagedAttentionView(this.pool!, [...this.blockTable], this.offset, this.direct);
469
+ }
470
+
471
+ updateAndFetch(k: MlxArray, v: MlxArray): [MlxArray, MlxArray] { return this.appendAndGather(k, v); }
472
+
473
+ /** Append, then the keys and values gathered from the blocks up to the new
474
+ * offset (dequantized for affine pages), each [1, H, offset, D]; the caller
475
+ * owns both. What the joined window read of `PagedKvRows` attends. */
476
+ appendAndGather(k: MlxArray, v: MlxArray): [MlxArray, MlxArray] {
477
+ this.append(k, v);
478
+ return this.#gathered();
479
+ }
480
+
481
+ /** The keys and values gathered from the blocks up to the offset
482
+ * (dequantized for affine pages), each [1, H, offset, D]; the caller owns
483
+ * both. */
484
+ #gathered(): [MlxArray, MlxArray] {
485
+ if (!this.pool) throw new Error("paged cache is empty");
486
+ if (this.turbo) return this.pool.gatherDecoded(this.blockTable, this.offset, false);
487
+ const H = this.pool.keys.shape[1]!, kD = this.pool.headDim, vD = this.pool.vHeadDim;
488
+ const [gk, gv] = this.pool.gather(this.blockTable);
489
+ const keys = gk.slice([0, 0, 0, 0], [1, H, this.offset, kD]);
490
+ const values = gv.slice([0, 0, 0, 0], [1, H, this.offset, vD]);
491
+ gk.dispose(); gv.dispose();
492
+ return [keys, values];
493
+ }
494
+
495
+ append(k: MlxArray, v: MlxArray): void {
496
+ const L = k.shape[2]!;
497
+ const [, H, , kD] = k.shape as [number, number, number, number];
498
+ const vD = v.shape[3]!;
499
+ if (!this.pool)
500
+ this.pool = new BlockPool({
501
+ numBlocks: poolBlocksFor(this.capacityTokens, this.blockSize),
502
+ blockSize: this.blockSize,
503
+ numKvHeads: H,
504
+ headDim: kD,
505
+ vHeadDim: vD,
506
+ dtype: k.dtype, quantization: this.quantization,
507
+ ...(this.turbo ? { turbo: { kBits: this.turbo.kBits, vBits: this.turbo.vBits, fusedDecode: this.turbo.fusedDecode } } : {}),
508
+ });
509
+ // Write, splitting the incoming L along block boundaries. Blocks past
510
+ // the current tail allocate from the free list as they're reached.
511
+ let written = 0;
512
+ while (written < L) {
513
+ const pos = this.offset + written;
514
+ const bi = Math.floor(pos / this.#blockSize);
515
+ const within = pos % this.#blockSize;
516
+ while (this.blockTable.length <= bi) {
517
+ if (!this.pool.freeBlocks) this.pool.grow(Math.max(this.pool.numBlocks + 1, Math.ceil(this.pool.numBlocks * 1.5)));
518
+ this.blockTable.push(this.pool.alloc());
519
+ }
520
+ const l = Math.min(this.#blockSize - within, L - written);
521
+ // Full-range pieces skip the slice (fresh-view slice would be a
522
+ // gratuitous op); partial pieces slice the [written, written+l) rows.
523
+ if (l === L && written === 0) {
524
+ this.pool.writeBlock(this.blockTable[bi]!, k, v, within);
525
+ } else {
526
+ const kp = k.slice([0, 0, written, 0], [1, H, written + l, kD]);
527
+ const vp = v.slice([0, 0, written, 0], [1, H, written + l, vD]);
528
+ this.pool.writeBlock(this.blockTable[bi]!, kp, vp, within);
529
+ kp.dispose();
530
+ vp.dispose();
531
+ }
532
+ written += l;
533
+ }
534
+ this.offset += L;
535
+
536
+ }
537
+
538
+ /** Compiled decode: nothing to prepare on the host; the pages grow when the
539
+ * step's row is appended. */
540
+ prepareDecodeStep(): DecodeStepPlan {
541
+ if (!this.pool) throw new Error("compiled decode on an empty cache");
542
+ return { fetch: "concat", writePos: this.offset, activeLen: this.offset };
543
+ }
544
+
545
+ decodePhase(): DecodeStepPlan["fetch"] { return "concat"; }
546
+
547
+ /** TurboQuant pages take TurboQuant storage's slot (the codes leave the
548
+ * graph); bf16 and affine pages the paged one (the row leaves it). */
549
+ decodeSlot(_plan: DecodeStepPlan): DecodeSlot {
550
+ if (this.direct) throw new Error("direct pages have no compiled decode form; their composition decodes eagerly (pagedDecodeCompiles)");
551
+ return this.turbo ? turboQuantDecodeSlot(this.offset, this.turbo.kBits, this.turbo.vBits, this.turbo.kernels)
552
+ : pagedDecodeSlot(this.offset, this.quantization);
553
+ }
554
+
555
+ /** The stored positions gathered (dequantized on affine pages, decoded in the
556
+ * kernel set's domain on TurboQuant pages), owned by the step. */
557
+ decodeInputs(_plan: DecodeStepPlan, step: DecodeStepInputs): MlxArray[] {
558
+ const [keys, values] = this.turbo
559
+ ? this.pool!.gatherDecoded(this.blockTable, this.offset, this.turbo.kernels.fetch === "rotated") : this.#gathered();
560
+ return [step.own(keys), step.own(values)];
561
+ }
562
+
563
+ /** The write half (taking ownership of `outputs`): bf16 and affine pages
564
+ * append the step's row as an ordinary append does; TurboQuant pages store
565
+ * the step's codes, packed as the codec packs them. Returns the pages to
566
+ * evaluate with the step. */
567
+ commitDecodeStep(_slot: DecodeSlot, outputs: MlxArray[]): MlxArray[] {
568
+ if (this.turbo) {
569
+ const [kCodes, kScales, kZeros, vCodes, vScales] = outputs as [MlxArray, MlxArray, MlxArray, MlxArray, MlxArray];
570
+ const kIdx = this.turbo.kBits < 8 ? tq.packBits(kCodes, this.turbo.kBits) : kCodes;
571
+ if (kIdx !== kCodes) kCodes.dispose();
572
+ const vPacked = this.turbo.vBits < 8 ? tq.packBits(vCodes, this.turbo.vBits) : vCodes;
573
+ if (vPacked !== vCodes) vCodes.dispose();
574
+ this.#store({ kIdx, kScales, kZeros, vPacked, vScales });
575
+ return this.pool!.arrays();
576
+ }
577
+ const [k, v] = outputs as [MlxArray, MlxArray];
578
+ try { this.append(k, v); } finally { k.dispose(); v.dispose(); }
579
+ return this.pool!.arrays();
580
+ }
581
+
582
+ /** One position's TurboQuant codes (taking ownership), stored at the offset. */
583
+ #store(codes: TurboQuantTensor): void {
584
+ const pool = this.pool!, bi = Math.floor(this.offset / this.blockSize), within = this.offset % this.blockSize;
585
+ while (this.blockTable.length <= bi) {
586
+ if (!pool.freeBlocks) pool.grow(Math.max(pool.numBlocks + 1, Math.ceil(pool.numBlocks * 1.5)));
587
+ this.blockTable.push(pool.alloc());
588
+ }
589
+ pool.writeCodes(this.blockTable[bi]!, codes, within);
590
+ this.offset += 1;
591
+ }
592
+
593
+ /** Same mask policy as KVCache.makeMask (it reads only offset). */
594
+ makeMask(N: number, windowSize: number | null): Mask {
595
+ if (N === 1) return { mode: "", arr: null };
596
+ if (windowSize === null) return { mode: "causal", arr: null };
597
+ if (this.offset === 0 && N <= windowSize) return { mode: "causal", arr: null };
598
+ return { mode: "array", arr: createCausalMask(N, this.offset, windowSize) };
599
+ }
600
+
601
+ /** Owned planes materialized at prefill and immutable publication boundaries. */
602
+ state(): MlxArray[] {
603
+ return this.pool ? this.pool.arrays() : [];
604
+ }
605
+
606
+ isTrimmable(): boolean {
607
+ return true;
608
+ }
609
+
610
+ /** Rewind the last n tokens and return now-unoccupied tail blocks to
611
+ * the free list. Stale bytes past offset are never read (fetch slices
612
+ * to offset; writes cover exact ranges) — the KVCache padding
613
+ * invariant, block-shaped. */
614
+ trim(n: number): void {
615
+ this.offset = Math.max(0, this.offset - n);
616
+ const need = this.#blocksFor(this.offset);
617
+ while (this.blockTable.length > need) this.pool!.free(this.blockTable.pop()!);
618
+ }
619
+
620
+ dispose(): void {
621
+ // The cache owns its pool in v1 (one sequence per pool).
622
+ this.pool?.dispose();
623
+ this.pool = null;
624
+ this.blockTable = [];
625
+ this.offset = 0;
626
+ this.masks.clear();
627
+ }
628
+ }
629
+
630
+ /** A read over one block view under a fixed string mask. Takes ownership of
631
+ * `view`. */
632
+ function pagedRead(view: KvAttentionView, mask: Mask): AttentionRead {
633
+ return { attend: (q, scale) => view.attend(q, scale, mask), dispose: () => view.dispose() };
634
+ }
635
+
636
+ /** Size a pool for one request: capacity tokens rounded up to whole
637
+ * blocks. Exported for the wiring in generate.ts (maybePageKv). */
638
+ export function poolBlocksFor(capacityTokens: number, blockSize: number): number {
639
+ return Math.max(1, Math.ceil(capacityTokens / blockSize));
640
+ }
641
+
642
+ /** A paged cache inside a compiled decode trace: the new position joins the
643
+ * gathered prefix as the pages store it (affine pages: quantized, then
644
+ * dequantized as the gather dequantizes them), read with no mask by the fused
645
+ * SDPA; the row as appended is the closure output. */
646
+ class TracedPagedKVCache implements DecodeTrace {
647
+ outs: MlxArray[] = [];
648
+ constructor(
649
+ readonly offset: number,
650
+ readonly quantization: PagedQuantization | undefined,
651
+ readonly prefixKeys: MlxArray,
652
+ readonly prefixValues: MlxArray,
653
+ readonly ropeOffsetArr: MlxArray,
654
+ ) {}
655
+
656
+ appendDecode(k: MlxArray, v: MlxArray): AttentionRead {
657
+ // same-shape reshape: owned aliases that survive the caller's dispose of k and v
658
+ this.outs = [ops.reshape(k, k.shape), ops.reshape(v, v.shape)];
659
+ const stored = (row: MlxArray): MlxArray => {
660
+ if (!this.quantization) return ops.reshape(row, row.shape);
661
+ const { bits, groupSize } = this.quantization;
662
+ const q = ops.quantize(row, groupSize, bits);
663
+ try { return ops.dequantize(q.packed, q.scales, q.biases, { bits, groupSize, mode: "affine" }); }
664
+ finally { q.packed.dispose(); q.scales.dispose(); q.biases.dispose(); }
665
+ };
666
+ const join = (prefix: MlxArray, row: MlxArray): MlxArray => {
667
+ using held = stored(row);
668
+ return ops.concatAxis([prefix, held], 2);
669
+ };
670
+ const keys = join(this.prefixKeys, k);
671
+ let values: MlxArray;
672
+ try { values = join(this.prefixValues, v); } catch (error) { keys.dispose(); throw error; }
673
+ return sdpaRead(keys, values, unmaskedLease);
674
+ }
675
+
676
+ signature(): string { return "kv:paged-trace"; }
677
+ state(): MlxArray[] { return []; }
678
+ isTrimmable(): boolean { return false; }
679
+ trim(): void { throw new Error("a compiled decode trace is not trimmed"); }
680
+ dispose(): void {}
681
+ }
682
+
683
+ /** The concat slot of a paged cache at `offset` whose pages store `quantization` (bf16 when absent). */
684
+ export function pagedDecodeSlot(offset: number, quantization: PagedQuantization | undefined): DecodeSlot {
685
+ return {
686
+ key: quantization ? `paged-cat:${quantization.bits}:${quantization.groupSize}` : "paged-cat:bf16", fetch: "concat", inputs: 2, outputs: 2,
687
+ trace: (inputs, ropeOffset) => new TracedPagedKVCache(offset, quantization, inputs[0]!, inputs[1]!, ropeOffset),
688
+ };
689
+ }