@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,529 @@
1
+ import { MlxArray } from "@mlx-bun/mlx/array";
2
+ import * as ops from "@mlx-bun/mlx/ops";
3
+ import type { AttentionRead,BidirectionalAttentionCache,BlockAttentionCache,CommittedAttentionCache,CompiledDecodeCache,DecodeSlot,DecodeStepInputs,DecodeStepPlan,DecodeTrace,EncoderAttentionCache,KvDonorRows,Mask } from "../contracts/mlx/cache";
4
+ import { Dtype } from "@mlx-bun/mlx/ffi";
5
+ import { bidirMask, createCausalMask } from "../kernels/attention/masks";
6
+ import { concatDecodeSlot, type AffineKernelSet } from "./kv";
7
+ import { RotatingQuantizedKVCache } from "./rotating-quantized-kv";
8
+ import { AttentionMasks, causalLease, decodeByPosition, encoderWindowMask, rotatingBlockMask, sdpaRead, unmaskedLease, withLease, type MaskLease } from "./attention-read";
9
+
10
+
11
+ /** Rotating (sliding-window) KV cache — port of mlx-lm RotatingKVCache
12
+ * with keep=0 (gemma4's configuration): a ring buffer of max_size
13
+ * entries, so decode attends over at most the window. RoPE offsets use
14
+ * the true position; masks use the buffer-clamped offset.
15
+ *
16
+ * Reads: the sliding window is `maxSize`, the window every graph passes for
17
+ * the layers it gives this cache. Decode writes in place at the ring head and
18
+ * attends the whole ring with no mask (eviction enforces the window). A window
19
+ * writes by concatenation, in temporal order, and attends under the causal
20
+ * mask, materialized with the sliding window once the window binds. Both use
21
+ * the stock fused SDPA, as the graphs do after `updateAndFetch` today, which
22
+ * picks the in-place write for one position and concatenation otherwise. The
23
+ * method picks here, so a one-row window concatenates: the same bits until the
24
+ * ring wraps, a different key order after. */
25
+ export class RotatingKVCache implements CompiledDecodeCache, CommittedAttentionCache, BidirectionalAttentionCache, EncoderAttentionCache, BlockAttentionCache {
26
+ declare minimumReusableOffset?: number;
27
+ static readonly STEP = 256;
28
+ /** Set only by compiled-decode trace adapters (see Cache). */
29
+ readonly ropeOffsetArr?: MlxArray;
30
+ keys: MlxArray | null = null;
31
+ values: MlxArray | null = null;
32
+ offset = 0;
33
+ #idx = 0;
34
+ readonly maxSize: number;
35
+
36
+ constructor(
37
+ maxSize: number,
38
+ /** The per-forward masks of the model this cache belongs to. Row layouts
39
+ * made from this cache (`state/layout`) share them. */
40
+ readonly masks = new AttentionMasks(),
41
+ ) {
42
+ this.maxSize = maxSize;
43
+ }
44
+
45
+ signature(): string { return "kv:rotating-plain"; }
46
+
47
+ appendDecode(k: MlxArray, v: MlxArray): AttentionRead {
48
+ const [keys, values] = this.#updateInPlace(k, v);
49
+ return sdpaRead(keys, values, unmaskedLease);
50
+ }
51
+
52
+ appendWindow(k: MlxArray, v: MlxArray): AttentionRead {
53
+ return withLease(this.#windowMask(k.shape[2]!), mask => {
54
+ const [keys, values] = this.#updateConcat(k, v);
55
+ return sdpaRead(keys, values, mask);
56
+ });
57
+ }
58
+
59
+ /** A committed span, one decode per position (`decodeByPosition`): the
60
+ * in-place ring write and read each position would get. */
61
+ appendCommitted(k: MlxArray, v: MlxArray): AttentionRead {
62
+ return decodeByPosition(this, k, v);
63
+ }
64
+
65
+ /** Gemma 4's vision prefill read on a sliding layer: `bidirMask` with this
66
+ * cache's window over a first window, then the fused SDPA. */
67
+ appendBidirectional(k: MlxArray, v: MlxArray, bidirectional: MlxArray): AttentionRead {
68
+ const L = k.shape[2]!;
69
+ const mask = this.masks.lease(`bidirectional:${this.maxSize}`, `${L}`,
70
+ () => bidirMask(L, this.maxSize, bidirectional), bidirectional);
71
+ return withLease(mask, held => {
72
+ const [keys, values] = this.#updateConcat(k, v);
73
+ return sdpaRead(keys, values, held);
74
+ });
75
+ }
76
+
77
+ /** DiffusionGemma's vision encoder read on a sliding layer, as its encoder
78
+ * computes it: the window's concatenating append (every position kept, even
79
+ * past `maxSize`), then the fused SDPA under the encoder window's mask
80
+ * (`encoderWindowMask`), which this cache's window does not limit. */
81
+ appendEncoderWindow(k: MlxArray, v: MlxArray, bidirectional: MlxArray): AttentionRead {
82
+ return withLease(encoderWindowMask(this.masks, k.shape[2]!, bidirectional), mask => {
83
+ const [keys, values] = this.#updateConcat(k, v);
84
+ return sdpaRead(keys, values, mask);
85
+ });
86
+ }
87
+
88
+ /** DiffusionGemma's canvas read on a sliding layer, as its decoder computes
89
+ * it: the live window in temporal order, cut to the newest `maxSize - 1`
90
+ * positions once it is full, then the block's keys and values. The mask is
91
+ * the decoder's (stored positions past the window start and the whole block),
92
+ * sliced to the kept columns. */
93
+ readBlock(k: MlxArray, v: MlxArray): AttentionRead {
94
+ let [storedK, storedV] = this.temporalView();
95
+ const window = Math.max(this.maxSize - 1, 0);
96
+ const storedLength = storedK.shape[2]!;
97
+ const L = k.shape[2]!;
98
+ const mask = storedLength <= window ? unmaskedLease : this.#blockMask(L, storedLength);
99
+ let maskArr = mask.mask.arr;
100
+ const held: MlxArray[] = [storedK, storedV];
101
+ try {
102
+ if (window && storedLength > window && this.offset >= storedLength) {
103
+ storedK = storedK.slice([0, 0, storedLength - window, 0], storedK.shape); held.push(storedK);
104
+ storedV = storedV.slice([0, 0, storedLength - window, 0], storedV.shape); held.push(storedV);
105
+ if (maskArr) {
106
+ const columns = maskArr.shape;
107
+ maskArr = maskArr.slice([0, 0, 0, columns[3]! - (window + L)], columns);
108
+ }
109
+ }
110
+ const keys = ops.concatAxis([storedK, k], 2);
111
+ let values: MlxArray;
112
+ try { values = ops.concatAxis([storedV, v], 2); }
113
+ catch (error) { keys.dispose(); throw error; }
114
+ const sliced = maskArr !== mask.mask.arr ? maskArr : null;
115
+ const mode = maskArr ? "array" : mask.mask.mode;
116
+ return {
117
+ attend: (q, scale) => ops.sdpa(q, keys, values, scale, mode, maskArr),
118
+ dispose() { keys.dispose(); values.dispose(); sliced?.dispose(); mask.release(); },
119
+ };
120
+ } catch (error) {
121
+ if (maskArr !== mask.mask.arr) maskArr?.dispose();
122
+ mask.release();
123
+ throw error;
124
+ } finally { for (const array of held) array.dispose(); }
125
+ }
126
+
127
+ /** The causal mask of a `N`-position window, before its append. */
128
+ #windowMask(N: number): MaskLease {
129
+ return rotatingWindowMask(this.masks, this.maxSize, this.offset, N);
130
+ }
131
+
132
+ /** DiffusionGemma's sliding-layer decoder mask over `storedLength` stored
133
+ * positions and an `N`-position block (`rotatingBlockMask`). */
134
+ #blockMask(N: number, storedLength: number): MaskLease {
135
+ return rotatingBlockMask(this.masks, this.maxSize, this.offset, N, storedLength);
136
+ }
137
+
138
+ bytesPerToken(): number {
139
+ const steps = this.keys?.shape[2] ?? 0;
140
+ return steps > 0 ? (this.keys!.nbytes + this.values!.nbytes) / steps : 0;
141
+ }
142
+
143
+ /** v with ring contents rearranged into temporal order (keep=0). */
144
+ #temporalOrder(v: MlxArray): MlxArray {
145
+ const S = v.shape[2]!;
146
+ const [B, H, , D] = v.shape as [number, number, number, number];
147
+ if (this.#idx === S) return v.slice([0, 0, 0, 0], [B, H, S, D]);
148
+ if (this.#idx < this.offset) {
149
+ const tail = v.slice([0, 0, this.#idx, 0], [B, H, S, D]);
150
+ const head = v.slice([0, 0, 0, 0], [B, H, this.#idx, D]);
151
+ const out = ops.concatAxis([tail, head], 2);
152
+ tail.dispose();
153
+ head.dispose();
154
+ return out;
155
+ }
156
+ return v.slice([0, 0, 0, 0], [B, H, this.#idx, D]);
157
+ }
158
+
159
+ #trim(trimSize: number, v: MlxArray, append: MlxArray | null): MlxArray {
160
+ const [B, H, S, D] = v.shape as [number, number, number, number];
161
+ let base: MlxArray;
162
+ if (trimSize > 0) {
163
+ base = v.slice([0, 0, trimSize, 0], [B, H, S, D]);
164
+ } else {
165
+ base = v.slice([0, 0, 0, 0], [B, H, S, D]);
166
+ }
167
+ if (!append) return base;
168
+ const out = ops.concatAxis([base, append], 2);
169
+ base.dispose();
170
+ return out;
171
+ }
172
+
173
+ #updateConcat(k: MlxArray, v: MlxArray): [MlxArray, MlxArray] {
174
+ const S = k.shape[2]!;
175
+ if (!this.keys || !this.values) {
176
+ // own copies (full-range slice = cheap view node)
177
+ const [B, H, , D] = k.shape as [number, number, number, number];
178
+ const vD = v.shape[3]!;
179
+ this.keys = k.slice([0, 0, 0, 0], [B, H, S, D]);
180
+ this.values = v.slice([0, 0, 0, 0], [B, H, S, vD]);
181
+ } else {
182
+ const tk = this.#temporalOrder(this.keys);
183
+ const tv = this.#temporalOrder(this.values);
184
+ this.keys.dispose();
185
+ this.values.dispose();
186
+ this.#idx = tk.shape[2]!;
187
+ const trimSize = this.#idx - this.maxSize + 1;
188
+ this.keys = this.#trim(trimSize, tk, k);
189
+ this.values = this.#trim(trimSize, tv, v);
190
+ tk.dispose();
191
+ tv.dispose();
192
+ }
193
+ this.offset += S;
194
+ this.#idx = this.keys.shape[2]!;
195
+ return this.#fetchAll();
196
+ }
197
+
198
+ #updateInPlace(k: MlxArray, v: MlxArray): [MlxArray, MlxArray] {
199
+ const [B, H, S, D] = k.shape as [number, number, number, number];
200
+ const vD = v.shape[3]!;
201
+ const prev = this.offset;
202
+
203
+ if (!this.keys || (prev >= this.keys.shape[2]! && this.keys.shape[2]! < this.maxSize)) {
204
+ const newSize = Math.min(RotatingKVCache.STEP, this.maxSize - prev);
205
+ const newK = ops.zeros([B, H, newSize, D], k.dtype);
206
+ const newV = ops.zeros([B, H, newSize, vD], v.dtype);
207
+ if (this.keys && this.values) {
208
+ const ck = ops.concatAxis([this.keys, newK], 2);
209
+ const cv = ops.concatAxis([this.values, newV], 2);
210
+ this.keys.dispose();
211
+ this.values.dispose();
212
+ newK.dispose();
213
+ newV.dispose();
214
+ this.keys = ck;
215
+ this.values = cv;
216
+ } else {
217
+ this.keys = newK;
218
+ this.values = newV;
219
+ }
220
+ this.#idx = prev;
221
+ }
222
+
223
+ const trimSize = this.keys!.shape[2]! - this.maxSize;
224
+ if (trimSize > 0) {
225
+ const tk = this.#trim(trimSize, this.keys!, null);
226
+ const tv = this.#trim(trimSize, this.values!, null);
227
+ this.keys!.dispose();
228
+ this.values!.dispose();
229
+ this.keys = tk;
230
+ this.values = tv;
231
+ this.#idx = this.maxSize;
232
+ }
233
+
234
+ if (this.#idx === this.maxSize) this.#idx = 0; // rotate (keep=0)
235
+
236
+ const [, , SK, DK] = this.keys!.shape as [number, number, number, number];
237
+ const k2 = ops.sliceUpdate(this.keys!, k, [0, 0, this.#idx, 0], [B, H, this.#idx + S, DK]);
238
+ const v2 = ops.sliceUpdate(this.values!, v, [0, 0, this.#idx, 0], [B, H, this.#idx + S, vD]);
239
+ this.keys!.dispose();
240
+ this.values!.dispose();
241
+ this.keys = k2;
242
+ this.values = v2;
243
+ this.offset += S;
244
+ this.#idx += S;
245
+
246
+ if (this.offset < this.maxSize) {
247
+ const kOut = this.keys.slice([0, 0, 0, 0], [B, H, this.offset, DK]);
248
+ const vOut = this.values.slice([0, 0, 0, 0], [B, H, this.offset, vD]);
249
+ return [kOut, vOut];
250
+ }
251
+ return this.#fetchAll();
252
+ }
253
+
254
+ #fetchAll(): [MlxArray, MlxArray] {
255
+ const [B, H, S, D] = this.keys!.shape as [number, number, number, number];
256
+ const vD = this.values!.shape[3]!;
257
+ return [
258
+ this.keys!.slice([0, 0, 0, 0], [B, H, S, D]),
259
+ this.values!.slice([0, 0, 0, 0], [B, H, S, vD]),
260
+ ];
261
+ }
262
+
263
+ updateAndFetch(k: MlxArray, v: MlxArray): [MlxArray, MlxArray] {
264
+ return k.shape[2]! === 1 ? this.#updateInPlace(k, v) : this.#updateConcat(k, v);
265
+ }
266
+
267
+ makeMask(N: number, windowSize: number | null): Mask {
268
+ const window = windowSize ?? this.maxSize;
269
+ if (N > 1) {
270
+ const offset = Math.min(this.maxSize - 1, this.offset);
271
+ if (offset + N > window)
272
+ return { mode: "array", arr: createCausalMask(N, offset, window) };
273
+ return { mode: "causal", arr: null };
274
+ }
275
+ // N == 1: eviction enforces the window (window === maxSize for gemma4)
276
+ return { mode: "", arr: null };
277
+ }
278
+
279
+ state(): MlxArray[] {
280
+ return this.keys && this.values ? [this.keys, this.values] : [];
281
+ }
282
+
283
+ /** Port of mlx-lm RotatingKVCache.is_trimmable/trim: only valid while
284
+ * the ring has never wrapped (still in temporal order). */
285
+ isTrimmable(): boolean {
286
+ return this.offset < this.maxSize;
287
+ }
288
+
289
+ trim(n: number, bypass = false): void {
290
+ const k = Math.min(this.offset, n);
291
+ if (!bypass) {
292
+ // mlx-lm semantics: only valid pre-wrap (guarded by isTrimmable upstream).
293
+ this.offset -= k;
294
+ this.#idx -= k;
295
+ return;
296
+ }
297
+ // Spec-decode rollback after a >1 #updateConcat write: the buffer is in
298
+ // temporal order with #idx === bufferLen and the newest tokens are the last
299
+ // rows, so physically slice the last k off and keep #idx === new length.
300
+ // (Only sound right after a concat write with k ≤ γ — DSpark's only caller.)
301
+ this.offset -= k;
302
+ if (k > 0 && this.keys && this.values) {
303
+ const [B, H, S, D] = this.keys.shape as [number, number, number, number];
304
+ const vD = this.values.shape[3]!;
305
+ const nk = this.keys.slice([0, 0, 0, 0], [B, H, S - k, D]);
306
+ const nv = this.values.slice([0, 0, 0, 0], [B, H, S - k, vD]);
307
+ this.keys.dispose();
308
+ this.values.dispose();
309
+ this.keys = nk;
310
+ this.values = nv;
311
+ this.#idx = nk.shape[2]!;
312
+ } else {
313
+ this.#idx -= k;
314
+ }
315
+ }
316
+
317
+ get ringIdx(): number {
318
+ return this.#idx;
319
+ }
320
+
321
+ /** Compiled decode: host-side bookkeeping of #updateInPlace (L=1) —
322
+ * growth, oversize trim, rotation — without the write. */
323
+ prepareDecodeStep(): DecodeStepPlan {
324
+ const prev = this.offset;
325
+ if (!this.keys || !this.values) throw new Error("compiled decode on an empty cache");
326
+ if (prev >= this.keys.shape[2]! && this.keys.shape[2]! < this.maxSize) {
327
+ const [B, H, , D] = this.keys.shape as [number, number, number, number];
328
+ const vD = this.values.shape[3]!;
329
+ const newSize = Math.min(RotatingKVCache.STEP, this.maxSize - prev);
330
+ const newK = ops.zeros([B, H, newSize, D], this.keys.dtype);
331
+ const newV = ops.zeros([B, H, newSize, vD], this.values.dtype);
332
+ const ck = ops.concatAxis([this.keys, newK], 2);
333
+ const cv = ops.concatAxis([this.values, newV], 2);
334
+ for (const a of [this.keys, this.values, newK, newV]) a.dispose();
335
+ this.keys = ck;
336
+ this.values = cv;
337
+ this.#idx = prev;
338
+ }
339
+ const trimSize = this.keys.shape[2]! - this.maxSize;
340
+ if (trimSize > 0) {
341
+ const tk = this.#trim(trimSize, this.keys, null);
342
+ const tv = this.#trim(trimSize, this.values, null);
343
+ this.keys.dispose();
344
+ this.values.dispose();
345
+ this.keys = tk;
346
+ this.values = tv;
347
+ this.#idx = this.maxSize;
348
+ }
349
+ if (this.#idx === this.maxSize) this.#idx = 0; // rotate (keep=0)
350
+ // Pre-window-fill the attended set is the [0..offset] prefix plus the
351
+ // new row (today's slice of the updated buffer); once writes wrap,
352
+ // it's the whole ring in ring order, which only the in-graph
353
+ // write-then-read-all form reproduces bit-exactly.
354
+ const fetch = prev + 1 < this.maxSize && this.#idx === prev ? "concat" : "ring";
355
+ return { fetch, writePos: this.#idx, activeLen: prev };
356
+ }
357
+
358
+ /** Non-mutating twin of prepareDecodeStep's fetch choice: the ring runs once
359
+ * the next write lands at or past the window. */
360
+ decodePhase(): DecodeStepPlan["fetch"] {
361
+ return this.offset + 1 < this.maxSize ? "concat" : "ring";
362
+ }
363
+
364
+ decodeSlot(plan: DecodeStepPlan): DecodeSlot {
365
+ return plan.fetch === "concat" ? concatDecodeSlot(this.offset) : ringDecodeSlot(this.offset, this.maxSize);
366
+ }
367
+
368
+ decodeInputs(plan: DecodeStepPlan, step: DecodeStepInputs): MlxArray[] {
369
+ return plan.fetch === "concat"
370
+ ? [step.activeView(this.keys!, plan.activeLen), step.activeView(this.values!, plan.activeLen)]
371
+ : [this.keys!, this.values!, step.writePosition(plan.writePos)];
372
+ }
373
+
374
+ commitDecodeStep(slot: DecodeSlot, outputs: MlxArray[]): MlxArray[] {
375
+ // a ring's updates are ancestors of the logits (the in-graph fetch reads the
376
+ // updated buffer): no explicit eval roots
377
+ if (slot.fetch === "ring") { this.adoptDecodeStep(outputs[0]!, outputs[1]!); return []; }
378
+ return this.writeDecodeStep(outputs[0]!, outputs[1]!);
379
+ }
380
+
381
+ /** Compiled decode, concat fetch: the write half. Takes ownership of
382
+ * kNew/vNew; returns the updated buffers to async-eval. */
383
+ writeDecodeStep(kNew: MlxArray, vNew: MlxArray): MlxArray[] {
384
+ const [B, H, , D] = this.keys!.shape as [number, number, number, number];
385
+ const vD = this.values!.shape[3]!;
386
+ const k2 = ops.sliceUpdate(this.keys!, kNew, [0, 0, this.#idx, 0], [B, H, this.#idx + 1, D]);
387
+ const v2 = ops.sliceUpdate(this.values!, vNew, [0, 0, this.#idx, 0], [B, H, this.#idx + 1, vD]);
388
+ this.keys!.dispose();
389
+ this.values!.dispose();
390
+ kNew.dispose();
391
+ vNew.dispose();
392
+ this.keys = k2;
393
+ this.values = v2;
394
+ this.offset += 1;
395
+ this.#idx += 1;
396
+ return [k2, v2];
397
+ }
398
+
399
+ /** Compiled decode, ring fetch: adopt the in-graph-updated buffers
400
+ * (closure outputs; ownership transfers here). */
401
+ adoptDecodeStep(newKeys: MlxArray, newValues: MlxArray): MlxArray[] {
402
+ this.keys!.dispose();
403
+ this.values!.dispose();
404
+ this.keys = newKeys;
405
+ this.values = newValues;
406
+ this.offset += 1;
407
+ this.#idx += 1;
408
+ return [newKeys, newValues];
409
+ }
410
+
411
+ /** The live window as donor rows (see temporalView). */
412
+ captureDonorRows(): KvDonorRows {
413
+ const [keys, values] = this.temporalView();
414
+ const B = keys.shape[0]!, width = keys.shape[2]!;
415
+ return { keys, values, offsets: Array(B).fill(this.offset),
416
+ starts: Array(B).fill(0), ends: Array(B).fill(width) };
417
+ }
418
+
419
+ /** The live window in chronological order: the newest min(offset, maxSize)
420
+ * positions. A multi-token write leaves the buffer oversized in temporal
421
+ * order (#updateConcat keeps maxSize - 1 history rows plus the whole
422
+ * block), so the window is the tail of that order, never its head. optiq's
423
+ * kv_view._read_cache_temporal takes the head, which is the oldest rows in
424
+ * that state; the batched layouts and rotatingSourcePosition already take
425
+ * the tail. */
426
+ temporalView(): [MlxArray, MlxArray] {
427
+ if (!this.keys || !this.values) throw new Error("cache is empty");
428
+ const tk = this.#temporalOrder(this.keys);
429
+ const tv = this.#temporalOrder(this.values);
430
+ const valid = Math.min(this.offset, this.maxSize);
431
+ const cut = (a: MlxArray): MlxArray => {
432
+ const [B, H, S, D] = a.shape as [number, number, number, number];
433
+ const s = a.slice([0, 0, Math.max(0, S - valid), 0], [B, H, S, D]);
434
+ a.dispose();
435
+ return s;
436
+ };
437
+ return [cut(tk), cut(tv)];
438
+ }
439
+
440
+ /** Adopt persisted state (takes ownership of the arrays). */
441
+ restoreState(keys: MlxArray, values: MlxArray, offset: number, idx: number): void {
442
+ this.dispose();
443
+ this.keys = keys;
444
+ this.values = values;
445
+ this.offset = offset;
446
+ this.#idx = idx;
447
+ }
448
+
449
+ /** Port of optiq rotating.py _replay_into_quantized: quantize the
450
+ * whole buffer AS-LAID-OUT (ring order, not temporal order — correct
451
+ * because ringIdx is preserved with it) into a
452
+ * RotatingQuantizedKVCache, which attends with the kernel set `kernels`
453
+ * builds for its bits, group size and this cache's activation dtype. */
454
+ toQuantized(groupSize: number, bits: number, kernels: AffineKernelSet): RotatingQuantizedKVCache {
455
+ const q = new RotatingQuantizedKVCache(this.maxSize, groupSize, bits,
456
+ kernels(bits, groupSize, this.keys?.dtype ?? Dtype.bfloat16), this.masks);
457
+ if (this.keys && this.values) {
458
+ q.keys = ops.quantize(this.keys, groupSize, bits);
459
+ q.values = ops.quantize(this.values, groupSize, bits);
460
+ }
461
+ q.offset = this.offset;
462
+ q.ringIdx = this.#idx;
463
+ this.dispose();
464
+ return q;
465
+ }
466
+
467
+ dispose(): void {
468
+ this.keys?.dispose();
469
+ this.values?.dispose();
470
+ this.keys = this.values = null;
471
+ this.offset = 0;
472
+ this.#idx = 0;
473
+ this.masks.clear();
474
+ }
475
+ }
476
+
477
+ /** The causal mask of an `N`-position window over a `maxSize` ring holding
478
+ * `offset` positions, before the window's append: the fused kernel's causal
479
+ * mask while the window fits, the materialized sliding-window mask once it
480
+ * binds (`makeMask(N, maxSize)` of the plain and the affine ring). Leased from
481
+ * `masks` under one family, so plain and affine rings of one model share it. */
482
+ export function rotatingWindowMask(masks: AttentionMasks, maxSize: number, offset: number, N: number): MaskLease {
483
+ const clamped = Math.min(maxSize - 1, offset);
484
+ if (clamped + N <= maxSize) return causalLease;
485
+ return masks.lease(`rotating:${maxSize}`, `${N}:${clamped}`,
486
+ () => ({ mode: "array", arr: createCausalMask(N, clamped, maxSize) }));
487
+ }
488
+
489
+ /** The rotating plain cache at steady state inside a compiled decode trace:
490
+ * write in graph at the dynamic ring position, fetch the full updated buffer
491
+ * (mirrors #updateInPlace -> #fetchAll); the updated buffers are closure outputs. */
492
+ class TracedRingKVCache extends RotatingKVCache implements DecodeTrace {
493
+ override readonly ropeOffsetArr: MlxArray;
494
+ outs: MlxArray[] = [];
495
+ constructor(
496
+ offset: number, maxSize: number,
497
+ readonly bufK: MlxArray,
498
+ readonly bufV: MlxArray,
499
+ readonly writePosArr: MlxArray,
500
+ ropeOffsetArr: MlxArray,
501
+ ) {
502
+ super(maxSize);
503
+ this.offset = offset;
504
+ this.ropeOffsetArr = ropeOffsetArr;
505
+ }
506
+
507
+ override updateAndFetch(k: MlxArray, v: MlxArray): [MlxArray, MlxArray] {
508
+ const updK = ops.sliceUpdateDynamic(this.bufK, k, this.writePosArr, [2]);
509
+ const updV = ops.sliceUpdateDynamic(this.bufV, v, this.writePosArr, [2]);
510
+ this.outs = [updK, updV];
511
+ const whole = (a: MlxArray): MlxArray => a.slice(a.shape.map(() => 0), a.shape);
512
+ return [whole(updK), whole(updV)];
513
+ }
514
+
515
+ /** The decode read over the in-graph write: the whole updated ring with no
516
+ * mask, as `RotatingKVCache.appendDecode` reads the ring it writes in place. */
517
+ override appendDecode(k: MlxArray, v: MlxArray): AttentionRead {
518
+ const [keys, values] = this.updateAndFetch(k, v);
519
+ return sdpaRead(keys, values, unmaskedLease);
520
+ }
521
+ }
522
+
523
+ function ringDecodeSlot(offset: number, maxSize: number): DecodeSlot {
524
+ return {
525
+ key: "p-ring:0:0", fetch: "ring", inputs: 3, outputs: 2,
526
+ trace: (inputs, ropeOffset) =>
527
+ new TracedRingKVCache(offset, maxSize, inputs[0]!, inputs[1]!, inputs[2]!, ropeOffset),
528
+ };
529
+ }