@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,369 @@
1
+ import { MlxArray } from "@mlx-bun/mlx/array";
2
+ import { Dtype } from "@mlx-bun/mlx/ffi";
3
+ import * as ops from "@mlx-bun/mlx/ops";
4
+ import type { AttentionRead,BlockAttentionCache,CommittedAttentionCache,CompiledDecodeCache,DecodeSlot,DecodeStepInputs,DecodeStepPlan,DecodeTrace,KvDonorAttention,Mask } from "../contracts/mlx/cache";
5
+ import type { QuantizedAttentionState } from "./kv-storage";
6
+ import { createCausalMask } from "../kernels/attention/masks";
7
+ import { dequantizedFetch, disposeTriple, mapTriple } from "./quantized-tensor";
8
+ import { AttentionMasks, unmaskedLease, withLease } from "./attention-read";
9
+ import { affineCausalLease, affineRead, committedAffineRead, unfusedAffineKernels, viewAffineKernel, type AffineKernels, type AffineKernelSet } from "./affine-attention";
10
+
11
+
12
+ /** Quantized KV cache — port of mlx-lm QuantizedKVCache: keys/values
13
+ * stored as (packed u32, scales, biases) triples, quantized along
14
+ * head_dim. Only full-attention layers convert (mlx-lm's rotating-cache
15
+ * quantization is NYI upstream; sliding layers are window-capped
16
+ * anyway).
17
+ *
18
+ * Built empty, it quantizes from token zero: the first append is read
19
+ * quantized, so the first chunk's numbers differ from today's served path
20
+ * and mlx-lm's, which read it bf16 (`Bf16FirstQuantizedKVCache`).
21
+ *
22
+ * Reads: each named read appends through `updateAndFetchQuantized` and
23
+ * attends the fetched prefix with the kernel `kernels` fixed for it at
24
+ * construction: decode with no mask, a window under the bottom-right causal
25
+ * matrix. The composition names the set (`composedAffineKernels`: tiled-causal
26
+ * for `--kv-quant config` and `--fused-sdpa on`, unfused otherwise). The
27
+ * committed read is `quantizedAppendAttention`. This cache has no sliding
28
+ * window (graphs compose `RotatingQuantizedKVCache` for sliding layers). */
29
+ export class QuantizedKVCache implements CompiledDecodeCache, CommittedAttentionCache, BlockAttentionCache {
30
+ minimumReusableOffset?: number;
31
+ static readonly STEP = 256;
32
+ /** Set only by compiled-decode trace adapters (see Cache). */
33
+ readonly ropeOffsetArr?: MlxArray;
34
+ keys: ops.QuantizedTensor | null = null;
35
+ values: ops.QuantizedTensor | null = null;
36
+ offset = 0;
37
+
38
+ constructor(
39
+ readonly groupSize: number,
40
+ readonly bits: number,
41
+ /** The kernel of each read. Row layouts made from this cache keep them. */
42
+ readonly kernels: AffineKernels,
43
+ /** The per-forward masks of the model this cache belongs to. Row layouts
44
+ * made from this cache (`state/layout`) share them. */
45
+ readonly masks = new AttentionMasks(),
46
+ ) {}
47
+
48
+ /** The kernel set affine storage restored at this cache's layer reads with
49
+ * (`state/persistence`): this cache's own kernels. */
50
+ get kernelSet(): AffineKernelSet { return () => this.kernels; }
51
+
52
+ get quantizedAttention(): QuantizedAttentionState { return this; }
53
+
54
+ signature(): string { return `kv:quant:${this.bits}:${this.groupSize}`; }
55
+
56
+ appendDecode(k: MlxArray, v: MlxArray): AttentionRead {
57
+ const [keys, values] = this.updateAndFetchQuantized(k, v);
58
+ return affineRead(keys, values, unmaskedLease, this.kernels.decode);
59
+ }
60
+
61
+ appendWindow(k: MlxArray, v: MlxArray): AttentionRead {
62
+ const L = k.shape[2]!;
63
+ return withLease(affineCausalLease(this.masks, L, this.offset + L), mask => {
64
+ const [keys, values] = this.updateAndFetchQuantized(k, v);
65
+ return affineRead(keys, values, mask, this.kernels.window);
66
+ });
67
+ }
68
+
69
+ appendCommitted(k: MlxArray, v: MlxArray): AttentionRead {
70
+ const [keys, values] = this.updateAndFetchQuantized(k, v);
71
+ return committedAffineRead(keys, values, this.groupSize, this.bits);
72
+ }
73
+
74
+ /** A block read (`BlockAttentionCache.readBlock`): the stored positions,
75
+ * then the block's keys and values quantized as this cache stores them and
76
+ * not appended, read with no mask by the masked-window kernel, which takes
77
+ * any query count. */
78
+ readBlock(k: MlxArray, v: MlxArray): AttentionRead {
79
+ if (!this.keys || !this.values) throw new Error("cache is empty");
80
+ const live = (a: MlxArray) => a.slice([0, 0, 0, 0], [a.shape[0]!, a.shape[1]!, this.offset, a.shape[3]!]);
81
+ const blockK = ops.quantize(k, this.groupSize, this.bits), blockV = ops.quantize(v, this.groupSize, this.bits);
82
+ const storedK = mapTriple(this.keys, live), storedV = mapTriple(this.values, live);
83
+ let keys: ops.QuantizedTensor;
84
+ try {
85
+ keys = catAxis2(storedK, blockK);
86
+ try { return affineRead(keys, catAxis2(storedV, blockV), unmaskedLease, this.kernels.maskedWindow); }
87
+ catch (error) { disposeTriple(keys); throw error; }
88
+ } finally { for (const t of [blockK, blockV, storedK, storedV]) disposeTriple(t); }
89
+ }
90
+
91
+ bytesPerToken(): number {
92
+ const steps = this.keys?.packed.shape[2] ?? 0;
93
+ if (steps === 0 || !this.keys || !this.values) return 0;
94
+ const tensors = [
95
+ this.keys.packed, this.keys.scales, this.keys.biases,
96
+ this.values.packed, this.values.scales, this.values.biases,
97
+ ];
98
+ return tensors.reduce((bytes, array) => bytes + array.nbytes, 0) / steps;
99
+ }
100
+
101
+ /** The dense fetch: appends, then the stored window dequantized. */
102
+ updateAndFetch(k: MlxArray, v: MlxArray): [MlxArray, MlxArray] {
103
+ return dequantizedFetch(this.updateAndFetchQuantized(k, v), this.groupSize, this.bits);
104
+ }
105
+
106
+ #grow(triple: ops.QuantizedTensor | null, lastDims: number[], B: number, H: number, steps: number, dtype: Dtype): ops.QuantizedTensor {
107
+ const mk = (dim: number, dt: Dtype) => ops.zeros([B, H, steps, dim], dt);
108
+ if (!triple)
109
+ return { packed: mk(lastDims[0]!, Dtype.uint32), scales: mk(lastDims[1]!, dtype), biases: mk(lastDims[2]!, dtype) };
110
+ const ext = (a: MlxArray, dim: number, dt: Dtype): MlxArray => {
111
+ const z = mk(dim, dt);
112
+ const out = ops.concatAxis([a, z], 2);
113
+ a.dispose();
114
+ z.dispose();
115
+ return out;
116
+ };
117
+ return {
118
+ packed: ext(triple.packed, lastDims[0]!, Dtype.uint32),
119
+ scales: ext(triple.scales, lastDims[1]!, dtype),
120
+ biases: ext(triple.biases, lastDims[2]!, dtype),
121
+ };
122
+ }
123
+
124
+ /** Quantize incoming k/v and append; returns quantized views to offset. */
125
+ updateAndFetchQuantized(k: MlxArray, v: MlxArray): [ops.QuantizedTensor, ops.QuantizedTensor] {
126
+ const [B, H, L, kD] = k.shape as [number, number, number, number];
127
+ const vD = v.shape[3]!;
128
+ const prev = this.offset;
129
+ const elPerInt = 32 / this.bits;
130
+
131
+ if (!this.keys || prev + L > this.keys.packed.shape[2]!) {
132
+ const newSteps = Math.floor((QuantizedKVCache.STEP + L - 1) / QuantizedKVCache.STEP) * QuantizedKVCache.STEP;
133
+ if (this.keys && prev % QuantizedKVCache.STEP !== 0) {
134
+ const trimTo = (t: ops.QuantizedTensor): ops.QuantizedTensor => {
135
+ const cut = (a: MlxArray): MlxArray => {
136
+ const [b, h, , d] = a.shape as [number, number, number, number];
137
+ const s = a.slice([0, 0, 0, 0], [b, h, prev, d]);
138
+ a.dispose();
139
+ return s;
140
+ };
141
+ return { packed: cut(t.packed), scales: cut(t.scales), biases: cut(t.biases) };
142
+ };
143
+ this.keys = trimTo(this.keys);
144
+ this.values = trimTo(this.values!);
145
+ }
146
+ const dtype = k.dtype;
147
+ this.keys = this.#grow(this.keys, [kD / elPerInt, kD / this.groupSize, kD / this.groupSize], B, H, newSteps, dtype);
148
+ this.values = this.#grow(this.values, [vD / elPerInt, vD / this.groupSize, vD / this.groupSize], B, H, newSteps, dtype);
149
+ }
150
+
151
+ this.offset += L;
152
+ const kq = ops.quantize(k, this.groupSize, this.bits);
153
+ const vq = ops.quantize(v, this.groupSize, this.bits);
154
+
155
+ const writeAll = (dst: ops.QuantizedTensor, src: ops.QuantizedTensor): ops.QuantizedTensor => {
156
+ const w = (d: MlxArray, s: MlxArray): MlxArray => {
157
+ const [b, h, , dd] = d.shape as [number, number, number, number];
158
+ const u = ops.sliceUpdate(d, s, [0, 0, prev, 0], [b, h, this.offset, dd]);
159
+ d.dispose();
160
+ s.dispose();
161
+ return u;
162
+ };
163
+ return {
164
+ packed: w(dst.packed, src.packed),
165
+ scales: w(dst.scales, src.scales),
166
+ biases: w(dst.biases, src.biases),
167
+ };
168
+ };
169
+ this.keys = writeAll(this.keys!, kq);
170
+ this.values = writeAll(this.values!, vq);
171
+
172
+ const fetch = (t: ops.QuantizedTensor): ops.QuantizedTensor => {
173
+ const f = (a: MlxArray): MlxArray => {
174
+ const [b, h, , d] = a.shape as [number, number, number, number];
175
+ return a.slice([0, 0, 0, 0], [b, h, this.offset, d]);
176
+ };
177
+ return { packed: f(t.packed), scales: f(t.scales), biases: f(t.biases) };
178
+ };
179
+ return [fetch(this.keys), fetch(this.values)];
180
+ }
181
+
182
+ makeMask(N: number, windowSize: number | null): Mask {
183
+ if (N === 1) return { mode: "", arr: null };
184
+ // Same rule as KVCache.makeMask (mlx-lm cache.py:114-125): windowless
185
+ // multi-token chunks are the string "causal" at any offset. The old
186
+ // causalEquivalent escape hatch existed precisely because this case
187
+ // used to ship an array; only windowed continuations materialize now.
188
+ if (windowSize === null || (this.offset === 0 && N <= windowSize))
189
+ return { mode: "causal", arr: null };
190
+ return { mode: "array", arr: createCausalMask(N, this.offset, windowSize) };
191
+ }
192
+
193
+ /** Compiled decode: growth half of updateAndFetchQuantized (L=1),
194
+ * without quantize/write (those live in the compiled graph / after). */
195
+ prepareDecodeStep(): DecodeStepPlan {
196
+ const prev = this.offset;
197
+ if (!this.keys || !this.values) throw new Error("compiled decode on an empty cache");
198
+ if (prev + 1 > this.keys.packed.shape[2]!) {
199
+ if (prev % QuantizedKVCache.STEP !== 0) {
200
+ const trimTo = (t: ops.QuantizedTensor): ops.QuantizedTensor => {
201
+ const cut = (a: MlxArray): MlxArray => {
202
+ const [b, h, , d] = a.shape as [number, number, number, number];
203
+ const s = a.slice([0, 0, 0, 0], [b, h, prev, d]);
204
+ a.dispose();
205
+ return s;
206
+ };
207
+ return { packed: cut(t.packed), scales: cut(t.scales), biases: cut(t.biases) };
208
+ };
209
+ this.keys = trimTo(this.keys);
210
+ this.values = trimTo(this.values);
211
+ }
212
+ const [B, H] = this.keys.packed.shape as [number, number, number, number];
213
+ const elPerInt = 32 / this.bits;
214
+ const kD = this.keys.packed.shape[3]! * elPerInt;
215
+ const vD = this.values.packed.shape[3]! * elPerInt;
216
+ const dtype = this.keys.scales.dtype;
217
+ this.keys = this.#grow(this.keys, [kD / elPerInt, kD / this.groupSize, kD / this.groupSize], B, H, QuantizedKVCache.STEP, dtype);
218
+ this.values = this.#grow(this.values, [vD / elPerInt, vD / this.groupSize, vD / this.groupSize], B, H, QuantizedKVCache.STEP, dtype);
219
+ }
220
+ return { fetch: "concat", writePos: prev, activeLen: prev };
221
+ }
222
+
223
+ decodePhase(): DecodeStepPlan["fetch"] { return "concat"; }
224
+
225
+ decodeSlot(_plan: DecodeStepPlan): DecodeSlot { return quantConcatDecodeSlot(this.offset, this.groupSize, this.bits); }
226
+
227
+ decodeInputs(plan: DecodeStepPlan, step: DecodeStepInputs): MlxArray[] {
228
+ return activeQuantViews(this.keys!, this.values!, plan.activeLen, step);
229
+ }
230
+
231
+ commitDecodeStep(_slot: DecodeSlot, outputs: MlxArray[]): MlxArray[] {
232
+ return this.writeDecodeStep(outputs);
233
+ }
234
+
235
+ /** Compiled decode: the write half — six sliceUpdates of the already-
236
+ * quantized step row (quantize ran in-graph). Takes ownership of the
237
+ * rows; returns the updated buffers to async-eval with the step. */
238
+ writeDecodeStep(rows: MlxArray[]): MlxArray[] {
239
+ const prev = this.offset;
240
+ const w = (d: MlxArray, srcRow: MlxArray): MlxArray => {
241
+ const [b, h, , dd] = d.shape as [number, number, number, number];
242
+ const u = ops.sliceUpdate(d, srcRow, [0, 0, prev, 0], [b, h, prev + 1, dd]);
243
+ d.dispose();
244
+ srcRow.dispose();
245
+ return u;
246
+ };
247
+ this.keys = {
248
+ packed: w(this.keys!.packed, rows[0]!),
249
+ scales: w(this.keys!.scales, rows[1]!),
250
+ biases: w(this.keys!.biases, rows[2]!),
251
+ };
252
+ this.values = {
253
+ packed: w(this.values!.packed, rows[3]!),
254
+ scales: w(this.values!.scales, rows[4]!),
255
+ biases: w(this.values!.biases, rows[5]!),
256
+ };
257
+ this.offset = prev + 1;
258
+ return this.state();
259
+ }
260
+
261
+ state(): MlxArray[] {
262
+ if (!this.keys || !this.values) return [];
263
+ return [
264
+ this.keys.packed, this.keys.scales, this.keys.biases,
265
+ this.values.packed, this.values.scales, this.values.biases,
266
+ ];
267
+ }
268
+
269
+ isTrimmable(): boolean {
270
+ return true;
271
+ }
272
+
273
+ trim(n: number): void {
274
+ this.offset = Math.max(0, this.offset - n);
275
+ }
276
+
277
+ /** Adopt persisted state (takes ownership of the triples' arrays) —
278
+ * the quantized twin of KVCache.restoreState (packages/inference/src/state/persistence.ts). */
279
+ restoreState(keys: ops.QuantizedTensor, values: ops.QuantizedTensor, offset: number): void {
280
+ this.dispose();
281
+ this.keys = keys;
282
+ this.values = values;
283
+ this.offset = offset;
284
+ }
285
+
286
+ /** Chronological (K, V) triples sliced to offset — the quantized twin of
287
+ * KVCache.temporalView (batched merge/extend/filter reads). Caller owns
288
+ * the returned views. */
289
+ captureDonorAttention(): KvDonorAttention {
290
+ const [keys, values] = this.temporalView();
291
+ const B = keys.packed.shape[0]!, width = keys.packed.shape[2]!;
292
+ return { width, dtype: keys.scales.dtype, offsets: Array(B).fill(this.offset),
293
+ starts: Array(B).fill(0), ends: Array(B).fill(width),
294
+ attend: (q, scale, mask) => viewAffineKernel(this.kernels, q.shape[2]!, mask)(q, keys, values, scale, mask),
295
+ dispose() { disposeTriple(keys); disposeTriple(values); },
296
+ };
297
+ }
298
+
299
+ temporalView(): [ops.QuantizedTensor, ops.QuantizedTensor] {
300
+ if (!this.keys || !this.values) throw new Error("cache is empty");
301
+ const cut = (t: ops.QuantizedTensor): ops.QuantizedTensor => {
302
+ const f = (a: MlxArray): MlxArray => {
303
+ const [b, h, , d] = a.shape as [number, number, number, number];
304
+ return a.slice([0, 0, 0, 0], [b, h, this.offset, d]);
305
+ };
306
+ return { packed: f(t.packed), scales: f(t.scales), biases: f(t.biases) };
307
+ };
308
+ return [cut(this.keys), cut(this.values)];
309
+ }
310
+
311
+ dispose(): void {
312
+ for (const a of this.state()) a.dispose();
313
+ this.keys = this.values = null;
314
+ this.offset = 0;
315
+ this.masks.clear();
316
+ }
317
+ }
318
+
319
+ /** The six active-prefix planes (keys then values) a concat fetch feeds a compiled decode step. */
320
+ export function activeQuantViews(
321
+ keys: ops.QuantizedTensor, values: ops.QuantizedTensor, length: number, step: DecodeStepInputs,
322
+ ): MlxArray[] {
323
+ return [keys, values].flatMap((t) => [
324
+ step.activeView(t.packed, length), step.activeView(t.scales, length), step.activeView(t.biases, length),
325
+ ]);
326
+ }
327
+
328
+ const catAxis2 = (a: ops.QuantizedTensor, b: ops.QuantizedTensor): ops.QuantizedTensor => ({
329
+ packed: ops.concatAxis([a.packed, b.packed], 2),
330
+ scales: ops.concatAxis([a.scales, b.scales], 2),
331
+ biases: ops.concatAxis([a.biases, b.biases], 2),
332
+ });
333
+
334
+ /** The growing quantized cache inside a compiled decode trace: quantize in
335
+ * graph, fetch = per-component concat, the six quantized row components are
336
+ * closure outputs (see TracedConcatKVCache). */
337
+ class TracedConcatQuantizedKVCache extends QuantizedKVCache implements DecodeTrace {
338
+ override readonly ropeOffsetArr: MlxArray;
339
+ outs: MlxArray[] = [];
340
+ constructor(
341
+ offset: number, groupSize: number, bits: number,
342
+ readonly activeKq: ops.QuantizedTensor,
343
+ readonly activeVq: ops.QuantizedTensor,
344
+ ropeOffsetArr: MlxArray,
345
+ ) {
346
+ // A compiled step is one-token decode, unfused in every composition.
347
+ super(groupSize, bits, unfusedAffineKernels(bits, groupSize, Dtype.bfloat16));
348
+ this.offset = offset;
349
+ this.ropeOffsetArr = ropeOffsetArr;
350
+ }
351
+
352
+ override updateAndFetchQuantized(k: MlxArray, v: MlxArray): [ops.QuantizedTensor, ops.QuantizedTensor] {
353
+ const kq = ops.quantize(k, this.groupSize, this.bits);
354
+ const vq = ops.quantize(v, this.groupSize, this.bits);
355
+ this.outs = [kq.packed, kq.scales, kq.biases, vq.packed, vq.scales, vq.biases];
356
+ return [catAxis2(this.activeKq, kq), catAxis2(this.activeVq, vq)];
357
+ }
358
+ }
359
+
360
+ /** The concat-fetch slot of a quantized cache at `offset` (QuantizedKVCache, and RotatingQuantizedKVCache before the window fills). */
361
+ export function quantConcatDecodeSlot(offset: number, groupSize: number, bits: number): DecodeSlot {
362
+ const triple = (inputs: readonly MlxArray[], at: number): ops.QuantizedTensor =>
363
+ ({ packed: inputs[at]!, scales: inputs[at + 1]!, biases: inputs[at + 2]! });
364
+ return {
365
+ key: `q-cat:${groupSize}:${bits}`, fetch: "concat", inputs: 6, outputs: 6,
366
+ trace: (inputs, ropeOffset) =>
367
+ new TracedConcatQuantizedKVCache(offset, groupSize, bits, triple(inputs, 0), triple(inputs, 3), ropeOffset),
368
+ };
369
+ }
@@ -0,0 +1,26 @@
1
+ import { MlxArray } from "@mlx-bun/mlx/array";
2
+ import * as ops from "@mlx-bun/mlx/ops";
3
+
4
+
5
+ export const mapTriple = (
6
+ t: ops.QuantizedTensor, f: (a: MlxArray) => MlxArray,
7
+ ): ops.QuantizedTensor => ({ packed: f(t.packed), scales: f(t.scales), biases: f(t.biases) });
8
+
9
+ export const disposeTriple = (t: ops.QuantizedTensor): void => {
10
+ t.packed.dispose();
11
+ t.scales.dispose();
12
+ t.biases.dispose();
13
+ };
14
+
15
+ /** The dense fetch of affine storage: the keys and values an affine append
16
+ * fetched (`updateAndFetchQuantized`), each dequantized (to the activation
17
+ * dtype its scales carry), as a read that attends keys and values as arrays
18
+ * takes them. Takes ownership of both triples; the caller owns the result. */
19
+ export function dequantizedFetch([keys, values]: [ops.QuantizedTensor, ops.QuantizedTensor],
20
+ groupSize: number, bits: number): [MlxArray, MlxArray] {
21
+ try {
22
+ const dense = (t: ops.QuantizedTensor) => ops.dequantize(t.packed, t.scales, t.biases, { groupSize, bits, mode: "affine" });
23
+ const k = dense(keys);
24
+ try { return [k, dense(values)]; } catch (error) { k.dispose(); throw error; }
25
+ } finally { disposeTriple(keys); disposeTriple(values); }
26
+ }
@@ -0,0 +1,48 @@
1
+ import type { KvStorageScheme, PagedKvComposition } from "../contracts/portable/composition";
2
+ import { type Cache } from "../contracts/mlx/cache";
3
+ import { PagedKVCache, type PagedQuantization, type PagedTurboQuant } from "./paged/cache";
4
+ import type { KVCache } from "./kv";
5
+ import { turboQuantFusedDecode } from "./turboquant-codec";
6
+ import { composedStorageOf, isPlainKvCache } from "./capabilities";
7
+
8
+ /** The affine pages the composition's KV scheme `kv` names for the layer at
9
+ * cache index `layer`: every layer of a uniform scheme, the listed layers of a
10
+ * per-layer one; none (bf16 or TurboQuant pages) otherwise. */
11
+ function pageQuantization(kv: KvStorageScheme, layer: number): PagedQuantization | undefined {
12
+ if (kv.kind === "affine") return { bits: kv.bits, groupSize: kv.groupSize };
13
+ if (kv.kind !== "affine-layers") return undefined;
14
+ const entry = kv.layers.find(item => item.layer === layer);
15
+ return entry ? { bits: entry.bits, groupSize: entry.groupSize } : undefined;
16
+ }
17
+
18
+ /** Replace fresh full-attention storage before prefill with pages storing what
19
+ * the composition's scheme `kv` names for each layer (bf16, affine at the
20
+ * layer's width, TurboQuant read with the layer's composed read), sized for
21
+ * `capacityTokens` (the pool grows past it). Pages store their format from the
22
+ * first position. Sliding layers retain their layout; pre-warmed caches retain
23
+ * their existing storage. Nothing changes without paging. */
24
+ export function maybePageKv(cache: Cache[], kv: KvStorageScheme, paged: PagedKvComposition | null, capacityTokens: number): void {
25
+ if (!paged) return;
26
+ if (cache.some((c) => c.offset > 0)) return;
27
+ for (let i = 0; i < cache.length; i++) {
28
+ const storage = composedStorageOf(cache[i]!);
29
+ if (!isPlainKvCache(storage)) continue;
30
+ const turbo: PagedTurboQuant | undefined = kv.kind === "turbo" ? Object.freeze({ kBits: kv.kBits, vBits: kv.vBits,
31
+ fusedDecode: turboQuantFusedDecode(), kernels: (storage as KVCache).turboQuantKernels }) : undefined;
32
+ cache[i]!.dispose(); // fresh (offset 0) — nothing stored yet
33
+ cache[i] = new PagedKVCache(capacityTokens, paged.blockSize, paged.direct, pageQuantization(kv, i), undefined, turbo);
34
+ }
35
+ }
36
+
37
+ /** The prefix namespace of paged state over a method's namespace `base`: the
38
+ * block size, what the pages store and their read (bf16 and uniform affine
39
+ * pages keep the namespace they had before per-layer and TurboQuant pages
40
+ * existed). */
41
+ export function pagedPrefixNamespace(kv: KvStorageScheme, paged: PagedKvComposition, base: string): string {
42
+ const direct = paged.direct;
43
+ if (kv.kind === "affine-layers")
44
+ return JSON.stringify(["paged-v1", paged.blockSize, "layers", kv.layers.map(entry => [entry.layer, entry.bits, entry.groupSize]), direct, base]);
45
+ if (kv.kind === "turbo") return JSON.stringify(["paged-v1", paged.blockSize, "turbo", kv.kBits, kv.vBits, direct, base]);
46
+ const pages = pageQuantization(kv, 0);
47
+ return JSON.stringify(["paged-v1", paged.blockSize, pages?.bits ?? 0, pages?.groupSize ?? 64, direct, base]);
48
+ }
@@ -0,0 +1,37 @@
1
+ /** RAM policy sees reuse metadata, never tensors, storage or scheduling. */
2
+ export interface RetentionCandidate {
3
+ bytes: number;
4
+ cost: number;
5
+ lastUsed: number;
6
+ uses: number;
7
+ priority: number;
8
+ }
9
+ export interface RetentionPolicy {
10
+ readonly name: string;
11
+ accessed(entry: RetentionCandidate): void;
12
+ victim<T extends RetentionCandidate>(entries: readonly T[]): T;
13
+ evicted(entry: RetentionCandidate): void;
14
+ }
15
+ export class LruRetention implements RetentionPolicy {
16
+ readonly name = "lru";
17
+ accessed(_entry: RetentionCandidate): void {}
18
+ victim<T extends RetentionCandidate>(entries: readonly T[]): T {
19
+ return entries.reduce((a, b) => a.lastUsed < b.lastUsed ? a : b);
20
+ }
21
+ evicted(_entry: RetentionCandidate): void {}
22
+ }
23
+
24
+ /** GreedyDual size/frequency: saved prefill tokens are the cost proxy.
25
+ * Advancing age on eviction lets old popularity decay without wall timers. */
26
+ export class CostSizeRetention implements RetentionPolicy {
27
+ readonly name = "cost-size";
28
+ #age = 0;
29
+ accessed(entry: RetentionCandidate): void {
30
+ entry.priority = this.#age + entry.cost * entry.uses / Math.max(1, entry.bytes);
31
+ }
32
+ victim<T extends RetentionCandidate>(entries: readonly T[]): T {
33
+ return entries.reduce((a, b) => a.priority < b.priority ||
34
+ (a.priority === b.priority && a.lastUsed < b.lastUsed) ? a : b);
35
+ }
36
+ evicted(entry: RetentionCandidate): void { this.#age = Math.max(this.#age, entry.priority); }
37
+ }
@@ -0,0 +1,131 @@
1
+ import type { SpeculativeTransaction } from "../contracts/portable/rollback";
2
+ import type { Cache } from "../contracts/mlx/cache";
3
+ import type { KvStorageScheme } from "../contracts/portable/composition";
4
+ import { isRotatingPlainCache } from "./capabilities";
5
+
6
+ /** Whether a serial run's caches, composed with the KV scheme `kv`, can drop
7
+ * the rejected tail of a verify block (`rollbackVerifyBlock`): bf16 storage
8
+ * that trims, a plain ring, or a recurrence that restores and replays. */
9
+ export function rollsBackVerifyBlock(kv: KvStorageScheme, caches: readonly Cache[]): boolean {
10
+ return kv.kind === "bf16" && caches.every(cache =>
11
+ cache.isTrimmable() || isRotatingPlainCache(cache) || typeof cache.specRoundRollback === "function");
12
+ }
13
+
14
+ /** Drop the rejected tail of a verify block of `block` positions that one
15
+ * concat write appended, keeping `accepted`: a recurrence restores its
16
+ * pre-round snapshot and bit-exactly replays the accepted prefix; other storage
17
+ * trims the rest. A plain ring stops being trimmable once it wraps, but the
18
+ * block was one concat write that keeps the newest window-1 positions plus the
19
+ * block in temporal order, so its rollback trim drops the tail physically
20
+ * (at least the window remains: `accepted` >= 1). */
21
+ export function rollbackVerifyBlock(caches: readonly Cache[], accepted: number, block: number): void {
22
+ for (const cache of caches) {
23
+ if (cache.specRoundRollback) cache.specRoundRollback(accepted);
24
+ else cache.trim(block - accepted, isRotatingPlainCache(cache));
25
+ }
26
+ }
27
+
28
+ interface TransactionLayer<Acceptance> {
29
+ fits(drafts: number): boolean;
30
+ begin(drafts: number): void;
31
+ resolve(accepted: Acceptance, drafts: number): void;
32
+ }
33
+
34
+ /** State storage that can retain a different verified prefix for each row.
35
+ * Both attention and recurrent layouts implement this same transaction. */
36
+ export interface RowSpeculativeState {
37
+ specRoundBegin(): void;
38
+ specRoundCommit(): void;
39
+ specRoundRollback(keep: number | readonly number[]): void;
40
+ }
41
+
42
+ export function bindRowCacheRollback(
43
+ caches: readonly RowSpeculativeState[], rows: number,
44
+ ): SpeculativeTransaction<readonly number[]> {
45
+ return bindTransaction(caches.map(cache => ({
46
+ fits: () => true,
47
+ // A zero-candidate round consumes only the committed pending token.
48
+ // It cannot reject any input, so retaining rollback state is unnecessary.
49
+ begin: drafts => { if (drafts > 0) cache.specRoundBegin(); },
50
+ resolve(accepted: readonly number[], drafts: number) {
51
+ if (drafts === 0) return;
52
+ if (accepted.every(count => count === drafts)) cache.specRoundCommit();
53
+ else cache.specRoundRollback(accepted.every(count => count === accepted[0])
54
+ ? accepted[0]! + 1 : accepted.map(count => count + 1));
55
+ },
56
+ })), (accepted, drafts) => accepted.length === rows &&
57
+ accepted.every(count => Number.isSafeInteger(count) && count >= 0 && count <= drafts));
58
+ }
59
+
60
+ /** A one-position window whose input may itself be rejected: a step built from a sample not yet read. `resolve`
61
+ * keeps each row's input (1) or drops it (0), restoring that row's state from before the window. */
62
+ export function bindRowStepRollback(
63
+ caches: readonly RowSpeculativeState[], rows: number,
64
+ ): SpeculativeTransaction<readonly number[]> {
65
+ return bindTransaction(caches.map(cache => ({
66
+ fits: () => true,
67
+ begin: () => cache.specRoundBegin(),
68
+ resolve(kept: readonly number[]) {
69
+ if (kept.every(count => count === 1)) cache.specRoundCommit();
70
+ else cache.specRoundRollback(kept.every(count => count === kept[0]) ? kept[0]! : kept);
71
+ },
72
+ })), (kept, inputs) => inputs === 1 && kept.length === rows && kept.every(count => count === 0 || count === 1));
73
+ }
74
+
75
+ /** Bind the recurrent transaction or the trim implementation once.
76
+ * Ring-capacity checks still happen at each safe boundary before any writes. */
77
+ export function bindCacheRollback(caches: readonly Cache[]): SpeculativeTransaction {
78
+ const layers = caches.map((cache) => {
79
+ const { specRoundBegin: begin, specRoundCommit: commit, specRoundRollback: rollback } = cache;
80
+ const hasRound = [begin, commit, rollback].some((method) => method !== undefined);
81
+ if (hasRound && !(typeof begin === "function" && typeof commit === "function" && typeof rollback === "function"))
82
+ throw new Error("cache exposes an incomplete speculative transaction");
83
+ return {
84
+ fits(drafts: number) {
85
+ if ("maxSize" in cache && typeof cache.maxSize === "number")
86
+ return cache.offset + drafts + 1 < cache.maxSize;
87
+ return hasRound || cache.isTrimmable();
88
+ },
89
+ begin: () => begin?.call(cache),
90
+ resolve(accepted: number, drafts: number) {
91
+ if (accepted < drafts) {
92
+ if (rollback) rollback.call(cache, accepted + 1);
93
+ else cache.trim(drafts - accepted);
94
+ } else commit?.call(cache);
95
+ },
96
+ };
97
+ });
98
+ return bindTransaction(layers, (accepted, drafts) =>
99
+ Number.isSafeInteger(accepted) && accepted >= 0 && accepted <= drafts);
100
+ }
101
+
102
+ /** One transaction lifecycle for scalar and row-wise state bindings. */
103
+ function bindTransaction<Acceptance>(
104
+ layers: readonly TransactionLayer<Acceptance>[],
105
+ accepts: (accepted: Acceptance, drafts: number) => boolean,
106
+ ): SpeculativeTransaction<Acceptance> {
107
+ let active: number | undefined;
108
+ let invalid = false;
109
+ const validate = (drafts: number) => {
110
+ if (invalid) throw new Error("speculative state is invalid and must be discarded");
111
+ if (!Number.isSafeInteger(drafts) || drafts < 0) throw new Error("invalid speculative draft count");
112
+ };
113
+ return {
114
+ canBegin(drafts) { validate(drafts); return active === undefined && layers.every((layer) => layer.fits(drafts)); },
115
+ begin(drafts) {
116
+ validate(drafts);
117
+ if (active !== undefined || !layers.every((layer) => layer.fits(drafts)))
118
+ throw new Error("speculative state cannot begin this round");
119
+ active = drafts;
120
+ try { for (const layer of layers) layer.begin(drafts); }
121
+ catch (error) { invalid = true; throw error; }
122
+ },
123
+ resolve(accepted) {
124
+ if (invalid) throw new Error("speculative state is invalid and must be discarded");
125
+ if (active === undefined || !accepts(accepted, active)) throw new Error("invalid speculative acceptance count");
126
+ try { for (const layer of layers) layer.resolve(accepted, active); }
127
+ catch (error) { invalid = true; throw error; }
128
+ active = undefined;
129
+ },
130
+ };
131
+ }
@@ -0,0 +1,35 @@
1
+ import type { KvDonorAttention } from "../contracts/mlx/cache";
2
+ import { BatchedRotatingState } from "./batched-rotating-state";
3
+ import { BatchedRotatingQuantCache } from "./batched-rotating-quant";
4
+ import { plainRowStorage, quantizedRowStorage, temporalStorageView } from "./batched-row-storage";
5
+ import { quantizedDonorAttention } from "./kv-attention-view";
6
+ import { decodedKvDonorAttention } from "./decoded-kv-donor";
7
+ import type { RotatingRing } from "./rotating-row-transaction";
8
+
9
+ /** Capture the same chronological donor rectangle for plain and encoded rings. */
10
+ export function captureRotatingDonorAttention(ring: RotatingRing): KvDonorAttention {
11
+ const position = new BatchedRotatingState(ring.maxSize, []);
12
+ position.restore(ring.positionSnapshot);
13
+ const offsets = position.offsets.map((_, row) => position.validOffset(row));
14
+ const ends = offsets.map((offset, row) => position.activeLength - (position.offsets[row]! - offset));
15
+ // Pending prefill keeps a shared physical rectangle, including when mixed
16
+ // rows are captured separately. Cropping from each row's valid end would
17
+ // give siblings different widths and can discard an earlier valid window.
18
+ const from = position.hasPendingPadding ? 0 : Math.max(0, position.activeLength - ring.maxSize);
19
+ const range = { from, to: position.activeLength };
20
+ const validity = { offsets,
21
+ starts: position.leftPad.map((pad, row) => Math.max(0, pad - from, ends[row]! - from - ring.maxSize)),
22
+ ends: ends.map(end => Math.max(0, end - from)) };
23
+ if (ring instanceof BatchedRotatingQuantCache) {
24
+ const keys = temporalStorageView(quantizedRowStorage, ring.keys!, position, range);
25
+ let values: import("@mlx-bun/mlx/ops").QuantizedTensor;
26
+ try { values = temporalStorageView(quantizedRowStorage, ring.values!, position, range); }
27
+ catch (error) { quantizedRowStorage.dispose(keys); throw error; }
28
+ return quantizedDonorAttention(keys, values, validity, ring.kernels);
29
+ }
30
+ const keys = temporalStorageView(plainRowStorage, ring.keys!, position, range);
31
+ let values: import("@mlx-bun/mlx/array").MlxArray;
32
+ try { values = temporalStorageView(plainRowStorage, ring.values!, position, range); }
33
+ catch (error) { keys.dispose(); throw error; }
34
+ return decodedKvDonorAttention({ keys, values, ...validity });
35
+ }