@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,616 @@
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
+
8
+ import { activeQuantViews,quantConcatDecodeSlot } from "./quantized-kv";
9
+ import { dequantizedFetch,disposeTriple,mapTriple } from "./quantized-tensor";
10
+ import { AttentionMasks, decodeByPosition, rotatingBlockMask, unmaskedLease, withLease, type MaskLease } from "./attention-read";
11
+ import { affineCausalLease, affineRead, unfusedAffineKernels, viewAffineKernel, type AffineKernel, type AffineKernels, type AffineKernelSet } from "./affine-attention";
12
+
13
+
14
+ /** Quantized rotating (sliding-window) KV cache — port of optiq
15
+ * runtime/kv/rotating.py RotatingQuantizedKVCache with keep=0
16
+ * (gemma4's configuration): RotatingKVCache's ring mechanics over
17
+ * (packed, scales, biases) triples, storage convention identical to
18
+ * QuantizedKVCache. Returns ACTIVE QUANTIZED SLICES — the oracle's
19
+ * module docstring claims dequantize-on-read, but its code does not
20
+ * (port follows the code). optiq's producer-registry
21
+ * + SDPA patches are unnecessary here: our SharedKv carries
22
+ * groupSize/bits through the donor→sharer plumbing explicitly.
23
+ *
24
+ * Built empty, it quantizes from token zero, unlike the served path's
25
+ * `Bf16FirstRotatingQuantizedKVCache`, so its first chunk's numbers differ.
26
+ *
27
+ * Reads: the sliding window is `maxSize`, the window every graph passes for
28
+ * the layers it gives this cache. Decode writes in place at the ring head and
29
+ * attends the whole ring with no mask (eviction enforces the window). A window
30
+ * writes by concatenation, in temporal order, under the bottom-right causal
31
+ * matrix with the window kernel, or `createCausalMask` with the sliding window
32
+ * and the masked-window kernel once the window binds. The kernels are fixed at
33
+ * construction. Today the graphs' `updateAndFetchQuantized` picks the in-place
34
+ * write for one position and `quantizedSdpa` picks the unfused port for one
35
+ * query, so a one-row window concatenates here (the same keys, in another
36
+ * order once the ring wraps) and takes the window kernel. */
37
+ export class RotatingQuantizedKVCache implements CompiledDecodeCache, CommittedAttentionCache, BlockAttentionCache {
38
+ get quantizedAttention(): QuantizedAttentionState { return this; }
39
+ declare minimumReusableOffset?: number;
40
+ static readonly STEP = 256;
41
+ /** Set only by compiled-decode trace adapters (see Cache). */
42
+ readonly ropeOffsetArr?: MlxArray;
43
+ keys: ops.QuantizedTensor | null = null;
44
+ values: ops.QuantizedTensor | null = null;
45
+ offset = 0;
46
+ /** Ring write index (oracle `_idx`); public so toQuantized replay
47
+ * and persistence can carry it. */
48
+ ringIdx = 0;
49
+ readonly maxSize: number;
50
+
51
+ constructor(
52
+ maxSize: number,
53
+ readonly groupSize: number,
54
+ readonly bits: number,
55
+ /** The kernel of each read. */
56
+ readonly kernels: AffineKernels,
57
+ /** The per-forward masks of the model this cache belongs to. Row layouts
58
+ * made from this cache (`state/layout`) share them. */
59
+ readonly masks = new AttentionMasks(),
60
+ ) {
61
+ this.maxSize = maxSize;
62
+ }
63
+
64
+ /** The kernel set affine storage restored at this cache's layer reads with
65
+ * (`state/persistence`): this cache's own kernels. */
66
+ get kernelSet(): AffineKernelSet { return () => this.kernels; }
67
+
68
+ signature(): string { return `kv:rotating-quant:${this.bits}:${this.groupSize}`; }
69
+
70
+ appendDecode(k: MlxArray, v: MlxArray): AttentionRead {
71
+ const [keys, values] = this.#updateInPlace(k, v);
72
+ return affineRead(keys, values, unmaskedLease, this.kernels.decode);
73
+ }
74
+
75
+ /** The mask is `makeMask`'s with the window at `maxSize`, before the append:
76
+ * plain causal while the window has room, the window-bound array after. */
77
+ appendWindow(k: MlxArray, v: MlxArray): AttentionRead {
78
+ const N = k.shape[2]!, offset = Math.min(this.maxSize - 1, this.offset);
79
+ return offset + N <= this.maxSize
80
+ ? this.#windowRead(k, v, affineCausalLease(this.masks, N, offset + N), this.kernels.window)
81
+ : this.#windowRead(k, v, this.masks.lease(`rotating:${this.maxSize}`, `${N}:${offset}`,
82
+ () => ({ mode: "array", arr: createCausalMask(N, offset, this.maxSize) })), this.kernels.maskedWindow);
83
+ }
84
+
85
+ /** A committed span, one decode per position (`decodeByPosition`): the
86
+ * in-place ring write and the decode kernel each position would get. */
87
+ appendCommitted(k: MlxArray, v: MlxArray): AttentionRead {
88
+ return decodeByPosition(this, k, v);
89
+ }
90
+
91
+ /** A block read (`BlockAttentionCache.readBlock`) as `RotatingKVCache`
92
+ * makes it: the live window in temporal order, cut to the newest
93
+ * `maxSize - 1` positions once it is full, then the block's keys and values
94
+ * quantized as this ring stores them and not appended, under the same mask
95
+ * (`rotatingBlockMask`), by the masked-window kernel. */
96
+ readBlock(k: MlxArray, v: MlxArray): AttentionRead {
97
+ let [storedK, storedV] = this.#temporalView();
98
+ const window = Math.max(this.maxSize - 1, 0);
99
+ const storedLength = storedK.packed.shape[2]!;
100
+ const L = k.shape[2]!;
101
+ const mask = storedLength <= window ? unmaskedLease : rotatingBlockMask(this.masks, this.maxSize, this.offset, L, storedLength);
102
+ let maskArr = mask.mask.arr;
103
+ const held: ops.QuantizedTensor[] = [storedK, storedV];
104
+ const blockK = ops.quantize(k, this.groupSize, this.bits), blockV = ops.quantize(v, this.groupSize, this.bits);
105
+ held.push(blockK, blockV);
106
+ try {
107
+ if (window && storedLength > window && this.offset >= storedLength) {
108
+ const newest = (a: MlxArray) => a.slice([0, 0, storedLength - window, 0], a.shape);
109
+ storedK = mapTriple(storedK, newest); held.push(storedK);
110
+ storedV = mapTriple(storedV, newest); held.push(storedV);
111
+ if (maskArr) {
112
+ const columns = maskArr.shape;
113
+ maskArr = maskArr.slice([0, 0, 0, columns[3]! - (window + L)], columns);
114
+ }
115
+ }
116
+ const keys = concatTriples(storedK, blockK);
117
+ let values: ops.QuantizedTensor;
118
+ try { values = concatTriples(storedV, blockV); }
119
+ catch (error) { disposeTriple(keys); throw error; }
120
+ const sliced = maskArr !== mask.mask.arr ? maskArr : null;
121
+ const read: Mask = maskArr ? { mode: "array", arr: maskArr } : mask.mask;
122
+ return {
123
+ attend: (q, scale) => this.kernels.maskedWindow(q, keys, values, scale, read),
124
+ dispose() { disposeTriple(keys); disposeTriple(values); sliced?.dispose(); mask.release(); },
125
+ };
126
+ } catch (error) {
127
+ if (maskArr !== mask.mask.arr) maskArr?.dispose();
128
+ mask.release();
129
+ throw error;
130
+ } finally { for (const t of held) disposeTriple(t); }
131
+ }
132
+
133
+ /** The live window in temporal order: the newest min(offset, maxSize)
134
+ * positions (`RotatingKVCache.temporalView`). The caller owns both. */
135
+ #temporalView(): [ops.QuantizedTensor, ops.QuantizedTensor] {
136
+ if (!this.keys || !this.values) throw new Error("cache is empty");
137
+ const valid = Math.min(this.offset, this.maxSize);
138
+ const newest = (t: ops.QuantizedTensor): ops.QuantizedTensor => {
139
+ const ordered = this.#temporalOrder(t);
140
+ try {
141
+ return mapTriple(ordered, a => {
142
+ const [B, H, S, D] = a.shape as [number, number, number, number];
143
+ return a.slice([0, 0, Math.max(0, S - valid), 0], [B, H, S, D]);
144
+ });
145
+ } finally { disposeTriple(ordered); }
146
+ };
147
+ const keys = newest(this.keys);
148
+ try { return [keys, newest(this.values)]; }
149
+ catch (error) { disposeTriple(keys); throw error; }
150
+ }
151
+
152
+ #windowRead(k: MlxArray, v: MlxArray, mask: MaskLease, kernel: AffineKernel): AttentionRead {
153
+ return withLease(mask, held => {
154
+ const [keys, values] = this.#updateConcat(k, v);
155
+ return affineRead(keys, values, held, kernel);
156
+ });
157
+ }
158
+
159
+ bytesPerToken(): number {
160
+ const steps = this.keys?.packed.shape[2] ?? 0;
161
+ if (steps === 0 || !this.keys || !this.values) return 0;
162
+ const tensors = [
163
+ this.keys.packed, this.keys.scales, this.keys.biases,
164
+ this.values.packed, this.values.scales, this.values.biases,
165
+ ];
166
+ return tensors.reduce((bytes, array) => bytes + array.nbytes, 0) / steps;
167
+ }
168
+
169
+ /** The dense fetch: appends, then the stored ring (as the affine fetch
170
+ * hands it out) dequantized. */
171
+ updateAndFetch(k: MlxArray, v: MlxArray): [MlxArray, MlxArray] {
172
+ return dequantizedFetch(this.updateAndFetchQuantized(k, v), this.groupSize, this.bits);
173
+ }
174
+
175
+ #seqLen(): number {
176
+ return this.keys ? this.keys.packed.shape[2]! : 0;
177
+ }
178
+
179
+ /** Empty (packed, scales, biases) triple of T tokens (oracle _alloc_pair). */
180
+ #allocPair(B: number, H: number, T: number, dim: number, dtype: Dtype): ops.QuantizedTensor {
181
+ const elPerInt = 32 / this.bits;
182
+ return {
183
+ packed: ops.zeros([B, H, T, dim / elPerInt], Dtype.uint32),
184
+ scales: ops.zeros([B, H, T, dim / this.groupSize], dtype),
185
+ biases: ops.zeros([B, H, T, dim / this.groupSize], dtype),
186
+ };
187
+ }
188
+
189
+ /** Ring contents rearranged into temporal order, per component (keep=0). */
190
+ #temporalOrder(t: ops.QuantizedTensor): ops.QuantizedTensor {
191
+ const S = t.packed.shape[2]!;
192
+ const cut = (a: MlxArray, from: number, to: number): MlxArray => {
193
+ const [B, H, , D] = a.shape as [number, number, number, number];
194
+ return a.slice([0, 0, from, 0], [B, H, to, D]);
195
+ };
196
+ if (this.ringIdx === S) return mapTriple(t, (a) => cut(a, 0, S));
197
+ if (this.ringIdx < this.offset) {
198
+ return mapTriple(t, (a) => {
199
+ const tail = cut(a, this.ringIdx, S);
200
+ const head = cut(a, 0, this.ringIdx);
201
+ const out = ops.concatAxis([tail, head], 2);
202
+ tail.dispose();
203
+ head.dispose();
204
+ return out;
205
+ });
206
+ }
207
+ return mapTriple(t, (a) => cut(a, 0, this.ringIdx));
208
+ }
209
+
210
+ #trim(trimSize: number, t: ops.QuantizedTensor, append: ops.QuantizedTensor | null): ops.QuantizedTensor {
211
+ const part = (a: MlxArray, ap: MlxArray | null): MlxArray => {
212
+ const [B, H, S, D] = a.shape as [number, number, number, number];
213
+ const base = a.slice([0, 0, trimSize > 0 ? trimSize : 0, 0], [B, H, S, D]);
214
+ if (!ap) return base;
215
+ const out = ops.concatAxis([base, ap], 2);
216
+ base.dispose();
217
+ return out;
218
+ };
219
+ return {
220
+ packed: part(t.packed, append?.packed ?? null),
221
+ scales: part(t.scales, append?.scales ?? null),
222
+ biases: part(t.biases, append?.biases ?? null),
223
+ };
224
+ }
225
+
226
+ #updateConcat(k: MlxArray, v: MlxArray): [ops.QuantizedTensor, ops.QuantizedTensor] {
227
+ const S = k.shape[2]!;
228
+ const kq = ops.quantize(k, this.groupSize, this.bits);
229
+ const vq = ops.quantize(v, this.groupSize, this.bits);
230
+ if (!this.keys || !this.values) {
231
+ this.keys = kq;
232
+ this.values = vq;
233
+ } else {
234
+ const tk = this.#temporalOrder(this.keys);
235
+ const tv = this.#temporalOrder(this.values);
236
+ disposeTriple(this.keys);
237
+ disposeTriple(this.values);
238
+ this.ringIdx = tk.packed.shape[2]!;
239
+ const trimSize = this.ringIdx - this.maxSize + 1;
240
+ this.keys = this.#trim(trimSize, tk, kq);
241
+ this.values = this.#trim(trimSize, tv, vq);
242
+ for (const t of [tk, tv, kq, vq]) disposeTriple(t);
243
+ }
244
+ this.offset += S;
245
+ this.ringIdx = this.#seqLen();
246
+ return this.#activeSlices();
247
+ }
248
+
249
+ #updateInPlace(k: MlxArray, v: MlxArray): [ops.QuantizedTensor, ops.QuantizedTensor] {
250
+ const [B, H, S, D] = k.shape as [number, number, number, number];
251
+ const vD = v.shape[3]!;
252
+ const prev = this.offset;
253
+
254
+ if (!this.keys || (prev >= this.#seqLen() && this.#seqLen() < this.maxSize)) {
255
+ const newSize = Math.min(RotatingQuantizedKVCache.STEP, this.maxSize - prev);
256
+ const newK = this.#allocPair(B, H, newSize, D, k.dtype);
257
+ const newV = this.#allocPair(B, H, newSize, vD, v.dtype);
258
+ if (this.keys && this.values) {
259
+ const grow = (old: ops.QuantizedTensor, add: ops.QuantizedTensor): ops.QuantizedTensor => {
260
+ const cat = (a: MlxArray, b: MlxArray): MlxArray => {
261
+ const out = ops.concatAxis([a, b], 2);
262
+ a.dispose();
263
+ b.dispose();
264
+ return out;
265
+ };
266
+ return {
267
+ packed: cat(old.packed, add.packed),
268
+ scales: cat(old.scales, add.scales),
269
+ biases: cat(old.biases, add.biases),
270
+ };
271
+ };
272
+ this.keys = grow(this.keys, newK);
273
+ this.values = grow(this.values, newV);
274
+ } else {
275
+ this.keys = newK;
276
+ this.values = newV;
277
+ }
278
+ this.ringIdx = prev;
279
+ }
280
+
281
+ const trimSize = this.#seqLen() - this.maxSize;
282
+ if (trimSize > 0) {
283
+ const tk = this.#trim(trimSize, this.keys!, null);
284
+ const tv = this.#trim(trimSize, this.values!, null);
285
+ disposeTriple(this.keys!);
286
+ disposeTriple(this.values!);
287
+ this.keys = tk;
288
+ this.values = tv;
289
+ this.ringIdx = this.maxSize;
290
+ }
291
+
292
+ if (this.ringIdx === this.maxSize) this.ringIdx = 0; // rotate (keep=0)
293
+
294
+ const kq = ops.quantize(k, this.groupSize, this.bits);
295
+ const vq = ops.quantize(v, this.groupSize, this.bits);
296
+ const writeAt = (dst: ops.QuantizedTensor, src: ops.QuantizedTensor): ops.QuantizedTensor => ({
297
+ packed: this.#assign(dst.packed, src.packed, S),
298
+ scales: this.#assign(dst.scales, src.scales, S),
299
+ biases: this.#assign(dst.biases, src.biases, S),
300
+ });
301
+ this.keys = writeAt(this.keys!, kq);
302
+ this.values = writeAt(this.values!, vq);
303
+ disposeTriple(kq);
304
+ disposeTriple(vq);
305
+
306
+ this.offset += S;
307
+ this.ringIdx += S;
308
+ return this.#activeSlices();
309
+ }
310
+
311
+ #assign(dst: MlxArray, src: MlxArray, S: number): MlxArray {
312
+ const [B, H, , D] = dst.shape as [number, number, number, number];
313
+ const out = ops.sliceUpdate(dst, src, [0, 0, this.ringIdx, 0], [B, H, this.ringIdx + S, D]);
314
+ dst.dispose();
315
+ return out;
316
+ }
317
+
318
+ /** Active window as quantized triples (oracle _active_slices; fresh
319
+ * view handles so callers own what they dispose). */
320
+ #activeSlices(): [ops.QuantizedTensor, ops.QuantizedTensor] {
321
+ const upTo = this.offset < this.maxSize ? this.offset : this.#seqLen();
322
+ const cut = (a: MlxArray): MlxArray => {
323
+ const [B, H, , D] = a.shape as [number, number, number, number];
324
+ return a.slice([0, 0, 0, 0], [B, H, upTo, D]);
325
+ };
326
+ return [mapTriple(this.keys!, cut), mapTriple(this.values!, cut)];
327
+ }
328
+
329
+ /** Quantize incoming k/v and write into the ring; returns active
330
+ * quantized triples (S=1 in place, S>1 via temporal-order + concat). */
331
+ updateAndFetchQuantized(k: MlxArray, v: MlxArray): [ops.QuantizedTensor, ops.QuantizedTensor] {
332
+ return k.shape[2]! === 1 ? this.#updateInPlace(k, v) : this.#updateConcat(k, v);
333
+ }
334
+
335
+ /** Same mask formula as RotatingKVCache (inherited in the oracle). */
336
+ makeMask(N: number, windowSize: number | null): Mask {
337
+ const window = windowSize ?? this.maxSize;
338
+ if (N > 1) {
339
+ const offset = Math.min(this.maxSize - 1, this.offset);
340
+ if (offset + N > window)
341
+ return { mode: "array", arr: createCausalMask(N, offset, window) };
342
+ return { mode: "causal", arr: null };
343
+ }
344
+ return { mode: "", arr: null };
345
+ }
346
+
347
+ state(): MlxArray[] {
348
+ if (!this.keys || !this.values) return [];
349
+ return [
350
+ this.keys.packed, this.keys.scales, this.keys.biases,
351
+ this.values.packed, this.values.scales, this.values.biases,
352
+ ];
353
+ }
354
+
355
+ /** Ring rule (inherited semantics): trimmable only before wrap. */
356
+ isTrimmable(): boolean {
357
+ return this.offset < this.maxSize;
358
+ }
359
+
360
+ trim(n: number, bypass = false): void {
361
+ const k = Math.min(this.offset, n);
362
+ if (!bypass) {
363
+ this.offset -= k;
364
+ this.ringIdx -= k;
365
+ return;
366
+ }
367
+ // Spec-decode rollback after a >1 concat write — physically slice the last k
368
+ // off each (packed, scales, biases) component and keep ringIdx === seqLen.
369
+ this.offset -= k;
370
+ if (k > 0 && this.keys && this.values) {
371
+ const S = this.#seqLen();
372
+ const cut = (a: MlxArray): MlxArray => {
373
+ const [B, H, , D] = a.shape as [number, number, number, number];
374
+ return a.slice([0, 0, 0, 0], [B, H, S - k, D]);
375
+ };
376
+ const nk = mapTriple(this.keys, cut);
377
+ const nv = mapTriple(this.values, cut);
378
+ disposeTriple(this.keys);
379
+ disposeTriple(this.values);
380
+ this.keys = nk;
381
+ this.values = nv;
382
+ this.ringIdx = this.#seqLen();
383
+ } else {
384
+ this.ringIdx -= k;
385
+ }
386
+ }
387
+
388
+ /** Adopt persisted state (takes ownership of the triples' arrays) —
389
+ * ring order as-laid-out, ringIdx carried with it
390
+ * (packages/inference/src/state/persistence.ts). */
391
+ restoreState(keys: ops.QuantizedTensor, values: ops.QuantizedTensor, offset: number, idx: number): void {
392
+ this.dispose();
393
+ this.keys = keys;
394
+ this.values = values;
395
+ this.offset = offset;
396
+ this.ringIdx = idx;
397
+ }
398
+
399
+ /** The live window as donor attention (see temporalView). Caller owns the returned views. */
400
+ captureDonorAttention(): KvDonorAttention {
401
+ const [keys, values] = this.temporalView();
402
+ const B = keys.packed.shape[0]!, width = keys.packed.shape[2]!;
403
+ return { width, dtype: keys.scales.dtype, offsets: Array(B).fill(this.offset),
404
+ starts: Array(B).fill(0), ends: Array(B).fill(width),
405
+ attend: (q, scale, mask) => viewAffineKernel(this.kernels, q.shape[2]!, mask)(q, keys, values, scale, mask),
406
+ dispose() { disposeTriple(keys); disposeTriple(values); },
407
+ };
408
+ }
409
+
410
+ /** The live window in chronological order: the newest min(offset, maxSize)
411
+ * positions, the quantized twin of RotatingKVCache.temporalView (the tail
412
+ * of an oversized multi-token block, never its head). */
413
+ temporalView(): [ops.QuantizedTensor, ops.QuantizedTensor] {
414
+ if (!this.keys || !this.values) throw new Error("cache is empty");
415
+ const valid = Math.min(this.offset, this.maxSize);
416
+ const cutValid = (t: ops.QuantizedTensor): ops.QuantizedTensor =>
417
+ mapTriple(t, (a) => {
418
+ const [B, H, S, D] = a.shape as [number, number, number, number];
419
+ return a.slice([0, 0, Math.max(0, S - valid), 0], [B, H, S, D]);
420
+ });
421
+ const tk = this.#temporalOrder(this.keys);
422
+ const tv = this.#temporalOrder(this.values);
423
+ const out: [ops.QuantizedTensor, ops.QuantizedTensor] = [cutValid(tk), cutValid(tv)];
424
+ disposeTriple(tk);
425
+ disposeTriple(tv);
426
+ return out;
427
+ }
428
+
429
+ /** Oracle: to_quantized on an already-quantized rotating cache is
430
+ * idempotent. */
431
+ toQuantized(): RotatingQuantizedKVCache {
432
+ return this;
433
+ }
434
+
435
+ /** Compiled decode: host-side bookkeeping of #updateInPlace (L=1) —
436
+ * growth, oversize trim, rotation — without quantize/write. */
437
+ prepareDecodeStep(): DecodeStepPlan {
438
+ const prev = this.offset;
439
+ if (!this.keys || !this.values) throw new Error("compiled decode on an empty cache");
440
+ if (prev >= this.#seqLen() && this.#seqLen() < this.maxSize) {
441
+ const [B, H, , pD] = this.keys.packed.shape as [number, number, number, number];
442
+ const elPerInt = 32 / this.bits;
443
+ const kD = pD * elPerInt;
444
+ const vD = this.values.packed.shape[3]! * elPerInt;
445
+ const dtype = this.keys.scales.dtype;
446
+ const newSize = Math.min(RotatingQuantizedKVCache.STEP, this.maxSize - prev);
447
+ const newK = this.#allocPair(B, H, newSize, kD, dtype);
448
+ const newV = this.#allocPair(B, H, newSize, vD, dtype);
449
+ const grow = (old: ops.QuantizedTensor, add: ops.QuantizedTensor): ops.QuantizedTensor => {
450
+ const cat = (a: MlxArray, b: MlxArray): MlxArray => {
451
+ const out = ops.concatAxis([a, b], 2);
452
+ a.dispose();
453
+ b.dispose();
454
+ return out;
455
+ };
456
+ return {
457
+ packed: cat(old.packed, add.packed),
458
+ scales: cat(old.scales, add.scales),
459
+ biases: cat(old.biases, add.biases),
460
+ };
461
+ };
462
+ this.keys = grow(this.keys, newK);
463
+ this.values = grow(this.values, newV);
464
+ this.ringIdx = prev;
465
+ }
466
+ const trimSize = this.#seqLen() - this.maxSize;
467
+ if (trimSize > 0) {
468
+ const tk = this.#trim(trimSize, this.keys, null);
469
+ const tv = this.#trim(trimSize, this.values, null);
470
+ disposeTriple(this.keys);
471
+ disposeTriple(this.values);
472
+ this.keys = tk;
473
+ this.values = tv;
474
+ this.ringIdx = this.maxSize;
475
+ }
476
+ if (this.ringIdx === this.maxSize) this.ringIdx = 0; // rotate (keep=0)
477
+ const fetch = prev + 1 < this.maxSize && this.ringIdx === prev ? "concat" : "ring";
478
+ return { fetch, writePos: this.ringIdx, activeLen: prev };
479
+ }
480
+
481
+ /** Non-mutating twin of prepareDecodeStep's fetch choice (see RotatingKVCache). */
482
+ decodePhase(): DecodeStepPlan["fetch"] {
483
+ return this.offset + 1 < this.maxSize ? "concat" : "ring";
484
+ }
485
+
486
+ decodeSlot(plan: DecodeStepPlan): DecodeSlot {
487
+ return plan.fetch === "concat"
488
+ ? quantConcatDecodeSlot(this.offset, this.groupSize, this.bits)
489
+ : quantRingDecodeSlot(this.offset, this.maxSize, this.groupSize, this.bits);
490
+ }
491
+
492
+ decodeInputs(plan: DecodeStepPlan, step: DecodeStepInputs): MlxArray[] {
493
+ if (plan.fetch === "concat") return activeQuantViews(this.keys!, this.values!, plan.activeLen, step);
494
+ return [...this.state(), step.writePosition(plan.writePos)];
495
+ }
496
+
497
+ commitDecodeStep(slot: DecodeSlot, outputs: MlxArray[]): MlxArray[] {
498
+ // see RotatingKVCache.commitDecodeStep
499
+ if (slot.fetch === "ring") { this.adoptDecodeStep(outputs); return []; }
500
+ return this.writeDecodeStep(outputs);
501
+ }
502
+
503
+ /** Compiled decode, concat fetch: six sliceUpdates of the in-graph-
504
+ * quantized step row. Takes ownership; returns updated buffers. */
505
+ writeDecodeStep(rows: MlxArray[]): MlxArray[] {
506
+ const w = (d: MlxArray, srcRow: MlxArray): MlxArray => {
507
+ const [b, h, , dd] = d.shape as [number, number, number, number];
508
+ const u = ops.sliceUpdate(d, srcRow, [0, 0, this.ringIdx, 0], [b, h, this.ringIdx + 1, dd]);
509
+ d.dispose();
510
+ srcRow.dispose();
511
+ return u;
512
+ };
513
+ this.keys = {
514
+ packed: w(this.keys!.packed, rows[0]!),
515
+ scales: w(this.keys!.scales, rows[1]!),
516
+ biases: w(this.keys!.biases, rows[2]!),
517
+ };
518
+ this.values = {
519
+ packed: w(this.values!.packed, rows[3]!),
520
+ scales: w(this.values!.scales, rows[4]!),
521
+ biases: w(this.values!.biases, rows[5]!),
522
+ };
523
+ this.offset += 1;
524
+ this.ringIdx += 1;
525
+ return this.state();
526
+ }
527
+
528
+ /** Compiled decode, ring fetch: adopt the six in-graph-updated buffers
529
+ * (closure outputs, in state() order; ownership transfers here). */
530
+ adoptDecodeStep(bufs: MlxArray[]): MlxArray[] {
531
+ disposeTriple(this.keys!);
532
+ disposeTriple(this.values!);
533
+ this.keys = { packed: bufs[0]!, scales: bufs[1]!, biases: bufs[2]! };
534
+ this.values = { packed: bufs[3]!, scales: bufs[4]!, biases: bufs[5]! };
535
+ this.offset += 1;
536
+ this.ringIdx += 1;
537
+ return bufs;
538
+ }
539
+
540
+ dispose(): void {
541
+ if (this.keys) disposeTriple(this.keys);
542
+ if (this.values) disposeTriple(this.values);
543
+ this.keys = this.values = null;
544
+ this.offset = 0;
545
+ this.ringIdx = 0;
546
+ this.masks.clear();
547
+ }
548
+ }
549
+
550
+ /** The rotating quantized cache at steady state inside a compiled decode trace:
551
+ * quantize plus six dynamic writes in graph, fetch the full updated buffers
552
+ * (mirrors the oracle's _update_in_place -> _active_slices). */
553
+ class TracedRingQuantizedKVCache extends RotatingQuantizedKVCache implements DecodeTrace {
554
+ override readonly ropeOffsetArr: MlxArray;
555
+ outs: MlxArray[] = [];
556
+ constructor(
557
+ offset: number, maxSize: number, groupSize: number, bits: number,
558
+ readonly bufKq: ops.QuantizedTensor,
559
+ readonly bufVq: ops.QuantizedTensor,
560
+ readonly writePosArr: MlxArray,
561
+ ropeOffsetArr: MlxArray,
562
+ ) {
563
+ // A compiled step is one-token decode, unfused in every composition.
564
+ super(maxSize, groupSize, bits, unfusedAffineKernels(bits, groupSize, Dtype.bfloat16));
565
+ this.offset = offset;
566
+ this.ropeOffsetArr = ropeOffsetArr;
567
+ }
568
+
569
+ override updateAndFetchQuantized(k: MlxArray, v: MlxArray): [ops.QuantizedTensor, ops.QuantizedTensor] {
570
+ const kq = ops.quantize(k, this.groupSize, this.bits);
571
+ const vq = ops.quantize(v, this.groupSize, this.bits);
572
+ const upd = (buf: ops.QuantizedTensor, row: ops.QuantizedTensor): ops.QuantizedTensor => ({
573
+ packed: ops.sliceUpdateDynamic(buf.packed, row.packed, this.writePosArr, [2]),
574
+ scales: ops.sliceUpdateDynamic(buf.scales, row.scales, this.writePosArr, [2]),
575
+ biases: ops.sliceUpdateDynamic(buf.biases, row.biases, this.writePosArr, [2]),
576
+ });
577
+ const updK = upd(this.bufKq, kq);
578
+ const updV = upd(this.bufVq, vq);
579
+ for (const t of [kq, vq])
580
+ for (const a of [t.packed, t.scales, t.biases]) a.dispose();
581
+ this.outs = [updK.packed, updK.scales, updK.biases, updV.packed, updV.scales, updV.biases];
582
+ const whole = (a: MlxArray): MlxArray => a.slice(a.shape.map(() => 0), a.shape);
583
+ return [
584
+ { packed: whole(updK.packed), scales: whole(updK.scales), biases: whole(updK.biases) },
585
+ { packed: whole(updV.packed), scales: whole(updV.scales), biases: whole(updV.biases) },
586
+ ];
587
+ }
588
+
589
+ /** The decode read over the in-graph write: the whole updated ring with no
590
+ * mask and the decode kernel, as `RotatingQuantizedKVCache.appendDecode`
591
+ * reads the ring it writes in place. */
592
+ override appendDecode(k: MlxArray, v: MlxArray): AttentionRead {
593
+ const [keys, values] = this.updateAndFetchQuantized(k, v);
594
+ return affineRead(keys, values, unmaskedLease, this.kernels.decode);
595
+ }
596
+ }
597
+
598
+ /** `a` then `b` along the sequence axis, per component; the caller owns the result. */
599
+ function concatTriples(a: ops.QuantizedTensor, b: ops.QuantizedTensor): ops.QuantizedTensor {
600
+ const packed = ops.concatAxis([a.packed, b.packed], 2);
601
+ try {
602
+ const scales = ops.concatAxis([a.scales, b.scales], 2);
603
+ try { return { packed, scales, biases: ops.concatAxis([a.biases, b.biases], 2) }; }
604
+ catch (error) { scales.dispose(); throw error; }
605
+ } catch (error) { packed.dispose(); throw error; }
606
+ }
607
+
608
+ function quantRingDecodeSlot(offset: number, maxSize: number, groupSize: number, bits: number): DecodeSlot {
609
+ const triple = (inputs: readonly MlxArray[], at: number): ops.QuantizedTensor =>
610
+ ({ packed: inputs[at]!, scales: inputs[at + 1]!, biases: inputs[at + 2]! });
611
+ return {
612
+ key: `q-ring:${groupSize}:${bits}`, fetch: "ring", inputs: 7, outputs: 6,
613
+ trace: (inputs, ropeOffset) =>
614
+ new TracedRingQuantizedKVCache(offset, maxSize, groupSize, bits, triple(inputs, 0), triple(inputs, 3), inputs[6]!, ropeOffset),
615
+ };
616
+ }
@@ -0,0 +1,35 @@
1
+ import * as ops from "@mlx-bun/mlx/ops";
2
+ import { BatchedRotatingCache } from "./batched-rotating";
3
+ import { BatchedRotatingQuantCache } from "./batched-rotating-quant";
4
+ import { rotatingRollbackPlan } from "./batched-rotating-state";
5
+ import { plainRowStorage, quantizedRowStorage, rowRollIndices } from "./batched-row-storage";
6
+
7
+ export type RotatingRing = BatchedRotatingCache | BatchedRotatingQuantCache;
8
+
9
+ /** Retain accepted columns without decoding or recomputing the ring. Ownership
10
+ * moves to the returned ring; aligned members preserve their shared rectangle. */
11
+ export function rollbackRotatingRing(ring: BatchedRotatingCache, before: readonly number[], keep: number | readonly number[], preserveColumns?: boolean): BatchedRotatingCache;
12
+ export function rollbackRotatingRing(ring: BatchedRotatingQuantCache, before: readonly number[], keep: number | readonly number[], preserveColumns?: boolean): BatchedRotatingQuantCache;
13
+ export function rollbackRotatingRing(ring: RotatingRing, before: readonly number[], keep: number | readonly number[], preserveColumns?: boolean): RotatingRing;
14
+ export function rollbackRotatingRing(ring: RotatingRing, before: readonly number[],
15
+ keep: number | readonly number[], preserveColumns = false): RotatingRing {
16
+ const plan = rotatingRollbackPlan(ring.positionSnapshot, before, keep, preserveColumns);
17
+ if ("trim" in plan) { ring.trim(plan.trim); ring.releaseRopeArr(); return ring; }
18
+ let next: RotatingRing;
19
+ if (ring instanceof BatchedRotatingQuantCache) {
20
+ using indices = rowRollIndices(ring.keys!.packed.shape[2]!, plan.shifts);
21
+ const keys = quantizedRowStorage.rollRows(ring.keys!, indices);
22
+ let values: ops.QuantizedTensor;
23
+ try { values = quantizedRowStorage.rollRows(ring.values!, indices); }
24
+ catch (error) { quantizedRowStorage.dispose(keys); throw error; }
25
+ next = BatchedRotatingQuantCache.adoptPhysical(keys, values, ring.groupSize, ring.bits, ring.kernels, plan.position, ring.masks);
26
+ } else {
27
+ using indices = rowRollIndices(ring.keys!.shape[2]!, plan.shifts);
28
+ const keys = plainRowStorage.rollRows(ring.keys!, indices);
29
+ let values: import("@mlx-bun/mlx/array").MlxArray;
30
+ try { values = plainRowStorage.rollRows(ring.values!, indices); }
31
+ catch (error) { keys.dispose(); throw error; }
32
+ next = BatchedRotatingCache.adoptPhysical(keys, values, plan.position, ring.masks);
33
+ }
34
+ ring.dispose(); return next;
35
+ }