@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,300 @@
1
+ import { MlxArray } from "@mlx-bun/mlx/array";
2
+ import { Dtype } from "@mlx-bun/mlx/ffi";
3
+ import * as ops from "@mlx-bun/mlx/ops";
4
+ import { createCausalMask } from "../kernels/attention/masks";
5
+ import { flashAttention, flashSupported, getTrainingAttn } from "../kernels/attention/flash";
6
+ import { gatedDeltaUpdate } from "../kernels/delta/gated";
7
+ import type { AttentionRead, Cache, GatedDeltaCache, GatedDeltaParameters, Mask, TrainingAttentionCache } from "../contracts/mlx/cache";
8
+ import { ropeBlocks } from "../layers/prefix-layout";
9
+ import { AttentionMasks, causalLease, unmaskedLease, withLease, type MaskLease } from "./attention-read";
10
+ import type { PrefixLayout } from "../contracts/mlx/trainable";
11
+ import { isRecurrentCache } from "./capabilities";
12
+
13
+ /** The kernel a training cache's reads attend with, fixed when the cache is
14
+ * built. `sdpa` is the stock fused SDPA, the kernel mlx-lm's tuner
15
+ * differentiates. `flash` is the opt-in O(L)-memory kernel
16
+ * (`MLX_BUN_TRAIN_ATTN=flash`): it serves a causal or masked read of a query
17
+ * it supports (`flashSupported`: head size, at least 32 positions) as causal
18
+ * attention within the cache's sliding window, and the SDPA serves the rest,
19
+ * the rule the training forwards applied per call before the caches owned
20
+ * their reads. */
21
+ export type TrainingAttention = "sdpa" | "flash";
22
+
23
+ /** The training attention the trainer selected for its run (`setTrainingAttn`,
24
+ * set around a run from `MLX_BUN_TRAIN_ATTN` once the graph's training
25
+ * declaration allows it). Read where a forward's training caches are built,
26
+ * never inside a read. */
27
+ export function selectedTrainingAttention(): TrainingAttention {
28
+ return getTrainingAttn() === "flash" ? "flash" : "sdpa";
29
+ }
30
+
31
+ /** A training cache's read: pass-through views of `k` and `v` (borrowed, as
32
+ * every append's are) under `mask`, which the read owns. */
33
+ function trainingRead(k: MlxArray, v: MlxArray, mask: MaskLease, attention: TrainingAttention, window: number | null): AttentionRead {
34
+ return withLease(mask, held => {
35
+ const keys = k.slice([0, 0, 0, 0], k.shape);
36
+ let values: MlxArray;
37
+ try { values = v.slice([0, 0, 0, 0], v.shape); } catch (error) { keys.dispose(); throw error; }
38
+ const { mode, arr } = held.mask;
39
+ const flash = attention === "flash" && (mode === "causal" || mode === "array");
40
+ return {
41
+ attend: (q, scale) => flash && flashSupported(q)
42
+ ? flashAttention(q, keys, values, scale, true, window ?? 0)
43
+ : ops.sdpa(q, keys, values, scale, mode, arr),
44
+ dispose() { keys.dispose(); values.dispose(); held.release(); },
45
+ };
46
+ });
47
+ }
48
+
49
+ /** The padded-batch training mask over `L` positions: [B, 1, L, L] bool, true
50
+ * where query i of row b may attend key j:
51
+ * (j <= i) AND (j < lengths[b]) [AND (i < j + window) under a sliding window]
52
+ * createCausalMask(L, 0, window) broadcast over the rows and masked by each
53
+ * row's key validity. Caller owns the result. */
54
+ export function paddedTrainingMask(B: number, L: number, lengths: readonly number[], window: number | null): MlxArray {
55
+ const causal2d = createCausalMask(L, 0, window);
56
+ const causal4d = ops.reshape(causal2d, [1, 1, L, L]);
57
+ causal2d.dispose();
58
+ const keyValidData = new Int32Array(B * L);
59
+ for (let b = 0; b < B; b++) {
60
+ const length = lengths[b]!;
61
+ for (let j = 0; j < L; j++) keyValidData[b * L + j] = j < length ? 1 : 0;
62
+ }
63
+ const keyValidI = MlxArray.fromInt32(keyValidData, [B, 1, 1, L]);
64
+ const zero = MlxArray.fromInt32(new Int32Array([0]), []);
65
+ const keyValid = ops.less(zero, keyValidI);
66
+ zero.dispose();
67
+ keyValidI.dispose();
68
+ const allow = ops.logicalAnd(causal4d, keyValid);
69
+ causal4d.dispose();
70
+ keyValid.dispose();
71
+ return allow;
72
+ }
73
+
74
+ /** Stateless cache for the training and scoring forward. Training is always a
75
+ * single offset-0 full-sequence pass, so nothing is stored and every append
76
+ * reads exactly the keys and values it is handed; the offset stays 0. The
77
+ * statelessness is REQUIRED: gradient checkpointing re-runs a layer's closure
78
+ * during the backward recompute, and a layer that shares another layer's keys
79
+ * and values reads them through that layer's cache again, so an append must
80
+ * leave the cache as it found it.
81
+ *
82
+ * Reads: pass-through views of `k` and `v`, attended with `attention`.
83
+ * Without `rowLengths`: no mask for decode, the fused causal mask for a
84
+ * window, and the offset-0 sliding-window mask once a window is longer than
85
+ * `window`. With `rowLengths` (a right-padded batch whose rows hold that many
86
+ * real positions), both reads take the padded-batch mask
87
+ * (`paddedTrainingMask`) for the cache's window, so no real query sees a
88
+ * padded key. A layer with a sliding window gets a cache composed with it; the
89
+ * caches of one forward share `masks`, so its layers build each mask once. */
90
+ export class TrainingCache implements TrainingAttentionCache {
91
+ constructor(
92
+ /** The layer's sliding window; null for full attention. */
93
+ readonly window: number | null = null,
94
+ /** The per-forward masks of the model this cache belongs to. */
95
+ readonly masks = new AttentionMasks(),
96
+ /** The kernel this cache's reads attend with. */
97
+ readonly attention: TrainingAttention = "sdpa",
98
+ /** Real positions per row of a right-padded batch; null for unpadded rows. */
99
+ readonly rowLengths: readonly number[] | null = null,
100
+ ) {}
101
+ /** Training-only adapter; never admitted, merged, or persisted. */
102
+ signature(): string { return "train:training"; }
103
+ offset = 0;
104
+ /** Uniform positions from the pinned offset. */
105
+ ropePositions(x: MlxArray, rope: (block: MlxArray, offset: number) => MlxArray): MlxArray { return rope(x, this.offset); }
106
+ appendDecode(k: MlxArray, v: MlxArray): AttentionRead {
107
+ return trainingRead(k, v, this.rowLengths ? this.#padded(k.shape[2]!) : unmaskedLease, this.attention, this.window);
108
+ }
109
+ appendWindow(k: MlxArray, v: MlxArray): AttentionRead {
110
+ const L = k.shape[2]!, window = this.window;
111
+ const mask = this.rowLengths ? this.#padded(L)
112
+ : window === null || L <= window ? causalLease
113
+ : this.masks.lease(`training-window:${window}`, `${L}`,
114
+ () => ({ mode: "array", arr: createCausalMask(L, 0, window) }));
115
+ return trainingRead(k, v, mask, this.attention, window);
116
+ }
117
+ #padded(L: number): MaskLease {
118
+ const lengths = this.rowLengths!;
119
+ return this.masks.lease(`training-padded:${this.window}`, `${L}|${lengths.join(",")}`,
120
+ () => ({ mode: "array", arr: paddedTrainingMask(lengths.length, L, lengths, this.window) }));
121
+ }
122
+ /** The dense fetch and mask the softcapped read (`state/softcap-attention.ts`)
123
+ * attends through: pass-through views and the mask the window read takes. */
124
+ updateAndFetch(k: MlxArray, v: MlxArray): [MlxArray, MlxArray] {
125
+ return [k.slice([0, 0, 0, 0], k.shape), v.slice([0, 0, 0, 0], v.shape)];
126
+ }
127
+ makeMask(N: number, windowSize: number | null): Mask {
128
+ if (this.rowLengths) return { mode: "array", arr: paddedTrainingMask(this.rowLengths.length, N, this.rowLengths, this.window) };
129
+ if (N === 1) return { mode: "", arr: null };
130
+ if (windowSize === null || N <= windowSize) return { mode: "causal", arr: null };
131
+ return { mode: "array", arr: createCausalMask(N, 0, windowSize) };
132
+ }
133
+ state(): MlxArray[] {
134
+ return [];
135
+ }
136
+ isTrimmable(): boolean {
137
+ return true;
138
+ }
139
+ trim(_n: number): void {
140
+ /* offset is pinned at 0 */
141
+ }
142
+ dispose(): void {
143
+ // Owns no arrays; drops the memo's holds on its masks.
144
+ this.masks.clear();
145
+ }
146
+ }
147
+
148
+ /** Stateless DeltaNet stand-in (same re-runnability contract as
149
+ * TrainingCache): state WRITES are discarded immediately, so a gradient-
150
+ * checkpoint recompute re-enters with the identical null-state conditions;
151
+ * reads always yield null (zeros inside the kernel) — a full-sequence
152
+ * forward from t=0, exactly the training/perplexity semantics. Before this
153
+ * existed, qwen3_5 routed its DeltaNet layers into plain TrainingCache and
154
+ * threw on the missing conv/recurrent/advance surface (the recorded
155
+ * "mlx-bun perplexity cannot score qwen3_5" gap).
156
+ *
157
+ * Its `recurDecode` and `recurWindow` run the recurrence with a backward
158
+ * attached (`gatedDeltaUpdate`'s `differentiable`), since the inference
159
+ * kernel has no gradient; the DeltaNet block hands them its projections as it
160
+ * does to any recurrent cache. */
161
+ export class TrainingSSMCache implements GatedDeltaCache {
162
+ /** Training-only adapter; never admitted, merged, or persisted. */
163
+ signature(): string { return "train:training-ssm"; }
164
+ offset = 0;
165
+ specRound: null = null;
166
+ recurDecode(qkv: MlxArray, a: MlxArray, b: MlxArray, layer: GatedDeltaParameters): MlxArray {
167
+ return this.#recur(qkv, a, b, layer);
168
+ }
169
+ recurWindow(qkv: MlxArray, a: MlxArray, b: MlxArray, layer: GatedDeltaParameters): MlxArray {
170
+ return this.#recur(qkv, a, b, layer);
171
+ }
172
+ /** The training recurrence: a zero conv state, the
173
+ * causal convolution, the block's glue, and the differentiable recurrence
174
+ * from a zero recurrent state. Nothing is stored, so the new recurrent
175
+ * state is disposed at once and no conv tail is kept. */
176
+ #recur(qkv: MlxArray, a: MlxArray, b: MlxArray, layer: GatedDeltaParameters): MlxArray {
177
+ const [B, , D] = qkv.shape as [number, number, number];
178
+ const convState = ops.zeros([B, layer.convWeight.shape[1]! - 1, D], qkv.dtype);
179
+ const input = ops.concatAxis([convState, qkv], 1);
180
+ convState.dispose();
181
+ qkv.dispose();
182
+ const conv = ops.conv1d(input, layer.convWeight, 1, 0, 1, D);
183
+ input.dispose();
184
+ const { q, k, v } = layer.heads(conv);
185
+ conv.dispose();
186
+ const [out, state] = gatedDeltaUpdate(q, k, v, a, b, layer.aLog, layer.dtBias, null, null, true);
187
+ for (const array of [q, k, v, a, b, state]) array.dispose();
188
+ return out;
189
+ }
190
+ advance(_n: number): void { /* offset pinned at 0 */ }
191
+ rowOffset(_i: number): number { return 0; }
192
+ state(): MlxArray[] { return []; }
193
+ isTrimmable(): boolean { return true; }
194
+ trim(_n: number): void { /* stateless */ }
195
+ dispose(): void { /* owns no arrays */ }
196
+ }
197
+
198
+ /** Block-sparse attention mask [T,T] (bool, true = attend) for the prefix-shared
199
+ * concat [prompt(P); chosen(Rc); rejected(Rr)]: physical causal AND NOT
200
+ * (rejected row -> chosen col). Chosen rows already can't see rejected cols
201
+ * (rejected come later), so the only extra cut is rejected->chosen. With a
202
+ * sliding `window`, one more AND on LOGICAL positions (`logpos[row] -
203
+ * logpos[col] < window`, `logpos[i] = i` for prompt+chosen and `i - Rc` for the
204
+ * rejected block, reset to P): a rejected token at physical P+Rc+k has logical
205
+ * position P+k, so its window to the prompt tail differs from its physical
206
+ * distance by Rc. Caller owns the result. */
207
+ export function blockSparsePrefixMask(layout: PrefixLayout, window: number | null): MlxArray {
208
+ const { P, Rc, Rr } = layout;
209
+ const T = P + Rc + Rr;
210
+ const causal = createCausalMask(T, 0, null); // [T,T] physical causal (a window is applied separately, on logical positions)
211
+ const idxFlat = ops.arange(0, T, 1, Dtype.int32);
212
+ const row = ops.reshape(idxFlat, [T, 1]);
213
+ const col = ops.reshape(idxFlat, [1, T]);
214
+ const pp = ops.fromInt32([P], []);
215
+ const pRc = ops.fromInt32([P + Rc], []);
216
+ // notForbid = NOT(rejRow AND chosenCol) = (i < P+Rc) OR (j < P) OR (j >= P+Rc)
217
+ const notRejRow = ops.less(row, pRc);
218
+ const colLtP = ops.less(col, pp);
219
+ const colGePRc = ops.greaterEqual(col, pRc);
220
+ const notChosenCol = ops.logicalOr(colLtP, colGePRc);
221
+ const notForbid = ops.logicalOr(notRejRow, notChosenCol);
222
+ let allow = ops.logicalAnd(causal, notForbid); // [T,T] bool
223
+
224
+ if (window !== null) {
225
+ const logposArr = new Int32Array(T);
226
+ for (let i = 0; i < T; i++) logposArr[i] = i < P + Rc ? i : i - Rc;
227
+ const logpos = MlxArray.fromInt32(logposArr, [T]);
228
+ const lrow = ops.reshape(logpos, [T, 1]);
229
+ const lcol = ops.reshape(logpos, [1, T]);
230
+ const dist = ops.sub(lrow, lcol); // logpos[row] - logpos[col] (>=0 wherever causal allows)
231
+ const w = ops.fromInt32([window], []);
232
+ const slidingOK = ops.less(dist, w); // attend only within `window` logical positions
233
+ const next = ops.logicalAnd(allow, slidingOK);
234
+ allow.dispose();
235
+ allow = next;
236
+ for (const a of [logpos, lrow, lcol, dist, w, slidingOK]) a.dispose();
237
+ }
238
+
239
+ for (const a of [causal, idxFlat, row, col, pp, pRc, notRejRow, colLtP, colGePRc, notChosenCol, notForbid]) a.dispose();
240
+ return allow;
241
+ }
242
+
243
+ /** Stateless pass-through cache (offset 0, like TrainingCache) for the
244
+ * prefix-shared forward over the concat [prompt(P); chosen(Rc); rejected(Rr)]:
245
+ * its window read takes exactly the concat's T positions under the
246
+ * block-sparse mask (`blockSparsePrefixMask`) for the cache's sliding window,
247
+ * attended with `attention`. The caches of one pass share `masks`, so each
248
+ * window's mask is built once per pass, across its layers, segments and
249
+ * backward recomputes. A decode read is not defined over a concat; it reads as
250
+ * the window does and refuses anything but the whole concat. */
251
+ export class PrefixSharedCache implements TrainingAttentionCache {
252
+ /** Training-only adapter; never admitted, merged, or persisted. */
253
+ signature(): string { return "train:prefix-shared"; }
254
+ offset = 0;
255
+ constructor(
256
+ private readonly layout: PrefixLayout,
257
+ /** The layer's sliding window, on logical positions; null for full attention. */
258
+ readonly window: number | null = null,
259
+ /** The per-pass masks of the model this cache belongs to. */
260
+ readonly masks = new AttentionMasks(),
261
+ /** The kernel this cache's reads attend with. */
262
+ readonly attention: TrainingAttention = "sdpa",
263
+ ) {}
264
+ /** The concat's block positions: the prompt from 0, each response restarted at P. */
265
+ ropePositions(x: MlxArray, rope: (block: MlxArray, offset: number) => MlxArray): MlxArray {
266
+ return ropeBlocks(x, this.layout, rope);
267
+ }
268
+ appendDecode(k: MlxArray, v: MlxArray): AttentionRead { return this.appendWindow(k, v); }
269
+ appendWindow(k: MlxArray, v: MlxArray): AttentionRead {
270
+ const { P, Rc, Rr } = this.layout, N = k.shape[2]!;
271
+ if (N !== P + Rc + Rr) throw new Error(`PrefixSharedCache: N=${N} != P+Rc+Rr=${P + Rc + Rr}`);
272
+ const mask = this.masks.lease(`prefix-shared:${this.window}`, `${P},${Rc},${Rr}`,
273
+ () => ({ mode: "array", arr: blockSparsePrefixMask(this.layout, this.window) }));
274
+ return trainingRead(k, v, mask, this.attention, this.window);
275
+ }
276
+ state(): MlxArray[] { return []; }
277
+ isTrimmable(): boolean { return true; }
278
+ trim(_n: number): void { /* offset pinned at 0 */ }
279
+ dispose(): void { this.masks.clear(); }
280
+ }
281
+
282
+ /** Prefix-shared caches for one pass, one per entry of `windows` (each layer's
283
+ * sliding window, or null), sharing one set of masks and attending with
284
+ * `attention`. */
285
+ export function prefixSharedCaches(layout: PrefixLayout, windows: readonly (number | null)[],
286
+ attention: TrainingAttention = "sdpa"): Cache[] {
287
+ const masks = new AttentionMasks();
288
+ return windows.map(window => new PrefixSharedCache(layout, window, masks, attention));
289
+ }
290
+
291
+ /** The stateless stand-in a training or scoring forward runs in place of one
292
+ * of the graph's fresh caches: recurrent state takes `TrainingSSMCache` (fixed
293
+ * size and causal, so right padding never reaches a real position and needs no
294
+ * mask); an attention cache takes `attention(window)`, the caller's stand-in
295
+ * over that cache's sliding window (its ring size; null for full attention). */
296
+ export function trainingStandIn(cache: Cache, attention: (window: number | null) => Cache): Cache {
297
+ if (isRecurrentCache(cache)) return new TrainingSSMCache();
298
+ const maxSize = (cache as { maxSize?: number }).maxSize;
299
+ return attention(typeof maxSize === "number" ? maxSize : null);
300
+ }
@@ -0,0 +1,267 @@
1
+ import type { MlxArray } from "@mlx-bun/mlx/array";
2
+ import * as ops from "@mlx-bun/mlx/ops";
3
+ import type { BatchableCache, Cache, CompiledDecodeCache, DecodeSlot, DecodeStepInputs, DecodeStepPlan, Mask } from "../contracts/mlx/cache";
4
+ import { KvTensorRows } from "./kv-tensor-rows";
5
+ import type { KvStorage } from "./kv-storage";
6
+ import { leaseCacheStates } from "./leases";
7
+ import { minimumReusableOffset } from "./views";
8
+ import { withResource, disposeResources } from "../runtime/resources";
9
+ import { cloneKvCaches } from "./persistence";
10
+
11
+ /** Encoded layouts retain physical row alignment across precision changes. */
12
+ export interface TransitionedKvLayout extends BatchableCache, KvStorage {
13
+ alignRows?(leftPad: readonly number[]): void;
14
+ }
15
+ export interface SpeculativeTransitionedKvLayout extends TransitionedKvLayout {
16
+ specRoundBegin(): void;
17
+ specRoundCommit(): void;
18
+ specRoundRollback(keep: number | readonly number[]): void;
19
+ }
20
+ /** One precision transition over a row layout, built with the layout's scheme:
21
+ * the same conversion the single-sequence legos make (`state/bf16-first-kv`). */
22
+ export interface KvRowTransition<Layout extends TransitionedKvLayout> {
23
+ readonly signature: string;
24
+ readonly conversionOffset?: number;
25
+ /** Converts, in place, every row whose stored length has reached the
26
+ * conversion offset. Converted rows, and rows below the offset, stay. */
27
+ settle(rows: Cache[]): void;
28
+ converted(row: Cache): boolean;
29
+ makeLayout(): Layout;
30
+ packRows?(layout: Layout, rows: readonly Cache[]): void;
31
+ prepareRows?(rows: Cache[]): Cache[];
32
+ extractRow?(row: Cache): Cache;
33
+ rollbackRow?(row: Cache, before: number, keep: number, preserveColumns: boolean): void;
34
+ /** How the lego these rows came from composes storage for serial use
35
+ * (`ComposedStorage.over`): a row this layout hands back to serial use is
36
+ * that lego again. Absent for rows that came as storage. */
37
+ compose?(storage: Cache): Cache;
38
+ /** The lego of this transition over one row's storage (taking ownership):
39
+ * what a compiled decode step drives this layout's lone row through. */
40
+ lone(storage: Cache): Cache & CompiledDecodeCache;
41
+ /** `row` handed to serial use as it is (ownership moves), or null when the
42
+ * row is a wrapper whose storage must be extracted. Absent: every row is
43
+ * extracted. */
44
+ adopt?(row: Cache): Cache | null;
45
+ }
46
+
47
+
48
+ export interface TransitioningKvPositions {
49
+ readonly rowOffsets: readonly number[];
50
+ readonly leftPad: readonly number[];
51
+ readonly offset: number;
52
+ readonly batchSize: number | null;
53
+ readonly ropeOffsetArr: MlxArray | undefined;
54
+ sync(rows: readonly Cache[], preserve: boolean): void;
55
+ filterRows(keep: readonly number[]): void;
56
+ makeMask(tokens: number, window: number | null): Mask;
57
+ dispose(): void;
58
+ }
59
+ export class FullKvPositions extends KvTensorRows implements TransitioningKvPositions {
60
+ sync(rows: readonly Cache[], preserve: boolean): void {
61
+ this.mergeRows(preserve
62
+ ? [{ planes: [], rowOffsets: rows.map(row => row.offset), leftPad: [...this.leftPad] }]
63
+ : rows.map(row => ({ planes: [], rowOffsets: [row.offset], leftPad: [0] })));
64
+ }
65
+ }
66
+
67
+ /** Owns membership, committed positions and precision transitions. Codec and
68
+ * attention adapters own tensor access; scheduling and persistence use the
69
+ * existing batch and checkpoint contracts. Row conversion never examines an
70
+ * uncommitted speculative suffix.
71
+ *
72
+ * Rows convert before an append outside prefill, and during prefill only at
73
+ * each row's committed chunk boundary (`prefillMaintenance.commitPrefill`), so
74
+ * a sibling that splits a row's planned chunk across forwards does not move
75
+ * that row's precision boundary. This is why the transition runs here rather
76
+ * than at the end of each append, as the single-sequence legos run it.
77
+ *
78
+ * Compiled decode: a layout holding one row (the lone row a batch adopted)
79
+ * drives it through a compiled decode step as the lego of its transition
80
+ * (`KvRowTransition.lone`): the first step that prepares this layout moves the
81
+ * row's storage into that lego, which converts at the same position the rows
82
+ * convert (the append that reaches the start is read before the conversion), and
83
+ * every compiled step after reaches the row there. Its offsets, state and
84
+ * signature read through the lego; any other operation (an ordinary append, a
85
+ * merge, an extraction, a speculative round) first moves the storage back into
86
+ * the rows. A one-row layout reads each decode position with the same bits as
87
+ * its lego (`tests/state/compiled-decode-protocol.test.ts`). */
88
+ export abstract class TransitioningKvRows<Layout extends TransitionedKvLayout> implements BatchableCache, CompiledDecodeCache {
89
+ readonly #positions: TransitioningKvPositions;
90
+ #rows: Cache[] = [];
91
+ #packed?: Layout;
92
+ /** The lone row as its lego, while a compiled decode step drives it. */
93
+ #lone: (Cache & CompiledDecodeCache) | null = null;
94
+ #prefilling = false;
95
+
96
+ readonly stateNeedsDispose = true;
97
+ constructor(readonly transition: KvRowTransition<Layout>, row?: Cache, positions: TransitioningKvPositions = new FullKvPositions()) {
98
+ this.#positions = positions;
99
+ if (row) { this.rows = transition.prepareRows?.([row]) ?? [row]; this.syncPositions(); }
100
+ }
101
+ // Every access to the rows, the packed layout or the positions finds the row
102
+ // home first (see the class).
103
+ protected get rows(): Cache[] { this.#home(); return this.#rows; }
104
+ protected set rows(rows: Cache[]) { this.#home(); this.#rows = rows; }
105
+ protected get packed(): Layout | undefined { this.#home(); return this.#packed; }
106
+ protected set packed(packed: Layout | undefined) { this.#home(); this.#packed = packed; }
107
+ protected get positions(): TransitioningKvPositions { this.#home(); return this.#positions; }
108
+ /** The lone row's storage, moved back from its lego into the rows. */
109
+ #home(): void {
110
+ const lone = this.#lone;
111
+ if (!lone) return;
112
+ this.#lone = null;
113
+ const storage = lone.composed ? lone.composed.release() : lone;
114
+ if (storage !== lone) lone.dispose();
115
+ this.#rows = this.transition.prepareRows?.([storage]) ?? [storage];
116
+ this.#positions.sync(this.#rows, false);
117
+ }
118
+ /** The lone row as its lego, moving its storage there on the first call. */
119
+ #loneLego(): Cache & CompiledDecodeCache {
120
+ if (this.#lone) return this.#lone;
121
+ if (this.batchSize !== 1) throw new Error(`compiled decode drives a layout of one row; this one holds ${this.batchSize}`);
122
+ const packed = this.#packed, row = this.#rows[0]!;
123
+ const adopted = packed ? null : this.transition.adopt?.(row) ?? null;
124
+ const storage = adopted ?? this.extractStorageRow(0);
125
+ let lego: Cache & CompiledDecodeCache;
126
+ try { lego = this.transition.lone(storage); }
127
+ catch (error) { if (!adopted) storage.dispose(); throw error; }
128
+ this.#rows = []; this.#packed = undefined;
129
+ if (packed) packed.dispose();
130
+ else if (!adopted) row.dispose();
131
+ this.#lone = lego;
132
+ return lego;
133
+ }
134
+ /** The fetch of the coming compiled step: the lego's while it holds the row,
135
+ * else the rows' (`decodePhaseOfRows`), without moving the row. */
136
+ decodePhase(): DecodeStepPlan["fetch"] { return this.#lone ? this.#lone.decodePhase() : this.decodePhaseOfRows(); }
137
+ /** The fetch a lone row's lego would choose, from this layout's positions. */
138
+ protected decodePhaseOfRows(): DecodeStepPlan["fetch"] { return "concat"; }
139
+ prepareDecodeStep(): DecodeStepPlan { return this.#loneLego().prepareDecodeStep(); }
140
+ decodeSlot(plan: DecodeStepPlan): DecodeSlot { return this.#loneLego().decodeSlot(plan); }
141
+ decodeInputs(plan: DecodeStepPlan, step: DecodeStepInputs): MlxArray[] { return this.#loneLego().decodeInputs(plan, step); }
142
+ commitDecodeStep(slot: DecodeSlot, outputs: MlxArray[]): MlxArray[] { return this.#loneLego().commitDecodeStep(slot, outputs); }
143
+ signature(): string { return this.transition.signature; }
144
+ get prefillMaintenance() { return this; }
145
+ beginPrefill(): void { this.#prefilling = true; }
146
+ commitPrefill(indices: readonly number[]): void {
147
+ if (this.packed) return;
148
+ for (const index of indices) {
149
+ const row = [this.rows[index]!];
150
+ try { this.transition.settle(row); }
151
+ finally { this.rows[index] = row[0]!; }
152
+ }
153
+ }
154
+ endPrefill(): void { this.#prefilling = false; }
155
+ get minimumReusableOffset(): number {
156
+ if (this.#lone) return this.#lone.minimumReusableOffset ?? 0;
157
+ return this.packed?.minimumReusableOffset ?? minimumReusableOffset(this.rows);
158
+ }
159
+ get rowOffsets(): readonly number[] { return this.#lone ? [this.#lone.offset] : this.packed?.rowOffsets ?? this.positions.rowOffsets; }
160
+ get leftPad(): readonly number[] { return this.#lone ? [0] : this.packed?.leftPad ?? this.positions.leftPad; }
161
+ get offset(): number { return this.#lone ? this.#lone.offset : this.packed?.offset ?? this.positions.offset; }
162
+ maxAppendTokens(): number {
163
+ if (this.packed) return this.packed.maxAppendTokens?.() ?? Infinity;
164
+ const end = this.transition.conversionOffset;
165
+ if (end === undefined) return Infinity;
166
+ return Math.min(Infinity, ...this.rowOffsets.map((offset, row) =>
167
+ offset < end && !this.transition.converted(this.rows[row]!) ? end - offset : Infinity));
168
+ }
169
+ get batchSize(): number | null { return this.#lone ? 1 : this.packed?.batchSize ?? this.positions.batchSize; }
170
+ get ropeOffsetArr(): MlxArray | undefined {
171
+ if (this.#lone) return this.#lone.ropeOffsetArr;
172
+ return this.packed ? this.packed.ropeOffsetArr : this.positions.ropeOffsetArr;
173
+ }
174
+ bytesPerToken(): number { return this.packed?.bytesPerToken?.() ?? Math.max(0, ...this.rows.map(row => row.bytesPerToken?.() ?? 0)); }
175
+ projectedBytes(tokens: number): number { return this.bytesPerToken() * tokens; }
176
+ makeMask(tokens: number, window: number | null): Mask { return (this.packed ?? this.positions).makeMask(tokens, window); }
177
+ isTrimmable(): boolean { return this.packed?.isTrimmable() ?? this.rows.every(row => row.isTrimmable()); }
178
+ trim(count: number): void {
179
+ if (this.packed) this.packed.trim(count);
180
+ else { for (const row of this.rows) row.trim(count); this.syncPositions(true); }
181
+ }
182
+ protected syncPositions(preserve = false): void { this.positions.sync(this.rows, preserve); }
183
+ protected advance(): void {
184
+ if (this.packed) return;
185
+ if (!this.#prefilling) this.transition.settle(this.rows);
186
+ if (this.rows.length && this.rows.every(row => this.transition.converted(row))) {
187
+ const packed = this.transition.makeLayout();
188
+ try {
189
+ if (this.transition.packRows) this.transition.packRows(packed, this.rows);
190
+ else packed.mergeRows(this.rows);
191
+ packed.alignRows?.(this.positions.leftPad);
192
+ }
193
+ catch (error) { packed.dispose(); throw error; }
194
+ const previous = this.rows; this.rows = []; this.packed = packed;
195
+ this.positions.dispose(); disposeResources(previous);
196
+ }
197
+ }
198
+ abstract updateAndFetch(k: MlxArray, v: MlxArray): [MlxArray, MlxArray];
199
+ abstract makeEmptyBatch(): TransitioningKvRows<Layout>;
200
+ mergeRows(rows: readonly Cache[]): void {
201
+ const owned: Cache[] = [];
202
+ try {
203
+ for (const row of rows) {
204
+ if (row instanceof TransitioningKvRows) {
205
+ for (let index = 0; index < row.batchSize!; index++) owned.push(row.extractStorageRow(index));
206
+ } else owned.push(...cloneKvCaches([row.composed ? row.composed.storage() : row]));
207
+ }
208
+ } catch (error) { disposeResources(owned); throw error; }
209
+ let prepared: Cache[];
210
+ try { prepared = this.transition.prepareRows?.(owned) ?? owned; }
211
+ catch (error) { disposeResources(owned); throw error; }
212
+ this.dispose(); this.rows = prepared; this.syncPositions();
213
+ }
214
+ filterRows(keep: readonly number[]): void {
215
+ if (this.packed) { this.packed.filterRows(keep); return; }
216
+ const previous = this.rows; this.rows = keep.map(index => previous[index]!);
217
+ disposeResources(previous.filter((_, index) => !keep.includes(index))); this.positions.filterRows(keep);
218
+ }
219
+ /** A row's storage, owned by the caller: what another row layout merges. */
220
+ extractStorageRow(row: number): Cache {
221
+ return this.packed?.extractRow(row) ?? (this.transition.extractRow?.(this.rows[row]!) ?? cloneKvCaches([this.rows[row]!])[0]!);
222
+ }
223
+ /** A row handed back to serial use: its storage, composed again by the lego
224
+ * the row came from (`KvRowTransition.compose`). */
225
+ extractRow(row: number): Cache {
226
+ const storage = this.extractStorageRow(row);
227
+ return this.transition.compose ? this.transition.compose(storage) : storage;
228
+ }
229
+ state(): MlxArray[] {
230
+ return withResource(leaseCacheStates(this.#lone ? [this.#lone] : this.packed ? [this.packed] : this.rows), arrays => {
231
+ const views: MlxArray[] = [];
232
+ try { for (const array of arrays) views.push(ops.contiguous(array)); return views; }
233
+ catch (error) { disposeResources(views); throw error; }
234
+ });
235
+ }
236
+ dispose(): void {
237
+ this.#prefilling = false;
238
+ const lone = this.#lone; this.#lone = null; lone?.dispose();
239
+ const previous = this.#rows; this.#rows = [];
240
+ this.#packed?.dispose(); this.#packed = undefined; this.#positions.dispose(); disposeResources(previous);
241
+ }
242
+ }
243
+
244
+ /** Rollback is a method capability, separate from ordinary cache membership. */
245
+ export abstract class SpeculativeTransitioningKvRows<Layout extends SpeculativeTransitionedKvLayout> extends TransitioningKvRows<Layout> {
246
+ #beforeRound?: number[];
247
+ specRoundBegin(): void {
248
+ this.advance(); this.#beforeRound = [...this.rowOffsets]; this.packed?.specRoundBegin();
249
+ }
250
+ specRoundCommit(): void {
251
+ this.packed?.specRoundCommit(); this.#beforeRound = undefined; this.advance();
252
+ }
253
+ specRoundRollback(keep: number | readonly number[]): void {
254
+ if (this.packed) this.packed.specRoundRollback(keep);
255
+ else {
256
+ for (const [index, row] of this.rows.entries()) {
257
+ const count = typeof keep === "number" ? keep : keep[index]!;
258
+ const before = this.#beforeRound![index]!;
259
+ if (this.transition.rollbackRow) this.transition.rollbackRow(row, before, count, this.rows.length > 1);
260
+ else row.trim(row.offset - before - count);
261
+ }
262
+ this.syncPositions(true);
263
+ }
264
+ this.#beforeRound = undefined; this.advance();
265
+ }
266
+ override dispose(): void { this.#beforeRound = undefined; super.dispose(); }
267
+ }
@@ -0,0 +1,97 @@
1
+ import type { MlxArray } from "@mlx-bun/mlx/array";
2
+ import * as ops from "@mlx-bun/mlx/ops";
3
+ import type { AttentionRead } from "../contracts/mlx/cache";
4
+ import * as tq from "../kernels/turboquant/ops";
5
+ import { sdpaRead, withLease, type MaskLease } from "./attention-read";
6
+
7
+ /** One append's window decoded with its values still in the codec's rotated
8
+ * domain, and the transform that returns an attention output over those
9
+ * values to the values' own domain. The caller owns `keys` and `values`. */
10
+ export interface RotatedWindow {
11
+ readonly keys: MlxArray;
12
+ readonly values: MlxArray;
13
+ /** Borrows an attention output [B, Hq, L, Dv] and returns an owned one. */
14
+ readonly restore: (output: MlxArray) => MlxArray;
15
+ }
16
+
17
+ /** The two ways TurboQuant storage can append one call's keys and values and
18
+ * decode its window. A read calls exactly one of them, once. */
19
+ export interface TurboQuantFetches {
20
+ /** Append, then decode the window with keys and values in their own domain
21
+ * (`updateAndFetch`). The caller owns both. */
22
+ decoded(): [MlxArray, MlxArray];
23
+ /** Append, then decode the window with values left rotated
24
+ * (`updateAndFetchDeferredV`). */
25
+ rotated(): RotatedWindow;
26
+ }
27
+
28
+ /** How a TurboQuant cache attends, fixed when the cache is built, like the
29
+ * affine kernel sets. The cache leases the mask for the phase and offers its
30
+ * fetches; the set decides which fetch runs and what attends over it. Both sets
31
+ * below are a graph's served numbers today, so the graph that composes the
32
+ * cache names its set: `denseTurboQuantKernels` for Qwen 3.5's,
33
+ * `deferredTurboQuantKernels` for Gemma 4's and MiniCPM5's. */
34
+ export interface TurboQuantKernels {
35
+ /** The fetch `read` calls: the window decoded to the values' own domain, or
36
+ * with values left rotated. A compiled decode step decodes the stored prefix
37
+ * in this domain outside its recorded graph (`TurboQuantKVCache`). */
38
+ readonly fetch: "decoded" | "rotated";
39
+ /** Appends through `fetches` and returns the read under `mask`. Takes
40
+ * ownership of `mask` (released if the fetch throws) and of what the fetch
41
+ * returns. */
42
+ read(fetches: TurboQuantFetches, mask: MaskLease): AttentionRead;
43
+ }
44
+
45
+ /** The read over fetched keys and values still in the codec's rotated domain:
46
+ * the stock fused SDPA under the leased mask, then `restore` on its output
47
+ * (the inverse value rotation, f32 through the transform, unless the storage
48
+ * says otherwise). Attention is linear in V, so the caller receives the output
49
+ * in the values' own domain. Takes ownership of `keys`, `values` and `mask`. */
50
+ function rotatedValueRead(keys: MlxArray, values: MlxArray, mask: MaskLease,
51
+ restore: (output: MlxArray) => MlxArray = tq.unrotateValues): AttentionRead {
52
+ return {
53
+ attend(q, scale) {
54
+ const rotated = ops.sdpa(q, keys, values, scale, mask.mask.mode, mask.mask.arr);
55
+ try { return restore(rotated); } finally { rotated.dispose(); }
56
+ },
57
+ dispose() { keys.dispose(); values.dispose(); mask.release(); },
58
+ };
59
+ }
60
+
61
+ /** Decode keys and values back to their own domain, then the stock fused SDPA
62
+ * under the mask. The Qwen graphs (Qwen3, Qwen3-MoE, Qwen 3.5 and the written
63
+ * Trellis graphs) and the universal graph compose this set; it computes their
64
+ * served numbers. */
65
+ export const denseTurboQuantKernels: TurboQuantKernels = Object.freeze({
66
+ fetch: "decoded" as const,
67
+ read: (fetches: TurboQuantFetches, mask: MaskLease) => withLease(mask, held => {
68
+ const [keys, values] = fetches.decoded();
69
+ return sdpaRead(keys, values, held);
70
+ }),
71
+ });
72
+
73
+ /** Leave values rotated, run the stock fused SDPA under the mask on them, then
74
+ * undo the rotation on the output: attention is linear in V, so one inverse
75
+ * rotation per query row replaces one per cached token. The Gemma 4 graphs
76
+ * (generic and written), DiffusionGemma and MiniCPM5 compose this set; it computes their served
77
+ * numbers. */
78
+ export const deferredTurboQuantKernels: TurboQuantKernels = Object.freeze({
79
+ fetch: "rotated" as const,
80
+ read: (fetches: TurboQuantFetches, mask: MaskLease) => withLease(mask, held => {
81
+ const { keys, values, restore } = fetches.rotated();
82
+ return rotatedValueRead(keys, values, held, restore);
83
+ }),
84
+ });
85
+
86
+ /** The set of plain storage whose graph composes no TurboQuant read: every
87
+ * graph but Qwen 3.5's, Gemma 4's and MiniCPM5's reads converted TurboQuant
88
+ * storage through the deprecated fetches, which take no set. A TurboQuant cache
89
+ * converted from such storage serves those fetches; its named reads throw. */
90
+ export const uncomposedTurboQuantKernels: TurboQuantKernels = Object.freeze({
91
+ fetch: "decoded" as const,
92
+ read(_fetches: TurboQuantFetches, mask: MaskLease): AttentionRead {
93
+ mask.release();
94
+ throw new Error("this TurboQuant cache was converted from storage whose graph composed no TurboQuant read: " +
95
+ "the graph's makeCache passes denseTurboQuantKernels or deferredTurboQuantKernels (state/turboquant-attention.ts) to its KVCache");
96
+ },
97
+ });