@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,389 @@
1
+ // Batched QUANTIZED KV for continuous batching — the quantized twins of
2
+ // batched-mask.ts's dynamic-B ops, over (packed, scales, biases) triples.
3
+ //
4
+ // Why this is safe surgery: mlx quantization packs along HEAD_DIM (the last
5
+ // axis; packed last-dim = D/(32/bits), scales/biases last-dim = D/groupSize),
6
+ // so the TOKEN axis (axis 2) stays token-granular in all three tensors —
7
+ // left-padding, concat, and row-filter at token granularity never touch a
8
+ // quantization group. Pad columns hold zeros; they are never attended (the
9
+ // padding mask covers them) and never dequantized into anything a real row
10
+ // reads. QuantizedKVCache.updateAndFetchQuantized already handles arbitrary
11
+ // starting widths (it trims to `offset` and regrows in STEP chunks), so a
12
+ // merged exact-width buffer needs no STEP alignment here.
13
+ //
14
+ // Composition contract (the L2 oracle binds per ROW): each joiner prefills
15
+ // alone through the per-layer conversion its bf16-first lego runs (the
16
+ // conversion at the scheme's start), so a row's quantized bytes are bit-exact
17
+ // vs the same request run alone under `--kv-quant config` by construction;
18
+ // this module only re-arranges those bytes across the batch axis. Gates: `02d723a:tests/parity/batched-kv-quant-parity.test.ts`.
19
+
20
+ import { MlxArray } from "@mlx-bun/mlx/array";
21
+ import * as ops from "@mlx-bun/mlx/ops";
22
+ import { QuantizedKVCache } from "./quantized-kv";
23
+ import { type AttentionCache, type AttentionRead, type BatchableCache, type Cache, type Mask } from "../contracts/mlx/cache";
24
+ import type { QuantizedAttentionState } from "./kv-storage";
25
+ import { buildBatchedDecodeMask } from "./batched-mask";
26
+ import { isQuantizedKvCache } from "./capabilities";
27
+ import { runtimeValue } from "../runtime/config";
28
+ import { AttentionMasks, withLease } from "./attention-read";
29
+ import { affineRead, rowLayoutKernels, type AffineKernel, type AffineKernels } from "./affine-attention";
30
+
31
+ export interface QuantRow {
32
+ keys: ops.QuantizedTensor;
33
+ values: ops.QuantizedTensor;
34
+ }
35
+
36
+ const disposeTriple = (t: ops.QuantizedTensor): void => {
37
+ t.packed.dispose();
38
+ t.scales.dispose();
39
+ t.biases.dispose();
40
+ };
41
+
42
+ /** Apply an MlxArray op to each member of a triple. Each tensor keeps its
43
+ * OWN last-dim (packed vs scales/biases differ) — helpers must never share
44
+ * one `D` across the triple (recon hazard, 2026-07-05). */
45
+ const tripleMap = (
46
+ t: ops.QuantizedTensor, f: (a: MlxArray) => MlxArray,
47
+ ): ops.QuantizedTensor => ({ packed: f(t.packed), scales: f(t.scales), biases: f(t.biases) });
48
+
49
+ /** Left-pad one tensor of a triple by `pad` tokens (axis 2) with zeros of
50
+ * its own dtype/last-dim. pad=0 returns a fresh full view (mirrors
51
+ * batched-mask.ts leftPadTo so ownership is uniform). */
52
+ const leftPadTo = (a: MlxArray, pad: number): MlxArray => {
53
+ if (pad === 0) return a.slice([0, 0, 0, 0], a.shape as number[]);
54
+ const [B, H, , D] = a.shape as [number, number, number, number];
55
+ const z = ops.zeros([B, H, pad, D], a.dtype);
56
+ const out = ops.concatAxis([z, a], 2);
57
+ z.dispose();
58
+ return out;
59
+ };
60
+
61
+ /** Quantized twin of mergeKVRows: stack N single-row quantized KV slices
62
+ * into one left-padded [B,H,Smax,*] triple pair. Caller owns the result;
63
+ * input rows are not disposed. */
64
+ export function mergeQuantRows(rows: QuantRow[]): {
65
+ keys: ops.QuantizedTensor; values: ops.QuantizedTensor; leftPad: number[]; width: number;
66
+ } {
67
+ const lens = rows.map((r) => r.keys.packed.shape[2]!);
68
+ const width = Math.max(...lens);
69
+ const leftPad = lens.map((l) => width - l);
70
+ const padded = rows.map((r, i) => ({
71
+ keys: tripleMap(r.keys, (a) => leftPadTo(a, leftPad[i]!)),
72
+ values: tripleMap(r.values, (a) => leftPadTo(a, leftPad[i]!)),
73
+ }));
74
+ const cat = (pick: (r: QuantRow) => ops.QuantizedTensor): ops.QuantizedTensor => ({
75
+ packed: ops.concatAxis(padded.map((r) => pick(r).packed), 0),
76
+ scales: ops.concatAxis(padded.map((r) => pick(r).scales), 0),
77
+ biases: ops.concatAxis(padded.map((r) => pick(r).biases), 0),
78
+ });
79
+ const keys = cat((r) => r.keys);
80
+ const values = cat((r) => r.values);
81
+ for (const r of padded) { disposeTriple(r.keys); disposeTriple(r.values); }
82
+ return { keys, values, leftPad, width };
83
+ }
84
+
85
+ /** Quantized twin of extendKVRows: append ONE right-justified quantized row
86
+ * to an existing batched triple pair in one pad + one B-axis concat.
87
+ * Existing pads grow, never shrink (mlx-lm extend semantics). Caller owns
88
+ * the result; inputs are not disposed. */
89
+ export function extendQuantRows(
90
+ keys: ops.QuantizedTensor, values: ops.QuantizedTensor, leftPad: number[], row: QuantRow,
91
+ ): { keys: ops.QuantizedTensor; values: ops.QuantizedTensor; leftPad: number[]; width: number } {
92
+ const S = keys.packed.shape[2]!;
93
+ const Lr = row.keys.packed.shape[2]!;
94
+ const width = Math.max(S, Lr);
95
+ const bk = tripleMap(keys, (a) => leftPadTo(a, width - S));
96
+ const bv = tripleMap(values, (a) => leftPadTo(a, width - S));
97
+ const rk = tripleMap(row.keys, (a) => leftPadTo(a, width - Lr));
98
+ const rv = tripleMap(row.values, (a) => leftPadTo(a, width - Lr));
99
+ const cat = (b: ops.QuantizedTensor, r: ops.QuantizedTensor): ops.QuantizedTensor => ({
100
+ packed: ops.concatAxis([b.packed, r.packed], 0),
101
+ scales: ops.concatAxis([b.scales, r.scales], 0),
102
+ biases: ops.concatAxis([b.biases, r.biases], 0),
103
+ });
104
+ const outK = cat(bk, rk);
105
+ const outV = cat(bv, rv);
106
+ for (const t of [bk, bv, rk, rv]) disposeTriple(t);
107
+ return {
108
+ keys: outK, values: outV,
109
+ leftPad: [...leftPad.map((p) => p + (width - S)), width - Lr],
110
+ width,
111
+ };
112
+ }
113
+
114
+ /** Quantized twin of extractKVRow (mlx-lm BatchKVCache.extract,
115
+ * models/cache.py:1080, over triples): pull row `i` past its left padding
116
+ * into a fresh SERIAL QuantizedKVCache with OWNED contiguous copies.
117
+ * Token-axis slicing is byte-safe because quantization packs along
118
+ * HEAD_DIM (file header) — a row's quantized bytes come out exactly as
119
+ * the serial oracle wrote them. */
120
+ export function extractQuantRow(
121
+ cache: QuantizedKVCache, leftPad: number, i: number, kernels: AffineKernels = cache.kernels,
122
+ ): QuantizedKVCache {
123
+ if (!cache.keys || !cache.values) throw new Error("extractQuantRow: empty cache");
124
+ const S = cache.offset;
125
+ const cut = (t: ops.QuantizedTensor): ops.QuantizedTensor =>
126
+ tripleMap(t, (a) => {
127
+ const [, H, , D] = a.shape as [number, number, number, number];
128
+ const view = a.slice([i, 0, leftPad, 0], [i + 1, H, S, D]);
129
+ const own = ops.copyOf(view); // TRUE copy: contiguous(view) is a no-op VIEW when already contiguous — pins the source buffer (2026-08-20 DeltaNet conv leak class)
130
+ view.dispose();
131
+ return own;
132
+ });
133
+ const out = new QuantizedKVCache(cache.groupSize, cache.bits, kernels);
134
+ out.restoreState(cut(cache.keys), cut(cache.values), S - leftPad);
135
+ return out;
136
+ }
137
+
138
+ /** Quantized twin of filterKVRows: keep `keep` rows along the batch axis.
139
+ * Caller owns the result; inputs are not disposed. */
140
+ export function filterQuantRows(
141
+ keys: ops.QuantizedTensor, values: ops.QuantizedTensor, keep: number[], trimLeft = 0,
142
+ ): { keys: ops.QuantizedTensor; values: ops.QuantizedTensor } {
143
+ const idx = MlxArray.fromInt32(Int32Array.from(keep), [keep.length]);
144
+ const take = (t: ops.QuantizedTensor): ops.QuantizedTensor =>
145
+ tripleMap(t, (a) => {
146
+ const rows = ops.takeAxis(a, idx, 0);
147
+ if (trimLeft === 0) return rows;
148
+ const trimmed = rows.slice([0, 0, trimLeft, 0], rows.shape);
149
+ rows.dispose();
150
+ return trimmed;
151
+ });
152
+ const k = take(keys);
153
+ const v = take(values);
154
+ idx.dispose();
155
+ return { keys: k, values: v };
156
+ }
157
+
158
+ /** Per-step cache wrapper for batched QUANTIZED decode with left-padded
159
+ * rows — the quantized twin of BatchedDecodeMaskCache. Subclasses
160
+ * QuantizedKVCache so attention's `instanceof` dispatch routes to
161
+ * quantizedSdpa exactly as for a serial quantized cache; delegates KV
162
+ * storage to the persistent batched inner and overrides the two things a
163
+ * padded batch needs: the padding-aware decode mask and per-row RoPE
164
+ * positions. Ephemeral — the scheduler builds a fresh wrapper each step
165
+ * and calls releaseRopeArr() after (never dispose(), which would free the
166
+ * inner). Rope is captured ONCE per step before updateAndFetch (attention
167
+ * reads it pre-write; see packages/inference/src/models/minicpm5/model.ts
168
+ * LlamaAttention.forward), so an
169
+ * eagerly-built [B] position array is exact.
170
+ *
171
+ * Reads: append to the inner storage, then the inner cache's kernel for the
172
+ * read under the padding-aware mask (`buildBatchedDecodeMask`) for the query
173
+ * count, built once per forward through the inner cache's `masks`. That mask
174
+ * is an array, so a window takes the masked-window kernel. */
175
+ export class BatchedQuantDecodeMaskCache extends QuantizedKVCache {
176
+ override readonly ropeOffsetArr: MlxArray;
177
+
178
+ constructor(
179
+ private readonly inner: QuantizedKVCache,
180
+ private readonly B: number,
181
+ private readonly leftPad: number[],
182
+ ) {
183
+ super(inner.groupSize, inner.bits, inner.kernels, inner.masks);
184
+ this.offset = inner.offset;
185
+ const data = new Int32Array(B);
186
+ for (let b = 0; b < B; b++) data[b] = inner.offset - leftPad[b]!;
187
+ this.ropeOffsetArr = MlxArray.fromInt32(data, [B]);
188
+ }
189
+
190
+ override appendDecode(k: MlxArray, v: MlxArray): AttentionRead { return this.#read(k, v, this.kernels.decode); }
191
+
192
+ override appendWindow(k: MlxArray, v: MlxArray): AttentionRead { return this.#read(k, v, this.kernels.maskedWindow); }
193
+
194
+ #read(k: MlxArray, v: MlxArray, kernel: AffineKernel): AttentionRead {
195
+ const N = k.shape[2]!, S = this.inner.offset + N;
196
+ const mask = this.masks.lease("padded-kv", `${N}|${S}|${this.leftPad.join(",")}`,
197
+ () => ({ mode: "array", arr: buildBatchedDecodeMask(this.B, N, S, this.leftPad, null) }));
198
+ return withLease(mask, held => {
199
+ const [keys, values] = this.updateAndFetchQuantized(k, v);
200
+ return affineRead(keys, values, held, kernel);
201
+ });
202
+ }
203
+
204
+ override updateAndFetchQuantized(k: MlxArray, v: MlxArray): [ops.QuantizedTensor, ops.QuantizedTensor] {
205
+ const r = this.inner.updateAndFetchQuantized(k, v);
206
+ this.offset = this.inner.offset;
207
+ return r;
208
+ }
209
+
210
+ override makeMask(N: number, _windowSize: number | null): Mask {
211
+ // Same convention as BatchedDecodeMaskCache: built pre-write, S spans
212
+ // offset + N; quantizedSdpaUnfused expands it for the 5-D GQA scores.
213
+ const S = this.inner.offset + N;
214
+ return { mode: "array", arr: buildBatchedDecodeMask(this.B, N, S, this.leftPad, null) };
215
+ }
216
+
217
+ /** Free the per-step RoPE array WITHOUT touching the persistent inner —
218
+ * name and signature must match the scheduler's structural
219
+ * `(c as { releaseRopeArr?: () => void })` cast. */
220
+ releaseRopeArr(): void {
221
+ this.ropeOffsetArr.dispose();
222
+ }
223
+
224
+ override state(): MlxArray[] {
225
+ return this.inner.state();
226
+ }
227
+ override isTrimmable(): boolean {
228
+ return this.inner.isTrimmable();
229
+ }
230
+ override trim(n: number): void {
231
+ this.inner.trim(n);
232
+ }
233
+ override dispose(): void {
234
+ this.inner.dispose();
235
+ }
236
+ }
237
+
238
+ /** The quantized twin of PaddedKVRows: a running batch's affine-quantized
239
+ * full-attention KV as (packed, scales, biases) triples with right-aligned rows.
240
+ * Its rows were converted by the serial per-layer maintenance before they
241
+ * joined, so their quantized bytes are the serial cache's; this layout only
242
+ * re-arranges them along the batch axis (`extendQuantRows`, or the re-merge
243
+ * with MLX_BUN_BATCH_EXTEND=0) and, while any row is padded, supplies the
244
+ * padding-aware mask and per-row RoPE positions. Sources are borrowed: a
245
+ * running layout or one serial QuantizedKVCache, then one per joining row.
246
+ *
247
+ * Reads: while no row is padded, the serial cache's reads; while one is, the
248
+ * step wrapper's (`BatchedQuantDecodeMaskCache`): the same kernels under the
249
+ * padding-aware mask, with the masked-window kernel for a window. The kernels
250
+ * are `kernels` while the layout holds one unpadded row and its `multiRow` set
251
+ * otherwise (`rowLayoutKernels`), chosen when the rows change; rows it
252
+ * extracts keep `kernels`. */
253
+ export class PaddedQuantKVRows implements AttentionCache, BatchableCache {
254
+ #inner: QuantizedKVCache;
255
+ #leftPad: number[] = [];
256
+ #padded = false;
257
+ #view: BatchedQuantDecodeMaskCache | null = null;
258
+
259
+ constructor(
260
+ readonly groupSize: number,
261
+ readonly bits: number,
262
+ /** The kernel of each read; empty batches and extracted rows keep them. */
263
+ readonly kernels: AffineKernels,
264
+ /** The per-forward masks of the model this layout serves. */
265
+ readonly masks = new AttentionMasks(),
266
+ ) { this.#inner = this.#storage([]); }
267
+
268
+ appendDecode(k: MlxArray, v: MlxArray): AttentionRead {
269
+ return (this.#padded ? this.#step() : this.#inner).appendDecode(k, v);
270
+ }
271
+
272
+ appendWindow(k: MlxArray, v: MlxArray): AttentionRead {
273
+ return (this.#padded ? this.#step() : this.#inner).appendWindow(k, v);
274
+ }
275
+
276
+ /** Empty serial storage that reads with this layout's masks and the kernels for rows at `leftPad`. */
277
+ #storage(leftPad: readonly number[]): QuantizedKVCache {
278
+ return new QuantizedKVCache(this.groupSize, this.bits, rowLayoutKernels(this.kernels, leftPad.length <= 1 && !leftPad[0]), this.masks);
279
+ }
280
+
281
+ signature(): string { return `kv:padded-rows-quant:${this.bits}:${this.groupSize}`; }
282
+ get batchSize(): number | null { return this.#leftPad.length || null; }
283
+ get leftPad(): readonly number[] { return this.#leftPad; }
284
+ get rowOffsets(): readonly number[] { return this.#leftPad.map((pad) => this.#inner.offset - pad); }
285
+ get offset(): number { return this.#inner.offset; }
286
+ /** Attention reads and appends the quantized planes directly. */
287
+ get quantizedAttention(): QuantizedAttentionState { return this.#inner; }
288
+ /** Present only while a row is padded: each row's own position. */
289
+ get ropeOffsetArr(): MlxArray | undefined { return this.#padded ? this.#step().ropeOffsetArr : undefined; }
290
+ makeEmptyBatch(): PaddedQuantKVRows { return new PaddedQuantKVRows(this.groupSize, this.bits, this.kernels, this.masks); }
291
+
292
+ bytesPerToken(): number {
293
+ return this.#leftPad.length ? this.#inner.bytesPerToken() / this.#leftPad.length : 0;
294
+ }
295
+ projectedBytes(tokens: number): number { return this.bytesPerToken() * tokens; }
296
+
297
+ /** One wrapper per step: its RoPE positions are fixed at construction, before the step's writes. */
298
+ #step(): BatchedQuantDecodeMaskCache {
299
+ return this.#view ??= new BatchedQuantDecodeMaskCache(this.#inner, this.#leftPad.length, [...this.#leftPad]);
300
+ }
301
+ updateAndFetch(k: MlxArray, v: MlxArray): [MlxArray, MlxArray] { return this.#inner.updateAndFetch(k, v); }
302
+ makeMask(N: number, windowSize: number | null): Mask {
303
+ return this.#padded ? this.#step().makeMask(N, windowSize) : this.#inner.makeMask(N, windowSize);
304
+ }
305
+ /** Free this step's RoPE positions after the step's graph is built; the KV stays. */
306
+ releaseRopeArr(): void { this.#view?.releaseRopeArr(); this.#view = null; }
307
+ state(): MlxArray[] { return this.#inner.state(); }
308
+ isTrimmable(): boolean { return this.#inner.isTrimmable(); }
309
+ trim(n: number): void { this.#inner.trim(n); }
310
+
311
+ #set(inner: QuantizedKVCache, leftPad: number[]): void {
312
+ this.releaseRopeArr();
313
+ this.#inner = inner; this.#leftPad = leftPad; this.#padded = leftPad.some((pad) => pad !== 0);
314
+ }
315
+
316
+ mergeRows(rows: readonly Cache[]): void {
317
+ if (this.#leftPad.length) throw new Error("PaddedQuantKVRows.mergeRows needs an empty batch");
318
+ if (!rows.length) return;
319
+ const held: ops.QuantizedTensor[] = [];
320
+ const own = (triple: ops.QuantizedTensor): ops.QuantizedTensor => { held.push(triple); return triple; };
321
+ type Part = { row: QuantRow; pads: number[] };
322
+ const partOf = (cache: Cache): Part => {
323
+ if (cache instanceof PaddedQuantKVRows) {
324
+ const [keys, values] = cache.#inner.temporalView();
325
+ return { row: { keys: own(keys), values: own(values) }, pads: [...cache.#leftPad] };
326
+ }
327
+ if (!isQuantizedKvCache(cache)) throw new Error(`quantized full-attention batch cannot merge ${cache.signature()}`);
328
+ const [keys, values] = cache.temporalView();
329
+ return { row: { keys: own(keys), values: own(values) }, pads: [0] };
330
+ };
331
+ const cut = (triple: ops.QuantizedTensor, b: number, pad: number): ops.QuantizedTensor => own(tripleMap(triple, (a) =>
332
+ a.slice([b, 0, pad, 0], [b + 1, a.shape[1]!, a.shape[2]!, a.shape[3]!])));
333
+ const extend = runtimeValue("MLX_BUN_BATCH_EXTEND") !== "0";
334
+ let final: { keys: ops.QuantizedTensor; values: ops.QuantizedTensor } | undefined;
335
+ try {
336
+ let acc = partOf(rows[0]!);
337
+ if (rows.length === 1) {
338
+ const single = mergeQuantRows(acc.pads.map((pad, b) => ({ keys: cut(acc.row.keys, b, pad), values: cut(acc.row.values, b, pad) })));
339
+ acc = { row: { keys: own(single.keys), values: own(single.values) }, pads: single.leftPad };
340
+ }
341
+ for (const source of rows.slice(1)) {
342
+ const next = partOf(source);
343
+ if (next.pads.length !== 1) throw new Error("PaddedQuantKVRows joins one solo row at a time");
344
+ let joined: { keys: ops.QuantizedTensor; values: ops.QuantizedTensor; leftPad: number[] };
345
+ if (extend) {
346
+ joined = extendQuantRows(acc.row.keys, acc.row.values, acc.pads, next.row);
347
+ } else {
348
+ const rowsOut = acc.pads.map((pad, b) => ({ keys: cut(acc.row.keys, b, pad), values: cut(acc.row.values, b, pad) }));
349
+ joined = mergeQuantRows([...rowsOut, next.row]);
350
+ }
351
+ acc = { row: { keys: own(joined.keys), values: own(joined.values) }, pads: joined.leftPad };
352
+ }
353
+ final = acc.row;
354
+ held.splice(held.indexOf(final.keys), 1); held.splice(held.indexOf(final.values), 1);
355
+ const cache = this.#storage(acc.pads);
356
+ cache.restoreState(final.keys, final.values, final.keys.packed.shape[2]!);
357
+ final = undefined;
358
+ this.#set(cache, acc.pads);
359
+ } finally {
360
+ for (const triple of held) disposeTriple(triple);
361
+ if (final) { disposeTriple(final.keys); disposeTriple(final.values); }
362
+ }
363
+ }
364
+
365
+ extractRow(row: number): Cache {
366
+ return this.#inner.keys ? extractQuantRow(this.#inner, this.#leftPad[row]!, row, this.kernels)
367
+ : new QuantizedKVCache(this.groupSize, this.bits, this.kernels);
368
+ }
369
+
370
+ filterRows(keep: readonly number[]): void {
371
+ if (!keep.length) { this.dispose(); return; }
372
+ const kept = keep.map((row) => this.#leftPad[row]!);
373
+ const shared = Math.min(...kept); // padding every survivor shares
374
+ const [keys, values] = this.#inner.temporalView();
375
+ let filtered: { keys: ops.QuantizedTensor; values: ops.QuantizedTensor };
376
+ try { filtered = filterQuantRows(keys, values, [...keep], shared); }
377
+ finally { disposeTriple(keys); disposeTriple(values); }
378
+ const pads = kept.map((pad) => pad - shared);
379
+ const cache = this.#storage(pads);
380
+ cache.restoreState(filtered.keys, filtered.values, this.#inner.offset - shared);
381
+ this.#inner.dispose();
382
+ this.#set(cache, pads);
383
+ }
384
+
385
+ dispose(): void {
386
+ this.#inner.dispose();
387
+ this.#set(this.#storage([]), []);
388
+ }
389
+ }
@@ -0,0 +1,209 @@
1
+ import type { MlxArray } from "@mlx-bun/mlx/array";
2
+ import * as ops from "@mlx-bun/mlx/ops";
3
+ import { cleanupFailure, disposeResources } from "../runtime/resources";
4
+ import { BatchedKVCache } from "./batched-kv";
5
+ import { KVCache } from "./kv";
6
+ import { QuantizedKVCache } from "./quantized-kv";
7
+ import { isQuantizedKvCache } from "./capabilities";
8
+ import { type AttentionRead, type BatchableCache, type Cache, type CommittedAttentionCache, type Mask, type PaddedPrefillCache, type PrefillPadding } from "../contracts/mlx/cache";
9
+ import type { QuantizedAttentionState } from "./kv-storage";
10
+
11
+ import { KvTensorRows } from "./kv-tensor-rows";
12
+ import { quantizedDonorAttention } from "./kv-attention-view";
13
+ import { dequantizedFetch } from "./quantized-tensor";
14
+ import { AttentionMasks, unmaskedLease, withLease, type MaskLease } from "./attention-read";
15
+ import { affineCausalLease, affineRead, committedAffineRead, rowLayoutKernels, type AffineKernel, type AffineKernels } from "./affine-attention";
16
+
17
+ const FIELDS = ["packed", "scales", "biases"] as const;
18
+
19
+ /** Affine KV uses the same row-position storage operations for its packed
20
+ * values, scales and biases. No dequantization or requantization is needed
21
+ * for merge, rollback, retirement or extraction. Inputs share this codec.
22
+ *
23
+ * Reads: append through `updateAndFetchQuantized`, then the kernel `kernels`
24
+ * fixed for the read at construction. While every row is unpadded and at one
25
+ * position, decode passes no mask and a window the bottom-right causal matrix,
26
+ * as `makeMask` leads the graphs to today; otherwise both pass the per-row mask
27
+ * (causal, past each row's left padding) for their query count, built once per
28
+ * forward through `masks`, and a window takes the masked-window kernel. The
29
+ * kernels are `kernels` while the layout holds one unpadded row and its
30
+ * `multiRow` set otherwise (`rowLayoutKernels`); rows it extracts keep
31
+ * `kernels`. The committed read is `quantizedAppendAttention`, for one row. */
32
+ export class BatchedQuantizedKVCache implements BatchableCache, QuantizedAttentionState, PaddedPrefillCache, CommittedAttentionCache {
33
+ #planes = FIELDS.map(() => new BatchedKVCache());
34
+ #minimum: number[] = [];
35
+ get minimumReusableOffset(): number { return Math.max(0, ...this.#minimum); }
36
+ constructor(
37
+ readonly groupSize: number,
38
+ readonly bits: number,
39
+ /** The kernel of each read; empty batches and extracted rows keep them. */
40
+ readonly kernels: AffineKernels,
41
+ /** The per-forward masks of the model this layout serves. */
42
+ readonly masks = new AttentionMasks(),
43
+ ) {}
44
+
45
+ appendDecode(k: MlxArray, v: MlxArray): AttentionRead {
46
+ return this.#read(k, v, this.#planes[0]!.aligned ? unmaskedLease : this.#rowMask(1), this.#kernels().decode);
47
+ }
48
+
49
+ appendWindow(k: MlxArray, v: MlxArray): AttentionRead {
50
+ const L = k.shape[2]!;
51
+ return this.#planes[0]!.aligned
52
+ ? this.#read(k, v, affineCausalLease(this.masks, L, this.offset + L), this.#kernels().window)
53
+ : this.#read(k, v, this.#rowMask(L), this.#kernels().maskedWindow);
54
+ }
55
+
56
+ /** `kernels` while this layout holds one unpadded row, its `multiRow` set otherwise. */
57
+ #kernels(): AffineKernels {
58
+ return rowLayoutKernels(this.kernels, this.batchSize === 1 && this.#planes[0]!.aligned);
59
+ }
60
+
61
+ /** Fill spans run on this layout (the target layout of a full-attention
62
+ * layer): the graph's independent-rows path. */
63
+ appendCommitted(k: MlxArray, v: MlxArray): AttentionRead {
64
+ const [keys, values] = this.updateAndFetchQuantized(k, v);
65
+ return committedAffineRead(keys, values, this.groupSize, this.bits);
66
+ }
67
+
68
+ #read(k: MlxArray, v: MlxArray, mask: MaskLease, kernel: AffineKernel): AttentionRead {
69
+ return withLease(mask, held => {
70
+ const [keys, values] = this.updateAndFetchQuantized(k, v);
71
+ return affineRead(keys, values, held, kernel);
72
+ });
73
+ }
74
+
75
+ /** The per-row mask of an `N`-position append, before it: the bf16 layout's
76
+ * (`BatchedKVCache`), whose positions every plane shares. */
77
+ #rowMask(N: number): MaskLease {
78
+ return this.masks.lease("batched-kv", `${N}|${this.rowOffsets.join(",")}|${this.leftPad.join(",")}`,
79
+ () => this.#planes[0]!.makeMask(N, null));
80
+ }
81
+ restorePrefillEnds(ends: readonly number[] | undefined): void { for (const plane of this.#planes) plane.restorePrefillEnds(ends); }
82
+ preparePrefill(padding: PrefillPadding): void {
83
+ for (const plane of this.#planes) plane.preparePrefill(padding);
84
+ if (!this.#minimum.length) this.#minimum = padding.lengths.map(() => 0);
85
+ }
86
+ finalizePrefill(): void { for (const plane of this.#planes) plane.finalizePrefill(); }
87
+ get quantizedAttention(): QuantizedAttentionState { return this; }
88
+ captureDonorAttention(): import("../contracts/mlx/cache").KvDonorAttention {
89
+ const views: import("../contracts/mlx/cache").KvDonorRows[] = [];
90
+ try {
91
+ for (const plane of this.#planes) views.push(plane.captureDonorRows());
92
+ const field = (name: "keys" | "values") => ({
93
+ packed: views[0]![name], scales: views[1]![name], biases: views[2]![name],
94
+ });
95
+ const { offsets, starts, ends } = views[0]!;
96
+ // The kernels this layout reads with for its rows (a one-sequence set
97
+ // reads several rows with its `multiRow` set).
98
+ return quantizedDonorAttention(field("keys"), field("values"), { offsets, starts, ends }, this.#kernels());
99
+ } catch (error) { disposeResources(views.flatMap(view => [view.keys, view.values])); throw error; }
100
+ }
101
+ signature(): string { return `kv:batched-quant:${this.bits}:${this.groupSize}`; }
102
+ get rowOffsets(): readonly number[] { return this.#planes[0]!.rowOffsets; }
103
+ get leftPad(): readonly number[] { return this.#planes[0]!.leftPad; }
104
+ get batchSize(): number | null { return this.#planes[0]!.batchSize; }
105
+ get offset(): number { return this.#planes[0]!.offset; }
106
+ get ropeOffsetArr(): MlxArray | undefined { return this.#planes[0]!.ropeOffsetArr; }
107
+ makeEmptyBatch(): BatchedQuantizedKVCache { return new BatchedQuantizedKVCache(this.groupSize, this.bits, this.kernels, this.masks); }
108
+ bytesPerToken(): number { return this.#planes.reduce((n, plane) => n + plane.bytesPerToken(), 0); }
109
+ projectedBytes(tokens: number): number { return this.bytesPerToken() * tokens; }
110
+ makeMask(tokens: number, window: number | null): Mask { return this.#planes[0]!.makeMask(tokens, window); }
111
+ isTrimmable(): boolean { return true; }
112
+ trim(count: number): void { for (const plane of this.#planes) plane.trim(count); }
113
+ specRoundBegin(): void { for (const plane of this.#planes) plane.specRoundBegin(); }
114
+ specRoundCommit(): void { for (const plane of this.#planes) plane.specRoundCommit(); }
115
+ specRoundRollback(keep: number | readonly number[]): void {
116
+ for (const plane of this.#planes) plane.specRoundRollback(keep);
117
+ }
118
+ /** The dense fetch: appends, then the rows' stored windows dequantized. */
119
+ updateAndFetch(k: MlxArray, v: MlxArray): [MlxArray, MlxArray] {
120
+ return dequantizedFetch(this.updateAndFetchQuantized(k, v), this.groupSize, this.bits);
121
+ }
122
+
123
+ updateAndFetchQuantized(k: MlxArray, v: MlxArray): [ops.QuantizedTensor, ops.QuantizedTensor] {
124
+ using inputs = new DisposableStack();
125
+ const key = ops.quantize(k, this.groupSize, this.bits);
126
+ for (const field of FIELDS) inputs.use(key[field]);
127
+ const value = ops.quantize(v, this.groupSize, this.bits);
128
+ for (const field of FIELDS) inputs.use(value[field]);
129
+ const held: MlxArray[] = [], keys: Partial<ops.QuantizedTensor> = {}, values: Partial<ops.QuantizedTensor> = {};
130
+ try {
131
+ for (const [index, field] of FIELDS.entries()) {
132
+ const [kk, vv] = this.#planes[index]!.updateAndFetch(key[field], value[field]);
133
+ held.push(kk, vv); keys[field] = kk; values[field] = vv;
134
+ }
135
+ return [keys as ops.QuantizedTensor, values as ops.QuantizedTensor];
136
+ } catch (error) { return cleanupFailure(error, () => disposeResources(held)); }
137
+ }
138
+
139
+ /** Borrow prepared request state. All planes stage replacements before
140
+ * releasing the current group, preserving donor ownership on failure. */
141
+ mergeRows(rows: readonly Cache[]): void {
142
+ const next: BatchedKVCache[] = [];
143
+ try {
144
+ for (const [index, field] of FIELDS.entries()) {
145
+ const held: KVCache[] = [], inputs: Cache[] = [];
146
+ try {
147
+ for (const row of rows) {
148
+ if (row instanceof BatchedQuantizedKVCache) inputs.push(row.#planes[index]!);
149
+ else if (isQuantizedKvCache(row)) {
150
+ const view = new KVCache(); held.push(view);
151
+ if (row.offset > 0) {
152
+ const key = ops.copyOf(row.keys![field]);
153
+ let value: MlxArray;
154
+ try { value = ops.copyOf(row.values![field]); }
155
+ catch (error) { key.dispose(); throw error; }
156
+ view.restoreState(key, value, row.offset);
157
+ }
158
+ inputs.push(view);
159
+ } else throw new Error(`quantized row layout cannot merge ${row.signature()}`);
160
+ }
161
+ const plane = new BatchedKVCache(); next.push(plane); plane.mergeRows(inputs);
162
+ } finally { disposeResources(held); }
163
+ }
164
+ } catch (error) { return cleanupFailure(error, () => disposeResources(next)); }
165
+ const previous = this.#planes; this.#planes = next; disposeResources(previous);
166
+ this.#minimum = rows.flatMap(row => row instanceof BatchedQuantizedKVCache ? row.#minimum : [row.minimumReusableOffset ?? 0]);
167
+ }
168
+
169
+ alignRows(leftPad: readonly number[]): void {
170
+ if (leftPad.every((pad, row) => pad === this.leftPad[row])) return;
171
+ const aligned = new KvTensorRows();
172
+ try {
173
+ aligned.mergeRows([{ planes: this.#planes.flatMap(plane => plane.keys ? [plane.keys, plane.values!] : []),
174
+ rowOffsets: this.rowOffsets, leftPad: this.leftPad }]);
175
+ aligned.alignRows(leftPad);
176
+ for (const plane of this.#planes) {
177
+ plane.keys?.dispose(); plane.values?.dispose();
178
+ plane.keys = aligned.planes.shift() ?? null; plane.values = aligned.planes.shift() ?? null;
179
+ plane.leftPad = [...leftPad];
180
+ }
181
+ } finally { aligned.dispose(); }
182
+ }
183
+ filterRows(keep: readonly number[]): void {
184
+ for (const plane of this.#planes) plane.filterRows(keep);
185
+ this.#minimum = keep.map(row => this.#minimum[row]!);
186
+ }
187
+ state(): MlxArray[] {
188
+ if (!this.#planes[0]!.keys) return [];
189
+ return [...this.#planes.map(plane => plane.keys!), ...this.#planes.map(plane => plane.values!)];
190
+ }
191
+
192
+ /** Return the existing persisted representation, owning compact arrays. */
193
+ extractRow(row: number): QuantizedKVCache {
194
+ const offset = this.rowOffsets[row]!, result = new QuantizedKVCache(this.groupSize, this.bits, this.kernels);
195
+ if (this.#minimum[row]) result.minimumReusableOffset = this.#minimum[row];
196
+ if (offset === 0) return result;
197
+ const held: KVCache[] = [];
198
+ try {
199
+ for (const plane of this.#planes) held.push(plane.extractRow(row));
200
+ result.restoreState(
201
+ { packed: held[0]!.keys!, scales: held[1]!.keys!, biases: held[2]!.keys! },
202
+ { packed: held[0]!.values!, scales: held[1]!.values!, biases: held[2]!.values! }, offset);
203
+ for (const cache of held) cache.keys = cache.values = null;
204
+ return result;
205
+ } finally { disposeResources(held); }
206
+ }
207
+
208
+ dispose(): void { disposeResources(this.#planes); this.#minimum = []; this.masks.clear(); }
209
+ }