@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,212 @@
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 { materializeCopy } from "@mlx-bun/mlx/materialize";
5
+ import { FullPrefillPadding } from "./full-prefill-padding";
6
+ import type { Mask, PrefillPadding } from "../contracts/mlx/cache";
7
+ import { cleanupFailure, disposeResources } from "../runtime/resources";
8
+
9
+ /** Borrowed tensor planes with shared token positions. Plane shapes may differ
10
+ * in head width and dtype. Every plane uses [row, head, token, field]. */
11
+ export interface KvTensorRowView {
12
+ readonly planes: readonly MlxArray[];
13
+ readonly rowOffsets: readonly number[];
14
+ readonly leftPad: readonly number[];
15
+ }
16
+
17
+ /** Position and membership operations shared by packed KV representations.
18
+ * A codec supplies tensor planes; this owner never encodes or decodes them. */
19
+ export class KvTensorRows implements KvTensorRowView {
20
+ planes: MlxArray[] = [];
21
+ rowOffsets: number[] = [];
22
+ leftPad: number[] = [];
23
+ #rope: MlxArray | undefined;
24
+ #beforeRound: number[] | undefined;
25
+ readonly #padding = new FullPrefillPadding();
26
+
27
+ restorePrefillEnds(ends: readonly number[] | undefined): void { this.#padding.restoreEnds(ends); }
28
+ preparePrefill(padding: PrefillPadding): void {
29
+ this.#padding.prepare(this, padding); this.#positionsChanged();
30
+ }
31
+ finalizePrefill(): void {
32
+ const next = this.#padding.finalize(this.planes, this);
33
+ if (next) this.#replace(next, this.rowOffsets, this.leftPad);
34
+ }
35
+
36
+ /** Snapshot logical validity without changing physical row alignment. */
37
+ captureDonorValidity(): Pick<import("../contracts/mlx/cache").KvDonorRows, "offsets" | "starts" | "ends"> {
38
+ const offsets = this.rowOffsets.map((offset, row) => Math.max(0, this.#padding.validOffset(offset, row)));
39
+ return { offsets, starts: [...this.leftPad], ends: offsets.map((offset, row) => offset + this.leftPad[row]!) };
40
+ }
41
+ get offset(): number { return Math.max(0, ...this.#ends()); }
42
+ get batchSize(): number | null { return this.rowOffsets.length || null; }
43
+ #ends(): number[] { return this.rowOffsets.map((offset, row) => offset + this.leftPad[row]!); }
44
+ get ropeOffsetArr(): MlxArray | undefined {
45
+ if (this.leftPad.every(pad => pad === 0) && this.rowOffsets.every(offset => offset === this.rowOffsets[0])) return undefined;
46
+ return this.#rope ??= ops.fromInt32(this.rowOffsets, [this.rowOffsets.length]);
47
+ }
48
+ #positionsChanged(): void { this.#rope?.dispose(); this.#rope = undefined; }
49
+ bytesPerToken(): number {
50
+ return this.planes.length && this.rowOffsets.length ? this.planes.reduce((bytes, plane) => bytes + plane.nbytes, 0) /
51
+ (this.rowOffsets.length * this.planes[0]!.shape[2]!) : 0;
52
+ }
53
+ trim(count: number): void {
54
+ this.rowOffsets = this.rowOffsets.map(offset => offset - count); this.#positionsChanged();
55
+ }
56
+ specRoundBegin(): void { this.#beforeRound = [...this.rowOffsets]; }
57
+ specRoundCommit(): void { this.#beforeRound = undefined; }
58
+ specRoundRollback(keep: number | readonly number[]): void {
59
+ this.rowOffsets = this.#beforeRound!.map((offset, row) => offset + (typeof keep === "number" ? keep : keep[row]!));
60
+ this.#beforeRound = undefined; this.#positionsChanged();
61
+ }
62
+ makeMask(N: number, window: number | null): Mask {
63
+ const B = this.rowOffsets.length, ends = this.#ends(), S = this.offset + N;
64
+ if (window === null && this.leftPad.every(pad => pad === 0) && ends.every(end => end === ends[0]))
65
+ return { mode: N === 1 ? "" : "causal", arr: null };
66
+ using starts = ops.fromInt32(ends, [B, 1, 1, 1]);
67
+ using relative = ops.arange(0, N, 1, Dtype.int32);
68
+ using queries = ops.reshape(relative, [1, 1, N, 1]);
69
+ using position = ops.add(starts, queries);
70
+ using indices = ops.arange(0, S, 1, Dtype.int32);
71
+ using keys = ops.reshape(indices, [1, 1, 1, S]);
72
+ using pads = ops.fromInt32(this.leftPad, [B, 1, 1, 1]);
73
+ using causal = ops.lessEqual(keys, position);
74
+ using valid = ops.greaterEqual(keys, pads);
75
+ const mask = ops.logicalAnd(causal, valid);
76
+ if (window === null) return { mode: "array", arr: mask };
77
+ using windowSize = ops.fromInt32([window], []);
78
+ using limit = ops.add(keys, windowSize);
79
+ using within = ops.less(position, limit);
80
+ try { return { mode: "array", arr: ops.logicalAnd(mask, within) }; }
81
+ finally { mask.dispose(); }
82
+ }
83
+ #replace(planes: MlxArray[], offsets: number[], pads: number[]): void {
84
+ const previous = this.planes;
85
+ this.planes = planes; this.rowOffsets = offsets; this.leftPad = pads;
86
+ this.#positionsChanged(); disposeResources(previous);
87
+ }
88
+ append(inputs: readonly MlxArray[]): void {
89
+ const B = this.rowOffsets.length, N = inputs[0]!.shape[2]!, ends = this.#ends();
90
+ const needed = Math.max(...ends) + N, capacity = Math.ceil(needed / 256) * 256;
91
+ const common = ends.every(end => end === ends[0]);
92
+ using positions = common ? null : ops.fromInt32(ends.flatMap(end =>
93
+ Array.from({ length: N }, (_, position) => end + position)), [B, 1, N, 1]);
94
+ const next: MlxArray[] = [];
95
+ try {
96
+ for (const [index, input] of inputs.entries()) {
97
+ const old = this.planes[index];
98
+ let base: MlxArray;
99
+ if (!old) base = ops.zeros([B, input.shape[1]!, capacity, input.shape[3]!], input.dtype);
100
+ else if (old.shape[2]! >= needed) base = old;
101
+ else {
102
+ using padding = ops.zeros([B, old.shape[1]!, capacity - old.shape[2]!, old.shape[3]!], old.dtype);
103
+ base = ops.concatAxis([old, padding], 2);
104
+ }
105
+ try {
106
+ const updated = common
107
+ ? ops.sliceUpdate(base, input, [0, 0, ends[0]!, 0], [B, base.shape[1]!, ends[0]! + N, base.shape[3]!])
108
+ : ops.putAlongAxis(base, positions!, input, 2);
109
+ next.push(updated);
110
+ } finally { if (base !== old) base.dispose(); }
111
+ }
112
+ } catch (error) { return cleanupFailure(error, () => disposeResources(next)); }
113
+ this.#replace(next, this.rowOffsets.map(offset => offset + N), this.leftPad);
114
+ }
115
+ mergeRows(rows: readonly KvTensorRowView[]): void {
116
+ const next: MlxArray[] = [], held: MlxArray[] = [];
117
+ let offsets: number[], pads: number[];
118
+ try {
119
+ if (rows.length === 1) {
120
+ const row = rows[0]!;
121
+ for (const plane of row.planes) next.push(ops.copyOf(plane));
122
+ offsets = [...row.rowOffsets]; pads = [...row.leftPad];
123
+ } else {
124
+ const widths = rows.map(row => Math.max(0, ...row.rowOffsets.map((offset, i) => offset + row.leftPad[i]!)));
125
+ const width = Math.max(0, ...widths);
126
+ if (width) {
127
+ const prototype = rows.find((_, index) => widths[index]! > 0)!;
128
+ for (const [field, geometry] of prototype.planes.entries()) {
129
+ const pieces = rows.map((row, index) => {
130
+ const active = widths[index]!;
131
+ if (!active) {
132
+ const empty = ops.zeros([row.rowOffsets.length, geometry.shape[1]!, width, geometry.shape[3]!], geometry.dtype);
133
+ held.push(empty); return empty;
134
+ }
135
+ const plane = row.planes[field]!;
136
+ const view = plane.slice([0, 0, 0, 0], [row.rowOffsets.length, plane.shape[1]!, active, plane.shape[3]!]);
137
+ held.push(view);
138
+ if (active === width) return view;
139
+ using padding = ops.zeros([row.rowOffsets.length, plane.shape[1]!, width - active, plane.shape[3]!], plane.dtype);
140
+ const padded = ops.concatAxis([padding, view], 2); held.push(padded); return padded;
141
+ });
142
+ next.push(ops.concatAxis(pieces, 0));
143
+ }
144
+ }
145
+ offsets = rows.flatMap(row => [...row.rowOffsets]);
146
+ pads = rows.flatMap((row, index) => row.leftPad.map(pad => pad + width - widths[index]!));
147
+ }
148
+ } catch (error) { return cleanupFailure(error, () => disposeResources(next)); }
149
+ finally { disposeResources(held); }
150
+ this.#replace(next, offsets, pads);
151
+ }
152
+ /** Preserve caller-owned physical positions when changing representation.
153
+ * Live tensor bytes are copied; padding never participates in a codec. */
154
+ alignRows(leftPad: readonly number[]): void {
155
+ if (leftPad.every((pad, row) => pad === this.leftPad[row])) return;
156
+ const width = Math.max(0, ...this.rowOffsets.map((offset, row) => offset + leftPad[row]!));
157
+ const rows: MlxArray[][] = [], planes: MlxArray[] = [];
158
+ try {
159
+ for (let row = 0; row < this.rowOffsets.length; row++) rows.push(this.extractRow(row));
160
+ for (let field = 0; field < this.planes.length; field++) {
161
+ const parts: MlxArray[] = [];
162
+ try {
163
+ for (let row = 0; row < rows.length; row++) {
164
+ const geometry = this.planes[field]!, live = rows[row]![field];
165
+ using before = ops.zeros([1, geometry.shape[1]!, leftPad[row]!, geometry.shape[3]!], geometry.dtype);
166
+ using after = ops.zeros([1, geometry.shape[1]!, width - leftPad[row]! - this.rowOffsets[row]!, geometry.shape[3]!], geometry.dtype);
167
+ parts.push(ops.concatAxis(live ? [before, live, after] : [before, after], 2));
168
+ }
169
+ planes.push(ops.concatAxis(parts, 0));
170
+ } finally { disposeResources(parts); }
171
+ }
172
+ } catch (error) { return cleanupFailure(error, () => disposeResources(planes)); }
173
+ finally { disposeResources(rows.flat()); }
174
+ this.#replace(planes, [...this.rowOffsets], [...leftPad]);
175
+ }
176
+ filterRows(keep: readonly number[]): void {
177
+ if (!keep.length) { this.dispose(); return; }
178
+ const next: MlxArray[] = [];
179
+ using indices = this.planes.length ? ops.fromInt32([...keep], [keep.length]) : null;
180
+ const pads = keep.map(row => this.leftPad[row]!);
181
+ const sharedPadding = Math.min(...pads);
182
+ try {
183
+ for (const plane of this.planes) {
184
+ const selected = ops.takeAxis(plane, indices!, 0);
185
+ if (!sharedPadding) next.push(selected);
186
+ else {
187
+ try { next.push(selected.slice([0, 0, sharedPadding, 0], [...selected.shape])); }
188
+ finally { selected.dispose(); }
189
+ }
190
+ }
191
+ }
192
+ catch (error) { return cleanupFailure(error, () => disposeResources(next)); }
193
+ this.#replace(next, keep.map(row => this.rowOffsets[row]!), pads.map(pad => pad - sharedPadding));
194
+ this.#padding.filter(keep);
195
+ }
196
+ extractRow(row: number): MlxArray[] {
197
+ const start = this.leftPad[row]!, count = this.rowOffsets[row]!, result: MlxArray[] = [];
198
+ if (!count) return result;
199
+ try {
200
+ for (const plane of this.planes) {
201
+ using view = plane.slice([row, 0, start, 0], [row + 1, plane.shape[1]!, start + count, plane.shape[3]!]);
202
+ result.push(materializeCopy(view));
203
+ }
204
+ return result;
205
+ } catch (error) { return cleanupFailure(error, () => disposeResources(result)); }
206
+ }
207
+ dispose(): void {
208
+ const previous = this.planes;
209
+ this.planes = []; this.rowOffsets = []; this.leftPad = []; this.#beforeRound = undefined; this.#padding.clear();
210
+ this.#positionsChanged(); disposeResources(previous);
211
+ }
212
+ }
@@ -0,0 +1,324 @@
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 type { AttentionRead,BidirectionalAttentionCache,BlockAttentionCache,Cache,CommittedAttentionCache,CompiledDecodeCache,DecodeSlot,DecodeStepInputs,DecodeStepPlan,DecodeTrace,EncoderAttentionCache,KvDonorRows,Mask } from "../contracts/mlx/cache";
5
+ import { bidirMask, createCausalMask } from "../kernels/attention/masks";
6
+ import { QuantizedKVCache } from "./quantized-kv";
7
+ import { AttentionMasks, causalLease, encoderWindowMask, sdpaRead, unmaskedLease, withLease, type DenseRead } from "./attention-read";
8
+ import type { AffineKernelSet } from "./affine-attention";
9
+ import { uncomposedTurboQuantKernels, type TurboQuantKernels } from "./turboquant-attention";
10
+
11
+ export type { AffineKernelSet };
12
+
13
+
14
+ /** KV cache — port of mlx-lm cache.py KVCache: preallocated in steps of
15
+ * 256 along the sequence axis, updated in place via slice_update.
16
+ *
17
+ * Reads: each named read appends through `updateAndFetch` and attends the
18
+ * fetched prefix with the stock fused SDPA, the call every graph makes after
19
+ * `updateAndFetch` today, a window through `windowRead` (that SDPA unless the
20
+ * composition names a read computing its bits otherwise). Decode passes no
21
+ * mask and a window the fused causal mask; at one query the two compute the
22
+ * same bits, so a one-row window needs no case of its own. This cache has no
23
+ * sliding window (graphs compose `RotatingKVCache` for sliding layers), so
24
+ * only the bidirectional and encoder reads build a mask array. */
25
+ export class KVCache implements CompiledDecodeCache, CommittedAttentionCache, BidirectionalAttentionCache, EncoderAttentionCache, BlockAttentionCache {
26
+ static readonly STEP = 256;
27
+ /** Set only by compiled-decode trace adapters (see Cache). */
28
+ readonly ropeOffsetArr?: MlxArray;
29
+ keys: MlxArray | null = null;
30
+ values: MlxArray | null = null;
31
+ offset = 0;
32
+
33
+ constructor(
34
+ /** The per-forward masks of the model this cache belongs to. Row layouts
35
+ * made from this cache (`state/layout`) share them. */
36
+ readonly masks = new AttentionMasks(),
37
+ /** The TurboQuant read of the graph that built this cache: a TurboQuant
38
+ * conversion of this storage (`TurboQuantKVCache.fromKVCache`, the delayed
39
+ * TurboQuant rows) attends with it, and copies of this cache carry it.
40
+ * Qwen 3.5's `makeCache` passes `denseTurboQuantKernels`, Gemma 4's and
41
+ * MiniCPM5's `deferredTurboQuantKernels`; a graph that reads TurboQuant
42
+ * storage only through the deprecated fetches passes none. */
43
+ readonly turboQuantKernels: TurboQuantKernels = uncomposedTurboQuantKernels,
44
+ /** The read of a causal window (`appendWindow`, `appendCommitted`): the
45
+ * stock fused SDPA, or the read the composition names
46
+ * (`foldedWindowRead`, bit for bit the same). Row layouts made from this
47
+ * storage and its clones read their windows with it. */
48
+ readonly windowRead: DenseRead = sdpaRead,
49
+ ) {}
50
+
51
+ signature(): string { return "kv:plain"; }
52
+
53
+ appendDecode(k: MlxArray, v: MlxArray): AttentionRead {
54
+ const [keys, values] = this.updateAndFetch(k, v);
55
+ return sdpaRead(keys, values, unmaskedLease);
56
+ }
57
+
58
+ appendWindow(k: MlxArray, v: MlxArray): AttentionRead {
59
+ const [keys, values] = this.updateAndFetch(k, v);
60
+ return this.windowRead(keys, values, causalLease);
61
+ }
62
+
63
+ /** A bf16 committed span reads exactly as a window does. */
64
+ appendCommitted(k: MlxArray, v: MlxArray): AttentionRead {
65
+ return this.appendWindow(k, v);
66
+ }
67
+
68
+ /** Gemma 4's vision prefill read: the causal mask OR'd with the image block
69
+ * overlay (`bidirMask`), then the fused SDPA with that array mask. One mask
70
+ * per forward: every layer passes the same `bidirectional` tensor. */
71
+ appendBidirectional(k: MlxArray, v: MlxArray, bidirectional: MlxArray): AttentionRead {
72
+ const L = k.shape[2]!;
73
+ const mask = this.masks.lease("bidirectional:none", `${L}`, () => bidirMask(L, null, bidirectional), bidirectional);
74
+ return withLease(mask, held => {
75
+ const [keys, values] = this.updateAndFetch(k, v);
76
+ return sdpaRead(keys, values, held);
77
+ });
78
+ }
79
+
80
+ /** DiffusionGemma's vision encoder read on a full-attention layer: the
81
+ * append `updateAndFetch` makes, then the fused SDPA under the encoder
82
+ * window's mask (`encoderWindowMask`), one per forward for every layer. */
83
+ appendEncoderWindow(k: MlxArray, v: MlxArray, bidirectional: MlxArray): AttentionRead {
84
+ return withLease(encoderWindowMask(this.masks, k.shape[2]!, bidirectional), mask => {
85
+ const [keys, values] = this.updateAndFetch(k, v);
86
+ return sdpaRead(keys, values, mask);
87
+ });
88
+ }
89
+
90
+ /** DiffusionGemma's canvas read on a full-attention layer: the stored
91
+ * positions in order, then the block's keys and values, with no mask. */
92
+ readBlock(k: MlxArray, v: MlxArray): AttentionRead {
93
+ const [storedK, storedV] = this.temporalView();
94
+ let keys: MlxArray, values: MlxArray;
95
+ try {
96
+ keys = ops.concatAxis([storedK, k], 2);
97
+ try { values = ops.concatAxis([storedV, v], 2); }
98
+ catch (error) { keys.dispose(); throw error; }
99
+ } finally { storedK.dispose(); storedV.dispose(); }
100
+ return sdpaRead(keys, values, unmaskedLease);
101
+ }
102
+
103
+ bytesPerToken(): number {
104
+ const steps = this.keys?.shape[2] ?? 0;
105
+ return steps > 0 ? (this.keys!.nbytes + this.values!.nbytes) / steps : 0;
106
+ }
107
+
108
+ updateAndFetch(k: MlxArray, v: MlxArray): [MlxArray, MlxArray] {
109
+ const prev = this.offset;
110
+ const L = k.shape[2]!;
111
+ if (!this.keys || prev + L > this.keys.shape[2]!) {
112
+ const [B, H, , D] = k.shape as [number, number, number, number];
113
+ const vD = v.shape[3]!;
114
+ const nSteps = Math.floor((KVCache.STEP + L - 1) / KVCache.STEP);
115
+ const newK = ops.zeros([B, H, nSteps * KVCache.STEP, D], k.dtype);
116
+ const newV = ops.zeros([B, H, nSteps * KVCache.STEP, vD], v.dtype);
117
+ if (this.keys && this.values) {
118
+ let oldK = this.keys;
119
+ let oldV = this.values;
120
+ if (prev % KVCache.STEP !== 0) {
121
+ const trimK = oldK.slice([0, 0, 0, 0], [B, H, prev, D]);
122
+ const trimV = oldV.slice([0, 0, 0, 0], [B, H, prev, vD]);
123
+ oldK.dispose();
124
+ oldV.dispose();
125
+ oldK = trimK;
126
+ oldV = trimV;
127
+ }
128
+ this.keys = ops.concatAxis([oldK, newK], 2);
129
+ this.values = ops.concatAxis([oldV, newV], 2);
130
+ for (const a of [oldK, oldV, newK, newV]) a.dispose();
131
+ } else {
132
+ this.keys = newK;
133
+ this.values = newV;
134
+ }
135
+ }
136
+
137
+ this.offset += L;
138
+ const [B, H, S, D] = this.keys!.shape as [number, number, number, number];
139
+ const vD = this.values!.shape[3]!;
140
+ const k2 = ops.sliceUpdate(this.keys!, k, [0, 0, prev, 0], [B, H, this.offset, D]);
141
+ const v2 = ops.sliceUpdate(this.values!, v, [0, 0, prev, 0], [B, H, this.offset, vD]);
142
+ this.keys!.dispose();
143
+ this.values!.dispose();
144
+ this.keys = k2;
145
+ this.values = v2;
146
+ return [
147
+ this.keys.slice([0, 0, 0, 0], [B, H, this.offset, D]),
148
+ this.values.slice([0, 0, 0, 0], [B, H, this.offset, vD]),
149
+ ];
150
+ }
151
+
152
+ makeMask(N: number, windowSize: number | null): Mask {
153
+ if (N === 1) return { mode: "", arr: null };
154
+ // Windowless multi-token chunks get the STRING "causal" at ANY offset —
155
+ // mlx-lm cache.py:114-125 create_attention_mask does exactly this, and
156
+ // mx.fast SDPA aligns the string bottom-right, i.e. the same values as
157
+ // our materialized matrix. Shipping the array instead (the old
158
+ // offset>0 branch) forced a materialize-and-add mask path: a bool
159
+ // [N, offset+N] built PER CHUNK and read by every full-attention layer
160
+ // — the e4b 16k prefill gap vs mlx-lm (2026-07-06b, hd=512 layers
161
+ // where the fallback dispatch can't block-skip an array mask).
162
+ if (windowSize === null) return { mode: "causal", arr: null };
163
+ if (this.offset === 0 && N <= windowSize) return { mode: "causal", arr: null };
164
+ return { mode: "array", arr: createCausalMask(N, this.offset, windowSize) };
165
+ }
166
+
167
+ /** Arrays to eval to materialize cache state (prefill chunk boundary). */
168
+ state(): MlxArray[] {
169
+ return this.keys && this.values ? [this.keys, this.values] : [];
170
+ }
171
+
172
+ isTrimmable(): boolean {
173
+ return true;
174
+ }
175
+
176
+ trim(n: number): void {
177
+ this.offset = Math.max(0, this.offset - n);
178
+ }
179
+
180
+ /** Chronological (K, V) view sliced to offset (drafter donor read). */
181
+ captureDonorRows(): KvDonorRows {
182
+ const [keys, values] = this.temporalView();
183
+ const B = keys.shape[0]!, width = keys.shape[2]!;
184
+ return { keys, values, offsets: Array(B).fill(this.offset),
185
+ starts: Array(B).fill(0), ends: Array(B).fill(width) };
186
+ }
187
+
188
+ temporalView(): [MlxArray, MlxArray] {
189
+ if (!this.keys || !this.values) throw new Error("cache is empty");
190
+ const [B, H, , D] = this.keys.shape as [number, number, number, number];
191
+ const vD = this.values.shape[3]!;
192
+ return [
193
+ this.keys.slice([0, 0, 0, 0], [B, H, this.offset, D]),
194
+ this.values.slice([0, 0, 0, 0], [B, H, this.offset, vD]),
195
+ ];
196
+ }
197
+
198
+ /** Compiled decode: host-side bookkeeping for a 1-token step — the
199
+ * growth/trim half of updateAndFetch (L=1), without the write. */
200
+ prepareDecodeStep(): DecodeStepPlan {
201
+ const prev = this.offset;
202
+ if (!this.keys || !this.values) throw new Error("compiled decode on an empty cache");
203
+ if (prev + 1 > this.keys.shape[2]!) {
204
+ const [B, H, , D] = this.keys.shape as [number, number, number, number];
205
+ const vD = this.values.shape[3]!;
206
+ let oldK = this.keys;
207
+ let oldV = this.values;
208
+ if (prev % KVCache.STEP !== 0) {
209
+ const trimK = oldK.slice([0, 0, 0, 0], [B, H, prev, D]);
210
+ const trimV = oldV.slice([0, 0, 0, 0], [B, H, prev, vD]);
211
+ oldK.dispose();
212
+ oldV.dispose();
213
+ oldK = trimK;
214
+ oldV = trimV;
215
+ }
216
+ const newK = ops.zeros([B, H, KVCache.STEP, D], oldK.dtype);
217
+ const newV = ops.zeros([B, H, KVCache.STEP, vD], oldV.dtype);
218
+ this.keys = ops.concatAxis([oldK, newK], 2);
219
+ this.values = ops.concatAxis([oldV, newV], 2);
220
+ for (const a of [oldK, oldV, newK, newV]) a.dispose();
221
+ }
222
+ return { fetch: "concat", writePos: prev, activeLen: prev };
223
+ }
224
+
225
+ decodePhase(): DecodeStepPlan["fetch"] { return "concat"; }
226
+
227
+ decodeSlot(_plan: DecodeStepPlan): DecodeSlot { return concatDecodeSlot(this.offset); }
228
+
229
+ decodeInputs(plan: DecodeStepPlan, step: DecodeStepInputs): MlxArray[] {
230
+ return [step.activeView(this.keys!, plan.activeLen), step.activeView(this.values!, plan.activeLen)];
231
+ }
232
+
233
+ commitDecodeStep(_slot: DecodeSlot, outputs: MlxArray[]): MlxArray[] {
234
+ return this.writeDecodeStep(outputs[0]!, outputs[1]!);
235
+ }
236
+
237
+ /** Compiled decode: the write half (same sliceUpdate as updateAndFetch).
238
+ * Takes ownership of kNew/vNew; returns the arrays to async-eval with
239
+ * the step (the updated buffers). */
240
+ writeDecodeStep(kNew: MlxArray, vNew: MlxArray): MlxArray[] {
241
+ const prev = this.offset;
242
+ const [B, H, , D] = this.keys!.shape as [number, number, number, number];
243
+ const vD = this.values!.shape[3]!;
244
+ const k2 = ops.sliceUpdate(this.keys!, kNew, [0, 0, prev, 0], [B, H, prev + 1, D]);
245
+ const v2 = ops.sliceUpdate(this.values!, vNew, [0, 0, prev, 0], [B, H, prev + 1, vD]);
246
+ this.keys!.dispose();
247
+ this.values!.dispose();
248
+ kNew.dispose();
249
+ vNew.dispose();
250
+ this.keys = k2;
251
+ this.values = v2;
252
+ this.offset = prev + 1;
253
+ return [k2, v2];
254
+ }
255
+
256
+ /** Adopt persisted state (takes ownership of the arrays). */
257
+ restoreState(keys: MlxArray, values: MlxArray, offset: number): void {
258
+ this.dispose();
259
+ this.keys = keys;
260
+ this.values = values;
261
+ this.offset = offset;
262
+ }
263
+
264
+ /** Port of mlx-lm KVCache.to_quantized: quantize the whole buffer
265
+ * (padding included — it's overwritten before being read). The converted
266
+ * cache attends with the kernel set `kernels` builds for its bits, group
267
+ * size and this cache's activation dtype. */
268
+ toQuantized(groupSize: number, bits: number, kernels: AffineKernelSet): QuantizedKVCache {
269
+ const q = new QuantizedKVCache(groupSize, bits, kernels(bits, groupSize, this.keys?.dtype ?? Dtype.bfloat16), this.masks);
270
+ q.offset = this.offset;
271
+ if (this.keys && this.values) {
272
+ q.keys = ops.quantize(this.keys, groupSize, bits);
273
+ q.values = ops.quantize(this.values, groupSize, bits);
274
+ }
275
+ this.dispose();
276
+ return q;
277
+ }
278
+
279
+ dispose(): void {
280
+ this.keys?.dispose();
281
+ this.values?.dispose();
282
+ this.keys = this.values = null;
283
+ this.offset = 0;
284
+ this.masks.clear();
285
+ }
286
+ }
287
+
288
+ /** The growing plain cache inside a compiled decode trace (also a rotating
289
+ * cache before its window fills): fetch = concat(active prefix, new row); the
290
+ * new row is a closure output for the write after the step. `offset` is set for
291
+ * completeness only: at L=1 every makeMask returns mode "" and the RoPE offset
292
+ * rides `ropeOffsetArr`, so the baked value cannot leak into the graph. */
293
+ class TracedConcatKVCache extends KVCache implements DecodeTrace {
294
+ override readonly ropeOffsetArr: MlxArray;
295
+ outs: MlxArray[] = [];
296
+ constructor(
297
+ offset: number,
298
+ readonly activeK: MlxArray,
299
+ readonly activeV: MlxArray,
300
+ ropeOffsetArr: MlxArray,
301
+ ) {
302
+ super();
303
+ this.offset = offset;
304
+ this.ropeOffsetArr = ropeOffsetArr;
305
+ }
306
+
307
+ override updateAndFetch(k: MlxArray, v: MlxArray): [MlxArray, MlxArray] {
308
+ // same-shape reshape: an owned alias that survives the caller's
309
+ // dispose of k/v, so the row can be a closure output
310
+ this.outs = [ops.reshape(k, k.shape), ops.reshape(v, v.shape)];
311
+ return [
312
+ ops.concatAxis([this.activeK, k], 2),
313
+ ops.concatAxis([this.activeV, v], 2),
314
+ ];
315
+ }
316
+ }
317
+
318
+ /** The concat-fetch slot of a plain cache at `offset` (KVCache, and RotatingKVCache before the window fills). */
319
+ export function concatDecodeSlot(offset: number): DecodeSlot {
320
+ return {
321
+ key: "p-cat:0:0", fetch: "concat", inputs: 2, outputs: 2,
322
+ trace: (inputs, ropeOffset) => new TracedConcatKVCache(offset, inputs[0]!, inputs[1]!, ropeOffset),
323
+ };
324
+ }
@@ -0,0 +1,46 @@
1
+ import { KV4_DECODE_BLOCK } from "../kernels/attention/kvq4-decode";
2
+ import { kv4DecodeAttention } from "../layers/kv4-decode-attention";
3
+ import { foldedQuantizedSdpa } from "../layers/quantized-attention";
4
+ import { unfusedKernel, type AffineKernels } from "./affine-attention";
5
+ import { AttentionMasks } from "./attention-read";
6
+ import { QuantizedKVCache } from "./quantized-kv";
7
+
8
+ /** The kernels of the Qwen3.8-27B Trellis compositions' full-attention layers
9
+ * for one sequence: the fused one-row KV4 decode kernel (`kv4DecodeAttention`,
10
+ * each packed key and value row read once for every query head of its KV head,
11
+ * `decodeBlock` tokens per threadgroup: the M4 Pro's 512 by default, the M1
12
+ * Max's 128 as its composition names) and the folded-GQA window kernel
13
+ * (`foldedQuantizedSdpa`, one quantized matmul per KV head for its query rows,
14
+ * causal over the last L positions). Neither reads the mask, and both take one
15
+ * row (B = 1), head dim 256 and an even GQA factor. `multiRow` is the
16
+ * composition's affine set, which a row layout reads with whenever it holds
17
+ * more than one row or one behind left padding: the old graph served several
18
+ * sequences on the generic path. A masked window on serial storage takes the
19
+ * unfused port, which this storage never needs (its mask is plain causal). */
20
+ export function kv4Head256Kernels(multiRow: AffineKernels, decodeBlock = KV4_DECODE_BLOCK): AffineKernels {
21
+ return {
22
+ decode: (q, keys, values, scale) => kv4DecodeAttention(q, keys, values, scale, 64, 4, decodeBlock),
23
+ window: (q, keys, values, scale) => foldedQuantizedSdpa(q, keys, values, scale, 64, 4),
24
+ maskedWindow: unfusedKernel(64, 4),
25
+ multiRow,
26
+ };
27
+ }
28
+
29
+ /** The 4-bit group-64 head-dim-256 full-attention storage the M4 Pro Qwen3.8
30
+ * composition reads, quantized from its first append: `QuantizedKVCache`
31
+ * storage with `kv4Head256Kernels`. The served lego is the bf16-first
32
+ * `Bf16FirstKv4Head256Cache` (`state/bf16-first-kv.ts`), whose converted
33
+ * storage reads with these kernels; this cache is the quantized-from-token-zero
34
+ * composition. The committed read is `QuantizedKVCache`'s.
35
+ *
36
+ * Each read is bit-identical to the kernel the old graph injected for one
37
+ * sequence; neither kernel is bit-identical to the unfused port (f32 online
38
+ * softmax, regrouped rows: bf16-level). A one-row window takes the folded
39
+ * kernel, where the old graph's one-row plan took the KV4 decode kernel. Row
40
+ * layouts made from this cache keep its kernels, `multiRow` included. */
41
+ export class Kv4Head256Cache extends QuantizedKVCache {
42
+ /** `multiRow`: the composition's affine kernels for 4-bit group 64. */
43
+ constructor(multiRow: AffineKernels, masks = new AttentionMasks()) {
44
+ super(64, 4, kv4Head256Kernels(multiRow), masks);
45
+ }
46
+ }