@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,663 @@
1
+ // The quantized KV legos a composition's `makeCache` builds: bf16 storage
2
+ // through the first append (or the first `start` tokens), quantized storage
3
+ // after. Today's served path and mlx-lm's generate loop both run the first
4
+ // forward against unquantized keys and values and convert afterwards, even with
5
+ // `quantized_kv_start` 0; these caches keep that order inside their own
6
+ // appends. A cache quantized from token zero (`QuantizedKVCache` built empty)
7
+ // is a different composition with different first-chunk numbers.
8
+ import type { MlxArray } from "@mlx-bun/mlx/array";
9
+ import { clearCache, Dtype } from "@mlx-bun/mlx/ffi";
10
+ import * as ops from "@mlx-bun/mlx/ops";
11
+ import type {
12
+ AttentionCache, AttentionRead, BidirectionalAttentionCache, BlockAttentionCache, Cache, CommittedAttentionCache, CompiledDecodeCache, ComposedStorage,
13
+ EncoderAttentionCache,
14
+ DecodeSlot, DecodeStepInputs, DecodeStepPlan, Mask,
15
+ } from "../contracts/mlx/cache";
16
+ import type { KvStorageScheme } from "../contracts/portable/composition";
17
+ import { composedAffineKernels, type AffineKernels, type AffineKernelSet } from "./affine-attention";
18
+ import { AttentionMasks, decodeByPosition, sdpaRead, type DenseRead } from "./attention-read";
19
+ import { KVCache } from "./kv";
20
+ import { KV4_DECODE_BLOCK } from "../kernels/attention/kvq4-decode";
21
+ import { kv4Head256Kernels } from "./kv4-head256-cache";
22
+ import { QuantizedKVCache } from "./quantized-kv";
23
+ import { RotatingKVCache } from "./rotating-kv";
24
+ import { RotatingQuantizedKVCache } from "./rotating-quantized-kv";
25
+ import { TurboQuantKVCache } from "./turboquant-kv";
26
+ import type { TurboQuantKernels } from "./turboquant-attention";
27
+ import { turboQuantFusedDecode } from "./turboquant-codec";
28
+ import { isQuantizedKvCache, isRotatingQuantizedCache, isTurboQuantKvCache } from "./capabilities";
29
+
30
+ /** Whether bf16 storage holding `offset` positions converts under a
31
+ * conversion start of `start` tokens: once it holds at least `start`
32
+ * positions, and never while empty, so the first append is bf16 even when the
33
+ * start is 0. */
34
+ export function transitionDue(offset: number, start: number): boolean {
35
+ return offset >= start && offset !== 0;
36
+ }
37
+
38
+ /** Finish one conversion: a nonzero start records the converted `offset`
39
+ * (read before the source was consumed) as the earliest reusable prefix, then
40
+ * the new storage is materialized and the allocator pool released before
41
+ * anything else converts, which bounds the live bf16 source plus quantized
42
+ * copy to one layer. Returns `converted`. */
43
+ export function completeConversion<C extends Cache>(converted: C, offset: number, start: number): C {
44
+ if (start > 0) converted.minimumReusableOffset = offset;
45
+ const state = converted.state();
46
+ try { ops.evalAll(state); }
47
+ finally { if (converted.stateNeedsDispose) for (const array of state) array.dispose(); }
48
+ clearCache();
49
+ return converted;
50
+ }
51
+
52
+ /** The affine conversion of one bf16 entry: the source's own `toQuantized`
53
+ * (which consumes the source) with `kernelSet`, the set the converted storage
54
+ * attends with, then the shared bookkeeping. */
55
+ export function convertToAffine<C extends Cache>(
56
+ source: { readonly offset: number; toQuantized(groupSize: number, bits: number, kernels: AffineKernelSet): C },
57
+ groupSize: number, bits: number, start: number, kernelSet: AffineKernelSet,
58
+ ): C {
59
+ const offset = source.offset;
60
+ return completeConversion(source.toQuantized(groupSize, bits, kernelSet), offset, start);
61
+ }
62
+
63
+ /** The TurboQuant conversion of one bf16 entry: encode the source's live
64
+ * window (consuming the source), then the shared bookkeeping. The converted
65
+ * cache attends with the source's TurboQuant read (`turboQuantKernels`);
66
+ * `fusedDecode` is its decode policy. */
67
+ export function convertToTurboQuant(source: KVCache, kBits: number, vBits: number, start: number,
68
+ fusedDecode: boolean): TurboQuantKVCache {
69
+ const offset = source.offset;
70
+ return completeConversion(TurboQuantKVCache.fromKVCache(source, kBits, vBits, source.turboQuantKernels, fusedDecode), offset, start);
71
+ }
72
+
73
+ /** Affine storage `source` holds, re-homed into a cache of the same storage
74
+ * class that attends with `kernels` and `masks`: the arrays move (no copy) and
75
+ * `source` is left empty. A restored, cloned or extracted entry enters a lego
76
+ * with the lego's own kernels. */
77
+ function rehomeAffine(source: QuantizedKVCache, kernels: AffineKernels, masks: AttentionMasks): QuantizedKVCache {
78
+ const cache = new QuantizedKVCache(source.groupSize, source.bits, kernels, masks);
79
+ if (source.keys && source.values) cache.restoreState(source.keys, source.values, source.offset);
80
+ else cache.offset = source.offset;
81
+ cache.minimumReusableOffset = source.minimumReusableOffset;
82
+ source.keys = source.values = null;
83
+ source.dispose();
84
+ return cache;
85
+ }
86
+
87
+ function rehomeRotatingAffine(source: RotatingQuantizedKVCache, kernels: AffineKernels, masks: AttentionMasks): RotatingQuantizedKVCache {
88
+ const cache = new RotatingQuantizedKVCache(source.maxSize, source.groupSize, source.bits, kernels, masks);
89
+ if (source.keys && source.values) cache.restoreState(source.keys, source.values, source.offset, source.ringIdx);
90
+ else { cache.offset = source.offset; cache.ringIdx = source.ringIdx; }
91
+ cache.minimumReusableOffset = source.minimumReusableOffset;
92
+ source.keys = source.values = null;
93
+ source.dispose();
94
+ return cache;
95
+ }
96
+
97
+ /** The deprecated plain fetch of a lego in bf16: the storage's fetch, then the
98
+ * transition its append may owe. The caller owns the fetched keys and values,
99
+ * which outlive the conversion that consumes the bf16 storage. */
100
+ function fetchThenSettle(fetched: [MlxArray, MlxArray], settle: () => void): [MlxArray, MlxArray] {
101
+ try { settle(); }
102
+ catch (error) { fetched[0].dispose(); fetched[1].dispose(); throw error; }
103
+ return fetched;
104
+ }
105
+
106
+ /** The bidirectional and encoder reads are defined for a sequence's first
107
+ * window only, which a lego always holds in bf16. */
108
+ function firstWindowOnly(read: string): never {
109
+ throw new Error(`${read} reads a sequence's first window (offset 0); this cache already holds positions`);
110
+ }
111
+
112
+ /**
113
+ * Affine-quantized keys and values for one full-attention layer, bf16 first:
114
+ * the lego `--kv-quant N` (and a `kv_config.json` layer) composes.
115
+ *
116
+ * Numbers: today's served path and mlx-lm's (`quantized_kv_start`). Storage is
117
+ * a `KVCache` until an append leaves it holding at least `start` positions (the
118
+ * first append when `start` is 0). That append is read bf16 and then, before it
119
+ * returns, the storage converts (`KVCache.toQuantized`, the reuse floor at a
120
+ * nonzero start, materialized at once). Every later append quantizes its
121
+ * positions and is read by the quantized storage's own reads, with
122
+ * `kernelSet`'s kernels. Stored values below the offset therefore equal the
123
+ * convert-after path's bit for bit, and so does every read. A
124
+ * `QuantizedKVCache` built empty is the other composition: it quantizes the
125
+ * first append too, so its first chunk's output differs
126
+ * (`tests/state/bf16-first-kv.test.ts` records by how much), and it has no
127
+ * mlx-lm oracle.
128
+ *
129
+ * The transition keys only on this cache's stored length and runs only inside
130
+ * its appends (or when it adopts storage that already holds `start`
131
+ * positions), so the caller's append boundaries are part of the numbers: a
132
+ * caller that splits one planned prefill chunk into two appends moves the
133
+ * conversion to the split. The rows layout the scheduler batches
134
+ * (`DelayedQuantizedKVCache`) converts with this same code but only at each
135
+ * row's planned chunk boundary (`TransitioningKvRows`). The transition is
136
+ * irreversible: trimming below the start afterwards keeps the quantized
137
+ * storage, so a verify window that crosses the start converts even if its tail
138
+ * is later rejected.
139
+ *
140
+ * The row layouts, clones and saved files take the storage, never this cache
141
+ * (`composed`): its signature is its own (`kv:bf16-first:` and the storage's),
142
+ * and its storage is what the convert-after path saves.
143
+ */
144
+ export class Bf16FirstQuantizedKVCache implements CommittedAttentionCache, BidirectionalAttentionCache, EncoderAttentionCache, BlockAttentionCache, CompiledDecodeCache {
145
+ #plain: KVCache | null;
146
+ #quantized: QuantizedKVCache | null = null;
147
+
148
+ /** `storage`: bf16 or affine storage of this layer to adopt (restored,
149
+ * cloned or extracted elsewhere); affine storage is re-homed to this lego's
150
+ * kernels, bf16 storage that already holds `start` positions converts now. */
151
+ constructor(
152
+ readonly groupSize: number,
153
+ readonly bits: number,
154
+ /** Positions held in bf16 before the conversion; 0 converts after the first append. */
155
+ readonly start: number,
156
+ /** The affine kernel set the converted storage attends with, named by the
157
+ * composition (`composedAffineKernels`, or the KV4 set). */
158
+ readonly kernelSet: AffineKernelSet,
159
+ /** The per-forward masks of the model this cache belongs to. */
160
+ readonly masks = new AttentionMasks(),
161
+ storage?: Cache,
162
+ ) {
163
+ if (!storage) { this.#plain = new KVCache(masks); return; }
164
+ if (isQuantizedKvCache(storage)) {
165
+ const source = storage as QuantizedKVCache;
166
+ this.#plain = null;
167
+ this.#quantized = rehomeAffine(source, this.#kernelsFor(source), masks);
168
+ return;
169
+ }
170
+ this.#plain = storage as KVCache;
171
+ this.#convertIfDue();
172
+ }
173
+
174
+ #kernelsFor(source: QuantizedKVCache): AffineKernels {
175
+ return this.kernelSet(this.bits, this.groupSize, source.keys?.scales.dtype ?? Dtype.bfloat16);
176
+ }
177
+
178
+ get #storage(): KVCache | QuantizedKVCache { return this.#quantized ?? this.#plain!; }
179
+ get offset(): number { return this.#storage.offset; }
180
+ get minimumReusableOffset(): number | undefined { return (this.#storage as Cache).minimumReusableOffset; }
181
+ set minimumReusableOffset(value: number | undefined) { (this.#storage as Cache).minimumReusableOffset = value; }
182
+ /** The donor reads of the storage held now: a drafter borrowing this layer's
183
+ * keys and values reads them as the storage hands them out. */
184
+ get captureDonorRows() { const storage = this.#storage as Cache; return storage.captureDonorRows?.bind(storage); }
185
+ get captureDonorAttention() { const storage = this.#storage as Cache; return storage.captureDonorAttention?.bind(storage); }
186
+
187
+ /** The storage the row layouts, clones and saved files take (see the class). */
188
+ readonly composed: ComposedStorage = {
189
+ storage: () => this.#storage,
190
+ over: (storage: Cache) => new Bf16FirstQuantizedKVCache(this.groupSize, this.bits, this.start, this.kernelSet, this.masks, storage),
191
+ release: () => { const storage = this.#storage; this.#plain = new KVCache(this.masks); this.#quantized = null; return storage; },
192
+ };
193
+
194
+ appendDecode(k: MlxArray, v: MlxArray): AttentionRead {
195
+ if (this.#quantized) return this.#quantized.appendDecode(k, v);
196
+ return this.#settle(this.#plain!.appendDecode(k, v));
197
+ }
198
+
199
+ appendWindow(k: MlxArray, v: MlxArray): AttentionRead {
200
+ if (this.#quantized) return this.#quantized.appendWindow(k, v);
201
+ return this.#settle(this.#plain!.appendWindow(k, v));
202
+ }
203
+
204
+ appendCommitted(k: MlxArray, v: MlxArray): AttentionRead {
205
+ if (this.#quantized) return this.#quantized.appendCommitted(k, v);
206
+ return this.#settle(this.#plain!.appendCommitted(k, v));
207
+ }
208
+
209
+ appendBidirectional(k: MlxArray, v: MlxArray, bidirectional: MlxArray): AttentionRead {
210
+ if (this.#quantized) return firstWindowOnly("appendBidirectional");
211
+ return this.#settle(this.#plain!.appendBidirectional(k, v, bidirectional));
212
+ }
213
+
214
+ /** The encoder window, a first window: bf16 storage's read, then the
215
+ * transition its append owes. */
216
+ appendEncoderWindow(k: MlxArray, v: MlxArray, bidirectional: MlxArray): AttentionRead {
217
+ if (this.#quantized) return firstWindowOnly("appendEncoderWindow");
218
+ return this.#settle(this.#plain!.appendEncoderWindow(k, v, bidirectional));
219
+ }
220
+
221
+ /** The block read of the storage held now (it appends nothing, so nothing
222
+ * converts). */
223
+ readBlock(k: MlxArray, v: MlxArray): AttentionRead { return this.#storage.readBlock(k, v); }
224
+
225
+ /** Positions an append may add before the transition: up to the start. */
226
+ maxAppendTokens(): number {
227
+ return this.#plain && this.#plain.offset < this.start ? this.start - this.#plain.offset : Infinity;
228
+ }
229
+
230
+ // The compiled decode step drives the storage held now; a committed step
231
+ // owes the transition an append owes.
232
+ prepareDecodeStep(): DecodeStepPlan { return this.#storage.prepareDecodeStep(); }
233
+ decodePhase(): DecodeStepPlan["fetch"] { return this.#storage.decodePhase(); }
234
+ decodeSlot(plan: DecodeStepPlan): DecodeSlot { return this.#storage.decodeSlot(plan); }
235
+ decodeInputs(plan: DecodeStepPlan, step: DecodeStepInputs): MlxArray[] { return this.#storage.decodeInputs(plan, step); }
236
+ commitDecodeStep(slot: DecodeSlot, outputs: MlxArray[]): MlxArray[] {
237
+ const evalWith = this.#storage.commitDecodeStep(slot, outputs);
238
+ if (!this.#plain || !transitionDue(this.#plain.offset, this.start)) return evalWith;
239
+ // The conversion consumes the arrays the step wrote and evaluates the
240
+ // converted storage before it returns: nothing is left to evaluate here.
241
+ this.#convertIfDue();
242
+ return [];
243
+ }
244
+
245
+ /** After a bf16 append: convert once the stored length reaches the start.
246
+ * The read already holds the bf16 keys and values it attends. */
247
+ #settle(read: AttentionRead): AttentionRead {
248
+ try { this.#convertIfDue(); }
249
+ catch (error) { read.dispose(); throw error; }
250
+ return read;
251
+ }
252
+
253
+ #convertIfDue(): void {
254
+ const plain = this.#plain!;
255
+ if (!transitionDue(plain.offset, this.start)) return;
256
+ this.#quantized = convertToAffine(plain, this.groupSize, this.bits, this.start, this.kernelSet);
257
+ this.#plain = null;
258
+ }
259
+
260
+ signature(): string { return `kv:bf16-first:${this.#storage.signature()}`; }
261
+ state(): MlxArray[] { return this.#storage.state(); }
262
+ bytesPerToken(): number { return this.#storage.bytesPerToken(); }
263
+ isTrimmable(): boolean { return this.#storage.isTrimmable(); }
264
+ trim(n: number): void { this.#storage.trim(n); }
265
+ /** @deprecated The reads build their own masks. */
266
+ makeMask(N: number, windowSize: number | null): Mask { return this.#storage.makeMask(N, windowSize); }
267
+ /** @deprecated Read through `appendDecode` or `appendWindow`. The dense
268
+ * fetch of the storage (the softcapped read's): bf16 storage fetches and then
269
+ * converts as an append does; affine storage fetches its window dequantized. */
270
+ updateAndFetch(k: MlxArray, v: MlxArray): [MlxArray, MlxArray] {
271
+ if (this.#quantized) return this.#quantized.updateAndFetch(k, v);
272
+ return fetchThenSettle(this.#plain!.updateAndFetch(k, v), () => this.#convertIfDue());
273
+ }
274
+
275
+ /** Releases the storage and returns to the constructed state: empty bf16. */
276
+ dispose(): void {
277
+ this.#quantized?.dispose();
278
+ this.#plain?.dispose();
279
+ this.#quantized = null;
280
+ this.#plain = new KVCache(this.masks);
281
+ this.masks.clear();
282
+ }
283
+ }
284
+
285
+ /**
286
+ * Affine-quantized keys and values for one sliding-window layer, bf16 first:
287
+ * the rotating counterpart of {@link Bf16FirstQuantizedKVCache}, with the same
288
+ * numbers, transition and obligations. Storage is a `RotatingKVCache` of
289
+ * `maxSize` until an append leaves it holding at least `start` positions, then
290
+ * converts (`RotatingKVCache.toQuantized`: the ring as laid out, its write
291
+ * index carried) into a `RotatingQuantizedKVCache`, whose own reads serve every
292
+ * later append. Every written ring position equals the convert-after path's
293
+ * bit for bit.
294
+ */
295
+ export class Bf16FirstRotatingQuantizedKVCache implements CommittedAttentionCache, BidirectionalAttentionCache, EncoderAttentionCache, BlockAttentionCache, CompiledDecodeCache {
296
+ #plain: RotatingKVCache | null;
297
+ #quantized: RotatingQuantizedKVCache | null = null;
298
+
299
+ constructor(
300
+ /** The sliding window: the ring's capacity and the window every read applies. */
301
+ readonly maxSize: number,
302
+ readonly groupSize: number,
303
+ readonly bits: number,
304
+ /** Positions held in bf16 before the conversion; 0 converts after the first append. */
305
+ readonly start: number,
306
+ /** The affine kernel set the converted storage attends with, named by the
307
+ * composition (`composedAffineKernels`). */
308
+ readonly kernelSet: AffineKernelSet,
309
+ /** The per-forward masks of the model this cache belongs to. */
310
+ readonly masks = new AttentionMasks(),
311
+ storage?: Cache,
312
+ ) {
313
+ if (!storage) { this.#plain = new RotatingKVCache(maxSize, masks); return; }
314
+ if (isRotatingQuantizedCache(storage)) {
315
+ const source = storage as RotatingQuantizedKVCache;
316
+ this.#plain = null;
317
+ this.#quantized = rehomeRotatingAffine(source,
318
+ this.kernelSet(this.bits, this.groupSize, source.keys?.scales.dtype ?? Dtype.bfloat16), masks);
319
+ return;
320
+ }
321
+ this.#plain = storage as RotatingKVCache;
322
+ this.#convertIfDue();
323
+ }
324
+
325
+ get #storage(): RotatingKVCache | RotatingQuantizedKVCache { return this.#quantized ?? this.#plain!; }
326
+ get offset(): number { return this.#storage.offset; }
327
+ get minimumReusableOffset(): number | undefined { return (this.#storage as Cache).minimumReusableOffset; }
328
+ set minimumReusableOffset(value: number | undefined) { (this.#storage as Cache).minimumReusableOffset = value; }
329
+ /** The donor reads of the storage held now: a drafter borrowing this layer's
330
+ * keys and values reads them as the storage hands them out. */
331
+ get captureDonorRows() { const storage = this.#storage as Cache; return storage.captureDonorRows?.bind(storage); }
332
+ get captureDonorAttention() { const storage = this.#storage as Cache; return storage.captureDonorAttention?.bind(storage); }
333
+
334
+ /** The storage the row layouts, clones and saved files take. */
335
+ readonly composed: ComposedStorage = {
336
+ storage: () => this.#storage,
337
+ over: (storage: Cache) => new Bf16FirstRotatingQuantizedKVCache(this.maxSize, this.groupSize, this.bits, this.start,
338
+ this.kernelSet, this.masks, storage),
339
+ release: () => { const storage = this.#storage; this.#plain = new RotatingKVCache(this.maxSize, this.masks); this.#quantized = null; return storage; },
340
+ };
341
+
342
+ appendDecode(k: MlxArray, v: MlxArray): AttentionRead {
343
+ if (this.#quantized) return this.#quantized.appendDecode(k, v);
344
+ return this.#settle(this.#plain!.appendDecode(k, v));
345
+ }
346
+
347
+ appendWindow(k: MlxArray, v: MlxArray): AttentionRead {
348
+ if (this.#quantized) return this.#quantized.appendWindow(k, v);
349
+ return this.#settle(this.#plain!.appendWindow(k, v));
350
+ }
351
+
352
+ /** A committed span, one decode per position through this lego
353
+ * (`decodeByPosition`), so the transition falls where `L` decodes put it. */
354
+ appendCommitted(k: MlxArray, v: MlxArray): AttentionRead {
355
+ return decodeByPosition(this, k, v);
356
+ }
357
+
358
+ appendBidirectional(k: MlxArray, v: MlxArray, bidirectional: MlxArray): AttentionRead {
359
+ if (this.#quantized) return firstWindowOnly("appendBidirectional");
360
+ return this.#settle(this.#plain!.appendBidirectional(k, v, bidirectional));
361
+ }
362
+
363
+ /** The encoder window, a first window: bf16 storage's read, then the
364
+ * transition its append owes. */
365
+ appendEncoderWindow(k: MlxArray, v: MlxArray, bidirectional: MlxArray): AttentionRead {
366
+ if (this.#quantized) return firstWindowOnly("appendEncoderWindow");
367
+ return this.#settle(this.#plain!.appendEncoderWindow(k, v, bidirectional));
368
+ }
369
+
370
+ /** The block read of the storage held now (it appends nothing, so nothing
371
+ * converts). */
372
+ readBlock(k: MlxArray, v: MlxArray): AttentionRead { return this.#storage.readBlock(k, v); }
373
+
374
+ maxAppendTokens(): number {
375
+ return this.#plain && this.#plain.offset < this.start ? this.start - this.#plain.offset : Infinity;
376
+ }
377
+
378
+ // The compiled decode step drives the ring held now; a committed step owes
379
+ // the transition an append owes.
380
+ prepareDecodeStep(): DecodeStepPlan { return this.#storage.prepareDecodeStep(); }
381
+ decodePhase(): DecodeStepPlan["fetch"] { return this.#storage.decodePhase(); }
382
+ decodeSlot(plan: DecodeStepPlan): DecodeSlot { return this.#storage.decodeSlot(plan); }
383
+ decodeInputs(plan: DecodeStepPlan, step: DecodeStepInputs): MlxArray[] { return this.#storage.decodeInputs(plan, step); }
384
+ commitDecodeStep(slot: DecodeSlot, outputs: MlxArray[]): MlxArray[] {
385
+ const evalWith = this.#storage.commitDecodeStep(slot, outputs);
386
+ if (!this.#plain || !transitionDue(this.#plain.offset, this.start)) return evalWith;
387
+ // The conversion consumes the arrays the step wrote and evaluates the
388
+ // converted storage before it returns: nothing is left to evaluate here.
389
+ this.#convertIfDue();
390
+ return [];
391
+ }
392
+
393
+ #settle(read: AttentionRead): AttentionRead {
394
+ try { this.#convertIfDue(); }
395
+ catch (error) { read.dispose(); throw error; }
396
+ return read;
397
+ }
398
+
399
+ #convertIfDue(): void {
400
+ const plain = this.#plain!;
401
+ if (!transitionDue(plain.offset, this.start)) return;
402
+ this.#quantized = convertToAffine(plain, this.groupSize, this.bits, this.start, this.kernelSet);
403
+ this.#plain = null;
404
+ }
405
+
406
+ signature(): string { return `kv:bf16-first:${this.#storage.signature()}`; }
407
+ state(): MlxArray[] { return this.#storage.state(); }
408
+ bytesPerToken(): number { return this.#storage.bytesPerToken(); }
409
+ isTrimmable(): boolean { return this.#storage.isTrimmable(); }
410
+ trim(n: number, bypass?: boolean): void { this.#storage.trim(n, bypass); }
411
+ /** @deprecated The reads build their own masks. */
412
+ makeMask(N: number, windowSize: number | null): Mask { return this.#storage.makeMask(N, windowSize); }
413
+ /** @deprecated Read through `appendDecode` or `appendWindow`. The dense
414
+ * fetch of the storage (the softcapped read's): a bf16 ring fetches and then
415
+ * converts as an append does; an affine ring fetches its ring dequantized. */
416
+ updateAndFetch(k: MlxArray, v: MlxArray): [MlxArray, MlxArray] {
417
+ if (this.#quantized) return this.#quantized.updateAndFetch(k, v);
418
+ return fetchThenSettle(this.#plain!.updateAndFetch(k, v), () => this.#convertIfDue());
419
+ }
420
+
421
+ /** Releases the storage and returns to the constructed state: empty bf16. */
422
+ dispose(): void {
423
+ this.#quantized?.dispose();
424
+ this.#plain?.dispose();
425
+ this.#quantized = null;
426
+ this.#plain = new RotatingKVCache(this.maxSize, this.masks);
427
+ this.masks.clear();
428
+ }
429
+ }
430
+
431
+ /**
432
+ * TurboQuant-encoded keys and values for one full-attention layer, bf16 first:
433
+ * the lego `--kv-quant turbo` composes (sliding-window layers keep a plain
434
+ * `RotatingKVCache`).
435
+ *
436
+ * Numbers: today's served path. Storage is a `KVCache` until an append leaves
437
+ * it holding at least `start` positions (the first append when `start` is 0).
438
+ * That append is read bf16 and then, before it returns, the storage converts
439
+ * (`TurboQuantKVCache.fromKVCache` over the live window, the reuse floor at a
440
+ * nonzero start, materialized at once). Every later append encodes its
441
+ * positions and is read through the codec. Stored values below the offset
442
+ * equal the convert-after path's bit for bit. The transition has the
443
+ * obligations described on {@link Bf16FirstQuantizedKVCache}.
444
+ *
445
+ * Reads: before the transition, `KVCache`'s reads; after it, the encoded
446
+ * storage's reads, which attend with `turboQuantKernels` (no mask for decode,
447
+ * the causal mask for a window): decoded values and the stock fused SDPA for
448
+ * Qwen 3.5's graph, or the deferred-V fetch, the stock fused SDPA and the
449
+ * inverse rotation of the output for Gemma 4's and MiniCPM5's. `fusedDecode` is
450
+ * resolved by whoever builds the cache; nothing here reads the runtime
451
+ * configuration.
452
+ */
453
+ export class Bf16FirstTurboQuantKVCache implements CommittedAttentionCache, BidirectionalAttentionCache, EncoderAttentionCache, BlockAttentionCache, CompiledDecodeCache {
454
+ #plain: KVCache | null;
455
+ #encoded: TurboQuantKVCache | null = null;
456
+
457
+ constructor(
458
+ readonly kBits: number,
459
+ readonly vBits: number,
460
+ /** Positions held in bf16 before the conversion; 0 converts after the first append. */
461
+ readonly start: number,
462
+ /** The codec's decode policy for the converted storage. */
463
+ readonly fusedDecode: boolean,
464
+ /** How the converted storage attends, named by the graph the cache serves
465
+ * (`denseTurboQuantKernels` for Qwen 3.5, `deferredTurboQuantKernels` for
466
+ * Gemma 4 and MiniCPM5). */
467
+ readonly turboQuantKernels: TurboQuantKernels,
468
+ /** The per-forward masks of the model this cache belongs to. */
469
+ readonly masks = new AttentionMasks(),
470
+ storage?: Cache,
471
+ ) {
472
+ if (!storage) { this.#plain = new KVCache(masks, turboQuantKernels); return; }
473
+ if (isTurboQuantKvCache(storage)) { this.#plain = null; this.#encoded = storage as TurboQuantKVCache; return; }
474
+ this.#plain = storage as KVCache;
475
+ this.#convertIfDue();
476
+ }
477
+
478
+ get #storage(): KVCache | TurboQuantKVCache { return this.#encoded ?? this.#plain!; }
479
+ get offset(): number { return this.#storage.offset; }
480
+ get minimumReusableOffset(): number | undefined { return (this.#storage as Cache).minimumReusableOffset; }
481
+ set minimumReusableOffset(value: number | undefined) { (this.#storage as Cache).minimumReusableOffset = value; }
482
+ /** The donor reads of the storage held now: a drafter borrowing this layer's
483
+ * keys and values reads them as the storage hands them out. */
484
+ get captureDonorRows() { const storage = this.#storage as Cache; return storage.captureDonorRows?.bind(storage); }
485
+ get captureDonorAttention() { const storage = this.#storage as Cache; return storage.captureDonorAttention?.bind(storage); }
486
+ /** `state()` returns fresh views once the storage is encoded. */
487
+ get stateNeedsDispose(): boolean { return this.#encoded !== null; }
488
+
489
+ /** The storage the row layouts, clones and saved files take. */
490
+ readonly composed: ComposedStorage = {
491
+ storage: () => this.#storage,
492
+ over: (storage: Cache) => new Bf16FirstTurboQuantKVCache(this.kBits, this.vBits, this.start, this.fusedDecode,
493
+ this.turboQuantKernels, this.masks, storage),
494
+ release: () => { const storage = this.#storage; this.#plain = new KVCache(this.masks, this.turboQuantKernels); this.#encoded = null; return storage; },
495
+ };
496
+
497
+ appendDecode(k: MlxArray, v: MlxArray): AttentionRead {
498
+ if (this.#encoded) return this.#encoded.appendDecode(k, v);
499
+ return this.#settle(this.#plain!.appendDecode(k, v));
500
+ }
501
+
502
+ appendWindow(k: MlxArray, v: MlxArray): AttentionRead {
503
+ if (this.#encoded) return this.#encoded.appendWindow(k, v);
504
+ return this.#settle(this.#plain!.appendWindow(k, v));
505
+ }
506
+
507
+ appendCommitted(k: MlxArray, v: MlxArray): AttentionRead {
508
+ if (this.#encoded) return this.#encoded.appendCommitted(k, v);
509
+ return this.#settle(this.#plain!.appendCommitted(k, v));
510
+ }
511
+
512
+ appendBidirectional(k: MlxArray, v: MlxArray, bidirectional: MlxArray): AttentionRead {
513
+ if (this.#encoded) return firstWindowOnly("appendBidirectional");
514
+ return this.#settle(this.#plain!.appendBidirectional(k, v, bidirectional));
515
+ }
516
+
517
+ /** The encoder window, a first window: bf16 storage's read, then the
518
+ * transition its append owes. */
519
+ appendEncoderWindow(k: MlxArray, v: MlxArray, bidirectional: MlxArray): AttentionRead {
520
+ if (this.#encoded) return firstWindowOnly("appendEncoderWindow");
521
+ return this.#settle(this.#plain!.appendEncoderWindow(k, v, bidirectional));
522
+ }
523
+
524
+ /** The block read of the storage held now (it appends nothing, so nothing
525
+ * converts). */
526
+ readBlock(k: MlxArray, v: MlxArray): AttentionRead { return this.#storage.readBlock(k, v); }
527
+
528
+ maxAppendTokens(): number {
529
+ return this.#plain && this.#plain.offset < this.start ? this.start - this.#plain.offset : Infinity;
530
+ }
531
+
532
+ // The compiled decode step drives the storage held now; a committed step
533
+ // owes the transition an append owes.
534
+ prepareDecodeStep(): DecodeStepPlan { return this.#storage.prepareDecodeStep(); }
535
+ decodePhase(): DecodeStepPlan["fetch"] { return this.#storage.decodePhase(); }
536
+ decodeSlot(plan: DecodeStepPlan): DecodeSlot { return this.#storage.decodeSlot(plan); }
537
+ decodeInputs(plan: DecodeStepPlan, step: DecodeStepInputs): MlxArray[] { return this.#storage.decodeInputs(plan, step); }
538
+ commitDecodeStep(slot: DecodeSlot, outputs: MlxArray[]): MlxArray[] {
539
+ const evalWith = this.#storage.commitDecodeStep(slot, outputs);
540
+ if (!this.#plain || !transitionDue(this.#plain.offset, this.start)) return evalWith;
541
+ // The conversion consumes the arrays the step wrote and evaluates the
542
+ // converted storage before it returns: nothing is left to evaluate here.
543
+ this.#convertIfDue();
544
+ return [];
545
+ }
546
+
547
+ #settle(read: AttentionRead): AttentionRead {
548
+ try { this.#convertIfDue(); }
549
+ catch (error) { read.dispose(); throw error; }
550
+ return read;
551
+ }
552
+
553
+ #convertIfDue(): void {
554
+ const plain = this.#plain!;
555
+ if (!transitionDue(plain.offset, this.start)) return;
556
+ this.#encoded = convertToTurboQuant(plain, this.kBits, this.vBits, this.start, this.fusedDecode);
557
+ this.#plain = null;
558
+ }
559
+
560
+ signature(): string { return `kv:bf16-first:${this.#storage.signature()}`; }
561
+ state(): MlxArray[] { return this.#storage.state(); }
562
+ bytesPerToken(): number { return this.#storage.bytesPerToken(); }
563
+ isTrimmable(): boolean { return this.#storage.isTrimmable(); }
564
+ trim(n: number): void { this.#storage.trim(n); }
565
+ /** @deprecated The reads build their own masks. */
566
+ makeMask(N: number, windowSize: number | null): Mask { return this.#storage.makeMask(N, windowSize); }
567
+ /** @deprecated Read through `appendDecode` or `appendWindow`. The plain
568
+ * fetch of the storage (the softcapped read's): bf16 storage fetches and then
569
+ * encodes as an append does; encoded storage decodes its window. */
570
+ updateAndFetch(k: MlxArray, v: MlxArray): [MlxArray, MlxArray] {
571
+ if (this.#encoded) return this.#encoded.updateAndFetch(k, v);
572
+ return fetchThenSettle(this.#plain!.updateAndFetch(k, v), () => this.#convertIfDue());
573
+ }
574
+
575
+ /** Releases the storage and returns to the constructed state: empty bf16. */
576
+ dispose(): void {
577
+ this.#encoded?.dispose();
578
+ this.#plain?.dispose();
579
+ this.#encoded = null;
580
+ this.#plain = new KVCache(this.masks, this.turboQuantKernels);
581
+ this.masks.clear();
582
+ }
583
+ }
584
+
585
+ /** The KV4 kernel set over the composition's affine set `plain`: the 4-bit
586
+ * group-64 head-dim-256 reads (`kv4Head256Kernels`, the decode kernel at
587
+ * `decodeBlock` tokens per threadgroup) for one sequence, and `plain`'s kernels
588
+ * for a row layout holding more. */
589
+ export function kv4Head256KernelSet(plain: AffineKernelSet, decodeBlock: number): AffineKernelSet {
590
+ return (bits, groupSize, dtype) => kv4Head256Kernels(plain(bits, groupSize, dtype), decodeBlock);
591
+ }
592
+
593
+ /**
594
+ * The 4-bit group-64 full-attention lego of the Qwen3.8-27B Trellis M4 Pro and
595
+ * M1 Max compositions, bf16 first: {@link Bf16FirstQuantizedKVCache} whose
596
+ * quantized storage reads with `Kv4Head256Cache`'s kernels, the decode kernel at
597
+ * the composition's `decodeBlock`. The first append is bf16, as
598
+ * the old graph's conversion after the first forward kept it; every later
599
+ * decode reads with the fused one-row KV4 kernel and every window with the
600
+ * folded-GQA kernel, the cores the old graph injected for one sequence. Its
601
+ * storage carries the KV4 set into the row layouts it joins, which read with
602
+ * the KV4 kernels while they hold one unpadded row and with `plain`'s affine
603
+ * kernels when they hold more (`AffineKernels.multiRow`), as the old graph read
604
+ * several sequences through the generic path.
605
+ */
606
+ export class Bf16FirstKv4Head256Cache extends Bf16FirstQuantizedKVCache {
607
+ /** `plain`: the composition's affine kernel set (`composedAffineKernels`); `decodeBlock`: the decode kernel's
608
+ * tokens per threadgroup its device's table names (the M4 Pro's by default). */
609
+ constructor(start: number, readonly plain: AffineKernelSet, masks = new AttentionMasks(),
610
+ readonly decodeBlock = KV4_DECODE_BLOCK, storage?: Cache) {
611
+ super(64, 4, start, kv4Head256KernelSet(plain, decodeBlock), masks, storage);
612
+ }
613
+ }
614
+
615
+ /** One-time notice that TurboQuant leaves sliding-window layers bf16. */
616
+ let warnedTurboRotating = false;
617
+
618
+ /** The cache a composition's KV scheme names for one full-attention layer
619
+ * (cache index `layer`) of a graph whose TurboQuant read is
620
+ * `turboQuantKernels` and whose bf16 window read is `windowRead` (the stock
621
+ * fused SDPA unless it names another): plain bf16 for `bf16` and for a layer a
622
+ * per-layer scheme does not list, the bf16-first affine lego for affine schemes
623
+ * (the scheme's read kernels, `composedAffineKernels`), the bf16-first
624
+ * TurboQuant lego for `turbo`. Every cache of one `makeCache` call shares
625
+ * `masks`. */
626
+ export function composedFullAttentionCache(kv: KvStorageScheme, layer: number, masks: AttentionMasks,
627
+ turboQuantKernels: TurboQuantKernels, windowRead: DenseRead = sdpaRead): AttentionCache {
628
+ const start = kv.kind === "bf16" ? 0 : kv.quantizedKvStart ?? 0;
629
+ switch (kv.kind) {
630
+ case "bf16": return new KVCache(masks, turboQuantKernels, windowRead);
631
+ case "affine": return new Bf16FirstQuantizedKVCache(kv.groupSize, kv.bits, start, composedAffineKernels(kv.kernels), masks);
632
+ case "affine-layers": {
633
+ const entry = kv.layers.find(item => item.layer === layer);
634
+ return entry ? new Bf16FirstQuantizedKVCache(entry.groupSize, entry.bits, start, composedAffineKernels(kv.kernels), masks)
635
+ : new KVCache(masks, turboQuantKernels, windowRead);
636
+ }
637
+ case "turbo": return new Bf16FirstTurboQuantKVCache(kv.kBits, kv.vBits, start, turboQuantFusedDecode(), turboQuantKernels, masks);
638
+ }
639
+ }
640
+
641
+ /** The cache a composition's KV scheme names for one sliding-window layer of
642
+ * `maxSize` (cache index `layer`): plain for `bf16`, for a layer a per-layer
643
+ * scheme does not list and for `turbo` (TurboQuant encodes full-attention
644
+ * layers only), the bf16-first rotating affine lego otherwise. */
645
+ export function composedSlidingAttentionCache(kv: KvStorageScheme, layer: number, maxSize: number,
646
+ masks: AttentionMasks): AttentionCache {
647
+ const start = kv.kind === "bf16" ? 0 : kv.quantizedKvStart ?? 0;
648
+ switch (kv.kind) {
649
+ case "bf16": return new RotatingKVCache(maxSize, masks);
650
+ case "affine": return new Bf16FirstRotatingQuantizedKVCache(maxSize, kv.groupSize, kv.bits, start, composedAffineKernels(kv.kernels), masks);
651
+ case "affine-layers": {
652
+ const entry = kv.layers.find(item => item.layer === layer);
653
+ return entry ? new Bf16FirstRotatingQuantizedKVCache(maxSize, entry.groupSize, entry.bits, start, composedAffineKernels(kv.kernels), masks)
654
+ : new RotatingKVCache(maxSize, masks);
655
+ }
656
+ case "turbo":
657
+ if (!warnedTurboRotating) {
658
+ warnedTurboRotating = true;
659
+ console.warn("[turbo-quant] sliding-window (RotatingKVCache) layers stay bf16 in v1 (full-attention only).");
660
+ }
661
+ return new RotatingKVCache(maxSize, masks);
662
+ }
663
+ }