@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,46 @@
1
+ import type { MlxArray } from "@mlx-bun/mlx/array";
2
+ import * as ops from "@mlx-bun/mlx/ops";
3
+ import type { Cache } from "../contracts/mlx/cache";
4
+ import { cleanupFailure,disposeResources,withResource } from "../runtime/resources";
5
+ import { leaseCacheStates } from "./leases";
6
+
7
+ export function attachmentBytes(attachments: readonly CheckpointAttachment[] = []): number {
8
+ return attachments.reduce((sum, entry) =>
9
+ sum + entry.tensors.reduce((bytes, tensor) => bytes + tensor.nbytes, 0), 0);
10
+ }
11
+
12
+ export function disposeAttachments(attachments: readonly CheckpointAttachment[] = []): void {
13
+ disposeResources(attachments.flatMap((entry) => entry.tensors));
14
+ }
15
+
16
+ export function cloneAttachments(attachments: readonly CheckpointAttachment[] = []): CheckpointAttachment[] {
17
+ const held: MlxArray[] = [];
18
+ try {
19
+ return attachments.map((entry) => ({
20
+ schema: entry.schema, metadata: { ...entry.metadata },
21
+ tensors: entry.tensors.map((tensor) => {
22
+ const copy = tensor.slice(tensor.shape.map(() => 0), [...tensor.shape]);
23
+ held.push(copy);
24
+ return copy;
25
+ }),
26
+ }));
27
+ } catch (error) { return cleanupFailure(error, () => disposeResources(held)); }
28
+ }
29
+
30
+ /** Shared cache port for MLX execution methods and their companion state. */
31
+ export type MlxPrefixCache = import("../contracts/portable/prefix-cache").PrefixCache<
32
+ import("../contracts/mlx/cache").Cache[], CheckpointAttachment[]
33
+ >;
34
+
35
+ /** Resolve snapshot copies before storage retains them. A deferred compact
36
+ * row still owns its full parent batch; persistence may wait until idle.
37
+ * This only settles GPU state and performs no serialization or disk I/O. */
38
+ export function materializeCheckpoint(caches: Cache[], attachments: readonly CheckpointAttachment[] = []): void {
39
+ withResource(leaseCacheStates(caches), state => {
40
+ const tensors = [...state, ...attachments.flatMap(attachment => attachment.tensors)];
41
+ if (tensors.length) ops.evalAll(tensors);
42
+ });
43
+ }
44
+
45
+ import { CheckpointAttachment } from "../contracts/mlx/checkpoint";
46
+ export { type CheckpointAttachment } from "../contracts/mlx/checkpoint";
@@ -0,0 +1,126 @@
1
+ import { SpillQueue } from "./persistence";
2
+ import { type SpillItem } from "./persistence-types";
3
+ import { cacheBytes } from "./prefix-cache";
4
+ import { disposeResources } from "../runtime/resources";
5
+ import type { Cache } from "../contracts/mlx/cache";
6
+ import type { SsdCacheStore } from "./ssd-cache";
7
+
8
+ /** The saved-state operations generation checkpoints use. */
9
+ export type ContinuationStore = Pick<SsdCacheStore, "findGenerationCheckpoint" | "restore" |
10
+ "storeGenerationCheckpoint" | "removeGenerationCheckpoints">;
11
+
12
+ /** A replayable generation's state: caches covering the prompt and the
13
+ * generated tokens, the token sampled from them but not yet emitted, and the
14
+ * seed its sampler continues from. */
15
+ export interface ContinuationState {
16
+ caches: Cache[];
17
+ cacheTokens: number[];
18
+ generatedTokens: number;
19
+ pendingToken: number;
20
+ seed: number;
21
+ }
22
+
23
+ type Metadata = Parameters<ContinuationStore["storeGenerationCheckpoint"]>[2];
24
+ /** One request's checkpoint writes under its key. */
25
+ export interface ContinuationAttempt { key: string; completed: boolean; pending: number; failed: boolean; version: number; retired: boolean }
26
+ type Attempt = ContinuationAttempt;
27
+ interface Job { attempt: Attempt; metadata?: Metadata; cleanup?: number }
28
+
29
+ /** One coordinator per checkpoint store. Queue owns snapshots through disk
30
+ * completion; per-key attempts order cleanup before subsequent writes. */
31
+ export class ContinuationPersistence {
32
+ readonly #attempts = new Map<string, Attempt>();
33
+ readonly #jobs = new WeakMap<SpillItem["caches"], Job>();
34
+ readonly #queue: SpillQueue;
35
+ readonly #cleanupFailures = new Map<string, Set<number>>();
36
+ readonly #cleanupPending = new Map<string, Set<number>>();
37
+ readonly #cleanupSucceeded = new Map<string, number>();
38
+ #nextBarrier = 0;
39
+ constructor(readonly store: ContinuationStore, options: {
40
+ maxBytes: number;
41
+ /** Optional owner-thread preparation hook; disk writes use the CPU worker. */
42
+ runStep?: <T>(step: () => T) => Promise<T>;
43
+ }) {
44
+ const runStep = options.runStep ?? (async <T>(step: () => T): Promise<T> => step());
45
+ this.#queue = new SpillQueue(options.maxBytes, cacheBytes, async item => {
46
+ const job = this.#jobs.get(item.caches)!;
47
+ if (job.cleanup) {
48
+ await runStep(() => store.removeGenerationCheckpoints(job.attempt.key));
49
+ return true;
50
+ }
51
+ const current = () => this.#attempts.get(job.attempt.key) === job.attempt && !job.attempt.completed;
52
+ if (!current()) return true;
53
+ const stored = await store.storeGenerationCheckpoint(item.tokens, item.caches, job.metadata!, options.runStep);
54
+ // No newer queued write can run until this callback returns. An old
55
+ // in-flight rename can therefore be removed without deleting new work.
56
+ if (!current()) await runStep(() => store.removeGenerationCheckpoints(job.attempt.key));
57
+ return stored;
58
+ }, disposeResources, item => !this.#jobs.get(item.caches)?.cleanup);
59
+ }
60
+ /** A completed response is ineligible immediately, before queued unlink. */
61
+ canRestore(key: string): boolean { return !this.#cleanupPending.has(key) && !this.#cleanupFailures.has(key); }
62
+ begin(key: string): Attempt {
63
+ const attempt = { key, completed: false, pending: 0, failed: false, version: 0, retired: false };
64
+ this.#attempts.set(key, attempt);
65
+ this.#queue.cancelWhere(item => !this.#jobs.get(item.caches)?.cleanup && this.#jobs.get(item.caches)?.attempt.key === key);
66
+ return attempt;
67
+ }
68
+ /** Takes ownership even when a completed/superseded attempt declines it. */
69
+ enqueue(attempt: Attempt, state: Omit<ContinuationState, "seed">, metadata: Metadata): void {
70
+ if (attempt.completed || this.#attempts.get(attempt.key) !== attempt) {
71
+ disposeResources(state.caches); return;
72
+ }
73
+ this.#jobs.set(state.caches, { attempt, metadata: { ...metadata } });
74
+ attempt.pending++;
75
+ const version = ++attempt.version;
76
+ let completion: Promise<boolean>;
77
+ try { completion = this.#queue.enqueue({ caches: state.caches, tokens: [...state.cacheTokens], ns: attempt.key }); }
78
+ catch (error) { attempt.pending--; disposeResources(state.caches); throw error; }
79
+ void completion.then(stored => {
80
+ attempt.pending--;
81
+ if (version === attempt.version && !attempt.completed) attempt.failed = !stored;
82
+ this.#releaseSettled(attempt);
83
+ });
84
+ }
85
+ complete(attempt: Attempt): void {
86
+ attempt.completed = true;
87
+ this.#queue.cancelWhere(item => !this.#jobs.get(item.caches)?.cleanup && this.#jobs.get(item.caches)?.attempt === attempt);
88
+ if (this.#attempts.get(attempt.key) === attempt) {
89
+ this.#attempts.delete(attempt.key);
90
+ const barrier = ++this.#nextBarrier;
91
+ const pending = this.#cleanupPending.get(attempt.key) ?? new Set<number>();
92
+ pending.add(barrier); this.#cleanupPending.set(attempt.key, pending);
93
+ const caches: SpillItem["caches"] = [];
94
+ this.#jobs.set(caches, { attempt, cleanup: barrier });
95
+ void this.#queue.enqueue({ caches, tokens: [], ns: attempt.key }).then(stored => {
96
+ const key = attempt.key;
97
+ const outstanding = this.#cleanupPending.get(key)!;
98
+ outstanding.delete(barrier);
99
+ if (!outstanding.size) this.#cleanupPending.delete(key);
100
+ const failures = this.#cleanupFailures.get(key) ?? new Set<number>();
101
+ if (stored) {
102
+ this.#cleanupSucceeded.set(key, Math.max(barrier, this.#cleanupSucceeded.get(key) ?? 0));
103
+ // A successful unlink covers older failures, never a newer barrier.
104
+ for (const failed of failures) if (failed <= barrier) failures.delete(failed);
105
+ } else if (barrier > (this.#cleanupSucceeded.get(key) ?? 0)) failures.add(barrier);
106
+ if (failures.size) this.#cleanupFailures.set(key, failures);
107
+ else this.#cleanupFailures.delete(key);
108
+ if (!outstanding.size) this.#cleanupSucceeded.delete(key);
109
+ });
110
+ }
111
+ }
112
+ /** Request cancellation/disposal preserves queued and durable state. */
113
+ release(attempt: Attempt): void { attempt.retired = true; this.#releaseSettled(attempt); }
114
+ #releaseSettled(attempt: Attempt): void {
115
+ if (attempt.retired && attempt.pending === 0 && !attempt.failed && this.#attempts.get(attempt.key) === attempt)
116
+ this.#attempts.delete(attempt.key);
117
+ }
118
+ get stats() { return { pendingCount: this.#queue.pendingCount, pendingBytes: this.#queue.pendingBytes,
119
+ dropped: this.#queue.droppedCount, failed: this.#queue.failedCount }; }
120
+ async flush(): Promise<{ durable: boolean; pendingBytes: number; failed: number }> {
121
+ await this.#queue.drain();
122
+ const active = [...this.#attempts.values()].filter(attempt => !attempt.completed);
123
+ return { durable: this.#cleanupFailures.size === 0 && active.every(attempt => attempt.pending === 0 && !attempt.failed),
124
+ pendingBytes: this.#queue.pendingBytes, failed: this.#cleanupFailures.size + active.filter(attempt => attempt.failed).length };
125
+ }
126
+ }
@@ -0,0 +1,31 @@
1
+ import { MlxArray } from "@mlx-bun/mlx/array";
2
+ import * as ops from "@mlx-bun/mlx/ops";
3
+ import type { DecodeStepInputs } from "../contracts/mlx/cache";
4
+
5
+ /** The per-step arrays a cache assembles its compiled-decode closure inputs from.
6
+ * Everything it creates is pushed onto `temps`, which the caller releases after the step. */
7
+ export function decodeStepInputs(temps: MlxArray[]): DecodeStepInputs {
8
+ const writePositions = new Map<number, MlxArray>();
9
+ return {
10
+ activeView(array, length) {
11
+ const stop = [...array.shape];
12
+ stop[2] = length;
13
+ const view = array.slice(stop.map(() => 0), stop);
14
+ temps.push(view);
15
+ return view;
16
+ },
17
+ own(array) {
18
+ temps.push(array);
19
+ return array;
20
+ },
21
+ writePosition(position) {
22
+ let a = writePositions.get(position);
23
+ if (!a) {
24
+ a = ops.fromInt32([position], [1]);
25
+ writePositions.set(position, a);
26
+ temps.push(a);
27
+ }
28
+ return a;
29
+ },
30
+ };
31
+ }
@@ -0,0 +1,12 @@
1
+ import * as ops from "@mlx-bun/mlx/ops";
2
+ import type { KvDonorAttention, KvDonorRows } from "../contracts/mlx/cache";
3
+
4
+ /** Takes ownership of decoded donor planes. Assistant attention uses decoded
5
+ * values, preserving its SDPA arithmetic rather than moving inverse rotation. */
6
+ export function decodedKvDonorAttention(view: KvDonorRows): KvDonorAttention {
7
+ const { keys, values, offsets, starts, ends } = view;
8
+ return { width: keys.shape[2]!, dtype: keys.dtype, offsets, starts, ends,
9
+ attend: (q, scale, mask) => ops.sdpa(q, keys, values, scale, mask.mode, mask.arr),
10
+ dispose() { keys.dispose(); values.dispose(); },
11
+ };
12
+ }
@@ -0,0 +1,141 @@
1
+ import { captureFullKvDonorAttention } from "./full-kv-row-donor";
2
+ import { appendFullKvRows } from "./full-kv-row-append";
3
+ import { FullPrefillRow, fullRowPadding, fullRowPhysicalLength } from "./full-prefill-row";
4
+ import { captureKvAttention } from "./kv-attention-view";
5
+ import type { MlxArray } from "@mlx-bun/mlx/array";
6
+ import { Dtype } from "@mlx-bun/mlx/ffi";
7
+ import * as ops from "@mlx-bun/mlx/ops";
8
+ import { FullTransitioningKvRows } from "./full-transitioning-kv-rows";
9
+ import { BatchedQuantizedKVCache } from "./batched-quantized-kv";
10
+ import { KVCache, type AffineKernelSet } from "./kv";
11
+ import { QuantizedKVCache } from "./quantized-kv";
12
+ import { Bf16FirstQuantizedKVCache, convertToAffine, transitionDue } from "./bf16-first-kv";
13
+ import { type AttentionRead, type Cache, type CommittedAttentionCache, type Mask, type KvAttentionView } from "../contracts/mlx/cache";
14
+ import { kvStorage, type KvAttentionState } from "./kv-storage";
15
+
16
+ /** The bf16 source of a full-attention row: a `KVCache`, or a padded
17
+ * `FullPrefillRow` over one. Converted rows have none. */
18
+ function affineSource(row: Cache): KVCache | FullPrefillRow | undefined {
19
+ return row instanceof FullPrefillRow ? row.affineConversion : row instanceof KVCache ? row : undefined;
20
+ }
21
+
22
+ /** The affine transition of `Bf16FirstQuantizedKVCache` over full-attention
23
+ * rows: each row converts with its own `toQuantized` once it holds `start`
24
+ * positions, and attends with `kernelSet` after. */
25
+ function settleAffineRows(rows: Cache[], groupSize: number, bits: number, start: number, kernelSet: AffineKernelSet): void {
26
+ for (let index = 0; index < rows.length; index++) {
27
+ const source = affineSource(rows[index]!);
28
+ if (source && transitionDue(source.offset, start)) rows[index] = convertToAffine<Cache>(source, groupSize, bits, start, kernelSet);
29
+ }
30
+ }
31
+
32
+ /** The rows of the affine bf16-first lego (`Bf16FirstQuantizedKVCache`) for
33
+ * continuous batching. Each row is bf16 until it holds `start` positions and
34
+ * converts by the same transition; rows cross independently, a converted row
35
+ * keeps its native quantized-attention arithmetic, and once all rows convert,
36
+ * their packed planes use the existing batched attention implementation. When
37
+ * a row converts is described on `TransitioningKvRows`. The reads are the
38
+ * graph's call today: this layout's mask before the append, then its attention
39
+ * view. Converted rows, the packed layout and every view attend with
40
+ * `kernelSet`, taken when the layout is built. A row this layout hands back
41
+ * to serial use is the lego it came from again (`compose`), or its storage when
42
+ * it came as storage. */
43
+ export class DelayedQuantizedKVCache extends FullTransitioningKvRows<BatchedQuantizedKVCache> implements KvAttentionState, CommittedAttentionCache {
44
+ constructor(readonly groupSize: number, readonly bits: number, readonly start: number,
45
+ /** The affine kernel set converted rows and every read attend with, named
46
+ * by the composition (`composedAffineKernels`). */
47
+ readonly kernelSet: AffineKernelSet, row?: Cache,
48
+ /** How the lego these rows came from composes the rows this layout hands
49
+ * back to serial use (`ComposedStorage.over`). */
50
+ readonly compose?: (storage: Cache) => Cache) {
51
+ super({ signature: `kv:delayed-quant:${bits}:${groupSize}:${start}`, conversionOffset: start,
52
+ settle: rows => settleAffineRows(rows, groupSize, bits, start, kernelSet),
53
+ converted: row => row instanceof QuantizedKVCache,
54
+ makeLayout: () => new BatchedQuantizedKVCache(groupSize, bits, kernelSet(bits, groupSize, Dtype.bfloat16)),
55
+ lone: storage => new Bf16FirstQuantizedKVCache(groupSize, bits, start, kernelSet, undefined, storage),
56
+ ...(compose ? { compose } : {}) }, row);
57
+ }
58
+ get attentionState(): KvAttentionState { return this; }
59
+ captureDonorAttention() {
60
+ return this.packed?.captureDonorAttention() ?? captureFullKvDonorAttention(this.rows, this.leftPad, this.offset);
61
+ }
62
+ makeEmptyBatch(): DelayedQuantizedKVCache {
63
+ return new DelayedQuantizedKVCache(this.groupSize, this.bits, this.start, this.kernelSet, undefined, this.compose);
64
+ }
65
+
66
+ appendDecode(k: MlxArray, v: MlxArray): AttentionRead { return this.#read(k, v, false); }
67
+ appendWindow(k: MlxArray, v: MlxArray): AttentionRead { return this.#read(k, v, false); }
68
+ /** The committed read (Qwen 3.5's `appendKnown` under `knownSpans`): the same
69
+ * mask and attention view, attended with independent positions, so a
70
+ * converted row keeps the single-position reduction order. */
71
+ appendCommitted(k: MlxArray, v: MlxArray): AttentionRead { return this.#read(k, v, true); }
72
+ /** The read: the mask from this layout before the append (no
73
+ * sliding window), then the attention view the append returns. */
74
+ #read(k: MlxArray, v: MlxArray, independentPositions: boolean): AttentionRead {
75
+ const mask = this.makeMask(k.shape[2]!, null);
76
+ let view: KvAttentionView;
77
+ try { view = this.appendAndFetch(k, v); } catch (error) { mask.arr?.dispose(); throw error; }
78
+ return { attend: (q, scale) => view.attend(q, scale, mask, independentPositions),
79
+ dispose() { view.dispose(); mask.arr?.dispose(); } };
80
+ }
81
+ /** The dense fetch at the model's B (the softcapped read's): after the
82
+ * transition the append runs, every row's keys and values as its storage
83
+ * fetches them dense (a converted row's dequantized), joined left-padded as
84
+ * the plain attention view joins them, or the packed layout's dense fetch
85
+ * once every row is converted; the caller owns them. */
86
+ updateAndFetch(k: MlxArray, v: MlxArray): [MlxArray, MlxArray] {
87
+ this.advance();
88
+ if (this.packed) return kvStorage(this.packed).updateAndFetch(k, v);
89
+ return this.#appendPlain(k, v);
90
+ }
91
+ #appendPlain(k: MlxArray, v: MlxArray): [MlxArray, MlxArray] {
92
+ const [keys, values] = appendFullKvRows(this.rows, this.leftPad, this.offset + k.shape[2]!, k, v);
93
+ try { this.syncPositions(true); } catch (error) { keys.dispose(); values.dispose(); throw error; }
94
+ return [keys, values];
95
+ }
96
+ appendAndFetch(k: MlxArray, v: MlxArray): KvAttentionView {
97
+ this.advance();
98
+ const kernels = this.kernelSet(this.bits, this.groupSize, k.dtype);
99
+ if (this.packed) return captureKvAttention(this.packed, k, v, kernels);
100
+ if (this.rows.every(row => !kvStorage(row).quantizedAttention)) {
101
+ const [keys, values] = this.#appendPlain(k, v);
102
+ return { attend: (q, scale, mask) => ops.sdpa(q, keys, values, scale, mask.mode, mask.arr),
103
+ dispose() { keys.dispose(); values.dispose(); } };
104
+ }
105
+ const views: KvAttentionView[] = [], pads = this.rows.map((row, index) => this.leftPad[index]! - fullRowPadding(row));
106
+ const lengths = this.rows.map(cache => fullRowPhysicalLength(cache) + k.shape[2]!);
107
+ try {
108
+ for (const [row, cache] of this.rows.entries()) {
109
+ const slice = (a: MlxArray) => a.slice([row, 0, 0, 0], [row + 1, a.shape[1]!, a.shape[2]!, a.shape[3]!]);
110
+ using kr = slice(k), vr = slice(v);
111
+ views.push(captureKvAttention(cache, kr, vr, kernels));
112
+ }
113
+ this.syncPositions(true);
114
+ } catch (error) { for (const view of views) view.dispose(); throw error; }
115
+ return {
116
+ attend(q, scale, mask, independentPositions) {
117
+ const outputs: MlxArray[] = [];
118
+ try {
119
+ for (const [row, view] of views.entries()) {
120
+ using qr = q.slice([row, 0, 0, 0], [row + 1, q.shape[1]!, q.shape[2]!, q.shape[3]!]);
121
+ // Capture alignment with the KV tensors, never consult live rows.
122
+ const start = mask.arr?.shape.map(() => 0), end = mask.arr ? [...mask.arr.shape] : undefined;
123
+ if (start && end) {
124
+ if (end.length === 4 && end[0]! > 1) { start[0] = row; end[0] = row + 1; }
125
+ start[start.length - 1] = pads[row]!;
126
+ end[end.length - 1] = pads[row]! + lengths[row]!;
127
+ }
128
+ using arr = mask.arr ? mask.arr.slice(start!, end!) : null;
129
+ outputs.push(view.attend(qr, scale, { mode: mask.mode, arr }, independentPositions));
130
+ }
131
+ return outputs.length === 1 ? ops.contiguous(outputs[0]!) : ops.concatAxis(outputs, 0);
132
+ } finally { for (const output of outputs) output.dispose(); }
133
+ },
134
+ dispose() { for (const view of views) view.dispose(); },
135
+ };
136
+ }
137
+ updateAndAttend(q: MlxArray, k: MlxArray, v: MlxArray, scale: number, mask: Mask): MlxArray {
138
+ const view = this.appendAndFetch(k, v);
139
+ try { return view.attend(q, scale, mask); } finally { view.dispose(); }
140
+ }
141
+ }
@@ -0,0 +1,178 @@
1
+ import type { 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 { RotatingKVCache } from "./rotating-kv";
5
+ import { type AttentionRead, type Cache, type CommittedAttentionCache, type KvAttentionView, type PaddedPrefillCache, type PrefillPadding } from "../contracts/mlx/cache";
6
+ import { kvStorage, type KvAttentionState } from "./kv-storage";
7
+ import { SpeculativeTransitioningKvRows } from "./transitioning-kv-rows";
8
+ import { AlignedRotatingCache, alignRotatingRows, RotatingAffineLayout, SpeculativeRotatingAffineLayout, RotatingKvPositions } from "./rotating-kv-layout";
9
+ import { BatchedRotatingQuantCache } from "./batched-rotating-quant";
10
+ import { captureKvAttention, combineKvDonorAttention } from "./kv-attention-view";
11
+ import { Bf16FirstRotatingQuantizedKVCache, convertToAffine, transitionDue } from "./bf16-first-kv";
12
+ import type { AffineKernelSet } from "./kv";
13
+ import type { DecodeStepPlan } from "../contracts/mlx/cache";
14
+
15
+ /** The affine transition of `Bf16FirstRotatingQuantizedKVCache` over aligned
16
+ * ring rows: a plain ring converts its physical columns with its own
17
+ * `toQuantized` once it holds `start` positions, and attends with `kernelSet`
18
+ * after. */
19
+ function settleAffineRings(rows: Cache[], groupSize: number, bits: number, start: number, kernelSet: AffineKernelSet): void {
20
+ for (let index = 0; index < rows.length; index++) {
21
+ const source = (rows[index] as AlignedRotatingCache).affineConversion;
22
+ if (source && transitionDue(source.offset, start)) rows[index] = convertToAffine(source, groupSize, bits, start, kernelSet);
23
+ }
24
+ }
25
+
26
+ /** The rows of the rotating affine bf16-first lego
27
+ * (`Bf16FirstRotatingQuantizedKVCache`) for continuous batching. Precision
28
+ * changes preserve each row's physical columns. The model owns queries and
29
+ * scale; the shared lifecycle owns membership and the transition (when a row
30
+ * converts is described on `TransitioningKvRows`). A `start` of `Infinity`
31
+ * never converts: the prefill cohort's layout for plain rings. The reads are
32
+ * the graph's call today: this layout's mask before the append, under the
33
+ * ring's window, then its attention view. Converted rings and the packed
34
+ * layout attend with `kernelSet`, taken when the layout is built; the rows a
35
+ * cohort extracts carry it into the layouts they join. */
36
+ export class DelayedRotatingQuantizedKVCache extends SpeculativeTransitioningKvRows<RotatingAffineLayout>
37
+ implements KvAttentionState, PaddedPrefillCache, CommittedAttentionCache {
38
+ constructor(readonly maxSize: number, readonly groupSize: number, readonly bits: number, readonly start: number,
39
+ /** The affine kernel set converted rings and every read attend with,
40
+ * named by the composition (`composedAffineKernels`). */
41
+ readonly kernelSet: AffineKernelSet, row?: Cache, readonly speculative = false,
42
+ /** How the lego these rings came from composes the rings this layout
43
+ * hands back to serial use (`ComposedStorage.over`). */
44
+ readonly compose?: (storage: Cache) => Cache) {
45
+ super({ signature: `kv:delayed-rotating-quant:${maxSize}:${bits}:${groupSize}:${start}`, conversionOffset: start,
46
+ ...(compose ? { compose } : {}),
47
+ settle: rows => settleAffineRings(rows, groupSize, bits, start, kernelSet),
48
+ converted: row => row instanceof AlignedRotatingCache && row.inner instanceof BatchedRotatingQuantCache,
49
+ makeLayout: () => { const kernels = kernelSet(bits, groupSize, Dtype.bfloat16);
50
+ return speculative ? new SpeculativeRotatingAffineLayout(maxSize, groupSize, bits, kernels) : new RotatingAffineLayout(maxSize, groupSize, bits, kernels); },
51
+ prepareRows: alignRotatingRows,
52
+ lone: storage => new Bf16FirstRotatingQuantizedKVCache(maxSize, groupSize, bits, start, kernelSet, undefined, storage),
53
+ packRows: (layout, rows) => layout.adoptAlignedRows(rows as readonly AlignedRotatingCache[]),
54
+ extractRow: row => (row as AlignedRotatingCache).extract(speculative ? maxSize : undefined),
55
+ rollbackRow: (row, before, keep, preserve) => (row as AlignedRotatingCache).rollback(before, keep, preserve) }, row, new RotatingKvPositions(maxSize));
56
+ }
57
+ /** A ring's lone row fetches by its ring: concat until the next write lands at
58
+ * the window, the ring after (`RotatingKVCache.decodePhase`). */
59
+ protected override decodePhaseOfRows(): DecodeStepPlan["fetch"] {
60
+ return this.rowOffsets[0]! + 1 < this.maxSize ? "concat" : "ring";
61
+ }
62
+ get attentionState(): KvAttentionState { return this; }
63
+ captureDonorAttention(): import("../contracts/mlx/cache").KvDonorAttention {
64
+ if (this.packed) return this.packed.captureDonorAttention();
65
+ const views: import("../contracts/mlx/cache").KvDonorAttention[] = [];
66
+ try {
67
+ for (const row of this.rows) views.push((row as AlignedRotatingCache).captureDonorAttention());
68
+ return combineKvDonorAttention(views);
69
+ } catch (error) { for (const view of views) view.dispose(); throw error; }
70
+ }
71
+ override makeMask(tokens: number, window: number | null) {
72
+ if (this.speculative && this.leftPad.every(pad => pad === 0) &&
73
+ this.offset + tokens <= Math.min(this.maxSize, window ?? this.maxSize))
74
+ return { mode: tokens === 1 ? "" as const : "causal" as const, arr: null };
75
+ return super.makeMask(tokens, window);
76
+ }
77
+
78
+ appendDecode(k: MlxArray, v: MlxArray): AttentionRead { return this.#read(k, v, false); }
79
+ appendWindow(k: MlxArray, v: MlxArray): AttentionRead { return this.#read(k, v, false); }
80
+ /** The committed read (Qwen 3.5's `appendKnown` under `knownSpans`): the same
81
+ * mask and attention view, attended with independent positions, which the
82
+ * packed layout's view honours and the per-row views ignore. */
83
+ appendCommitted(k: MlxArray, v: MlxArray): AttentionRead { return this.#read(k, v, true); }
84
+ /** The read: the mask from this layout before the append, with
85
+ * the ring's window, then the attention view the append returns. */
86
+ #read(k: MlxArray, v: MlxArray, independentPositions: boolean): AttentionRead {
87
+ const mask = this.makeMask(k.shape[2]!, this.maxSize);
88
+ let view: KvAttentionView;
89
+ try { view = this.appendAndFetch(k, v); } catch (error) { mask.arr?.dispose(); throw error; }
90
+ return { attend: (q, scale) => view.attend(q, scale, mask, independentPositions),
91
+ dispose() { view.dispose(); mask.arr?.dispose(); } };
92
+ }
93
+
94
+ preparePrefill(padding: PrefillPadding): void {
95
+ if (!this.batchSize) {
96
+ const rows = padding.lengths.map(() => new RotatingKVCache(this.maxSize));
97
+ try { this.mergeRows(rows); } finally { for (const row of rows) row.dispose(); }
98
+ }
99
+ if (this.packed) { this.packed.preparePrefill(padding); return; }
100
+ // Every physical row follows the same block geometry, even when only a
101
+ // sibling has right padding. The token lengths still belong to each row.
102
+ const rightPaddedBatch = padding.rightPadding?.some(pad => pad > 0);
103
+ for (const [row, cache] of this.rows.entries()) (cache as AlignedRotatingCache).inner.preparePrefill({
104
+ lengths: [padding.lengths[row]!],
105
+ ...(padding.leftPadding ? { leftPadding: [padding.leftPadding[row]!] } : {}),
106
+ ...(padding.rightPadding ? { rightPadding: [padding.rightPadding[row]!] } : {}),
107
+ }, rightPaddedBatch);
108
+ this.syncPositions(true);
109
+ }
110
+ finalizePrefill(): void {
111
+ if (this.packed) { this.packed.finalizePrefill(); return; }
112
+ for (const cache of this.rows) (cache as AlignedRotatingCache).inner.finalizePrefill();
113
+ this.syncPositions(true);
114
+ }
115
+ get maxTokens(): number { return this.maxSize; }
116
+ projectedBytes(tokens: number): number { return this.bytesPerToken() * Math.min(tokens, this.maxSize); }
117
+ makeEmptyBatch(): DelayedRotatingQuantizedKVCache {
118
+ return new DelayedRotatingQuantizedKVCache(this.maxSize, this.groupSize, this.bits, this.start, this.kernelSet, undefined,
119
+ this.speculative, this.compose);
120
+ }
121
+ /** The dense fetch (the softcapped read's): after the transition the append
122
+ * runs, each row's own ring answers as its storage fetches it dense (a
123
+ * converted ring dequantized; one row keeps its physical columns and phase;
124
+ * several share one aligned geometry) and rows join along the batch, or the
125
+ * packed layout's dense fetch once every row is converted; the caller owns them. */
126
+ updateAndFetch(k: MlxArray, v: MlxArray): [MlxArray, MlxArray] {
127
+ this.advance();
128
+ if (this.packed) return kvStorage(this.packed).updateAndFetch(k, v);
129
+ const rows = this.rows.map(kvStorage);
130
+ if (rows.length === 1) {
131
+ const [keys, values] = rows[0]!.updateAndFetch(k, v);
132
+ try { this.syncPositions(true); } catch (error) { keys.dispose(); values.dispose(); throw error; }
133
+ return [keys, values];
134
+ }
135
+ const parts: MlxArray[][] = [];
136
+ try {
137
+ for (const [row, cache] of rows.entries()) {
138
+ const slice = (a: MlxArray) => a.slice([row, 0, 0, 0], [row + 1, a.shape[1]!, a.shape[2]!, a.shape[3]!]);
139
+ using kr = slice(k), vr = slice(v);
140
+ parts.push(cache.updateAndFetch(kr, vr));
141
+ }
142
+ this.syncPositions(true);
143
+ const keys = ops.concatAxis(parts.map(part => part[0]!), 0);
144
+ try { return [keys, ops.concatAxis(parts.map(part => part[1]!), 0)]; } catch (error) { keys.dispose(); throw error; }
145
+ } finally { for (const array of parts.flat()) array.dispose(); }
146
+ }
147
+ releaseRopeArr(): void { this.packed?.releaseRopeArr(); }
148
+ appendAndFetch(k: MlxArray, v: MlxArray): KvAttentionView {
149
+ this.advance();
150
+ const kernels = this.kernelSet(this.bits, this.groupSize, k.dtype);
151
+ if (this.packed) return captureKvAttention(this.packed, k, v, kernels);
152
+ const views: KvAttentionView[] = [];
153
+ try {
154
+ for (const [row, cache] of this.rows.entries()) {
155
+ const slice = (a: MlxArray) => a.slice([row, 0, 0, 0], [row + 1, a.shape[1]!, a.shape[2]!, a.shape[3]!]);
156
+ using kr = slice(k), vr = slice(v);
157
+ views.push(captureKvAttention(cache, kr, vr, kernels));
158
+ }
159
+ this.syncPositions(true);
160
+ } catch (error) { for (const view of views) view.dispose(); throw error; }
161
+ return {
162
+ attend(q, scale, mask) {
163
+ const outputs: MlxArray[] = [];
164
+ try {
165
+ for (const [row, view] of views.entries()) {
166
+ using qr = q.slice([row, 0, 0, 0], [row + 1, q.shape[1]!, q.shape[2]!, q.shape[3]!]);
167
+ const start = mask.arr?.shape.map(() => 0), end = mask.arr ? [...mask.arr.shape] : undefined;
168
+ if (start && end && end.length === 4 && end[0]! > 1) { start[0] = row; end[0] = row + 1; }
169
+ using arr = mask.arr ? mask.arr.slice(start!, end!) : null;
170
+ outputs.push(view.attend(qr, scale, { mode: mask.mode, arr }));
171
+ }
172
+ return outputs.length === 1 ? ops.contiguous(outputs[0]!) : ops.concatAxis(outputs, 0);
173
+ } finally { for (const output of outputs) output.dispose(); }
174
+ },
175
+ dispose() { for (const view of views) view.dispose(); },
176
+ };
177
+ }
178
+ }