@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,123 @@
1
+ import { captureFullKvDonorRows } from "./full-kv-row-donor";
2
+ import { decodedKvDonorAttention } from "./decoded-kv-donor";
3
+ import { appendFullKvRows } from "./full-kv-row-append";
4
+ import type { MlxArray } from "@mlx-bun/mlx/array";
5
+ import { Dtype } from "@mlx-bun/mlx/ffi";
6
+ import * as ops from "@mlx-bun/mlx/ops";
7
+ import { unrotateValues } from "../kernels/turboquant/ops";
8
+ import { type AttentionRead, type Cache, type CommittedAttentionCache } from "../contracts/mlx/cache";
9
+ import { kvStorage, type RotatedValueAttentionState } from "./kv-storage";
10
+ import { TurboQuantKVCache } from "./turboquant-kv";
11
+ import { BatchedTurboQuantKVCache } from "./batched-turboquant-kv";
12
+ import { FullTransitioningKvRows } from "./full-transitioning-kv-rows";
13
+ import { FullPrefillRow } from "./full-prefill-row";
14
+ import { KVCache } from "./kv";
15
+ import { Bf16FirstTurboQuantKVCache, completeConversion, convertToTurboQuant, transitionDue } from "./bf16-first-kv";
16
+ import type { TurboQuantKernels } from "./turboquant-attention";
17
+
18
+ /** The TurboQuant transition of `Bf16FirstTurboQuantKVCache` over
19
+ * full-attention rows: a bf16 row (`KVCache`, or a padded `FullPrefillRow`
20
+ * over one) encodes its live window once it holds `start` positions. */
21
+ function settleTurboQuantRows(rows: Cache[], kBits: number, vBits: number, start: number, fusedDecode: boolean): void {
22
+ for (let index = 0; index < rows.length; index++) {
23
+ const row = rows[index]!;
24
+ if (row instanceof FullPrefillRow) {
25
+ const source = row.turboConversion;
26
+ if (!source || !transitionDue(source.offset, start)) continue;
27
+ const offset = source.offset;
28
+ rows[index] = completeConversion(source.toTurboQuantized(kBits, vBits, fusedDecode), offset, start);
29
+ } else if (row instanceof KVCache && transitionDue(row.offset, start)) {
30
+ rows[index] = convertToTurboQuant(row, kBits, vBits, start, fusedDecode);
31
+ }
32
+ }
33
+ }
34
+
35
+ /** The rows of the TurboQuant bf16-first lego (`Bf16FirstTurboQuantKVCache`)
36
+ * for continuous batching: TurboQuant tensor access and value-domain handling
37
+ * over the shared row-transition lifecycle (when a row converts is described on
38
+ * `TransitioningKvRows`). Model projections and attention remain batched.
39
+ * `fusedDecode` is resolved by whoever builds the layout. `turboQuantKernels` is
40
+ * the read of the graph whose caches these rows hold (dense for Qwen 3.5,
41
+ * deferred for Gemma 4 and MiniCPM5); the rows this layout makes and the
42
+ * layout it packs them into carry it, and a converted row attends with its
43
+ * source's.
44
+ *
45
+ * The reads are the graph's call today: this layout's mask before the append
46
+ * (no sliding window), then the kernel set's fetch and attention. The dense set
47
+ * fetches every row decoded (`updateAndFetch`) and runs the fused SDPA, Qwen
48
+ * 3.5's call; the deferred set fetches converted rows rotated
49
+ * (`updateAndFetchDeferredV`), runs the fused SDPA and applies the value
50
+ * transform that fetch captured, Gemma 4's and MiniCPM5's. */
51
+ export class DelayedTurboQuantKVCache extends FullTransitioningKvRows<BatchedTurboQuantKVCache>
52
+ implements RotatedValueAttentionState, CommittedAttentionCache {
53
+ #rotated: boolean[] = [];
54
+ constructor(readonly kBits: number, readonly vBits: number, readonly start: number,
55
+ readonly fusedDecode: boolean, readonly turboQuantKernels: TurboQuantKernels, row?: Cache,
56
+ /** How the lego these rows came from composes the rows this layout hands
57
+ * back to serial use (`ComposedStorage.over`). */
58
+ readonly compose?: (storage: Cache) => Cache) {
59
+ super({ signature: `kv:delayed-turboquant:${kBits}:${vBits}:${start}`, conversionOffset: start,
60
+ settle: rows => settleTurboQuantRows(rows, kBits, vBits, start, fusedDecode),
61
+ converted: row => row instanceof TurboQuantKVCache,
62
+ makeLayout: () => new BatchedTurboQuantKVCache(kBits, vBits, turboQuantKernels, fusedDecode),
63
+ lone: storage => new Bf16FirstTurboQuantKVCache(kBits, vBits, start, fusedDecode, turboQuantKernels, undefined, storage),
64
+ ...(compose ? { compose } : {}) }, row);
65
+ }
66
+ /** Rows made for an empty layout carry the graph's read into their conversion. */
67
+ protected override plainRow(): KVCache { return new KVCache(undefined, this.turboQuantKernels); }
68
+ captureDonorRows(): import("../contracts/mlx/cache").KvDonorRows {
69
+ return this.packed?.captureDonorRows() ?? captureFullKvDonorRows(this.rows, this.leftPad, this.offset);
70
+ }
71
+ captureDonorAttention(): import("../contracts/mlx/cache").KvDonorAttention {
72
+ return decodedKvDonorAttention(this.captureDonorRows());
73
+ }
74
+ get rotatedValueAttention(): RotatedValueAttentionState { return this; }
75
+
76
+ appendDecode(k: MlxArray, v: MlxArray): AttentionRead { return this.#read(k, v); }
77
+ appendWindow(k: MlxArray, v: MlxArray): AttentionRead { return this.#read(k, v); }
78
+ /** The committed read is the window read: a committed span reads this layout
79
+ * through the kernel set's fetch and the fused SDPA under the mask the layout
80
+ * builds, as a window does. */
81
+ appendCommitted(k: MlxArray, v: MlxArray): AttentionRead { return this.#read(k, v); }
82
+ /** The read: the mask from this layout before the append (no sliding
83
+ * window), then the kernel set's fetch and attention. */
84
+ #read(k: MlxArray, v: MlxArray): AttentionRead {
85
+ const mask = this.makeMask(k.shape[2]!, null);
86
+ return this.turboQuantKernels.read({
87
+ decoded: () => this.updateAndFetch(k, v),
88
+ rotated: () => {
89
+ const [keys, values] = this.updateAndFetchDeferredV(k, v);
90
+ return { keys, values, restore: this.captureValueTransform() };
91
+ },
92
+ }, { mask, release: () => mask.arr?.dispose() });
93
+ }
94
+
95
+ #append(k: MlxArray, v: MlxArray, deferred: boolean): [MlxArray, MlxArray] {
96
+ this.advance();
97
+ if (this.packed) {
98
+ this.#rotated = Array.from({ length: this.batchSize! }, () => true);
99
+ return deferred ? this.packed.updateAndFetchDeferredV(k, v) : this.packed.updateAndFetch(k, v);
100
+ }
101
+ this.#rotated = this.rows.map(row => !!kvStorage(row).rotatedValueAttention);
102
+ const result = appendFullKvRows(this.rows, this.leftPad, this.offset + k.shape[2]!, k, v, deferred);
103
+ try { this.syncPositions(true); } catch (error) { result[0].dispose(); result[1].dispose(); throw error; }
104
+ return result;
105
+ }
106
+
107
+ updateAndFetch(k: MlxArray, v: MlxArray): [MlxArray, MlxArray] { return this.#append(k, v, false); }
108
+ updateAndFetchDeferredV(k: MlxArray, v: MlxArray): [MlxArray, MlxArray] { return this.#append(k, v, true); }
109
+ captureValueTransform(): (output: MlxArray) => MlxArray {
110
+ const rotated = [...this.#rotated];
111
+ if (rotated.every(value => value)) return unrotateValues;
112
+ if (rotated.every(value => !value)) return output => ops.contiguous(output);
113
+ return output => {
114
+ using restored = unrotateValues(output);
115
+ using flags = ops.fromInt32(rotated.map(value => Number(value)), [rotated.length, 1, 1, 1]);
116
+ using mask = flags.astype(Dtype.bool);
117
+ return ops.where(mask, restored, output);
118
+ };
119
+ }
120
+ makeEmptyBatch(): DelayedTurboQuantKVCache {
121
+ return new DelayedTurboQuantKVCache(this.kBits, this.vBits, this.start, this.fusedDecode, this.turboQuantKernels, undefined, this.compose);
122
+ }
123
+ }
@@ -0,0 +1,214 @@
1
+ import { cloneAttachments } from "./checkpoint";
2
+ import { cleanupFailure, disposeResources } from "../runtime/resources";
3
+ import type { Cache } from "../contracts/mlx/cache";
4
+ import type { PromptCacheEntry } from "./prefix-cache";
5
+ import type { SpillItem } from "./persistence-types";
6
+ import type { SpillQueue } from "./persistence";
7
+
8
+ export interface DurabilityPromptCache {
9
+ findExact(tokens: number[], ns?: string): PromptCacheEntry | null;
10
+ }
11
+
12
+ export interface DurabilitySnapshotStats {
13
+ pendingSnapshots: number;
14
+ pendingSpills: number;
15
+ pendingSpillBytes: number;
16
+ droppedSpills: number;
17
+ failedSpills: number;
18
+ }
19
+
20
+ export interface DurabilityFlushResult extends DurabilitySnapshotStats {
21
+ durable: boolean;
22
+ flushedSnapshots: number;
23
+ missingSnapshots: number;
24
+ elapsedMs: number;
25
+ }
26
+
27
+ interface DirtySnapshot {
28
+ key: string;
29
+ tokens: number[];
30
+ ns: string;
31
+ }
32
+
33
+ type StoreOutcome = "stored" | "missing" | "failed";
34
+
35
+ /**
36
+ * Turns debounced prompt-cache puts into a durability boundary.
37
+ *
38
+ * A dirty record stays present until SpillQueue reports that its atomic store
39
+ * completed. Queue drops and write failures therefore remain retryable during
40
+ * an explicit flush or graceful shutdown.
41
+ */
42
+ export class SsdDurabilityCoordinator {
43
+ readonly #dirty = new Map<string, DirtySnapshot>();
44
+ readonly #timers = new Map<string, ReturnType<typeof setTimeout>>();
45
+ readonly #attempts = new Map<string, Promise<StoreOutcome>>();
46
+ #flush: Promise<DurabilityFlushResult> | null = null;
47
+
48
+ constructor(
49
+ readonly promptCache: DurabilityPromptCache,
50
+ readonly spillQueue: SpillQueue,
51
+ readonly cloneCaches: (caches: Cache[]) => Cache[],
52
+ readonly isAlreadyDurable: (tokens: number[], ns: string) => boolean = () => false,
53
+ readonly debounceMs = 0,
54
+ readonly retryMs = 5_000,
55
+ readonly onStored: () => void = () => {},
56
+ ) {}
57
+
58
+ schedule(tokens: number[], ns = "", replace = true): void {
59
+ // Exact tensor objects use an empty history and still need persistence.
60
+ if (tokens.length === 0 && !this.promptCache.findExact(tokens, ns)?.attachments?.length) return;
61
+ // PromptCache can hold unrelated entries with the same namespace and
62
+ // length. Include the tokens so one conversation cannot cancel another
63
+ // conversation's pending durability record.
64
+ const key = `${ns.length}:${ns}:${tokens.join(",")}`;
65
+ if (!replace && this.#dirty.has(key)) return;
66
+ const rec: DirtySnapshot = { key, tokens: [...tokens], ns };
67
+ this.#dirty.set(key, rec);
68
+ this.#arm(key, this.debounceMs);
69
+ }
70
+
71
+ get stats(): DurabilitySnapshotStats {
72
+ return {
73
+ pendingSnapshots: this.#dirty.size,
74
+ pendingSpills: this.spillQueue.pendingCount,
75
+ pendingSpillBytes: this.spillQueue.pendingBytes,
76
+ droppedSpills: this.spillQueue.droppedCount,
77
+ failedSpills: this.spillQueue.failedCount,
78
+ };
79
+ }
80
+
81
+ /** Force every dirty RAM snapshot through the queue and wait for storage. */
82
+ flush(): Promise<DurabilityFlushResult> {
83
+ if (this.#flush) return this.#flush;
84
+ const started = performance.now();
85
+ this.#flush = this.#flushInner(started).finally(() => { this.#flush = null; });
86
+ return this.#flush;
87
+ }
88
+
89
+ #arm(key: string, delayMs: number): void {
90
+ const old = this.#timers.get(key);
91
+ if (old) clearTimeout(old);
92
+ const timer = setTimeout(() => {
93
+ this.#timers.delete(key);
94
+ void this.#attempt(key, false);
95
+ }, delayMs);
96
+ timer.unref?.();
97
+ this.#timers.set(key, timer);
98
+ }
99
+
100
+ async #attempt(key: string, force: boolean): Promise<StoreOutcome> {
101
+ const running = this.#attempts.get(key);
102
+ if (running) {
103
+ const outcome = await running;
104
+ if (!force || !this.#dirty.has(key)) return outcome;
105
+ }
106
+
107
+ const rec = this.#dirty.get(key);
108
+ if (!rec) return "stored";
109
+
110
+ const task = this.#store(rec);
111
+ this.#attempts.set(key, task);
112
+ try {
113
+ const outcome = await task;
114
+ if (outcome === "stored" && this.#dirty.get(key) === rec)
115
+ this.#dirty.delete(key);
116
+ if (outcome !== "stored" && !force && this.#dirty.get(key) === rec)
117
+ this.#arm(key, this.retryMs);
118
+ return outcome;
119
+ } finally {
120
+ if (this.#attempts.get(key) === task) this.#attempts.delete(key);
121
+ }
122
+ }
123
+
124
+ async #store(rec: DirtySnapshot): Promise<StoreOutcome> {
125
+ let snap: SpillItem | null = null;
126
+ try {
127
+ snap = (() => {
128
+ if (this.isAlreadyDurable(rec.tokens, rec.ns)) return null;
129
+ const entry = this.promptCache.findExact(rec.tokens, rec.ns);
130
+ if (!entry) return null;
131
+ const caches = this.cloneCaches(entry.caches);
132
+ try {
133
+ return { tokens: [...entry.tokens], caches, ns: rec.ns,
134
+ attachments: cloneAttachments(entry.attachments) };
135
+ } catch (error) {
136
+ return cleanupFailure(error, () => disposeResources(caches));
137
+ }
138
+ })();
139
+ } catch {
140
+ return "failed";
141
+ }
142
+ if (!snap)
143
+ return this.isAlreadyDurable(rec.tokens, rec.ns) ? "stored" : "missing";
144
+ const stored = await this.spillQueue.enqueue(snap);
145
+ // The queue releases its views before this continuation runs. Residency
146
+ // policy now sees memory without the completed writer's ownership.
147
+ if (stored) this.onStored();
148
+ return stored ? "stored" : "failed";
149
+ }
150
+
151
+ async #flushInner(started: number): Promise<DurabilityFlushResult> {
152
+ for (const timer of this.#timers.values()) clearTimeout(timer);
153
+ this.#timers.clear();
154
+
155
+ let flushedSnapshots = 0;
156
+ const missing = new Map<string, DirtySnapshot>();
157
+ const droppedBefore = this.spillQueue.droppedCount;
158
+ const failedBefore = this.spillQueue.failedCount;
159
+
160
+ if (this.#attempts.size > 0) {
161
+ const results = await Promise.allSettled([...this.#attempts.values()]);
162
+ flushedSnapshots += results.filter(result => result.status === "fulfilled" && result.value === "stored").length;
163
+ }
164
+ await this.spillQueue.drain();
165
+ // A background failure may have rearmed its retry while flush waited.
166
+ // Forced attempts below own retries now; no timer may outlive this flush.
167
+ for (const timer of this.#timers.values()) clearTimeout(timer);
168
+ this.#timers.clear();
169
+
170
+ // Flush dirty entries serially without a second queue of snapshots.
171
+ // Attempt each record version once. Missing state stays dirty, so a
172
+ // second flush cannot report success without a real durable snapshot.
173
+ const attempted = new Set<DirtySnapshot>();
174
+ while (this.#dirty.size > 0) {
175
+ const records = [...this.#dirty.entries()].filter(([, rec]) => !attempted.has(rec));
176
+ if (records.length === 0) break;
177
+ for (const [key, rec] of records) {
178
+ if (this.#dirty.get(key) !== rec) continue;
179
+ attempted.add(rec);
180
+ const outcome = await this.#attempt(key, true);
181
+ await this.spillQueue.drain();
182
+ if (outcome === "stored") {
183
+ flushedSnapshots++;
184
+ } else if (outcome === "missing") {
185
+ missing.set(key, rec);
186
+ }
187
+ }
188
+ }
189
+
190
+ // A later trimmable snapshot may now cover a superseded RAM ancestor.
191
+ // Reconcile against committed SSD coverage after all writes settle;
192
+ // never clear a missing prefix merely because another write succeeded.
193
+ for (const [key, rec] of missing) {
194
+ if (!this.isAlreadyDurable(rec.tokens, rec.ns)) continue;
195
+ if (this.#dirty.get(key) === rec) this.#dirty.delete(key);
196
+ missing.delete(key);
197
+ flushedSnapshots++;
198
+ }
199
+
200
+ const stats = this.stats;
201
+ return {
202
+ ...stats,
203
+ durable:
204
+ stats.pendingSnapshots === 0 &&
205
+ stats.pendingSpills === 0 &&
206
+ stats.droppedSpills === droppedBefore &&
207
+ stats.failedSpills === failedBefore &&
208
+ missing.size === 0,
209
+ flushedSnapshots,
210
+ missingSnapshots: missing.size,
211
+ elapsedMs: performance.now() - started,
212
+ };
213
+ }
214
+ }
@@ -0,0 +1,26 @@
1
+ import type { ObjectCache } from "../contracts/portable/object-cache";
2
+ import { cloneAttachments, disposeAttachments } from "./checkpoint";
3
+ import { type CheckpointAttachment } from "../contracts/mlx/checkpoint";
4
+ import type { MlxArray } from "@mlx-bun/mlx/array";
5
+
6
+ /** Encoder-owned tensor schema over the cache's exact-object interface. */
7
+ export class EncoderCache {
8
+ constructor(private readonly objects: ObjectCache<CheckpointAttachment[]>,
9
+ readonly identity: string) {}
10
+
11
+ async take(key: string): Promise<MlxArray | null> {
12
+ const lease = await this.objects.take(`encoder-v1:${this.identity}:${key}`);
13
+ if (!lease) return null;
14
+ try {
15
+ const tensor = lease.value[0]!.tensors[0]!;
16
+ return tensor.slice(tensor.shape.map(() => 0), [...tensor.shape]);
17
+ } finally { lease.dispose(); }
18
+ }
19
+
20
+ /** Borrow the result; storage retains its own immutable native view. */
21
+ put(key: string, features: MlxArray): void {
22
+ const owned = cloneAttachments([{ schema: "encoder-features-v1", metadata: {}, tensors: [features] }]);
23
+ try { this.objects.put(`encoder-v1:${this.identity}:${key}`, owned); }
24
+ catch (error) { disposeAttachments(owned); throw error; }
25
+ }
26
+ }
@@ -0,0 +1,33 @@
1
+ import type { MlxArray } from "@mlx-bun/mlx/array";
2
+ import * as ops from "@mlx-bun/mlx/ops";
3
+ import type { Cache } from "../contracts/mlx/cache";
4
+ import { fullRowPadding } from "./full-prefill-row";
5
+ import { kvStorage } from "./kv-storage";
6
+ import { disposeResources } from "../runtime/resources";
7
+
8
+ /** Assemble full-attention tensors at the model's B while storage retains
9
+ * per-row formats and positions. Returned arrays own their captured state. */
10
+ export function appendFullKvRows(rows: readonly Cache[], leftPad: readonly number[], width: number,
11
+ k: MlxArray, v: MlxArray, deferredValues = false): [MlxArray, MlxArray] {
12
+ const parts: [MlxArray, MlxArray][] = [], padded: MlxArray[] = [];
13
+ try {
14
+ for (const [index, row] of rows.map(kvStorage).entries()) {
15
+ using kr = k.slice([index, 0, 0, 0], [index + 1, k.shape[1]!, k.shape[2]!, k.shape[3]!]);
16
+ using vr = v.slice([index, 0, 0, 0], [index + 1, v.shape[1]!, v.shape[2]!, v.shape[3]!]);
17
+ parts.push(deferredValues && row.rotatedValueAttention
18
+ ? row.rotatedValueAttention.updateAndFetchDeferredV(kr, vr) : row.updateAndFetch(kr, vr));
19
+ }
20
+ const combine = (field: 0 | 1) => {
21
+ const views = parts.map((part, row) => {
22
+ const a = part[field], before = leftPad[row]! - fullRowPadding(rows[row]!), after = width - before - a.shape[2]!;
23
+ if (!before && !after) return a;
24
+ using prefix = ops.zeros([1, a.shape[1]!, before, a.shape[3]!], a.dtype);
25
+ using suffix = ops.zeros([1, a.shape[1]!, after, a.shape[3]!], a.dtype);
26
+ const result = ops.concatAxis([prefix, a, suffix], 2); padded.push(result); return result;
27
+ });
28
+ return views.length === 1 ? ops.contiguous(views[0]!) : ops.concatAxis(views, 0);
29
+ };
30
+ const keys = combine(0);
31
+ try { return [keys, combine(1)]; } catch (error) { keys.dispose(); throw error; }
32
+ } finally { disposeResources([...parts.flat(), ...padded]); }
33
+ }
@@ -0,0 +1,54 @@
1
+ import { captureKvDonorAttention, combineKvDonorAttention } from "./kv-attention-view";
2
+ import type { MlxArray } from "@mlx-bun/mlx/array";
3
+ import * as ops from "@mlx-bun/mlx/ops";
4
+ import type { Cache, KvDonorRows } from "../contracts/mlx/cache";
5
+ import { fullRowInner, fullRowPadding } from "./full-prefill-row";
6
+ import { disposeResources } from "../runtime/resources";
7
+
8
+ /** Read full-attention rows in their existing physical columns. Valid token
9
+ * coverage excludes prefill padding; no append, conversion or compaction. */
10
+ export function captureFullKvDonorRows(rows: readonly Cache[], leftPad: readonly number[], width: number): KvDonorRows {
11
+ const parts: KvDonorRows[] = [], padded: MlxArray[] = [];
12
+ try {
13
+ for (const row of rows) parts.push(fullRowInner(row).captureDonorRows!());
14
+ const combine = (field: "keys" | "values") => {
15
+ const views = parts.map((part, index) => {
16
+ const a = part[field], before = leftPad[index]! - fullRowPadding(rows[index]!), after = width - before - a.shape[2]!;
17
+ if (!before && !after) return a;
18
+ using prefix = ops.zeros([1, a.shape[1]!, before, a.shape[3]!], a.dtype);
19
+ using suffix = ops.zeros([1, a.shape[1]!, after, a.shape[3]!], a.dtype);
20
+ const result = ops.concatAxis([prefix, a, suffix], 2); padded.push(result); return result;
21
+ });
22
+ return views.length === 1 ? ops.contiguous(views[0]!) : ops.concatAxis(views, 0);
23
+ };
24
+ const offsets = rows.map(row => row.offset), starts = [...leftPad];
25
+ const keys = combine("keys");
26
+ try { return { keys, values: combine("values"), offsets, starts, ends: offsets.map((offset, row) => starts[row]! + offset) }; }
27
+ catch (error) { keys.dispose(); throw error; }
28
+ } finally { disposeResources([...parts.flatMap(part => [part.keys, part.values]), ...padded]); }
29
+ }
30
+
31
+
32
+ /** Capture full rows in global columns while each encoded view retains its
33
+ * own physical width and numerical attention operation. */
34
+ export function captureFullKvDonorAttention(rows: readonly Cache[], leftPad: readonly number[], width: number): import("../contracts/mlx/cache").KvDonorAttention {
35
+ const captured: import("../contracts/mlx/cache").KvDonorAttention[] = [];
36
+ try {
37
+ for (const [index, row] of rows.entries()) {
38
+ const view = captureKvDonorAttention(fullRowInner(row));
39
+ const pad = leftPad[index]! - fullRowPadding(row);
40
+ captured.push({ width, dtype: view.dtype, offsets: [row.offset],
41
+ starts: [leftPad[index]!], ends: [leftPad[index]! + row.offset],
42
+ attend(q, scale, mask) {
43
+ if (!mask.arr) return view.attend(q, scale, mask);
44
+ const from = mask.arr.shape.map(() => 0), to = [...mask.arr.shape];
45
+ from[from.length - 1] = pad; to[to.length - 1] = pad + view.width;
46
+ using arr = mask.arr.slice(from, to);
47
+ return view.attend(q, scale, { mode: mask.mode, arr });
48
+ },
49
+ dispose: () => view.dispose(),
50
+ });
51
+ }
52
+ return combineKvDonorAttention(captured);
53
+ } catch (error) { disposeResources(captured); throw error; }
54
+ }
@@ -0,0 +1,56 @@
1
+ import type { MlxArray } from "@mlx-bun/mlx/array";
2
+ import * as ops from "@mlx-bun/mlx/ops";
3
+ import type { PrefillPadding } from "../contracts/mlx/cache";
4
+ import { rowRollIndices } from "./batched-row-storage";
5
+ import { disposeResources } from "../runtime/resources";
6
+
7
+ interface Positions { rowOffsets: number[]; leftPad: number[] }
8
+
9
+ /** mlx-lm BatchKVCache.prepare/finalize (MIT), over arbitrary stored planes.
10
+ * Padding changes token positions; the representation's codec is uninvolved. */
11
+ export class FullPrefillPadding {
12
+ constructor(readonly tokenAxis = 2) {}
13
+ #ends?: number[];
14
+ get endOffsets(): readonly number[] | undefined { return this.#ends; }
15
+ restoreEnds(ends: readonly number[] | undefined): void { this.#ends = ends ? [...ends] : undefined; }
16
+ validOffset(offset: number, row: number): number { return Math.min(offset, this.#ends?.[row] ?? offset); }
17
+
18
+ prepare(positions: Positions, padding: PrefillPadding): void {
19
+ if (!positions.rowOffsets.length) {
20
+ positions.rowOffsets = padding.lengths.map(() => 0);
21
+ positions.leftPad = [...positions.rowOffsets];
22
+ }
23
+ if (padding.leftPadding) {
24
+ positions.leftPad = positions.leftPad.map((pad, row) => pad + padding.leftPadding![row]!);
25
+ positions.rowOffsets = positions.rowOffsets.map((offset, row) => offset - padding.leftPadding![row]!);
26
+ }
27
+ // Like rotating storage, retain absolute valid ends. A cohort can finish
28
+ // before its planned width when a longer request leaves during prefill.
29
+ this.#ends = padding.rightPadding?.some(pad => pad > 0)
30
+ ? padding.lengths.map((length, row) => positions.rowOffsets[row]! + length) : undefined;
31
+ }
32
+
33
+ /** Borrow planes; return owned replacements only when padding needs moving.
34
+ * Stage every plane before committing positions, including on FFI failure. */
35
+ finalize(planes: readonly MlxArray[], positions: Positions): MlxArray[] | undefined {
36
+ if (!this.#ends) return;
37
+ const padding = this.#ends.map((end, row) => Math.max(0, positions.rowOffsets[row]! - end));
38
+ if (!padding.some(pad => pad > 0)) { this.#ends = undefined; return; }
39
+ const next: MlxArray[] = [];
40
+ try {
41
+ if (planes.length) {
42
+ const width = planes[0]!.shape[this.tokenAxis]!;
43
+ using indices = rowRollIndices(width, padding);
44
+ using compressed = this.tokenAxis === 1 ? ops.reshape(indices, [padding.length, width, 1]) : null;
45
+ for (const plane of planes) next.push(ops.takeAlongAxis(plane, compressed ?? indices, this.tokenAxis));
46
+ }
47
+ } catch (error) { disposeResources(next); throw error; }
48
+ positions.rowOffsets = positions.rowOffsets.map((offset, row) => offset - padding[row]!);
49
+ positions.leftPad = positions.leftPad.map((pad, row) => pad + padding[row]!);
50
+ this.#ends = undefined;
51
+ return next;
52
+ }
53
+
54
+ filter(keep: readonly number[]): void { if (this.#ends) this.#ends = keep.map(row => this.#ends![row]!); }
55
+ clear(): void { this.#ends = undefined; }
56
+ }
@@ -0,0 +1,112 @@
1
+ import type { MlxArray } from "@mlx-bun/mlx/array";
2
+ import { materializeCopy } from "@mlx-bun/mlx/materialize";
3
+ import { withResource, disposeResources } from "../runtime/resources";
4
+ import { leaseCacheState } from "./leases";
5
+ import { FullPrefillPadding } from "./full-prefill-padding";
6
+ import { KVCache, type AffineKernelSet } from "./kv";
7
+ import { QuantizedKVCache } from "./quantized-kv";
8
+ import { TurboQuantKVCache } from "./turboquant-kv";
9
+ import { type Cache, type Mask, type PrefillPadding } from "../contracts/mlx/cache";
10
+ import { kvStorage } from "./kv-storage";
11
+
12
+ type FullCache = KVCache | QuantizedKVCache | TurboQuantKVCache;
13
+
14
+ /** A physical full-attention row with separate valid-token coverage. Codecs
15
+ * consume the same physical tensors; padding never advances their threshold. */
16
+ export class FullPrefillRow implements Cache {
17
+ readonly padding = new FullPrefillPadding();
18
+ leftPadding = 0;
19
+ constructor(public inner: FullCache) {}
20
+ get minimumReusableOffset(): number { return (this.inner as Cache).minimumReusableOffset ?? 0; }
21
+ set minimumReusableOffset(value: number) { (this.inner as Cache).minimumReusableOffset = value; }
22
+ get positionOffset(): number { return this.inner.offset - this.leftPadding; }
23
+ get physicalLength(): number { return this.inner.offset; }
24
+ get offset(): number { return Math.max(0, this.padding.validOffset(this.positionOffset, 0)); }
25
+ get stateNeedsDispose(): boolean { return (this.inner as Cache).stateNeedsDispose ?? false; }
26
+ get affineConversion(): FullPrefillRow | undefined { return this.inner instanceof KVCache ? this : undefined; }
27
+ get turboConversion(): FullPrefillRow | undefined { return this.inner instanceof KVCache ? this : undefined; }
28
+ get quantizedAttention() { return kvStorage(this.inner).quantizedAttention; }
29
+ get rotatedValueAttention() { return kvStorage(this.inner).rotatedValueAttention; }
30
+ signature(): string { return this.inner.signature(); }
31
+ state(): MlxArray[] { return this.inner.state(); }
32
+ makeMask(tokens: number, window: number | null): Mask { return this.inner.makeMask(tokens, window); }
33
+ isTrimmable(): boolean { return true; }
34
+ trim(count: number): void { this.inner.trim(count); }
35
+ updateAndFetch(k: MlxArray, v: MlxArray): [MlxArray, MlxArray] { return this.inner.updateAndFetch(k, v); }
36
+ bytesPerToken(): number { return (this.inner as Cache).bytesPerToken?.() ?? 0; }
37
+ preparePrefill(padding: PrefillPadding): void {
38
+ const positions = { rowOffsets: [this.positionOffset], leftPad: [this.leftPadding] };
39
+ this.padding.prepare(positions, padding);
40
+ this.leftPadding = positions.leftPad[0]!;
41
+ }
42
+ finalizePrefill(): void {
43
+ const positions = { rowOffsets: [this.positionOffset], leftPad: [this.leftPadding] };
44
+ const next = withResource(leaseCacheState(this.inner), state => this.padding.finalize(state, positions));
45
+ if (next) this.replace(next, this.inner.offset);
46
+ this.leftPadding = positions.leftPad[0]!;
47
+ }
48
+ removeLeftPadding(count: number): void {
49
+ if (!count) return;
50
+ const next: MlxArray[] = [];
51
+ try {
52
+ withResource(leaseCacheState(this.inner), state => {
53
+ for (const plane of state) next.push(plane.slice([0, 0, count, 0], [...plane.shape]));
54
+ });
55
+ this.replace(next, this.inner.offset - count); next.length = 0;
56
+ this.leftPadding -= count;
57
+ } finally { disposeResources(next); }
58
+ }
59
+ private restored(planes: MlxArray[], offset: number): FullCache {
60
+ const source = this.inner;
61
+ if (source instanceof KVCache) {
62
+ const result = new KVCache(undefined, source.turboQuantKernels, source.windowRead);
63
+ if (planes.length) result.restoreState(planes[0]!, planes[1]!, offset);
64
+ return result;
65
+ }
66
+ if (source instanceof QuantizedKVCache) {
67
+ const result = new QuantizedKVCache(source.groupSize, source.bits, source.kernels);
68
+ if (planes.length) result.restoreState({ packed: planes[0]!, scales: planes[1]!, biases: planes[2]! },
69
+ { packed: planes[3]!, scales: planes[4]!, biases: planes[5]! }, offset);
70
+ return result;
71
+ }
72
+ const result = new TurboQuantKVCache(source.kBits, source.vBits, source.turboQuantKernels, source.fusedDecode);
73
+ if (planes.length) result.restoreState({ kIdx: planes[0]!, kScales: planes[1]!, kZeros: planes[2]!,
74
+ vPacked: planes[3]!, vScales: planes[4]! }, offset, source.headDim!);
75
+ return result;
76
+ }
77
+ private replace(planes: MlxArray[], offset: number): void {
78
+ const next = this.restored(planes, offset), previous = this.inner;
79
+ (next as Cache).minimumReusableOffset = this.minimumReusableOffset;
80
+ this.inner = next; previous.dispose();
81
+ }
82
+ extract(): Cache {
83
+ const count = this.offset, planes: MlxArray[] = [];
84
+ try {
85
+ if (count) withResource(leaseCacheState(this.inner), state => {
86
+ for (const plane of state) {
87
+ using view = plane.slice([0, 0, this.leftPadding, 0], [1, plane.shape[1]!, this.leftPadding + count, plane.shape[3]!]);
88
+ planes.push(materializeCopy(view));
89
+ }
90
+ });
91
+ const result = this.restored(planes, count); planes.length = 0;
92
+ (result as Cache).minimumReusableOffset = this.minimumReusableOffset;
93
+ return result;
94
+ } finally { disposeResources(planes); }
95
+ }
96
+ /** The plain inner storage's conversion (`KVCache.toQuantized`): the
97
+ * converted row attends with the set `kernels` builds. */
98
+ toQuantized(groupSize: number, bits: number, kernels: AffineKernelSet): FullPrefillRow {
99
+ this.inner = (this.inner as KVCache).toQuantized(groupSize, bits, kernels);
100
+ return this;
101
+ }
102
+ toTurboQuantized(kBits: number, vBits: number, fusedDecode?: boolean): FullPrefillRow {
103
+ const source = this.inner as KVCache;
104
+ this.inner = TurboQuantKVCache.fromKVCache(source, kBits, vBits, source.turboQuantKernels, fusedDecode);
105
+ return this;
106
+ }
107
+ dispose(): void { this.inner.dispose(); this.padding.clear(); }
108
+ }
109
+
110
+ export function fullRowInner(row: Cache): Cache { return row instanceof FullPrefillRow ? row.inner : row; }
111
+ export function fullRowPadding(row: Cache): number { return row instanceof FullPrefillRow ? row.leftPadding : 0; }
112
+ export function fullRowPhysicalLength(row: Cache): number { return row instanceof FullPrefillRow ? row.physicalLength : row.offset; }