@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,103 @@
1
+ // Training forward pass: full-sequence logits with no KV reuse.
2
+ //
3
+ // Every forward runs the graph's scoring forward (sequenceHidden) over fresh
4
+ // stateless training caches (state/training-cache.ts), one per cache the graph
5
+ // composes: a TrainingCache with that layer's sliding window for attention, a
6
+ // TrainingSSMCache for recurrent state. They share one mask memo and attend
7
+ // with the training attention the run selected; the caches own their reads, so
8
+ // nothing here builds a mask or reads keys and values.
9
+ //
10
+ // B=1 (no padding): offset-0 causal (windowed) reads, safe to recompute under
11
+ // gradient checkpointing — bit-identical to the single-example trainer.
12
+ //
13
+ // B>1 (padded batch): every row in the batch is padded to a common length L,
14
+ // but the rows have different *true* lengths. A plain causal mask would let a
15
+ // padded query attend padded keys, so the caches take the rows' lengths and
16
+ // read under the padded-batch mask: causal (within the window) AND key within
17
+ // its row's true length, built at offset 0 like the unpadded read.
18
+
19
+ import type { MlxArray } from "@mlx-bun/mlx/array";
20
+ import { type Cache } from "../contracts/mlx/cache";
21
+ import { AttentionMasks } from "../state/attention-read";
22
+ import { selectedTrainingAttention, TrainingCache, TrainingSSMCache, trainingStandIn } from "../state/training-cache";
23
+ import type { MlxSequenceForward, MlxSequenceRange } from "../contracts/mlx/graph";
24
+ import type { RuntimeModel } from "../models/factory";
25
+
26
+ export { TrainingCache, TrainingSSMCache };
27
+ /** The padded-batch training mask (`paddedTrainingMask`): [B, 1, L, L] bool,
28
+ * causal (within `windowSize`) AND key within its row's valid length. Caller
29
+ * owns the result. */
30
+ export { paddedTrainingMask as buildBatchedPadMask } from "../state/training-cache";
31
+
32
+ /** Run a full-sequence forward for training.
33
+ * @param ids int32 array [B, L].
34
+ * @returns logits [B, L, V] (caller owns; dispose when done).
35
+ *
36
+ * When `validLengths` is omitted (or every row is full-length L) the batch
37
+ * has no padding and the caches read causally — the B=1 result is
38
+ * bit-identical to the original trainer. When some row is shorter than L,
39
+ * the caches read under the padding-aware batched mask. */
40
+ export function trainForwardHidden(
41
+ model: RuntimeModel,
42
+ ids: MlxArray,
43
+ validLengths?: number[],
44
+ ): MlxArray {
45
+ const [B, L] = ids.shape as [number, number];
46
+ const width = (B * L);
47
+ const rowLengths = validLengths !== undefined && validLengths.some((v) => v < L) ? validLengths : null;
48
+ // Stateless offset-0 caches (one per cache the graph composes), so the
49
+ // forward is pure and safe to recompute under gradient checkpointing. Each
50
+ // keeps its layer's sliding window; recurrent layers take TrainingSSMCache,
51
+ // whose causal recurrence never carries a right-padded position into a real
52
+ // one, so it needs no mask.
53
+ const probe = model.makeCache();
54
+ const masks = new AttentionMasks(), attention = selectedTrainingAttention();
55
+ const cache: Cache[] = probe.map((c) =>
56
+ trainingStandIn(c, window => new TrainingCache(window, masks, attention, rowLengths)));
57
+ for (const c of probe) c.dispose();
58
+ try {
59
+ return sequenceHiddenOperation(model, ids, cache, width); // [B, L, hidden]
60
+ } finally {
61
+ for (const c of cache) c.dispose();
62
+ }
63
+ }
64
+
65
+ /** Full-sequence forward returning logits [B, L, V]. Convenience over
66
+ * trainForwardHidden + logitsFromHidden. The SFT B=1 path deliberately skips
67
+ * this and applies the LM head only at response positions (responseOnlyCe)
68
+ * to avoid materializing the [B, L, vocab] logits — the dominant training
69
+ * memory term at long context with a 262k vocab. */
70
+ export function trainForward(
71
+ model: RuntimeModel,
72
+ ids: MlxArray,
73
+ validLengths?: number[],
74
+ ): MlxArray {
75
+ const h = trainForwardHidden(model, ids, validLengths);
76
+ const logits = sequenceLogitsOperation(model,h);
77
+ h.dispose();
78
+ return logits;
79
+ }
80
+
81
+
82
+ /** Select the full-sequence operation from its exact token rows. */
83
+ type SequenceOperations = { readonly sequence?: readonly MlxSequenceRange[]; readonly sequenceHidden?: MlxSequenceForward };
84
+ export function sequenceHiddenOperation(model: SequenceOperations | object,
85
+ ids: MlxArray, cache: Cache[], rows = ids.shape[0]! * ids.shape[1]!): MlxArray {
86
+ const ports = model as SequenceOperations;
87
+ if (ports.sequence) return ports.sequence.find(range => rows >= range.from && rows <= range.to)!.operation(ids, cache);
88
+ if (!ports.sequenceHidden) throw new TypeError("the graph declares no full-sequence operation");
89
+ return ports.sequenceHidden(ids, cache, rows);
90
+ }
91
+
92
+ /** Full-sequence heads may cover more rows than the serving scheduler's table. */
93
+ export function sequenceLogitsOperation(model: object, hidden: MlxArray): MlxArray {
94
+ const ports=model as {readonly sequenceLogits?: readonly import("../contracts/mlx/graph").MlxHeadRange[]; logitsFromHidden?(hidden:MlxArray):MlxArray};
95
+ const rows=hidden.shape.slice(0,-1).reduce((a,b)=>a*b,1);
96
+ if (ports.sequenceLogits) {
97
+ const range=ports.sequenceLogits.find(range=>rows>=range.from && rows<=range.to);
98
+ if (!range) throw new Error(`graph has no sequence head for ${rows} rows`);
99
+ return range.operation(hidden);
100
+ }
101
+ if (ports.logitsFromHidden) return ports.logitsFromHidden(hidden);
102
+ throw new TypeError("graph declares no full-sequence head");
103
+ }
@@ -0,0 +1,9 @@
1
+ export * from "../kernels/logits";
2
+ export * from "../kernels/softcap";
3
+ export * from "./full-sequence";
4
+ export { trainForward as forwardSequence, trainForwardHidden as forwardSequenceHidden } from "./full-sequence";
5
+ export * from "./perplexity";
6
+ export * from "./kl";
7
+ export * from "./decisions";
8
+ export * from "./response-logps";
9
+
@@ -0,0 +1,29 @@
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
+
5
+
6
+ /** KL(p ‖ q) per token for logits [1, T, V]; returns a length-T array. */
7
+ export function klPerToken(pLogits: MlxArray, qLogits: MlxArray): Float32Array {
8
+ const f32 = (a: MlxArray): { arr: MlxArray; owned: boolean } =>
9
+ a.dtype === Dtype.float32 ? { arr: a, owned: false } : { arr: a.astype(Dtype.float32), owned: true };
10
+ const pp = f32(pLogits);
11
+ const qq = f32(qLogits);
12
+ const p = pp.arr;
13
+ const q = qq.arr;
14
+
15
+ const lseP = ops.logsumexpAxis(p, -1, true); // [1,T,1]
16
+ const lseQ = ops.logsumexpAxis(q, -1, true);
17
+ const logP = ops.sub(p, lseP); // [1,T,V]
18
+ const logQ = ops.sub(q, lseQ);
19
+ const pProbs = ops.softmaxAxis(p, -1, true); // precise softmax
20
+ const diff = ops.sub(logP, logQ);
21
+ const prod = ops.mul(pProbs, diff);
22
+ const kl = ops.sumAxis(prod, -1, false); // [1,T]
23
+ const out = kl.toFloat32();
24
+
25
+ for (const a of [lseP, lseQ, logP, logQ, pProbs, diff, prod, kl]) a.dispose();
26
+ if (pp.owned) p.dispose();
27
+ if (qq.owned) q.dispose();
28
+ return out;
29
+ }
@@ -0,0 +1,165 @@
1
+ // Perplexity over a local text/JSONL dataset — mlx_lm.perplexity methodology.
2
+ //
3
+ // The reference (mlx_lm/perplexity.py, mlx-lm 0.31.3) does:
4
+ // 1. tokenize dataset samples, visit them in a seeded random permutation,
5
+ // 2. concatenate tokens into one stream and cut it into NON-OVERLAPPING
6
+ // rows of `sequence_length` (no sliding-window stride, no BOS re-seeding
7
+ // per row), keeping at most `num_samples` rows,
8
+ // 3. per batch: logits = model(batch[:, :-1]) in f32, per-token
9
+ // cross-entropy vs batch[:, 1:] — EVERY position counts,
10
+ // 4. ppl = exp(mean loss); SE via the delta method:
11
+ // se_ppl = ppl · std(losses, ddof=1) / √N.
12
+ //
13
+ // We reproduce 2-4 exactly. Step 1 differs only in the data source: the
14
+ // reference pulls a Hugging Face dataset (network); we take a LOCAL .txt or
15
+ // .jsonl file — never a download. The permutation RNG is a seeded
16
+ // deterministic shuffle (not NumPy's), so row selection is reproducible per
17
+ // seed but not bit-matched to the Python tool's sampling.
18
+ //
19
+ // The forward reuses the existing full-sequence machinery
20
+ // (packages/inference/src/scoring/full-sequence.ts trainForward →
21
+ // the graph's sequenceHidden + logitsFromHidden).
22
+
23
+ import { clearCache, Dtype } from "@mlx-bun/mlx/ffi";
24
+ import * as ops from "@mlx-bun/mlx/ops";
25
+ import { trainForward } from "./full-sequence";
26
+ import type { RuntimeModel } from "../models/factory";
27
+
28
+ export interface PplDataOptions {
29
+ sequenceLength: number;
30
+ /** Max rows to keep (-1 / 0 = all available). */
31
+ numSamples: number;
32
+ /** Seed for the sample-order shuffle (reference default: 123). */
33
+ seed: number;
34
+ }
35
+
36
+ export interface PplResult {
37
+ ppl: number;
38
+ /** Delta-method standard error of the perplexity. */
39
+ standardError: number;
40
+ meanLoss: number;
41
+ tokens: number;
42
+ rows: number;
43
+ }
44
+
45
+ /** Deterministic PRNG (mulberry32) for the seeded sample shuffle. */
46
+ function mulberry32(seed: number): () => number {
47
+ let s = seed >>> 0;
48
+ return () => {
49
+ s = (s + 0x6d2b79f5) >>> 0;
50
+ let t = s;
51
+ t = Math.imul(t ^ (t >>> 15), t | 1);
52
+ t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
53
+ return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
54
+ };
55
+ }
56
+
57
+ /** Split a raw dataset file into text samples: `.jsonl` → one sample per
58
+ * line from the `text` field; anything else → the whole file is one sample. */
59
+ export function parseSamples(raw: string, path: string): string[] {
60
+ if (path.endsWith(".jsonl") || path.endsWith(".ndjson")) {
61
+ const samples: string[] = [];
62
+ for (const [i, line] of raw.split("\n").entries()) {
63
+ if (!line.trim()) continue;
64
+ let row: unknown;
65
+ try {
66
+ row = JSON.parse(line);
67
+ } catch {
68
+ throw new Error(`${path}:${i + 1}: not valid JSON`);
69
+ }
70
+ const text = (row as { text?: unknown }).text;
71
+ if (typeof text !== "string")
72
+ throw new Error(`${path}:${i + 1}: expected a {"text": "..."} row`);
73
+ samples.push(text);
74
+ }
75
+ return samples;
76
+ }
77
+ return raw.length > 0 ? [raw] : [];
78
+ }
79
+
80
+ /** Tokenize samples (seeded shuffle), concatenate, and pack into
81
+ * non-overlapping rows of `sequenceLength` tokens (reference load_data). */
82
+ export function packRows(
83
+ sampleTokens: number[][],
84
+ opts: PplDataOptions,
85
+ ): Int32Array[] {
86
+ const { sequenceLength: L, numSamples, seed } = opts;
87
+ const order = sampleTokens.map((_, i) => i);
88
+ const rnd = mulberry32(seed);
89
+ for (let i = order.length - 1; i > 0; i--) {
90
+ const j = Math.floor(rnd() * (i + 1));
91
+ [order[i], order[j]] = [order[j]!, order[i]!];
92
+ }
93
+
94
+ const wanted = numSamples > 0 ? L * numSamples : Infinity;
95
+ const stream: number[] = [];
96
+ for (const idx of order) {
97
+ if (stream.length >= wanted) break;
98
+ stream.push(...sampleTokens[idx]!);
99
+ }
100
+
101
+ const nRows = Math.min(
102
+ Math.floor(stream.length / L),
103
+ numSamples > 0 ? numSamples : Infinity,
104
+ );
105
+ const rows: Int32Array[] = [];
106
+ for (let r = 0; r < nRows; r++)
107
+ rows.push(Int32Array.from(stream.slice(r * L, (r + 1) * L)));
108
+ return rows;
109
+ }
110
+
111
+ /** Evaluate perplexity over packed rows. Per batch (all rows are the same
112
+ * length, so the stock unpadded forward applies): f32 logits over
113
+ * batch[:, :-1], per-token CE vs batch[:, 1:], every position counted. */
114
+ export function evalPpl(
115
+ model: RuntimeModel,
116
+ rows: Int32Array[],
117
+ batchSize: number,
118
+ onBatch?: (done: number, total: number) => void,
119
+ ): PplResult {
120
+ const L = rows[0]!.length;
121
+ const allLosses: number[] = [];
122
+ const numBatches = Math.ceil(rows.length / batchSize);
123
+
124
+ for (let s = 0, bi = 0; s < rows.length; s += batchSize, bi++) {
125
+ const batch = rows.slice(s, s + batchSize);
126
+ const B = batch.length;
127
+ const inputs: number[] = [];
128
+ const targets: number[] = [];
129
+ for (const row of batch) {
130
+ for (let i = 0; i < L - 1; i++) inputs.push(row[i]!);
131
+ for (let i = 1; i < L; i++) targets.push(row[i]!);
132
+ }
133
+ const ids = ops.fromInt32(inputs, [B, L - 1]);
134
+ const tgt = ops.fromInt32(targets, [B, L - 1, 1]);
135
+ const logits = trainForward(model, ids);
136
+ const f32 = logits.astype(Dtype.float32);
137
+ logits.dispose();
138
+ // CE[t] = logsumexp(logits[t]) − logits[t, target] (f32, like nn.losses)
139
+ const lse = ops.logsumexpAxis(f32, 2, false); // [B, L-1]
140
+ const picked = ops.takeAlongAxis(f32, tgt, 2); // [B, L-1, 1]
141
+ f32.dispose();
142
+ const picked2d = ops.reshape(picked, [B, L - 1]);
143
+ picked.dispose();
144
+ const losses = ops.sub(lse, picked2d);
145
+ lse.dispose();
146
+ picked2d.dispose();
147
+ const vals = losses.toFloat32();
148
+ losses.dispose();
149
+ ids.dispose();
150
+ tgt.dispose();
151
+ for (const v of vals) allLosses.push(v);
152
+ clearCache();
153
+ onBatch?.(bi + 1, numBatches);
154
+ }
155
+
156
+ const n = allLosses.length;
157
+ const mean = allLosses.reduce((a, b) => a + b, 0) / n;
158
+ const ppl = Math.exp(mean);
159
+ // Sample std (ddof=1) → delta-method SE of the perplexity.
160
+ const varSum = allLosses.reduce((a, b) => a + (b - mean) ** 2, 0);
161
+ const std = n > 1 ? Math.sqrt(varSum / (n - 1)) : 0;
162
+ const standardError = ppl * (std / Math.sqrt(n));
163
+
164
+ return { ppl, standardError, meanLoss: mean, tokens: n, rows: rows.length };
165
+ }
@@ -0,0 +1,65 @@
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 { sequenceLogitsOperation } from "./full-sequence";
5
+
6
+ /** The existing DPO/ORPO head geometry, shared with inference. Two- and
7
+ * three-row response spans pad to four before the head and slice back. Borrows
8
+ * hidden and model, returns caller-owned logits; preserves training numerics. */
9
+ export function logitsFromHiddenPadM(
10
+ model: object,
11
+ hSpan: MlxArray, // [1, M, hidden]
12
+ ): MlxArray {
13
+ const M = hSpan.shape[1]!;
14
+ if (M !== 2 && M !== 3) return sequenceLogitsOperation(model, hSpan);
15
+ const [B, , H] = hSpan.shape as [number, number, number];
16
+ const pad = ops.zeros([B, 4 - M, H], hSpan.dtype);
17
+ const hPad = ops.concatAxis([hSpan, pad], 1);
18
+ pad.dispose();
19
+ const lgPad = sequenceLogitsOperation(model, hPad); // [1, 4, V]
20
+ hPad.dispose();
21
+ const start = MlxArray.fromInt32(new Int32Array([0]), [1]);
22
+ const lg = ops.sliceDynamic(lgPad, start, [1], [B, M, lgPad.shape[2]!]);
23
+ start.dispose();
24
+ lgPad.dispose();
25
+ return lg;
26
+ }
27
+
28
+ /** Per-row summed response log likelihood, extracted unchanged from DPO.
29
+ * logits [B,L-1,V] predict ids[:,1:]; mask is indexed in the original [B,L]
30
+ * token coordinates. Prompt and padding masks are zero, answer masks one.
31
+ * Borrows every input and returns an owned differentiable f32 tensor [B].
32
+ * Subtraction precedes the f32 cast, retaining the existing DPO operation order.
33
+ */
34
+ export function sequenceLogProbabilities(logits: MlxArray, ids: number[][], mask: number[][]): MlxArray {
35
+ const B = ids.length;
36
+ const L = ids[0]!.length;
37
+ const T = L - 1;
38
+ const V = logits.shape[2]!;
39
+ const N = B * T;
40
+
41
+ const targetsHost = new Int32Array(N);
42
+ const maskHost = new Float32Array(N);
43
+ for (let b = 0; b < B; b++)
44
+ for (let t = 0; t < T; t++) {
45
+ const flat = b * T + t;
46
+ targetsHost[flat] = ids[b]![t + 1]!;
47
+ maskHost[flat] = mask[b]![t + 1]!; // resp_mask[:, 1:]
48
+ }
49
+ const logits2d = ops.reshape(logits, [N, V]);
50
+ const targets = MlxArray.fromInt32(targetsHost, [N, 1]);
51
+ const m = MlxArray.fromFloat32(maskHost, [N]);
52
+
53
+ const lse = ops.logsumexpAxis(logits2d, -1, false); // [N]
54
+ const gathered = ops.takeAlongAxis(logits2d, targets, -1); // [N,1]
55
+ const picked = ops.reshape(gathered, [N]); // [N]
56
+ const logp = ops.sub(picked, lse); // log_softmax at the label
57
+ const logpF = logp.dtype === Dtype.float32 ? logp : logp.astype(Dtype.float32);
58
+ const masked = ops.mul(logpF, m); // [N]
59
+ const masked2d = ops.reshape(masked, [B, T]); // [B, T]
60
+ const perRow = ops.sumAxis(masked2d, 1, false); // [B]
61
+
62
+ for (const a of [logits2d, targets, m, lse, gathered, picked, logp, masked, masked2d]) a.dispose();
63
+ if (logpF !== logp) logpF.dispose();
64
+ return perRow;
65
+ }
@@ -0,0 +1,180 @@
1
+ import type { MlxArray } from "@mlx-bun/mlx/array";
2
+ import { Dtype } from "@mlx-bun/mlx/ffi";
3
+ import * as ops from "@mlx-bun/mlx/ops";
4
+ import type { AttentionRead, Mask } from "../contracts/mlx/cache";
5
+ import type { AffineKernelChoice } from "../contracts/portable/composition";
6
+ import { quantizedAppendAttention } from "../layers/quantized-append-attention";
7
+ import { fusedSdpaConfigSupported, quantizedSdpaTiled, quantizedSdpaUnfused } from "../layers/quantized-attention";
8
+ import type { AttentionMasks, MaskLease } from "./attention-read";
9
+ import { disposeTriple } from "./quantized-tensor";
10
+
11
+ /** One attention kernel over affine-quantized keys and values: queries
12
+ * [B, Hq, L, D] to an owned output [B, Hq, L, Dv]. Borrows every input. */
13
+ export type AffineKernel = (q: MlxArray, keys: ops.QuantizedTensor, values: ops.QuantizedTensor,
14
+ scale: number, mask: Mask) => MlxArray;
15
+
16
+ /** A kernel set named by the composition it serves (`composedAffineKernels`,
17
+ * `unfusedAffineKernels`, `tiledCausalAffineKernels`, `tiledAffineKernels`),
18
+ * which a conversion builds for the converted cache's bits, group size and
19
+ * activation dtype. */
20
+ export type AffineKernelSet = (bits: number, groupSize: number, dtype: Dtype) => AffineKernels;
21
+
22
+ /** The kernel an affine cache attends with for each read. The cache takes it
23
+ * when it is built and never chooses again: `unfusedAffineKernels` for a
24
+ * uniform `--kv-quant N`, `tiledCausalAffineKernels` for `--kv-quant config`,
25
+ * `tiledAffineKernels` for the opt-in always-tiled composition, or a
26
+ * device-specific cache's own. */
27
+ export interface AffineKernels {
28
+ /** The decode read: one query per row. */
29
+ readonly decode: AffineKernel;
30
+ /** The window read (`L` causal queries per row) while the cache's own mask
31
+ * is plain causal. */
32
+ readonly window: AffineKernel;
33
+ /** The window read once the cache's own state makes its mask an array:
34
+ * padded or unequal rows in a batched cache, the sliding window bound in a
35
+ * rotating cache. The cache picks it on the branch that picks that mask,
36
+ * from state it owns, never from anything the caller passes. */
37
+ readonly maskedWindow: AffineKernel;
38
+ /** The kernels a row layout reads with unless it holds exactly one row at
39
+ * its own positions (several rows, or one behind left padding); absent,
40
+ * these kernels at every row count. Only a set whose kernels take one
41
+ * sequence has it (the M4 Pro composition's KV4 set, `Kv4Head256Cache`). The
42
+ * layout picks it from its own row layout, as it picks `maskedWindow` from
43
+ * its own mask, never from the queries it is handed. */
44
+ readonly multiRow?: AffineKernels;
45
+ }
46
+
47
+ /** The kernels a row layout reads with: `kernels` while it holds exactly one
48
+ * row at its own positions (`singleUnpaddedRow`), their `multiRow` set (when
49
+ * they have one) otherwise. */
50
+ export function rowLayoutKernels(kernels: AffineKernels, singleUnpaddedRow: boolean): AffineKernels {
51
+ return singleUnpaddedRow ? kernels : kernels.multiRow ?? kernels;
52
+ }
53
+
54
+ /** The kernel set a composition's affine caches attend with
55
+ * (`KvStorageScheme.kernels`): `unfused`, mlx-lm's port for every read;
56
+ * `tiled-causal`, OptiQ's wrapper rule where the tiled port takes the bits,
57
+ * group size and activation dtype and the unfused port where it does not, as
58
+ * the served path computed each configuration. Resolved for each cache when its
59
+ * quantized storage is built. */
60
+ export function composedAffineKernels(choice: AffineKernelChoice): AffineKernelSet {
61
+ return choice === "unfused" ? unfusedAffineKernels
62
+ : (bits, groupSize, dtype) => fusedSdpaConfigSupported(groupSize, bits, dtype)
63
+ ? tiledCausalAffineKernels(bits, groupSize, dtype) : unfusedAffineKernels(bits, groupSize, dtype);
64
+ }
65
+
66
+ /** The read the row layouts' and donors' attention views make over fetched
67
+ * affine keys and values (`captureKvAttention`, `quantizedDonorAttention`):
68
+ * one query reads with `kernels.decode`; a window under a plain causal mask, or
69
+ * a causal-equivalent bool matrix, with `kernels.window`; under any other array
70
+ * mask with `kernels.maskedWindow`. The views choose by query count because the
71
+ * row layouts' attention views and the donor reads receive their queries
72
+ * without a phase; the caches' named reads choose by phase. */
73
+ export function viewAffineKernel(kernels: AffineKernels, queries: number, mask: Mask): AffineKernel {
74
+ if (queries === 1) return kernels.decode;
75
+ const causal = mask.mode === "causal" || mask.mode === "" ||
76
+ (mask.mode === "array" && mask.causalEquivalent === true && mask.arr !== null && mask.arr.ndim === 2 && mask.arr.dtype === Dtype.bool);
77
+ return causal ? kernels.window : kernels.maskedWindow;
78
+ }
79
+
80
+ /** The stock unfused port (`quantizedSdpaUnfused`). */
81
+ export function unfusedKernel(groupSize: number, bits: number): AffineKernel {
82
+ return (q, keys, values, scale, mask) => quantizedSdpaUnfused(q, keys, values, scale, mask, groupSize, bits);
83
+ }
84
+
85
+ /** The N-tiled fused port (`quantizedSdpaTiled`). It folds GQA into 5-D
86
+ * scores, so a per-row mask [B, 1, L, N] takes the unit KV-head axis there,
87
+ * as the unfused port inserts it for itself. */
88
+ export function tiledKernel(groupSize: number, bits: number): AffineKernel {
89
+ return (q, keys, values, scale, mask) => {
90
+ if (mask.arr?.ndim === 4 && q.shape[1] !== keys.packed.shape[1]) {
91
+ using arr = ops.expandDims(mask.arr, 1);
92
+ return quantizedSdpaTiled(q, keys, values, scale, { mode: "array", arr }, groupSize, bits);
93
+ }
94
+ return quantizedSdpaTiled(q, keys, values, scale, mask, groupSize, bits);
95
+ };
96
+ }
97
+
98
+ /** Every read unfused: mlx-lm's quantized SDPA port. The composition of a
99
+ * uniform `--kv-quant N` (KV scheme `affine`, `kernels: "unfused"`), which
100
+ * keeps mlx-lm parity, and of `--fused-sdpa off`. The unfused port takes any
101
+ * bits, group size and activation dtype, so the dtype only names the
102
+ * configuration. */
103
+ export function unfusedAffineKernels(bits: number, groupSize: number, _dtype: Dtype): AffineKernels {
104
+ const unfused = unfusedKernel(groupSize, bits);
105
+ return { decode: unfused, window: unfused, maskedWindow: unfused };
106
+ }
107
+
108
+ /** OptiQ's wrapper rule: decode unfused (what one query gets in OptiQ's
109
+ * wrapper); a window through the N-tiled fused port while the cache's own mask
110
+ * is plain causal, and through the unfused port once the cache's own state
111
+ * makes that mask an array (padded or unequal rows in a batched cache, the
112
+ * sliding window bound in a rotating cache). The choice keys only on state the
113
+ * cache owns, like the ring wrap or the delayed-quantization transition, never
114
+ * on anything the caller passes. The composition of `--kv-quant config` (KV
115
+ * scheme `affine-layers`, `kernels: "tiled-causal"`), which keeps OptiQ parity,
116
+ * and of `--fused-sdpa on` (`composedAffineKernels`). Throws at construction
117
+ * when the tiled port does not take these bits, group size and activation
118
+ * `dtype`. */
119
+ export function tiledCausalAffineKernels(bits: number, groupSize: number, dtype: Dtype): AffineKernels {
120
+ supportsTiling("tiledCausalAffineKernels", bits, groupSize, dtype);
121
+ const unfused = unfusedKernel(groupSize, bits);
122
+ return { decode: unfused, window: tiledKernel(groupSize, bits), maskedWindow: unfused };
123
+ }
124
+
125
+ /** Decode unfused, every window through the N-tiled fused port, masked ones
126
+ * included. The always-tiled composition: no oracle computes it, so it is
127
+ * opt-in and gated by KL. Throws at construction when the tiled port does not
128
+ * take these bits, group size and activation `dtype`
129
+ * (`fusedSdpaConfigSupported`); it never falls back to the unfused port. */
130
+ export function tiledAffineKernels(bits: number, groupSize: number, dtype: Dtype): AffineKernels {
131
+ supportsTiling("tiledAffineKernels", bits, groupSize, dtype);
132
+ const tiled = tiledKernel(groupSize, bits);
133
+ return { decode: unfusedKernel(groupSize, bits), window: tiled, maskedWindow: tiled };
134
+ }
135
+
136
+ function supportsTiling(name: string, bits: number, groupSize: number, dtype: Dtype): void {
137
+ if (!fusedSdpaConfigSupported(groupSize, bits, dtype))
138
+ throw new Error(`${name}: the tiled port takes 4- or 8-bit, group 32, 64 or 128, bf16 or f16 ` +
139
+ `activations; got ${bits}-bit, group ${groupSize}, dtype ${dtype}`);
140
+ }
141
+
142
+ /** A read over fetched affine keys and values: `kernel` under the leased
143
+ * mask. Takes ownership of `keys`, `values` and `mask`. */
144
+ export function affineRead(keys: ops.QuantizedTensor, values: ops.QuantizedTensor, mask: MaskLease,
145
+ kernel: AffineKernel): AttentionRead {
146
+ return {
147
+ attend: (q, scale) => kernel(q, keys, values, scale, mask.mask),
148
+ dispose() { disposeTriple(keys); disposeTriple(values); mask.release(); },
149
+ };
150
+ }
151
+
152
+ /** The committed read over fetched affine keys and values: each position of
153
+ * the span attends its own causal prefix through the one-query unfused port
154
+ * (`quantizedAppendAttention`, the graphs' independent-rows path). One row.
155
+ * Takes ownership of `keys` and `values`. */
156
+ export function committedAffineRead(keys: ops.QuantizedTensor, values: ops.QuantizedTensor,
157
+ groupSize: number, bits: number): AttentionRead {
158
+ return {
159
+ attend: (q, scale) => quantizedAppendAttention(q, keys, values, scale, groupSize, bits),
160
+ dispose() { disposeTriple(keys); disposeTriple(values); },
161
+ };
162
+ }
163
+
164
+ /** The bottom-right-aligned causal matrix, bool [L, N] (query `i` sees keys
165
+ * `0..N-L+i`), that both affine kernels build for themselves from the
166
+ * "causal" mask, here built once per forward through `masks`. */
167
+ export function affineCausalLease(masks: AttentionMasks, L: number, N: number): MaskLease {
168
+ return masks.lease("affine-causal", `${L}|${N}`, () => ({ mode: "array", arr: bottomRightCausal(L, N) }));
169
+ }
170
+
171
+ /** The kernels' "causal" branch, verbatim. */
172
+ function bottomRightCausal(L: number, N: number): MlxArray {
173
+ const qIdx = ops.arange(N - L, N, 1, Dtype.int32);
174
+ const kIdx = ops.arange(0, N, 1, Dtype.int32);
175
+ const qCol = ops.reshape(qIdx, [L, 1]);
176
+ const kRow = ops.reshape(kIdx, [1, N]);
177
+ const mask = ops.greaterEqual(qCol, kRow);
178
+ for (const a of [qIdx, kIdx, qCol, kRow]) a.dispose();
179
+ return mask;
180
+ }
@@ -0,0 +1,68 @@
1
+ import type { Cache } from "../contracts/mlx/cache";
2
+ import type { MlxArray } from "@mlx-bun/mlx/array";
3
+ import * as ops from "@mlx-bun/mlx/ops";
4
+ import { legacyCacheCodecs } from "./persistence";
5
+ import { type CacheCodecProvider, type CacheHeaderEntry, type SnapshotContext } from "./persistence-types";
6
+ import { disposeResources } from "../runtime/resources";
7
+ import type { CheckpointAttachment } from "../contracts/mlx/checkpoint";
8
+ type TensorSlot = SnapshotContext["slots"][number];
9
+
10
+ /** Reuse the backend cache codecs for method-owned companion state. Tensor
11
+ * slots address attachment indices here; the persistence tier supplies the
12
+ * outer file offsets and checksums. No host serialization of tensors occurs. */
13
+ export function captureCacheAttachment(schema: string, caches: readonly Cache[],
14
+ metadata: CheckpointAttachment["metadata"], codecs: CacheCodecProvider = legacyCacheCodecs): CheckpointAttachment {
15
+ const tensors: MlxArray[] = [], headers: Array<CacheHeaderEntry | null> = [];
16
+ try {
17
+ for (const cache of caches) {
18
+ if (!cache.offset) { headers.push(null); continue; }
19
+ const slots: TensorSlot[] = [];
20
+ const liveSlice = (array: MlxArray, length: number, axis = 2) => {
21
+ const end = [...array.shape]; end[axis] = length;
22
+ return array.slice(array.shape.map(() => 0), end);
23
+ };
24
+ const push = (array: MlxArray, owned: boolean) => {
25
+ const value = owned ? array : ops.copyOf(array);
26
+ const off = tensors.length; tensors.push(value);
27
+ slots.push({ off, bytes: value.nbytes, shape: [...value.shape], dtype: value.dtype, hash: "" });
28
+ };
29
+ const context: SnapshotContext = { slots, push, liveSlice,
30
+ liveMlaSlice: (array, length) => liveSlice(array, length, 1),
31
+ pushTriple(triple, length) {
32
+ for (const array of [triple.packed, triple.scales, triple.biases])
33
+ push(length === null ? array : liveSlice(array, length), length !== null);
34
+ },
35
+ };
36
+ const stored = cache.composed ? cache.composed.storage() : cache;
37
+ const entry = codecs.forCache(stored).snapshot(stored, context);
38
+ if (stored.minimumReusableOffset !== undefined) entry.minimumReusableOffset = stored.minimumReusableOffset;
39
+ headers.push(entry);
40
+ }
41
+ return { schema, metadata: { ...metadata, cacheCodecs: codecs.id, cacheHeaders: JSON.stringify(headers) }, tensors };
42
+ } catch (error) { disposeResources(tensors); throw error; }
43
+ }
44
+
45
+ /** Returned caches own immutable aliases. The caller keeps an external SSD
46
+ * lease until its normal state materialization boundary has completed. */
47
+ export function restoreCacheAttachment(attachment: CheckpointAttachment, makeCache: () => Cache[],
48
+ codecs: CacheCodecProvider = legacyCacheCodecs): Cache[] {
49
+ if (attachment.metadata.cacheCodecs !== codecs.id) throw new Error("companion cache codec mismatch");
50
+ const headers = JSON.parse(attachment.metadata.cacheHeaders as string) as Array<CacheHeaderEntry | null>;
51
+ const caches = makeCache(), pending: MlxArray[] = [];
52
+ const arr = (slot: TensorSlot) => {
53
+ const value = ops.copyOf(attachment.tensors[slot.off]!); pending.push(value); return value;
54
+ };
55
+ try {
56
+ for (const [index, entry] of headers.entries()) {
57
+ if (!entry) continue;
58
+ const cache = codecs.forHeader(entry).load(entry, { path: attachment.schema, prototype: caches[index]!, arr, grownArr: arr,
59
+ triple: (slots, at) => ({ packed: arr(slots[at]!), scales: arr(slots[at + 1]!), biases: arr(slots[at + 2]!) }),
60
+ });
61
+ pending.length = 0;
62
+ cache.minimumReusableOffset = entry.minimumReusableOffset ?? 0;
63
+ const previous = caches[index]; caches[index] = cache;
64
+ previous?.dispose();
65
+ }
66
+ return caches;
67
+ } catch (error) { disposeResources([...pending, ...caches]); throw error; }
68
+ }