@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,150 @@
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
+ /** Per-adapter LoRA weights for one linear (mlx-lm LoRALinear shapes:
6
+ * a [in_features, rank], b [rank, out_features], typically f32). */
7
+ export interface LoraWeights {
8
+ a: MlxArray;
9
+ b: MlxArray;
10
+ scale: number;
11
+ rank: number;
12
+ }
13
+
14
+ /** Active-adapter state, shared by every mounted linear of one model.
15
+ * A plain field, NOT a ContextVar port: under continuous batching requests
16
+ * interleave, so the field is only valid for the forward being built. Each
17
+ * execution group binds its rows' adapter list (`bindAdapterContext`) and
18
+ * sets/restores it around every forward; `adapterScoped` does the same for a
19
+ * single generation. */
20
+ export class LoraState {
21
+ #active: string[] = [];
22
+ readonly #bindings = new Set<(active: readonly string[]) => void>();
23
+ get active(): string[] { return this.#active; }
24
+ set active(ids: string[]) {
25
+ const neutral = this.#active.length === 0 && ids.length === 0;
26
+ this.#active = ids;
27
+ if (!neutral) this.refreshBindings();
28
+ }
29
+ /** Bind request-scoped compound operations before a forward is built. */
30
+ bindOperation(binding: (active: readonly string[]) => void): () => void {
31
+ this.#bindings.add(binding);
32
+ binding(this.#active);
33
+ return () => { this.#bindings.delete(binding); };
34
+ }
35
+ refreshBindings(): void { for (const bind of this.#bindings) bind(this.#active); }
36
+ /** Training-only LoRA-input dropout. `rate` is the drop probability; `seed` is
37
+ * set per micro-step by the trainer and is CONSTANT across that step's forward
38
+ * and any recompute (segmented / gradient-checkpoint), so each layer's mask —
39
+ * keyed by (seed, the linear's dropoutId) — is reproduced exactly in the
40
+ * backward. `seed === null` (the default, and for inference) disables it. */
41
+ dropoutRate = 0;
42
+ dropoutSeed: number | null = null;
43
+ }
44
+
45
+ /** Inverted LoRA-input dropout, keyed by (seed, id) so it is deterministic —
46
+ * the same (seed, id, shape) reproduces the mask, which is what makes the
47
+ * segmented / gradient-checkpoint recompute correct. kept ⇒ x/(1-p),
48
+ * dropped ⇒ 0 (preserves the expectation). Caller owns the result. */
49
+ export function loraInputDropout(x: MlxArray, p: number, seed: number, id: number): MlxArray {
50
+ const key = ops.randomKey(BigInt(seed) * 100003n + BigInt(id));
51
+ const u = ops.randomUniform(x.shape, Dtype.float32, 0, 1, key); // [shape] in [0,1)
52
+ key.dispose();
53
+ const pArr = ops.scalarLike(p, u);
54
+ const keep = ops.less(pArr, u); // u > p ⇒ keep (bool [shape])
55
+ const scaleX = ops.scalarLike(1 / (1 - p), x);
56
+ const zeroX = ops.scalarLike(0, x);
57
+ const mask = ops.where(keep, scaleX, zeroX); // x.dtype
58
+ const xd = ops.mul(x, mask);
59
+ for (const a of [u, pArr, keep, scaleX, zeroX, mask]) a.dispose();
60
+ return xd;
61
+ }
62
+
63
+ /** Adapter delta after any projection. Borrows x and consumes the projection
64
+ * output, returning its owned replacement (or the same handle with no delta).
65
+ * Request routing sets the shared active state before the operation runs. */
66
+ export class LoraDelta {
67
+ constructor(readonly inFeatures = 0, readonly outFeatures = 0) {}
68
+ adapters: Map<string, LoraWeights> | null = null;
69
+ #state: LoraState | null = null;
70
+ readonly #stateBindings = new Set<() => void>();
71
+ get loraState(): LoraState | null { return this.#state; }
72
+ set loraState(value: LoraState | null) {
73
+ this.#state = value;
74
+ for (const bind of this.#stateBindings) bind();
75
+ }
76
+ observeState(binding: () => void): () => void {
77
+ this.#stateBindings.add(binding); binding();
78
+ return () => { this.#stateBindings.delete(binding); };
79
+ }
80
+ dropoutId = 0;
81
+
82
+ forward(x: MlxArray, out: MlxArray): MlxArray {
83
+ // LoRA residual — composition is mlx-lm LoRALinear / optiq apply.py:
84
+ // y + (scale · ((x @ A) @ B)).astype(x.dtype)
85
+ // (optiq mount.py omits the astype, leaking the f32 residual into the
86
+ // bf16 stream; the
87
+ // cast form is what the adapters were trained behind.)
88
+ const st = this.loraState;
89
+ if (st && st.active.length > 0 && this.adapters && this.adapters.size > 0) {
90
+ // Training-only LoRA-input dropout (PEFT applies dropout to x before A;
91
+ // the base quantized path is untouched). Keyed by (seed, dropoutId) so the
92
+ // backward recompute reproduces the exact mask.
93
+ let xLora = x;
94
+ let xDrop: MlxArray | null = null;
95
+ if (st.dropoutSeed !== null && st.dropoutRate > 0) {
96
+ xDrop = loraInputDropout(x, st.dropoutRate, st.dropoutSeed, this.dropoutId);
97
+ xLora = xDrop;
98
+ }
99
+ for (const id of st.active) {
100
+ const lw = this.adapters.get(id);
101
+ if (!lw) continue;
102
+ const xa = ops.matmul(xLora, lw.a);
103
+ const z = ops.matmul(xa, lw.b);
104
+ xa.dispose();
105
+ const zs = ops.mulScalar(z, lw.scale);
106
+ z.dispose();
107
+ const zc = zs.astype(x.dtype);
108
+ zs.dispose();
109
+ const previous = out; out = ops.add(out, zc); previous.dispose();
110
+ zc.dispose();
111
+ }
112
+ xDrop?.dispose();
113
+ }
114
+ return out;
115
+ }
116
+ }
117
+
118
+
119
+ /** A compound kernel's adapter operation, bound by the request context before
120
+ * forward. Fused no-adapter arithmetic is retained; the adapted operation
121
+ * exposes projections so their deltas precede activation. */
122
+ export class LoraOperation {
123
+ #forward: (x: MlxArray) => MlxArray;
124
+ readonly #unbind: (() => void)[] = [];
125
+ readonly #states = new Map<LoraState, () => void>();
126
+ constructor(plain: (x: MlxArray) => MlxArray, adapted: (x: MlxArray) => MlxArray,
127
+ state: LoraState | undefined, targets: readonly LoraDelta[]) {
128
+ this.#forward = plain;
129
+ const refresh = () => {
130
+ this.#forward = targets.some(target => (state?.active ?? target.loraState?.active ?? [])
131
+ .some(id => target.adapters?.has(id))) ? adapted : plain;
132
+ };
133
+ const bind = () => {
134
+ const states = new Set(state ? [state] : targets.flatMap(target => target.loraState ? [target.loraState] : []));
135
+ for (const [previous, unbind] of this.#states) if (!states.has(previous)) { unbind(); this.#states.delete(previous); }
136
+ for (const current of states) if (!this.#states.has(current)) this.#states.set(current, current.bindOperation(refresh));
137
+ refresh();
138
+ };
139
+ if (state) bind();
140
+ else for (const target of targets) this.#unbind.push(target.observeState(bind));
141
+ }
142
+ forward(x: MlxArray): MlxArray { return this.#forward(x); }
143
+ dispose(): void {
144
+ for (const unbind of this.#unbind) unbind();
145
+ for (const unbind of this.#states.values()) unbind();
146
+ this.#states.clear();
147
+ }
148
+
149
+
150
+ }
@@ -0,0 +1,9 @@
1
+ import { MlxArray } from "@mlx-bun/mlx/array";
2
+ import * as ops from "@mlx-bun/mlx/ops";
3
+
4
+ export class RMSNorm {
5
+ constructor(readonly weight: MlxArray | null, readonly eps: number) {}
6
+ forward(x: MlxArray): MlxArray {
7
+ return ops.rmsNorm(x, this.weight, this.eps);
8
+ }
9
+ }
@@ -0,0 +1,33 @@
1
+ import type { MlxArray } from "@mlx-bun/mlx/array";
2
+ import * as ops from "@mlx-bun/mlx/ops";
3
+ import type { PrefixLayout } from "../contracts/mlx/trainable";
4
+
5
+ /** Block-wise RoPE for the prefix-shared concat [prompt; chosen; rejected] along
6
+ * the sequence axis (axis 2 of [B,H,T,D]): prompt rotated at offset 0, each
7
+ * response at offset P (reset), so ONE forward over the concat equals the two
8
+ * separate [prompt; response] forwards. RoPE is per-token, so roping each
9
+ * contiguous block at its scalar offset and concatenating == roping with
10
+ * per-token position-ids [0..P-1, P..P+Rc-1, P..P+Rr-1]. `rope` applies the
11
+ * layer's own rotation (its base, frequency table and dims) to one block at a
12
+ * scalar offset. The prefix-shared training cache (`PrefixSharedCache`) owns
13
+ * the layout and ropes through this. Caller disposes the input. */
14
+ export function ropeBlocks(
15
+ x: MlxArray, plan: PrefixLayout, rope: (block: MlxArray, offset: number) => MlxArray,
16
+ ): MlxArray {
17
+ const { P, Rc, Rr } = plan;
18
+ const [B, H, , D] = x.shape as [number, number, number, number];
19
+ const blocks = [
20
+ { start: 0, len: P, off: 0 },
21
+ { start: P, len: Rc, off: P },
22
+ { start: P + Rc, len: Rr, off: P },
23
+ ].filter((b) => b.len > 0);
24
+ const parts = blocks.map((b) => {
25
+ const sl = x.slice([0, 0, b.start, 0], [B, H, b.start + b.len, D]);
26
+ const r = rope(sl, b.off);
27
+ sl.dispose();
28
+ return r;
29
+ });
30
+ const out = ops.concatAxis(parts, 2);
31
+ for (const p of parts) p.dispose();
32
+ return out;
33
+ }
@@ -0,0 +1,32 @@
1
+ import type { MlxArray } from "@mlx-bun/mlx/array";
2
+ import * as ops from "@mlx-bun/mlx/ops";
3
+ import { quantizedSdpaUnfused } from "./quantized-attention";
4
+
5
+ /** Committed positions retain single-token attention arithmetic (the unfused
6
+ * port, which one query always takes). Projection work can still share the
7
+ * span; each query sees its own causal KV prefix. */
8
+ export function quantizedAppendAttention(
9
+ q: MlxArray, keys: ops.QuantizedTensor, values: ops.QuantizedTensor,
10
+ scale: number, groupSize: number, bits: number,
11
+ ): MlxArray {
12
+ const [batch, heads, length, dim] = q.shape as [number, number, number, number];
13
+ const prefix = keys.packed.shape[2]! - length;
14
+ const outputs: MlxArray[] = [];
15
+ const trim = (tensor: ops.QuantizedTensor, end: number) => {
16
+ const slice = (a: MlxArray) => a.slice([0, 0, 0, 0],
17
+ [a.shape[0]!, a.shape[1]!, end, a.shape[3]!]);
18
+ return { packed: slice(tensor.packed), scales: slice(tensor.scales), biases: slice(tensor.biases) };
19
+ };
20
+ try {
21
+ for (let row = 0; row < length; row++) {
22
+ using queryView = q.slice([0, 0, row, 0], [batch, heads, row + 1, dim]);
23
+ using query = ops.contiguous(queryView);
24
+ const k = trim(keys, prefix + row + 1), v = trim(values, prefix + row + 1);
25
+ try { outputs.push(quantizedSdpaUnfused(query, k, v, scale, { mode: "", arr: null }, groupSize, bits)); }
26
+ finally {
27
+ for (const t of [k, v]) { t.packed.dispose(); t.scales.dispose(); t.biases.dispose(); }
28
+ }
29
+ }
30
+ return ops.concatAxis(outputs, 2);
31
+ } finally { for (const output of outputs) output.dispose(); }
32
+ }
@@ -0,0 +1,353 @@
1
+ import { MlxArray } from "@mlx-bun/mlx/array";
2
+ import { Dtype } from "@mlx-bun/mlx/ffi";
3
+ import * as ops from "@mlx-bun/mlx/ops";
4
+ import type { Mask } from "../contracts/mlx/cache";
5
+ import { isCompiledTrace } from "../runtime/compiled-trace";
6
+
7
+
8
+ export const FINFO_MIN: Partial<Record<Dtype, number>> = {
9
+ [Dtype.bfloat16]: -3.3895313892515355e38,
10
+ [Dtype.float16]: -65504,
11
+ [Dtype.float32]: -3.4028234663852886e38,
12
+ };
13
+
14
+ /** Port of base.py quantized_scaled_dot_product_attention (stock,
15
+ * non-tiled): scores and output via quantized_matmul against the
16
+ * quantized KV triples; GQA via a 5-d reshape. Exported for the
17
+ * fused-vs-unfused parity tests. */
18
+ export function quantizedSdpaUnfused(
19
+ q: MlxArray, kq: ops.QuantizedTensor, vq: ops.QuantizedTensor,
20
+ scale: number, mask: Mask, groupSize: number, bits: number,
21
+ ): MlxArray {
22
+ return unfused(q, kq, vq, scale, mask, groupSize, bits, false);
23
+ }
24
+
25
+ /** `quantizedSdpaUnfused` with the key matmul over three GQA heads per batch:
26
+ * the queries of each KV head enter the key quantized_matmul as [2, 9] rows
27
+ * instead of 18, and the scores are restored to [nRep, L] before the mask,
28
+ * softmax and value matmul. On the M4 Pro GPU (applegpu_g16s) MLX's native
29
+ * key matvec keeps exact arithmetic in that grouping at 8192 or more keys.
30
+ * A kernel for one composition: depth-2 speculative verify (L = 3) on 24
31
+ * query heads, 4 KV heads, head dim 256, B <= 2, a 4-bit group-64 cache and
32
+ * bf16 or f32 queries, built only by the cache composed for it. Other shapes
33
+ * are outside its contract (the [2, 9] split needs nRep * L = 18). */
34
+ export function quantizedSdpaGroupedHeads(
35
+ q: MlxArray, kq: ops.QuantizedTensor, vq: ops.QuantizedTensor,
36
+ scale: number, mask: Mask, groupSize: number, bits: number,
37
+ ): MlxArray {
38
+ return unfused(q, kq, vq, scale, mask, groupSize, bits, true);
39
+ }
40
+
41
+ function unfused(
42
+ q: MlxArray, kq: ops.QuantizedTensor, vq: ops.QuantizedTensor,
43
+ scale: number, mask: Mask, groupSize: number, bits: number, groupHeads: boolean,
44
+ ): MlxArray {
45
+ const [B, H, L, D] = q.shape as [number, number, number, number];
46
+ const KV = kq.packed.shape[1]!;
47
+ const nRep = H / KV;
48
+ const N = kq.packed.shape[2]!;
49
+
50
+ // scale is 1.0 for Gemma4 (q/k are RMS-normed) — skip the identity multiply.
51
+ let queries = q;
52
+ const owned: MlxArray[] = [];
53
+ if (scale !== 1.0) { queries = ops.mulScalar(q, scale); owned.push(queries); }
54
+
55
+ let kT = kq;
56
+ let vT = vq;
57
+ if (nRep > 1) {
58
+ const qr = ops.reshape(queries, [B, KV, nRep, L, D]);
59
+ owned.push(qr);
60
+ queries = qr;
61
+ const expand = (t: ops.QuantizedTensor): ops.QuantizedTensor => {
62
+ const e = (a: MlxArray): MlxArray => {
63
+ // expand_dims(axis=-3) like the reference — view-preserving;
64
+ // reshape would copy the strided slice and change kernel paths
65
+ const r = ops.expandDims(a, -3);
66
+ owned.push(r);
67
+ return r;
68
+ };
69
+ return { packed: e(t.packed), scales: e(t.scales), biases: e(t.biases) };
70
+ };
71
+ kT = expand(kq);
72
+ vT = expand(vq);
73
+ }
74
+
75
+ // Grouped heads (quantizedSdpaGroupedHeads): three GQA heads per key
76
+ // matmul batch; restore score geometry before softmax/value work.
77
+ let keyQueries = queries;
78
+ if (groupHeads) {
79
+ keyQueries = ops.reshape(queries, [B, KV, 2, 9, D]);
80
+ owned.push(keyQueries);
81
+ }
82
+ let scores = ops.quantizedMatmulQT(keyQueries, kT, true, groupSize, bits);
83
+ owned.push(scores);
84
+ if (groupHeads) {
85
+ scores = ops.reshape(scores, [B, KV, nRep, L, N]);
86
+ owned.push(scores);
87
+ }
88
+
89
+ let maskArr: MlxArray | null = null;
90
+ let ownsMask = false;
91
+ if (mask.mode === "causal") {
92
+ const qIdx = ops.arange(N - L, N, 1, Dtype.int32);
93
+ const kIdx = ops.arange(0, N, 1, Dtype.int32);
94
+ const qCol = ops.reshape(qIdx, [L, 1]);
95
+ const kRow = ops.reshape(kIdx, [1, N]);
96
+ maskArr = ops.greaterEqual(qCol, kRow);
97
+ ownsMask = true;
98
+ for (const a of [qIdx, kIdx, qCol, kRow]) a.dispose();
99
+ } else if (mask.mode === "array") {
100
+ maskArr = mask.arr;
101
+ // Batched padding masks are [B,1,L,S] (buildBatchedDecodeMask). When GQA
102
+ // reshaped the scores to 5-D [B,KV,nRep,L,S], trailing-dim broadcasting
103
+ // would misalign the mask's B with KV — insert the unit axis explicitly:
104
+ // [B,1,1,L,S] broadcasts correctly over (KV, nRep).
105
+ if (maskArr && maskArr.ndim === 4 && nRep > 1) {
106
+ maskArr = ops.expandDims(maskArr, 1);
107
+ ownsMask = true;
108
+ }
109
+ }
110
+ if (maskArr) {
111
+ let masked: MlxArray;
112
+ if (maskArr.dtype === Dtype.bool) {
113
+ using ninf = ops.scalarLike(FINFO_MIN[scores.dtype] ?? -3.4e38, scores);
114
+ masked = ops.where(maskArr, scores, ninf);
115
+ } else masked = ops.add(scores, maskArr);
116
+ if (ownsMask) maskArr.dispose();
117
+ owned.push(masked);
118
+ scores = masked;
119
+ }
120
+
121
+ const probs = ops.softmaxAxis(scores, -1, true);
122
+ owned.push(probs);
123
+ let out = ops.quantizedMatmulQT(probs, vT, false, groupSize, bits);
124
+ if (nRep > 1) {
125
+ const r = ops.reshape(out, [B, H, L, D]);
126
+ out.dispose();
127
+ out = r;
128
+ }
129
+ for (const a of owned) a.dispose();
130
+ return out;
131
+ }
132
+
133
+ /** GQA folded into rows over a quantized cache: each KV head's rep·L query
134
+ * rows share one quantized_matmul against its packed keys and one against
135
+ * its values, so the cache is read once per KV head instead of once per query
136
+ * head (the broadcast in quantizedSdpaUnfused). B = 1; the L queries are the
137
+ * last L positions (causal), no array mask. Not bit-identical to quantizedSdpa
138
+ * (bf16-level). */
139
+ export function foldedQuantizedSdpaEligible(q: MlxArray, kq: ops.QuantizedTensor, mask: Mask): boolean {
140
+ const [B, H, L] = q.shape as [number, number, number, number];
141
+ const KVH = kq.packed.shape[1]!;
142
+ return B === 1 && H % KVH === 0 && !mask.arr && (L === 1 || mask.mode === "causal") && L <= kq.packed.shape[2]!;
143
+ }
144
+
145
+ export function foldedQuantizedSdpa(
146
+ q: MlxArray, kq: ops.QuantizedTensor, vq: ops.QuantizedTensor,
147
+ scale: number, groupSize: number, bits: number,
148
+ ): MlxArray {
149
+ const [, H, L, D] = q.shape as [number, number, number, number];
150
+ const KVH = kq.packed.shape[1]!, N = kq.packed.shape[2]!, rep = H / KVH;
151
+ using q4 = ops.reshape(q, [1, KVH, rep * L, D]); // rows (r, i): head KVH·rep + r, query i
152
+ using qs = ops.mulScalar(q4, scale);
153
+ using scores = ops.quantizedMatmulQT(qs, kq, true, groupSize, bits); // [1, KVH, rep·L, N]
154
+ let probs: MlxArray;
155
+ if (L > 1) {
156
+ using cols = ops.arange(0, N, 1, Dtype.int32);
157
+ using colRow = ops.reshape(cols, [1, N]);
158
+ using limits = ops.fromInt32(Array.from({ length: rep * L }, (_, row) => N - L + (row % L)), [rep * L, 1]);
159
+ using allowed = ops.lessEqual(colRow, limits);
160
+ using ninf = ops.scalarLike(FINFO_MIN[scores.dtype] ?? -3.4e38, scores);
161
+ using masked = ops.where(allowed, scores, ninf);
162
+ probs = ops.softmaxAxis(masked, -1, true);
163
+ } else probs = ops.softmaxAxis(scores, -1, true);
164
+ using p = probs;
165
+ using out = ops.quantizedMatmulQT(p, vq, false, groupSize, bits); // [1, KVH, rep·L, D]
166
+ return ops.reshape(out, [1, H, L, out.shape[3]!]);
167
+ }
168
+
169
+ /** Tile size for the fused quantized-SDPA prefill path (oracle default). */
170
+ export const FUSED_N_CHUNK = 512;
171
+
172
+ /** Port of optiq fused_quant_sdpa._prefill_flashattn_n_tiled: a
173
+ * FlashAttention-2 loop over the KV N axis with mx.quantized_matmul as
174
+ * the inner kernel. Never materializes the full [..., L, N] scores
175
+ * matrix — the per-tile transient is bounded by FUSED_N_CHUNK, which is
176
+ * the whole point (stock u4 KV prefill peaks ABOVE fp16 KV at long
177
+ * context; see the oracle's module docstring). Op composition order
178
+ * mirrors the oracle exactly: parity is tier a (bit-exact) vs the python
179
+ * fused path; vs quantizedSdpaUnfused it is tier b by construction
180
+ * (online softmax ≠ one-shot precise softmax in bf16). */
181
+ export function quantizedSdpaTiled(
182
+ q: MlxArray, kq: ops.QuantizedTensor, vq: ops.QuantizedTensor,
183
+ scale: number, mask: Mask, groupSize: number, bits: number,
184
+ ): MlxArray {
185
+ const [B, H, L, D] = q.shape as [number, number, number, number];
186
+ const KV = kq.packed.shape[1]!;
187
+ const nRep = H / KV;
188
+ const N = kq.packed.shape[2]!;
189
+
190
+ // scale is 1.0 for Gemma4 (q/k are RMS-normed) — skip the identity multiply.
191
+ let queries = q;
192
+ let ownsQueries = false;
193
+ if (scale !== 1.0) { queries = ops.mulScalar(q, scale); ownsQueries = true; }
194
+
195
+ let kT = kq;
196
+ let vT = vq;
197
+ const expanded: MlxArray[] = [];
198
+ if (nRep > 1) {
199
+ const qr = ops.reshape(queries, [B, KV, nRep, L, D]);
200
+ if (ownsQueries) queries.dispose();
201
+ queries = qr;
202
+ ownsQueries = true;
203
+ const expand = (t: ops.QuantizedTensor): ops.QuantizedTensor => {
204
+ const e = (a: MlxArray): MlxArray => {
205
+ const r = ops.expandDims(a, -3);
206
+ expanded.push(r);
207
+ return r;
208
+ };
209
+ return { packed: e(t.packed), scales: e(t.scales), biases: e(t.biases) };
210
+ };
211
+ kT = expand(kq);
212
+ vT = expand(vq);
213
+ }
214
+
215
+ // The oracle builds a bottom-right-aligned [L, N] bool causal matrix
216
+ // and slices columns per tile; our "array" masks (createCausalMask at
217
+ // offset > 0) are that same matrix already materialized — both slice
218
+ // identically below.
219
+ let maskArr: MlxArray | null = null;
220
+ let ownsMask = false;
221
+ if (mask.mode === "causal") {
222
+ const qIdx = ops.arange(N - L, N, 1, Dtype.int32);
223
+ const kIdx = ops.arange(0, N, 1, Dtype.int32);
224
+ const qCol = ops.reshape(qIdx, [L, 1]);
225
+ const kRow = ops.reshape(kIdx, [1, N]);
226
+ maskArr = ops.greaterEqual(qCol, kRow);
227
+ ownsMask = true;
228
+ for (const a of [qIdx, kIdx, qCol, kRow]) a.dispose();
229
+ } else if (mask.mode === "array") {
230
+ maskArr = mask.arr;
231
+ }
232
+
233
+ // Slice [..., n0:n1, :] (KV triples) / [..., n0:n1] (mask chunks).
234
+ // Under a compiled-decode trace, subrange Slice swaps to DynamicSlice
235
+ // (identical values; shapeless compile rejects Slice — same pattern as
236
+ // the per-layer-input and MoE top-k slices).
237
+ const sliceAxis = (a: MlxArray, axisFromEnd: 1 | 2, n0: number, n1: number): MlxArray => {
238
+ const dims = a.shape;
239
+ const axis = dims.length - axisFromEnd;
240
+ if (isCompiledTrace()) {
241
+ const start = ops.fromInt32([n0], [1]);
242
+ const size = [...dims];
243
+ size[axis] = n1 - n0;
244
+ const out = ops.sliceDynamic(a, start, [axis], size);
245
+ start.dispose();
246
+ return out;
247
+ }
248
+ const start = dims.map(() => 0);
249
+ const stop = [...dims];
250
+ start[axis] = n0;
251
+ stop[axis] = n1;
252
+ return a.slice(start, stop);
253
+ };
254
+
255
+ let oAcc: MlxArray | null = null;
256
+ let rowMax: MlxArray | null = null;
257
+ let rowSum: MlxArray | null = null;
258
+
259
+ for (let n0 = 0; n0 < N; n0 += FUSED_N_CHUNK) {
260
+ const n1 = Math.min(n0 + FUSED_N_CHUNK, N);
261
+ const kChunk: ops.QuantizedTensor = {
262
+ packed: sliceAxis(kT.packed, 2, n0, n1),
263
+ scales: sliceAxis(kT.scales, 2, n0, n1),
264
+ biases: sliceAxis(kT.biases, 2, n0, n1),
265
+ };
266
+ const vChunk: ops.QuantizedTensor = {
267
+ packed: sliceAxis(vT.packed, 2, n0, n1),
268
+ scales: sliceAxis(vT.scales, 2, n0, n1),
269
+ biases: sliceAxis(vT.biases, 2, n0, n1),
270
+ };
271
+
272
+ let scores = ops.quantizedMatmulQT(queries, kChunk, true, groupSize, bits);
273
+ for (const a of [kChunk.packed, kChunk.scales, kChunk.biases]) a.dispose();
274
+
275
+ if (maskArr) {
276
+ const maskChunk = sliceAxis(maskArr, 1, n0, n1);
277
+ let masked: MlxArray;
278
+ if (maskChunk.dtype === Dtype.bool) {
279
+ const ninf = ops.scalarLike(FINFO_MIN[scores.dtype] ?? -3.4e38, scores);
280
+ masked = ops.where(maskChunk, scores, ninf);
281
+ ninf.dispose();
282
+ } else {
283
+ masked = ops.add(scores, maskChunk);
284
+ }
285
+ maskChunk.dispose();
286
+ scores.dispose();
287
+ scores = masked;
288
+ }
289
+
290
+ const chunkMax = ops.maxAxis(scores, -1, true);
291
+ if (oAcc === null) {
292
+ rowMax = chunkMax;
293
+ const shifted = ops.sub(scores, rowMax);
294
+ const exps = ops.exp(shifted);
295
+ shifted.dispose();
296
+ rowSum = ops.sumAxis(exps, -1, true);
297
+ oAcc = ops.quantizedMatmulQT(exps, vChunk, false, groupSize, bits);
298
+ exps.dispose();
299
+ } else {
300
+ const newMax = ops.maximum(rowMax!, chunkMax);
301
+ chunkMax.dispose();
302
+ const maxDiff = ops.sub(rowMax!, newMax);
303
+ const factor = ops.exp(maxDiff);
304
+ maxDiff.dispose();
305
+ const shifted = ops.sub(scores, newMax);
306
+ const exps = ops.exp(shifted);
307
+ shifted.dispose();
308
+ // new_sum = factor * row_sum + sum(exps) (association order kept)
309
+ const carried = ops.mul(factor, rowSum!);
310
+ const sumExps = ops.sumAxis(exps, -1, true);
311
+ const newSum = ops.add(carried, sumExps);
312
+ carried.dispose();
313
+ sumExps.dispose();
314
+ const deltaOut = ops.quantizedMatmulQT(exps, vChunk, false, groupSize, bits);
315
+ exps.dispose();
316
+ // o_acc = o_acc * factor + delta_out
317
+ const scaledAcc = ops.mul(oAcc!, factor);
318
+ factor.dispose();
319
+ const nextAcc = ops.add(scaledAcc, deltaOut);
320
+ scaledAcc.dispose();
321
+ deltaOut.dispose();
322
+ oAcc!.dispose();
323
+ oAcc = nextAcc;
324
+ rowMax!.dispose();
325
+ rowMax = newMax;
326
+ rowSum!.dispose();
327
+ rowSum = newSum;
328
+ }
329
+ scores.dispose();
330
+ for (const a of [vChunk.packed, vChunk.scales, vChunk.biases]) a.dispose();
331
+ }
332
+
333
+ let out = ops.div(oAcc!, rowSum!);
334
+ oAcc!.dispose();
335
+ rowMax!.dispose();
336
+ rowSum!.dispose();
337
+ if (ownsMask) maskArr!.dispose();
338
+ for (const a of expanded) a.dispose();
339
+ if (ownsQueries) queries.dispose();
340
+ if (nRep > 1) {
341
+ const r = ops.reshape(out, [B, H, L, D]);
342
+ out.dispose();
343
+ out = r;
344
+ }
345
+ return out;
346
+ }
347
+
348
+ /** The (bits, group size, dtype) the tiled kernel takes: the composition's
349
+ * tiled-causal set (`composedAffineKernels`) tiles exactly these. */
350
+ export function fusedSdpaConfigSupported(groupSize: number, bits: number, dtype: Dtype): boolean {
351
+ return (bits === 4 || bits === 8) && (groupSize === 32 || groupSize === 64 || groupSize === 128) &&
352
+ (dtype === Dtype.bfloat16 || dtype === Dtype.float16);
353
+ }
@@ -0,0 +1,39 @@
1
+ import { MlxArray } from "@mlx-bun/mlx/array";
2
+ import * as ops from "@mlx-bun/mlx/ops";
3
+ import { quantFor,type ModelConfig } from "../artifacts/config";
4
+ import type { Weights } from "../artifacts/weights";
5
+
6
+ export class QuantizedEmbedding {
7
+ constructor(
8
+ readonly w: MlxArray,
9
+ readonly scales: MlxArray,
10
+ readonly biases: MlxArray | null,
11
+ readonly spec: ops.QuantSpec,
12
+ ) {}
13
+
14
+ static load(weights: Weights, path: string, config: ModelConfig): QuantizedEmbedding {
15
+ const spec = quantFor(config.quantization, path)!;
16
+ return new QuantizedEmbedding(
17
+ weights.tensor(`${path}.weight`),
18
+ weights.tensor(`${path}.scales`),
19
+ weights.has(`${path}.biases`) ? weights.tensor(`${path}.biases`) : null,
20
+ spec,
21
+ );
22
+ }
23
+
24
+ /** ids [1, L] (int/uint) → embeddings [1, L, hidden]. */
25
+ encode(ids: MlxArray): MlxArray {
26
+ const rows = ops.takeAxis(this.w, ids, 0);
27
+ const scaleRows = ops.takeAxis(this.scales, ids, 0);
28
+ const biasRows = this.biases ? ops.takeAxis(this.biases, ids, 0) : null;
29
+ const out = ops.dequantize(rows, scaleRows, biasRows, this.spec);
30
+ for (const a of [rows, scaleRows]) a.dispose();
31
+ biasRows?.dispose();
32
+ return out;
33
+ }
34
+
35
+ /** Tied output head: h [1, L, hidden] → logits [1, L, vocab]. */
36
+ asLinear(h: MlxArray): MlxArray {
37
+ return ops.quantizedMatmul(h, this.w, this.scales, this.biases, this.spec, true);
38
+ }
39
+ }