@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,215 @@
1
+ import type { MlxArray } from "@mlx-bun/mlx/array";
2
+ import { Dtype } from "@mlx-bun/mlx/ffi";
3
+ import * as ops from "@mlx-bun/mlx/ops";
4
+ import type { AttentionCache, AttentionRead, Mask } from "../contracts/mlx/cache";
5
+ import { bidirMask } from "../kernels/attention/masks";
6
+ import { foldedSdpa, foldedSdpaEligible } from "../layers/folded-attention";
7
+ import { disposeResources } from "../runtime/resources";
8
+
9
+ /** A mask held by one attention read until that read is disposed. */
10
+ export interface MaskLease {
11
+ readonly mask: Mask;
12
+ /** Drops this read's hold on the mask. Call exactly once. */
13
+ release(): void;
14
+ }
15
+
16
+ /** No mask: one query over every stored position. Nothing to release. */
17
+ export const unmaskedLease: MaskLease = { mask: { mode: "", arr: null }, release() {} };
18
+ /** The fused kernel's bottom-right-aligned causal mask. Nothing to release. */
19
+ export const causalLease: MaskLease = { mask: { mode: "causal", arr: null }, release() {} };
20
+
21
+ interface MaskSlot {
22
+ readonly key: string;
23
+ readonly source: object | null;
24
+ readonly mask: Mask;
25
+ /** This memo's hold plus one per unreleased lease. */
26
+ holders: number;
27
+ }
28
+
29
+ /**
30
+ * The attention masks of one forward, built once and shared by every layer
31
+ * cache composed with this instance.
32
+ *
33
+ * A cache leases its mask here inside each named read, under a family and a
34
+ * key. The family names how the mask is built and is fixed when the cache is
35
+ * constructed: the cache's mask builder and its sliding window. The key holds
36
+ * every input that builder reads from the cache before the append: the query
37
+ * count and the row offsets, padding and ring position, plus `source`, compared
38
+ * by identity, for a mask built from a caller's tensor. The first lease of a key
39
+ * builds the mask. The other layers of the same forward stand at the same
40
+ * positions, so they lease the same mask. The next forward's appends have moved
41
+ * the offsets, so its first lease builds a new mask and drops this memo's hold
42
+ * on the old one. Each family keeps one mask, so a model's sliding and full
43
+ * layers do not evict each other. Equal keys always mean equal masks, so sharing
44
+ * an instance can only save builds, never change a result; caches at different
45
+ * positions that share one and alternate within a forward rebuild on each turn.
46
+ *
47
+ * A lease keeps its mask alive until the read holding it is disposed, so a read
48
+ * stays valid after a later forward replaces the mask. `clear()` drops only this
49
+ * memo's holds; a cache calls it from `dispose()`.
50
+ *
51
+ * Neither the graph nor the scheduler sees this. A model composes one instance
52
+ * into the caches its `makeCache()` makes (each cache constructor takes it; a
53
+ * cache built without one keeps its own); empty batches and the row layouts
54
+ * made from a cache (`state/layout`) take their source's instance. Not a module
55
+ * singleton: caches share masks only through an instance they were given.
56
+ */
57
+ export class AttentionMasks {
58
+ readonly #slots = new Map<string, MaskSlot>();
59
+ #builds = 0;
60
+
61
+ /** Masks built so far (diagnostic; tests count it). */
62
+ get builds(): number { return this.#builds; }
63
+
64
+ /** The mask `build` makes for `family` at `key` (and `source`), built on the
65
+ * first lease of that key. The caller owns the lease, not the mask: release
66
+ * the lease, never dispose `lease.mask.arr`. */
67
+ lease(family: string, key: string, build: () => Mask, source: object | null = null): MaskLease {
68
+ let slot = this.#slots.get(family);
69
+ if (!slot || slot.key !== key || slot.source !== source) {
70
+ const mask = build();
71
+ this.#builds++;
72
+ if (slot) drop(slot);
73
+ slot = { key, source, mask, holders: 1 };
74
+ this.#slots.set(family, slot);
75
+ }
76
+ const held = slot;
77
+ held.holders++;
78
+ return { mask: held.mask, release: () => drop(held) };
79
+ }
80
+
81
+ /** Drop this memo's holds. Leased masks stay valid until released. */
82
+ clear(): void {
83
+ for (const slot of this.#slots.values()) drop(slot);
84
+ this.#slots.clear();
85
+ }
86
+ }
87
+
88
+ function drop(slot: MaskSlot): void {
89
+ if (--slot.holders === 0) slot.mask.arr?.dispose();
90
+ }
91
+
92
+ /** The encoder window's mask over an `L`-position first window
93
+ * (`EncoderAttentionCache.appendEncoderWindow`): causal, OR'd with the overlay
94
+ * of positions both flagged in `bidirectional`, with no sliding window, as one
95
+ * [1, 1, L, L] bool array (the shape the DiffusionGemma encoder built on the
96
+ * host). No cache's window enters it, so every cache of a model leases it
97
+ * under one family and a forward builds it once. */
98
+ export function encoderWindowMask(masks: AttentionMasks, L: number, bidirectional: MlxArray): MaskLease {
99
+ return masks.lease("encoder-window", `${L}`, () => {
100
+ const { arr } = bidirMask(L, null, bidirectional);
101
+ try { return { mode: "array", arr: ops.reshape(arr!, [1, 1, L, L]) }; }
102
+ finally { arr!.dispose(); }
103
+ }, bidirectional);
104
+ }
105
+
106
+ /** DiffusionGemma's sliding-layer decoder mask (its `#makeDecoderMasks`) for a
107
+ * block read over a ring of `maxSize` holding `offset` positions:
108
+ * `storedLength` stored positions in temporal order, of which the newest
109
+ * `maxSize - 1` that hold a valid position are visible, then all `N` block
110
+ * positions, as one bool [1, 1, 1, storedLength + N] row every query shares.
111
+ * The rings of a model lease it under one family per window. */
112
+ export function rotatingBlockMask(masks: AttentionMasks, maxSize: number, offset: number, N: number,
113
+ storedLength: number): MaskLease {
114
+ const valid = Math.min(offset, storedLength);
115
+ return masks.lease(`rotating-block:${maxSize}`, `${N}:${storedLength}:${valid}`, () => {
116
+ const start = Math.max(0, valid - Math.max(maxSize - 1, 0));
117
+ const positions = ops.arange(0, storedLength, 1, Dtype.int32);
118
+ const startArr = ops.scalarLike(start, positions);
119
+ const validArr = ops.scalarLike(valid, positions);
120
+ const geStart = ops.greaterEqual(positions, startArr);
121
+ const ltValid = ops.less(positions, validArr);
122
+ const stored = ops.logicalAnd(geStart, ltValid);
123
+ positions.dispose();
124
+ startArr.dispose();
125
+ validArr.dispose();
126
+ geStart.dispose();
127
+ ltValid.dispose();
128
+ using ones = ops.fromInt32(new Array(N).fill(1), [N]);
129
+ const block = ones.astype(stored.dtype);
130
+ const row = ops.concatAxis([stored, block], 0);
131
+ stored.dispose();
132
+ block.dispose();
133
+ const reshaped = ops.reshape(row, [1, 1, 1, storedLength + N]);
134
+ row.dispose();
135
+ return { mode: "array", arr: reshaped };
136
+ });
137
+ }
138
+
139
+ /** A read over dense stored keys and values under a leased mask; it takes
140
+ * ownership of `keys`, `values` and `mask`. A bf16 storage reads its windows
141
+ * with the one its composition names (`KVCache.windowRead`). */
142
+ export type DenseRead = (keys: MlxArray, values: MlxArray, mask: MaskLease) => AttentionRead;
143
+
144
+ /** The read over dense stored keys and values: the stock fused SDPA kernel
145
+ * (`ops.sdpa`) under the leased mask. Takes ownership of `keys`, `values` and
146
+ * `mask`. */
147
+ export function sdpaRead(keys: MlxArray, values: MlxArray, mask: MaskLease): AttentionRead {
148
+ return {
149
+ attend: (q, scale) => ops.sdpa(q, keys, values, scale, mask.mask.mode, mask.mask.arr),
150
+ dispose() { keys.dispose(); values.dispose(); mask.release(); },
151
+ };
152
+ }
153
+
154
+ /** `sdpaRead`, reading each KV head's keys and values once for a causal window
155
+ * MLX's fused SDPA hands to its unfused fallback (up to 8 queries per row, more
156
+ * than 32 per KV head: `foldedSdpaEligible`), where that fallback reads them
157
+ * once per query head: such a window folds GQA into rows (`foldedSdpa`: the
158
+ * fallback's bits on `applegpu_g13s`, where it was measured, bf16 precision
159
+ * elsewhere); every other read is the stock fused SDPA. Takes ownership of
160
+ * `keys`, `values` and `mask`. */
161
+ export function foldedWindowRead(keys: MlxArray, values: MlxArray, mask: MaskLease): AttentionRead {
162
+ return {
163
+ attend: (q, scale) => mask.mask.mode === "causal" && foldedSdpaEligible(q, keys)
164
+ ? foldedSdpa(q, keys, values, scale) : ops.sdpa(q, keys, values, scale, mask.mask.mode, mask.mask.arr),
165
+ dispose() { keys.dispose(); values.dispose(); mask.release(); },
166
+ };
167
+ }
168
+
169
+ /** A committed span (`CommittedAttentionCache.appendCommitted`) read one
170
+ * position at a time: each of the span's `L` positions appends through
171
+ * `cache.appendDecode` and keeps that one-position read, so query `i` attends
172
+ * its own causal prefix with the decode arithmetic and the storage holds what
173
+ * `L` decodes leave. The committed read of ring storage, whose window read is
174
+ * not the one-position arithmetic (a ring writes a window by concatenation and,
175
+ * once it wraps, reorders its keys). `k` and `v` are borrowed, as for every
176
+ * append. */
177
+ export function decodeByPosition(cache: AttentionCache, k: MlxArray, v: MlxArray): AttentionRead {
178
+ const L = k.shape[2]!;
179
+ const at = (a: MlxArray, i: number) => a.slice([0, 0, i, 0], [a.shape[0]!, a.shape[1]!, i + 1, a.shape[3]!]);
180
+ const reads: AttentionRead[] = [];
181
+ try {
182
+ for (let i = 0; i < L; i++) {
183
+ using key = at(k, i), value = at(v, i);
184
+ reads.push(cache.appendDecode(key, value));
185
+ }
186
+ } catch (error) { disposeResources(reads); throw error; }
187
+ return {
188
+ attend(q, scale) {
189
+ const outputs: MlxArray[] = [];
190
+ try {
191
+ for (const [i, read] of reads.entries()) {
192
+ using query = at(q, i);
193
+ outputs.push(read.attend(query, scale));
194
+ }
195
+ return outputs.length === 1 ? ops.contiguous(outputs[0]!) : ops.concatAxis(outputs, 2);
196
+ } finally { disposeResources(outputs); }
197
+ },
198
+ dispose() { disposeResources(reads); },
199
+ };
200
+ }
201
+
202
+ /** Run a read's storage step under a mask leased before it, releasing the
203
+ * lease if the step throws. */
204
+ export function withLease(mask: MaskLease, read: (mask: MaskLease) => AttentionRead): AttentionRead {
205
+ try { return read(mask); }
206
+ catch (error) { mask.release(); throw error; }
207
+ }
208
+
209
+ /** Retain an independent array handle for positions used across cache writes.
210
+ * The contiguous view preserves the integer values and shares their storage;
211
+ * callers release it with the fetched attention state. */
212
+ export function captureRopeOffsets(cache: { readonly ropeOffsetArr?: MlxArray }): MlxArray | undefined {
213
+ const offsets = cache.ropeOffsetArr;
214
+ return offsets ? ops.contiguous(offsets) : undefined;
215
+ }
@@ -0,0 +1,294 @@
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 { materializeCopy } from "@mlx-bun/mlx/materialize";
5
+ import { KVCache } from "./kv";
6
+ import { isPlainKvCache } from "./capabilities";
7
+ import { type AttentionRead, type BatchableCache, type Cache, type CommittedAttentionCache, type Mask, type PaddedPrefillCache, type PrefillPadding } from "../contracts/mlx/cache";
8
+ import { FullPrefillPadding } from "./full-prefill-padding";
9
+ import { AttentionMasks, causalLease, sdpaRead, unmaskedLease, withLease, type DenseRead, type MaskLease } from "./attention-read";
10
+
11
+ /** Full-attention storage with independently advancing row positions.
12
+ * Speculative rollback changes coverage, without moving the retained KV.
13
+ * Subsequent appends overwrite each row's rejected suffix in place when MLX
14
+ * can donate the buffer. Queue membership is outside this layout.
15
+ *
16
+ * Reads: append through `updateAndFetch`, then the stock fused SDPA, a window
17
+ * through `windowRead` (its source storage's). While every row is unpadded and
18
+ * at one position, decode passes no mask and a window the fused causal mask;
19
+ * otherwise both pass the per-row mask (causal, past each row's left padding)
20
+ * for their query count, built once per forward through `masks`. */
21
+ export class BatchedKVCache implements BatchableCache, PaddedPrefillCache, CommittedAttentionCache {
22
+ keys: MlxArray | null = null;
23
+ values: MlxArray | null = null;
24
+ rowOffsets: number[] = [];
25
+ leftPad: number[] = [];
26
+ #rope: MlxArray | undefined;
27
+ #beforeRound: number[] | undefined;
28
+ readonly #padding = new FullPrefillPadding();
29
+
30
+ constructor(
31
+ /** The per-forward masks of the model this layout serves. */
32
+ readonly masks = new AttentionMasks(),
33
+ /** The window read of the storage it was made from (`KVCache.windowRead`). */
34
+ readonly windowRead: DenseRead = sdpaRead,
35
+ ) {}
36
+
37
+ appendDecode(k: MlxArray, v: MlxArray): AttentionRead {
38
+ return this.#read(k, v, this.#mask(1, unmaskedLease), sdpaRead);
39
+ }
40
+
41
+ appendWindow(k: MlxArray, v: MlxArray): AttentionRead {
42
+ return this.#read(k, v, this.#mask(k.shape[2]!, causalLease), this.windowRead);
43
+ }
44
+
45
+ /** Fill spans run on this layout (the target layout of a full-attention
46
+ * layer). A bf16 committed span reads exactly as a window does. */
47
+ appendCommitted(k: MlxArray, v: MlxArray): AttentionRead {
48
+ return this.appendWindow(k, v);
49
+ }
50
+
51
+ #read(k: MlxArray, v: MlxArray, mask: MaskLease, read: DenseRead): AttentionRead {
52
+ return withLease(mask, held => {
53
+ const [keys, values] = this.updateAndFetch(k, v);
54
+ return read(keys, values, held);
55
+ });
56
+ }
57
+
58
+ /** `aligned` while every row is unpadded at one position; otherwise the
59
+ * per-row mask of an `N`-position append, before it. */
60
+ #mask(N: number, aligned: MaskLease): MaskLease {
61
+ if (this.#aligned()) return aligned;
62
+ return this.masks.lease("batched-kv", `${N}|${this.rowOffsets.join(",")}|${this.leftPad.join(",")}`,
63
+ () => ({ mode: "array", arr: this.#rowMask(N, null) }));
64
+ }
65
+
66
+ restorePrefillEnds(ends: readonly number[] | undefined): void { this.#padding.restoreEnds(ends); }
67
+ preparePrefill(padding: PrefillPadding): void {
68
+ this.#padding.prepare(this, padding); this.#positionsChanged();
69
+ }
70
+ finalizePrefill(): void {
71
+ const next = this.#padding.finalize(this.state(), this);
72
+ if (!next) return;
73
+ this.keys?.dispose(); this.values?.dispose();
74
+ this.keys = next[0] ?? null; this.values = next[1] ?? null;
75
+ this.#positionsChanged();
76
+ }
77
+
78
+ captureDonorRows(): import("../contracts/mlx/cache").KvDonorRows {
79
+ const B = this.rowOffsets.length, width = this.offset;
80
+ const keys = this.keys!.slice([0,0,0,0], [B,this.keys!.shape[1]!,width,this.keys!.shape[3]!]);
81
+ let values: MlxArray;
82
+ try { values = this.values!.slice([0,0,0,0], [B,this.values!.shape[1]!,width,this.values!.shape[3]!]); }
83
+ catch (error) { keys.dispose(); throw error; }
84
+ return { keys, values, offsets: [...this.rowOffsets], starts: [...this.leftPad], ends: this.#ends() };
85
+ }
86
+
87
+ signature(): string { return "kv:batched-positions"; }
88
+ get batchSize(): number | null { return this.rowOffsets.length || null; }
89
+ get offset(): number { return Math.max(0, ...this.#ends()); }
90
+ #ends(): number[] { return this.rowOffsets.map((offset, row) => offset + this.leftPad[row]!); }
91
+ get ropeOffsetArr(): MlxArray | undefined {
92
+ // A common unpadded position uses the existing scalar RoPE operation.
93
+ if (this.leftPad.every(pad => pad === 0) && this.rowOffsets.every(offset => offset === this.rowOffsets[0])) return undefined;
94
+ return this.#rope ??= ops.fromInt32(this.rowOffsets, [this.rowOffsets.length]);
95
+ }
96
+ #positionsChanged(): void { this.#rope?.dispose(); this.#rope = undefined; }
97
+ makeEmptyBatch(): BatchedKVCache { return new BatchedKVCache(this.masks, this.windowRead); }
98
+ bytesPerToken(): number {
99
+ if (!this.keys || !this.values) return 0;
100
+ return (this.keys.nbytes + this.values.nbytes) / (this.rowOffsets.length * this.keys.shape[2]!);
101
+ }
102
+ projectedBytes(tokens: number): number { return this.bytesPerToken() * tokens; }
103
+ state(): MlxArray[] { return this.keys && this.values ? [this.keys, this.values] : []; }
104
+ isTrimmable(): boolean { return true; }
105
+ trim(count: number): void {
106
+ this.rowOffsets = this.rowOffsets.map(offset => offset - count);
107
+ this.#positionsChanged();
108
+ }
109
+ specRoundBegin(): void { this.#beforeRound = [...this.rowOffsets]; }
110
+ specRoundCommit(): void { this.#beforeRound = undefined; }
111
+ specRoundRollback(keep: number | readonly number[]): void {
112
+ this.rowOffsets = this.#beforeRound!.map((offset, row) => offset + (typeof keep === "number" ? keep : keep[row]!));
113
+ this.#beforeRound = undefined;
114
+ this.#positionsChanged();
115
+ }
116
+
117
+ makeMask(N: number, window: number | null): Mask {
118
+ if (window === null && this.#aligned()) return { mode: N === 1 ? "" : "causal", arr: null };
119
+ return { mode: "array", arr: this.#rowMask(N, window) };
120
+ }
121
+
122
+ /** Every row unpadded and at one position: a windowless read needs no
123
+ * per-row mask. Layouts that keep their positions in this one read it. */
124
+ get aligned(): boolean { return this.#aligned(); }
125
+
126
+ #aligned(): boolean {
127
+ const ends = this.#ends();
128
+ return this.leftPad.every(pad => pad === 0) && ends.every(end => end === ends[0]);
129
+ }
130
+
131
+ /** Causal, past each row's left padding, within `window`: [B, 1, N, S]. */
132
+ #rowMask(N: number, window: number | null): MlxArray {
133
+ const B = this.rowOffsets.length, ends = this.#ends(), S = this.offset + N;
134
+ using starts = ops.fromInt32(ends, [B, 1, 1, 1]);
135
+ using relative = ops.arange(0, N, 1, Dtype.int32);
136
+ using queries = ops.reshape(relative, [1, 1, N, 1]);
137
+ using position = ops.add(starts, queries);
138
+ using indices = ops.arange(0, S, 1, Dtype.int32);
139
+ using keys = ops.reshape(indices, [1, 1, 1, S]);
140
+ using pads = ops.fromInt32(this.leftPad, [B, 1, 1, 1]);
141
+ using causal = ops.lessEqual(keys, position);
142
+ using valid = ops.greaterEqual(keys, pads);
143
+ const mask = ops.logicalAnd(causal, valid);
144
+ if (window === null) return mask;
145
+ using windowSize = ops.fromInt32([window], []);
146
+ using limit = ops.add(keys, windowSize);
147
+ using within = ops.less(position, limit);
148
+ try { return ops.logicalAnd(mask, within); }
149
+ finally { mask.dispose(); }
150
+ }
151
+
152
+ updateAndFetch(k: MlxArray, v: MlxArray): [MlxArray, MlxArray] {
153
+ const B = this.rowOffsets.length, N = k.shape[2]!, ends = this.#ends();
154
+ const needed = Math.max(...ends) + N;
155
+ const grow = (array: MlxArray) => {
156
+ const [rows, heads, capacity, width] = array.shape as [number, number, number, number];
157
+ if (capacity >= needed) return array;
158
+ using padding = ops.zeros([rows, heads, Math.ceil(needed / 256) * 256 - capacity, width], array.dtype);
159
+ const grown = ops.concatAxis([array, padding], 2);
160
+ array.dispose();
161
+ return grown;
162
+ };
163
+ const allocate = (input: MlxArray) => ops.zeros(
164
+ [B, input.shape[1]!, Math.ceil(needed / 256) * 256, input.shape[3]!], input.dtype);
165
+ this.keys = this.keys ? grow(this.keys) : allocate(k);
166
+ this.values = this.values ? grow(this.values) : allocate(v);
167
+ const common = ends.every(end => end === ends[0]);
168
+ using positions = common ? null : ops.fromInt32(ends.flatMap(end =>
169
+ Array.from({ length: N }, (_, position) => end + position)), [B, 1, N, 1]);
170
+ const append = (array: MlxArray, values: MlxArray) => common
171
+ ? ops.sliceUpdate(array, values, [0, 0, ends[0]!, 0], [B, array.shape[1]!, ends[0]! + N, array.shape[3]!])
172
+ : ops.putAlongAxis(array, positions!, values, 2);
173
+ const keys = append(this.keys, k);
174
+ let values: MlxArray;
175
+ try { values = append(this.values, v); }
176
+ catch (error) { keys.dispose(); throw error; }
177
+ this.keys.dispose(); this.values.dispose();
178
+ this.keys = keys; this.values = values;
179
+ this.rowOffsets = this.rowOffsets.map(offset => offset + N);
180
+ this.#positionsChanged();
181
+ return [keys.slice([0, 0, 0, 0], [B, keys.shape[1]!, needed, keys.shape[3]!]),
182
+ values.slice([0, 0, 0, 0], [B, values.shape[1]!, needed, values.shape[3]!])];
183
+ }
184
+
185
+ mergeRows(rows: readonly Cache[]): void {
186
+ const only = rows.length === 1 ? rows[0] : undefined;
187
+ if (only && (only instanceof BatchedKVCache || isPlainKvCache(only))) {
188
+ // Preserve allocated capacity when a request starts alone. Immutable
189
+ // aliases transfer ownership without copying its prefill storage.
190
+ const keys = only.keys ? ops.copyOf(only.keys) : null;
191
+ let values: MlxArray | null;
192
+ try { values = only.values ? ops.copyOf(only.values) : null; }
193
+ catch (error) { keys?.dispose(); throw error; }
194
+ const offsets = only instanceof BatchedKVCache ? [...only.rowOffsets] : [only.offset];
195
+ const pads = only instanceof BatchedKVCache ? [...only.leftPad] : [0];
196
+ this.dispose(); this.keys = keys; this.values = values;
197
+ this.rowOffsets = offsets; this.leftPad = pads;
198
+ return;
199
+ }
200
+ const chunks: Array<{ keys: MlxArray | null; values: MlxArray | null; offsets: number[]; pads: number[]; width: number }> = [];
201
+ const held: MlxArray[] = [];
202
+ try {
203
+ for (const cache of rows) {
204
+ let keys: MlxArray | null, values: MlxArray | null, offsets: number[], pads: number[];
205
+ if (cache instanceof BatchedKVCache && cache.offset === 0) {
206
+ keys = values = null; offsets = [...cache.rowOffsets]; pads = [...cache.leftPad];
207
+ } else if (cache instanceof BatchedKVCache) {
208
+ const B = cache.rowOffsets.length, width = cache.offset;
209
+ keys = cache.keys!.slice([0, 0, 0, 0], [B, cache.keys!.shape[1]!, width, cache.keys!.shape[3]!]);
210
+ held.push(keys);
211
+ values = cache.values!.slice([0, 0, 0, 0], [B, cache.values!.shape[1]!, width, cache.values!.shape[3]!]);
212
+ held.push(values);
213
+ offsets = [...cache.rowOffsets]; pads = [...cache.leftPad];
214
+ } else if (isPlainKvCache(cache)) {
215
+ if (cache.offset === 0) keys = values = null;
216
+ else { [keys, values] = cache.temporalView(); held.push(keys, values); }
217
+ offsets = [cache.offset]; pads = [0];
218
+ } else throw new Error(`full-attention batch cannot merge ${cache.signature()}`);
219
+ chunks.push({ keys, values, offsets, pads, width: keys?.shape[2] ?? 0 });
220
+ }
221
+ const width = Math.max(0, ...chunks.map(chunk => chunk.width));
222
+ const padded = (array: MlxArray, count: number) => {
223
+ if (!count) return array;
224
+ using padding = ops.zeros([array.shape[0]!, array.shape[1]!, count, array.shape[3]!], array.dtype);
225
+ const result = ops.concatAxis([padding, array], 2); held.push(result); return result;
226
+ };
227
+ const merge = (field: "keys" | "values") => {
228
+ if (!width) return null; // Geometry arrives with the first append.
229
+ const prototype = chunks.find(chunk => chunk[field])![field]!;
230
+ return ops.concatAxis(chunks.map(chunk => {
231
+ const array = chunk[field];
232
+ if (array) return padded(array, width - chunk.width);
233
+ const empty = ops.zeros([chunk.offsets.length, prototype.shape[1]!, width, prototype.shape[3]!], prototype.dtype);
234
+ held.push(empty); return empty;
235
+ }), 0);
236
+ };
237
+ const keys = merge("keys");
238
+ let values: MlxArray | null;
239
+ try { values = merge("values"); }
240
+ catch (error) { keys?.dispose(); throw error; }
241
+ this.dispose();
242
+ this.keys = keys; this.values = values;
243
+ this.rowOffsets = chunks.flatMap(chunk => chunk.offsets);
244
+ this.leftPad = chunks.flatMap(chunk => chunk.pads.map(pad => pad + width - chunk.width));
245
+ } finally { for (const array of held) array.dispose(); }
246
+ }
247
+
248
+ filterRows(rows: readonly number[]): void {
249
+ if (!rows.length) { this.dispose(); return; }
250
+ const pads = rows.map(row => this.leftPad[row]!);
251
+ const sharedPadding = Math.min(...pads);
252
+ using indices = this.keys ? ops.fromInt32([...rows], [rows.length]) : null;
253
+ const select = (array: MlxArray | null): MlxArray | null => {
254
+ if (!array) return null;
255
+ const selected = ops.takeAxis(array, indices!, 0);
256
+ if (!sharedPadding) return selected;
257
+ // mlx-lm BatchKVCache.filter removes padding common to all survivors.
258
+ // Keeping it changes attention reduction geometry after retirement.
259
+ try { return selected.slice([0, 0, sharedPadding, 0], [...selected.shape]); }
260
+ finally { selected.dispose(); }
261
+ };
262
+ const keys = select(this.keys);
263
+ let values: MlxArray | null;
264
+ try { values = select(this.values); }
265
+ catch (error) { keys?.dispose(); throw error; }
266
+ this.keys?.dispose(); this.values?.dispose();
267
+ this.keys = keys; this.values = values;
268
+ this.rowOffsets = rows.map(row => this.rowOffsets[row]!);
269
+ this.leftPad = pads.map(pad => pad - sharedPadding);
270
+ this.#padding.filter(rows);
271
+ this.#positionsChanged();
272
+ }
273
+
274
+ extractRow(row: number): KVCache {
275
+ const start = this.leftPad[row]!, count = this.rowOffsets[row]!;
276
+ if (count === 0) return new KVCache();
277
+ const copy = (array: MlxArray) => {
278
+ using view = array.slice([row, 0, start, 0], [row + 1, array.shape[1]!, start + count, array.shape[3]!]);
279
+ return materializeCopy(view);
280
+ };
281
+ const keys = copy(this.keys!);
282
+ let values: MlxArray;
283
+ try { values = copy(this.values!); }
284
+ catch (error) { keys.dispose(); throw error; }
285
+ const cache = new KVCache(); cache.restoreState(keys, values, count); return cache;
286
+ }
287
+
288
+ dispose(): void {
289
+ this.keys?.dispose(); this.values?.dispose(); this.#positionsChanged();
290
+ this.keys = this.values = null;
291
+ this.rowOffsets = []; this.leftPad = []; this.#beforeRound = undefined; this.#padding.clear();
292
+ this.masks.clear();
293
+ }
294
+ }