@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,812 @@
1
+ // GLM-5.2 multi-head latent attention (MLA), correctness path.
2
+ //
3
+ // The checkpoint stores a compressed KV representation:
4
+ // latent: [B, T, kv_lora_rank]
5
+ // rope: [B, T, qk_rope_head_dim]
6
+ //
7
+ // Prefill reconstructs per-head no-PE keys and values through kv_b_proj.
8
+ // Single-token decode absorbs the key/value projections around attention so
9
+ // the full per-head K/V history is never materialized.
10
+
11
+ import { MlxArray } from "@mlx-bun/mlx/array";
12
+ import { positiveInteger } from "../../runtime/integers";
13
+ import { Dtype } from "@mlx-bun/mlx/ffi";
14
+ import * as ops from "@mlx-bun/mlx/ops";
15
+ import type { Glm52Config } from "../../artifacts/glm52-config";
16
+ import type { ColibriGlm52Weights } from "../../artifacts/glm52/weights";
17
+ import type { LatentAttentionCache, LatentAttentionState } from "../../contracts/mlx/cache";
18
+
19
+ export interface Glm52MlaProjection {
20
+ /** [B,T,H,qk_nope_head_dim]. */
21
+ qNope: MlxArray;
22
+ /** [B,T,H,qk_rope_head_dim], already rotated. */
23
+ qRope: MlxArray;
24
+ /** [B,T,kv_lora_rank], normalized compressed content state. */
25
+ latent: MlxArray;
26
+ /** [B,T,qk_rope_head_dim], already rotated. */
27
+ rope: MlxArray;
28
+ }
29
+
30
+ /** How an MLA read attends the stored latent state, which the phase names (the
31
+ * graph's per-phase reads, `Glm52PhaseReads`): `reconstructed` rebuilds per-head
32
+ * no-PE keys and values through `kv_b_proj` for a window; `absorbed` folds the
33
+ * key and value projections around attention for one query per sequence. */
34
+ export type Glm52MlaPath = "reconstructed" | "absorbed";
35
+
36
+ /** How one phase's MLA reads attend (`Glm52MlaPath`), fixed per phase when the
37
+ * graph builds its phase table. `window` is the path of the phase's own reads
38
+ * (every query of a sequence's window at once); `tokenSerial` is the path of the
39
+ * DSA loops that read a window one query at a time (a sparse verify window, a
40
+ * prompt past `index_topk`), each read being one query of one sequence. */
41
+ export interface Glm52PhaseReads {
42
+ readonly window: Glm52MlaPath;
43
+ readonly tokenSerial: Glm52MlaPath;
44
+ }
45
+
46
+ /** The phase reads of GLM-5.2's graph, as data: a phase whose forwards carry one
47
+ * token per sequence (decode, one known token, a one-token scoring row) reads
48
+ * absorbed; a phase of windows (a prompt chunk or tail, a verify window, several
49
+ * known tokens) reconstructs, and its token-serial DSA loops read absorbed. This
50
+ * is the token-count rule the block applied per call before, stated per phase. */
51
+ export const GLM52_PHASE_READS: Readonly<{ readonly oneToken: Glm52PhaseReads; readonly window: Glm52PhaseReads }> = Object.freeze({
52
+ oneToken: Object.freeze({ window: "absorbed", tokenSerial: "absorbed" }),
53
+ window: Object.freeze({ window: "reconstructed", tokenSerial: "absorbed" }),
54
+ });
55
+ export interface Glm52MlaBatchedSelection {
56
+ readonly rows: readonly (MlxArray | null)[];
57
+ }
58
+ export type Glm52MlaSelectedPositions =
59
+ | readonly number[]
60
+ | MlxArray
61
+ | Glm52MlaBatchedSelection
62
+ | null;
63
+
64
+ function isBatchedSelection(
65
+ value: Glm52MlaSelectedPositions,
66
+ ): value is Glm52MlaBatchedSelection {
67
+ return value !== null &&
68
+ !(value instanceof MlxArray) &&
69
+ !Array.isArray(value) &&
70
+ "rows" in value;
71
+ }
72
+
73
+ function sameShape(actual: readonly number[], expected: readonly number[]): boolean {
74
+ return actual.length === expected.length &&
75
+ actual.every((dimension, index) => dimension === expected[index]);
76
+ }
77
+
78
+ function validateShape(name: string, array: MlxArray, expected: readonly number[]): void {
79
+ if (!sameShape(array.shape, expected))
80
+ throw new Error(
81
+ `${name} shape ${JSON.stringify(array.shape)} != ${JSON.stringify(expected)}`,
82
+ );
83
+ }
84
+
85
+ function normalizedAxis(axis: number, rank: number): number {
86
+ const result = axis < 0 ? rank + axis : axis;
87
+ if (!Number.isSafeInteger(result) || result < 0 || result >= rank)
88
+ throw new Error(`axis ${axis} is invalid for rank ${rank}`);
89
+ return result;
90
+ }
91
+
92
+ /**
93
+ * Exact GLM partial RoPE layout.
94
+ *
95
+ * The rotary prefix enters as pair-interleaved `[a0,b0,a1,b1,...]` and leaves
96
+ * as split real/imaginary halves `[r0,r1,...,i0,i1,...]`. A non-rotary tail is
97
+ * preserved. Positions advance along `tokenAxis`.
98
+ */
99
+ export function partialInterleavedRopeMlx(
100
+ input: MlxArray,
101
+ positionOffset: number | readonly number[],
102
+ rotaryDimensions: number,
103
+ theta: number,
104
+ tokenAxis = 1,
105
+ ): MlxArray {
106
+ const shape = input.shape;
107
+ if (shape.length < 2)
108
+ throw new Error("partial interleaved RoPE requires rank >= 2");
109
+ const axis = normalizedAxis(tokenAxis, shape.length);
110
+ if (axis === shape.length - 1)
111
+ throw new Error("RoPE token axis cannot be the feature axis");
112
+ const positionOffsets = typeof positionOffset === "number"
113
+ ? null
114
+ : [...positionOffset];
115
+ if (positionOffsets) {
116
+ if (axis === 0)
117
+ throw new Error("per-row RoPE offsets require a separate token axis");
118
+ if (positionOffsets.length !== shape[0])
119
+ throw new Error(
120
+ `RoPE row offsets ${positionOffsets.length} != batch ${shape[0]}`,
121
+ );
122
+ for (const offset of positionOffsets) {
123
+ if (!Number.isSafeInteger(offset) || offset < 0)
124
+ throw new Error("RoPE position offset must be a non-negative integer");
125
+ }
126
+ } else if (
127
+ !Number.isSafeInteger(positionOffset) || (positionOffset as number) < 0
128
+ ) {
129
+ throw new Error("RoPE position offset must be a non-negative integer");
130
+ }
131
+ if (
132
+ !Number.isSafeInteger(rotaryDimensions) ||
133
+ rotaryDimensions <= 0 ||
134
+ (rotaryDimensions & 1) !== 0 ||
135
+ rotaryDimensions > shape.at(-1)!
136
+ ) {
137
+ throw new Error("RoPE dimensions must be positive, even, and fit the feature axis");
138
+ }
139
+ if (!Number.isFinite(theta) || theta <= 0)
140
+ throw new Error("RoPE theta must be positive");
141
+
142
+ const tokens = shape[axis]!;
143
+ const batches = positionOffsets ? shape[0]! : 1;
144
+ const half = rotaryDimensions / 2;
145
+ const angles = new Float32Array(batches * tokens * half);
146
+ for (let batch = 0; batch < batches; batch++) {
147
+ const offset = positionOffsets?.[batch] ?? positionOffset as number;
148
+ for (let token = 0; token < tokens; token++) {
149
+ const position = offset + token;
150
+ for (let pair = 0; pair < half; pair++) {
151
+ const exponent = Math.fround(Math.fround(-2 * pair) / rotaryDimensions);
152
+ const inverseFrequency = Math.fround(
153
+ Math.pow(Math.fround(theta), exponent),
154
+ );
155
+ angles[(batch * tokens + token) * half + pair] = Math.fround(
156
+ position * inverseFrequency,
157
+ );
158
+ }
159
+ }
160
+ }
161
+
162
+ const frequencyShape = new Array(shape.length).fill(1);
163
+ if (positionOffsets) frequencyShape[0] = batches;
164
+ frequencyShape[axis] = tokens;
165
+ frequencyShape[shape.length - 1] = half;
166
+ const angleArray = MlxArray.fromFloat32(angles, frequencyShape);
167
+ const cosine = ops.cos(angleArray);
168
+ const sine = ops.sin(angleArray);
169
+ angleArray.dispose();
170
+
171
+ const prefixStart = new Array(shape.length).fill(0);
172
+ const prefixStop = [...shape];
173
+ prefixStop[shape.length - 1] = rotaryDimensions;
174
+ const prefix = input.slice(prefixStart, prefixStop);
175
+ const pairShape = [...shape.slice(0, -1), half, 2];
176
+ const pairs = ops.reshape(prefix, pairShape);
177
+ prefix.dispose();
178
+
179
+ const evenStart = new Array(pairShape.length).fill(0);
180
+ const evenStop = [...pairShape];
181
+ evenStop[pairShape.length - 1] = 1;
182
+ const oddStart = [...evenStart];
183
+ const oddStop = [...pairShape];
184
+ oddStart[pairShape.length - 1] = 1;
185
+ oddStop[pairShape.length - 1] = 2;
186
+ const evenColumn = pairs.slice(evenStart, evenStop);
187
+ const oddColumn = pairs.slice(oddStart, oddStop);
188
+ pairs.dispose();
189
+ const halfShape = [...shape.slice(0, -1), half];
190
+ const even = ops.reshape(evenColumn, halfShape);
191
+ const odd = ops.reshape(oddColumn, halfShape);
192
+ evenColumn.dispose();
193
+ oddColumn.dispose();
194
+
195
+ const evenCos = ops.mul(even, cosine);
196
+ const oddSin = ops.mul(odd, sine);
197
+ const real = ops.sub(evenCos, oddSin);
198
+ const oddCos = ops.mul(odd, cosine);
199
+ const evenSin = ops.mul(even, sine);
200
+ const imaginary = ops.add(oddCos, evenSin);
201
+ const rotated = ops.concatAxis([real, imaginary], shape.length - 1);
202
+
203
+ for (const temporary of [
204
+ cosine,
205
+ sine,
206
+ even,
207
+ odd,
208
+ evenCos,
209
+ oddSin,
210
+ real,
211
+ oddCos,
212
+ evenSin,
213
+ imaginary,
214
+ ]) {
215
+ temporary.dispose();
216
+ }
217
+
218
+ if (rotaryDimensions === shape.at(-1)) return rotated;
219
+ const tailStart = new Array(shape.length).fill(0);
220
+ tailStart[shape.length - 1] = rotaryDimensions;
221
+ const tail = input.slice(tailStart, shape);
222
+ const output = ops.concatAxis([rotated, tail], shape.length - 1);
223
+ rotated.dispose();
224
+ tail.dispose();
225
+ return output;
226
+ }
227
+
228
+ export function rmsNormF32Mlx(
229
+ input: MlxArray,
230
+ weight: MlxArray,
231
+ epsilon: number,
232
+ ): MlxArray {
233
+ const width = input.shape.at(-1)!;
234
+ validateShape("RMSNorm weight", weight, [width]);
235
+ if (!Number.isFinite(epsilon) || epsilon < 0)
236
+ throw new Error("RMSNorm epsilon must be non-negative");
237
+
238
+ const inputF32 = input.dtype === Dtype.float32
239
+ ? input
240
+ : input.astype(Dtype.float32);
241
+ const weightF32 = weight.dtype === Dtype.float32
242
+ ? weight
243
+ : weight.astype(Dtype.float32);
244
+ const square = ops.square(inputF32);
245
+ const mean = ops.meanAxis(square, -1, true);
246
+ const epsilonArray = ops.scalarLike(epsilon, mean);
247
+ const shifted = ops.add(mean, epsilonArray);
248
+ const inverseRoot = ops.rsqrt(shifted);
249
+ const normalized = ops.mul(inputF32, inverseRoot);
250
+ const output = ops.mul(normalized, weightF32);
251
+ for (const temporary of [
252
+ square,
253
+ mean,
254
+ epsilonArray,
255
+ shifted,
256
+ inverseRoot,
257
+ normalized,
258
+ ]) {
259
+ temporary.dispose();
260
+ }
261
+ if (inputF32 !== input) inputF32.dispose();
262
+ if (weightF32 !== weight) weightF32.dispose();
263
+ return output;
264
+ }
265
+
266
+ function disposeProjection(projection: Glm52MlaProjection): void {
267
+ projection.qNope.dispose();
268
+ projection.qRope.dispose();
269
+ projection.latent.dispose();
270
+ projection.rope.dispose();
271
+ }
272
+
273
+ function disposeState(state: LatentAttentionState): void {
274
+ state.latent.dispose();
275
+ state.rope.dispose();
276
+ state.indexKeys?.dispose();
277
+ }
278
+
279
+ /** How an MLA block reads its compressed cache, fixed when the block is built:
280
+ * the append of its projected state and the state read back. */
281
+ export interface LatentAttentionReads {
282
+ append(cache: LatentAttentionCache, latent: MlxArray, rope: MlxArray, indexKeys: MlxArray | null): LatentAttentionState;
283
+ }
284
+
285
+ /** The cache's own read (`LatentAttentionCache.appendLatent`): the default. */
286
+ export const latentCacheRead: LatentAttentionReads = Object.freeze({
287
+ append: (cache: LatentAttentionCache, latent: MlxArray, rope: MlxArray, indexKeys: MlxArray | null) =>
288
+ cache.appendLatent(latent, rope, indexKeys),
289
+ });
290
+
291
+ /**
292
+ * MLX correctness implementation of GLM-5.2 MLA.
293
+ *
294
+ * This is deliberately independent from the future fused MLA kernel. It loads
295
+ * the existing Colibri artifact directly through ColibriGlm52Weights and pins
296
+ * the mathematical contract used by that optimized path. `reads` is how the
297
+ * block reads its compressed cache (`LatentAttentionCache`), fixed when the
298
+ * block is built.
299
+ */
300
+ export class Glm52Mla {
301
+ readonly config: Glm52Config;
302
+ readonly weights: Glm52MlaWeightSource;
303
+ readonly prefix: string;
304
+
305
+ constructor(
306
+ config: Glm52Config,
307
+ weights: ColibriGlm52Weights | Glm52MlaWeightSource,
308
+ layerIndex: number,
309
+ readonly reads: LatentAttentionReads = latentCacheRead,
310
+ ) {
311
+ if (!Number.isSafeInteger(layerIndex) || layerIndex < 0 ||
312
+ layerIndex > config.numHiddenLayers) {
313
+ throw new Error(`invalid GLM-5.2 layer index ${layerIndex}`);
314
+ }
315
+ if (!config.ropeInterleave)
316
+ throw new Error("GLM-5.2 MLA requires pair-interleaved RoPE");
317
+ positiveInteger(config.numAttentionHeads, "attention heads");
318
+ positiveInteger(config.qLoraRank, "q LoRA rank");
319
+ positiveInteger(config.kvLoraRank, "kv LoRA rank");
320
+ positiveInteger(config.qkNopeHeadDim, "no-PE head dimension");
321
+ positiveInteger(config.qkRopeHeadDim, "RoPE head dimension");
322
+ positiveInteger(config.vHeadDim, "value head dimension");
323
+ this.config = config;
324
+ this.weights = weights;
325
+ this.prefix = `model.layers.${layerIndex}.self_attn`;
326
+ }
327
+
328
+ /**
329
+ * Project a token block into query state and compressed cache state.
330
+ * Returned arrays are caller-owned.
331
+ */
332
+ project(
333
+ input: MlxArray,
334
+ positionOffset: number | readonly number[],
335
+ ): Glm52MlaProjection {
336
+ const shape = input.shape;
337
+ if (shape.length !== 3)
338
+ throw new Error(`GLM-5.2 MLA input must have rank 3 (got ${shape.length})`);
339
+ const [batch, tokens, hidden] = shape as [number, number, number];
340
+ positiveInteger(batch, "MLA batch");
341
+ positiveInteger(tokens, "MLA tokens");
342
+ if (hidden !== this.config.hiddenSize)
343
+ throw new Error(`MLA hidden width ${hidden} != ${this.config.hiddenSize}`);
344
+
345
+ const heads = this.config.numAttentionHeads;
346
+ const qA = this.weights.linear(
347
+ input,
348
+ `${this.prefix}.q_a_proj.weight`,
349
+ this.config.qLoraRank,
350
+ this.config.hiddenSize,
351
+ );
352
+ const qNorm = rmsNormF32Mlx(
353
+ qA,
354
+ this.weights.tensor(`${this.prefix}.q_a_layernorm.weight`),
355
+ this.config.rmsNormEps,
356
+ );
357
+ qA.dispose();
358
+ const qFlat = this.weights.linear(
359
+ qNorm,
360
+ `${this.prefix}.q_b_proj.weight`,
361
+ heads * this.config.qkHeadDim,
362
+ this.config.qLoraRank,
363
+ );
364
+ qNorm.dispose();
365
+ const q = ops.reshape(
366
+ qFlat,
367
+ [batch, tokens, heads, this.config.qkHeadDim],
368
+ );
369
+ qFlat.dispose();
370
+ const qNope = q.slice(
371
+ [0, 0, 0, 0],
372
+ [batch, tokens, heads, this.config.qkNopeHeadDim],
373
+ );
374
+ const qRopeRaw = q.slice(
375
+ [0, 0, 0, this.config.qkNopeHeadDim],
376
+ [batch, tokens, heads, this.config.qkHeadDim],
377
+ );
378
+ q.dispose();
379
+ const qRope = partialInterleavedRopeMlx(
380
+ qRopeRaw,
381
+ positionOffset,
382
+ this.config.qkRopeHeadDim,
383
+ this.config.ropeTheta,
384
+ 1,
385
+ );
386
+ qRopeRaw.dispose();
387
+
388
+ const compressed = this.weights.linear(
389
+ input,
390
+ `${this.prefix}.kv_a_proj_with_mqa.weight`,
391
+ this.config.kvLoraRank + this.config.qkRopeHeadDim,
392
+ this.config.hiddenSize,
393
+ );
394
+ const latentRaw = compressed.slice(
395
+ [0, 0, 0],
396
+ [batch, tokens, this.config.kvLoraRank],
397
+ );
398
+ const ropeRaw = compressed.slice(
399
+ [0, 0, this.config.kvLoraRank],
400
+ [batch, tokens, this.config.kvLoraRank + this.config.qkRopeHeadDim],
401
+ );
402
+ compressed.dispose();
403
+ const latent = rmsNormF32Mlx(
404
+ latentRaw,
405
+ this.weights.tensor(`${this.prefix}.kv_a_layernorm.weight`),
406
+ this.config.rmsNormEps,
407
+ );
408
+ latentRaw.dispose();
409
+ const rope = partialInterleavedRopeMlx(
410
+ ropeRaw,
411
+ positionOffset,
412
+ this.config.qkRopeHeadDim,
413
+ this.config.ropeTheta,
414
+ 1,
415
+ );
416
+ ropeRaw.dispose();
417
+ return { qNope, qRope, latent, rope };
418
+ }
419
+
420
+ /**
421
+ * Append the compressed state and execute attention.
422
+ *
423
+ * `path` is the phase's read: the caller names it, the block never derives it
424
+ * from its input (`absorbed` takes one token per sequence and refuses more).
425
+ * DSA state, when present, is cached atomically with the MLA state but DSA
426
+ * selection itself is owned by the indexer primitive.
427
+ */
428
+ forward(
429
+ input: MlxArray,
430
+ cache: LatentAttentionCache,
431
+ dsa: MlxArray | null,
432
+ path: Glm52MlaPath,
433
+ selectedPositions: Glm52MlaSelectedPositions = null,
434
+ ): MlxArray {
435
+ if (cache.kvLoraRank !== this.config.kvLoraRank ||
436
+ cache.ropeHeadDim !== this.config.qkRopeHeadDim) {
437
+ throw new Error("MLA cache geometry does not match the model");
438
+ }
439
+ const tokens = input.shape[1]!;
440
+ if (path === "absorbed" && tokens !== 1)
441
+ throw new Error("absorbed MLA is valid only for single-token decode");
442
+ const batch = input.shape[0]!;
443
+ const positionOffset = batch > 1
444
+ ? (cache.rowOffsets.length
445
+ ? cache.rowOffsets
446
+ : new Array(batch).fill(0) as number[])
447
+ : cache.offset;
448
+ const projection = this.project(input, positionOffset);
449
+ const state = this.reads.append(cache, projection.latent, projection.rope, dsa);
450
+ if (selectedPositions !== null && path !== "absorbed")
451
+ throw new Error("DSA-selected MLA requires single-token absorbed decode");
452
+ try {
453
+ if (path === "absorbed") {
454
+ if (batch > 1) {
455
+ const selections = isBatchedSelection(selectedPositions)
456
+ ? selectedPositions.rows
457
+ : null;
458
+ if (selectedPositions !== null && selections === null)
459
+ throw new Error("batched MLA requires per-row DSA selections");
460
+ if (selections && selections.length !== batch)
461
+ throw new Error(
462
+ `batched MLA selections ${selections.length} != batch ${batch}`,
463
+ );
464
+ const rows: MlxArray[] = [];
465
+ try {
466
+ for (let row = 0; row < batch; row++) {
467
+ const start = cache.leftPad[row]!;
468
+ const qNope = projection.qNope.slice(
469
+ [row, 0, 0, 0],
470
+ [row + 1, 1, projection.qNope.shape[2]!, projection.qNope.shape[3]!],
471
+ );
472
+ const qRope = projection.qRope.slice(
473
+ [row, 0, 0, 0],
474
+ [row + 1, 1, projection.qRope.shape[2]!, projection.qRope.shape[3]!],
475
+ );
476
+ const latent = state.latent.slice(
477
+ [row, start, 0],
478
+ [row + 1, state.latent.shape[1]!, state.latent.shape[2]!],
479
+ );
480
+ const ropeState = state.rope.slice(
481
+ [row, start, 0],
482
+ [row + 1, state.rope.shape[1]!, state.rope.shape[2]!],
483
+ );
484
+ try {
485
+ rows.push(this.attendAbsorbed(
486
+ { qNope, qRope },
487
+ { latent, rope: ropeState },
488
+ selections?.[row] ?? null,
489
+ ));
490
+ } finally {
491
+ qNope.dispose();
492
+ qRope.dispose();
493
+ latent.dispose();
494
+ ropeState.dispose();
495
+ }
496
+ }
497
+ return ops.concatAxis(rows, 0);
498
+ } finally {
499
+ for (const row of rows) row.dispose();
500
+ }
501
+ }
502
+ return this.attendAbsorbed(projection, state, selectedPositions);
503
+ }
504
+ return this.attendReconstructed(projection, state, cache.leftPad);
505
+ } finally {
506
+ disposeState(state);
507
+ disposeProjection(projection);
508
+ }
509
+ }
510
+
511
+ /** Reconstruct no-PE keys and values for the complete cached prefix. */
512
+ attendReconstructed(
513
+ projection: Pick<Glm52MlaProjection, "qNope" | "qRope">,
514
+ state: Pick<LatentAttentionState, "latent" | "rope">,
515
+ leftPadding: readonly number[] = [],
516
+ ): MlxArray {
517
+ const [batch, tokens] = projection.qNope.shape;
518
+ const keys = state.latent.shape[1]!;
519
+ const heads = this.config.numAttentionHeads;
520
+ validateShape("MLA q(no-PE)", projection.qNope, [
521
+ batch!,
522
+ tokens!,
523
+ heads,
524
+ this.config.qkNopeHeadDim,
525
+ ]);
526
+ validateShape("MLA q(RoPE)", projection.qRope, [
527
+ batch!,
528
+ tokens!,
529
+ heads,
530
+ this.config.qkRopeHeadDim,
531
+ ]);
532
+ validateShape("MLA latent state", state.latent, [
533
+ batch!,
534
+ keys,
535
+ this.config.kvLoraRank,
536
+ ]);
537
+ validateShape("MLA RoPE state", state.rope, [
538
+ batch!,
539
+ keys,
540
+ this.config.qkRopeHeadDim,
541
+ ]);
542
+ if (keys < tokens!)
543
+ throw new Error("MLA state cannot be shorter than the query block");
544
+
545
+ const kvWidth = this.config.qkNopeHeadDim + this.config.vHeadDim;
546
+ const reconstructedFlat = this.weights.linear(
547
+ state.latent,
548
+ `${this.prefix}.kv_b_proj.weight`,
549
+ heads * kvWidth,
550
+ this.config.kvLoraRank,
551
+ );
552
+ const reconstructed = ops.reshape(
553
+ reconstructedFlat,
554
+ [batch!, keys, heads, kvWidth],
555
+ );
556
+ reconstructedFlat.dispose();
557
+ const keyNope = reconstructed.slice(
558
+ [0, 0, 0, 0],
559
+ [batch!, keys, heads, this.config.qkNopeHeadDim],
560
+ );
561
+ const values = reconstructed.slice(
562
+ [0, 0, 0, this.config.qkNopeHeadDim],
563
+ [batch!, keys, heads, kvWidth],
564
+ );
565
+ reconstructed.dispose();
566
+
567
+ const ropeOneHead = ops.expandDims(state.rope, 2);
568
+ const ropeHeads = heads === 1
569
+ ? ropeOneHead
570
+ : ops.concatAxis(
571
+ Array.from({ length: heads }, () => ropeOneHead),
572
+ 2,
573
+ );
574
+ const query = ops.concatAxis(
575
+ [projection.qNope, projection.qRope],
576
+ 3,
577
+ );
578
+ const key = ops.concatAxis([keyNope, ropeHeads], 3);
579
+ const queryHeads = ops.transposeAxes(query, [0, 2, 1, 3]);
580
+ const keyHeads = ops.transposeAxes(key, [0, 2, 3, 1]);
581
+ const valueHeads = ops.transposeAxes(values, [0, 2, 1, 3]);
582
+ const rawScores = ops.matmul(queryHeads, keyHeads);
583
+ const scores = ops.mulScalar(
584
+ rawScores,
585
+ 1 / Math.sqrt(this.config.qkHeadDim),
586
+ );
587
+ rawScores.dispose();
588
+
589
+ const prefixTokens = keys - tokens!;
590
+ let maskedScores = scores;
591
+ const padded = leftPadding.some(value => value > 0);
592
+ if (tokens! > 1 || padded) {
593
+ const maskRows = padded ? batch! : 1;
594
+ const maskValues = new Float32Array(maskRows * tokens! * keys);
595
+ for (let row = 0; row < maskRows; row++) for (let queryToken = 0; queryToken < tokens!; queryToken++) {
596
+ const lastVisible = prefixTokens + queryToken;
597
+ const start = (row * tokens! + queryToken) * keys;
598
+ for (let keyToken = 0; keyToken < (leftPadding[row] ?? 0); keyToken++)
599
+ maskValues[start + keyToken] = -Infinity;
600
+ for (let keyToken = lastVisible + 1; keyToken < keys; keyToken++)
601
+ maskValues[start + keyToken] = -Infinity;
602
+ }
603
+ const mask = MlxArray.fromFloat32(maskValues, [maskRows, 1, tokens!, keys]);
604
+ maskedScores = ops.add(scores, mask);
605
+ mask.dispose();
606
+ scores.dispose();
607
+ }
608
+ const probabilities = ops.softmaxAxis(maskedScores, -1, true);
609
+ const contextHeads = ops.matmul(probabilities, valueHeads);
610
+ const contextTokens = ops.transposeAxes(contextHeads, [0, 2, 1, 3]);
611
+ const context = ops.reshape(
612
+ contextTokens,
613
+ [batch!, tokens!, heads * this.config.vHeadDim],
614
+ );
615
+ const output = this.weights.linear(
616
+ context,
617
+ `${this.prefix}.o_proj.weight`,
618
+ this.config.hiddenSize,
619
+ heads * this.config.vHeadDim,
620
+ );
621
+
622
+ for (const temporary of [
623
+ keyNope,
624
+ values,
625
+ ropeOneHead,
626
+ ...(ropeHeads === ropeOneHead ? [] : [ropeHeads]),
627
+ query,
628
+ key,
629
+ queryHeads,
630
+ keyHeads,
631
+ valueHeads,
632
+ maskedScores,
633
+ probabilities,
634
+ contextHeads,
635
+ contextTokens,
636
+ context,
637
+ ]) {
638
+ temporary.dispose();
639
+ }
640
+ return output;
641
+ }
642
+
643
+ /**
644
+ * Weight-absorbed single-token decode.
645
+ *
646
+ * For each head, `q_nope @ Wk` attends directly over cached latent vectors;
647
+ * the weighted latent is then projected through `Wv`. Only the small
648
+ * decoupled RoPE term is scored separately.
649
+ */
650
+ attendAbsorbed(
651
+ projection: Pick<Glm52MlaProjection, "qNope" | "qRope">,
652
+ state: Pick<LatentAttentionState, "latent" | "rope">,
653
+ selectedPositions: Glm52MlaSelectedPositions = null,
654
+ ): MlxArray {
655
+ const [batch, tokens] = projection.qNope.shape;
656
+ if (tokens !== 1)
657
+ throw new Error("absorbed MLA is valid only for single-token decode");
658
+ const keys = state.latent.shape[1]!;
659
+ const heads = this.config.numAttentionHeads;
660
+ validateShape("MLA q(no-PE)", projection.qNope, [
661
+ batch!,
662
+ 1,
663
+ heads,
664
+ this.config.qkNopeHeadDim,
665
+ ]);
666
+ validateShape("MLA q(RoPE)", projection.qRope, [
667
+ batch!,
668
+ 1,
669
+ heads,
670
+ this.config.qkRopeHeadDim,
671
+ ]);
672
+ validateShape("MLA latent state", state.latent, [
673
+ batch!,
674
+ keys,
675
+ this.config.kvLoraRank,
676
+ ]);
677
+ validateShape("MLA RoPE state", state.rope, [
678
+ batch!,
679
+ keys,
680
+ this.config.qkRopeHeadDim,
681
+ ]);
682
+
683
+ let activeLatent = state.latent;
684
+ let activeRope = state.rope;
685
+ if (selectedPositions !== null) {
686
+ if (isBatchedSelection(selectedPositions)) {
687
+ throw new Error("per-row DSA selection requires batched MLA");
688
+ }
689
+ let indices: MlxArray;
690
+ let ownsIndices = false;
691
+ if (selectedPositions instanceof MlxArray) {
692
+ if (selectedPositions.ndim !== 1 || selectedPositions.size === 0)
693
+ throw new Error("DSA device selection must be a non-empty vector");
694
+ if (selectedPositions.dtype !== Dtype.uint32 &&
695
+ selectedPositions.dtype !== Dtype.int32) {
696
+ throw new Error("DSA device selection must use uint32 or int32 indices");
697
+ }
698
+ indices = selectedPositions;
699
+ } else {
700
+ if (selectedPositions.length === 0)
701
+ throw new Error("DSA selection cannot be empty");
702
+ for (const position of selectedPositions) {
703
+ if (!Number.isSafeInteger(position) || position < 0 || position >= keys)
704
+ throw new Error(`DSA position ${position} is outside cached prefix ${keys}`);
705
+ }
706
+ indices = ops.fromInt32(
707
+ [...selectedPositions],
708
+ [selectedPositions.length],
709
+ );
710
+ ownsIndices = true;
711
+ }
712
+ activeLatent = ops.takeAxis(state.latent, indices, 1);
713
+ activeRope = ops.takeAxis(state.rope, indices, 1);
714
+ if (ownsIndices) indices.dispose();
715
+ }
716
+
717
+ const kvWidth = this.config.qkNopeHeadDim + this.config.vHeadDim;
718
+ const kvWeights = this.weights.dequantized(
719
+ `${this.prefix}.kv_b_proj.weight`,
720
+ heads * kvWidth,
721
+ this.config.kvLoraRank,
722
+ );
723
+ const latentTranspose = ops.transposeAxes(activeLatent, [0, 2, 1]);
724
+ const ropeTranspose = ops.transposeAxes(activeRope, [0, 2, 1]);
725
+ const headOutputs: MlxArray[] = [];
726
+ try {
727
+ for (let head = 0; head < heads; head++) {
728
+ const row = head * kvWidth;
729
+ const qNopeHeadView = projection.qNope.slice(
730
+ [0, 0, head, 0],
731
+ [batch!, 1, head + 1, this.config.qkNopeHeadDim],
732
+ );
733
+ const qNopeHead = ops.reshape(
734
+ qNopeHeadView,
735
+ [batch!, 1, this.config.qkNopeHeadDim],
736
+ );
737
+ qNopeHeadView.dispose();
738
+ const qRopeHeadView = projection.qRope.slice(
739
+ [0, 0, head, 0],
740
+ [batch!, 1, head + 1, this.config.qkRopeHeadDim],
741
+ );
742
+ const qRopeHead = ops.reshape(
743
+ qRopeHeadView,
744
+ [batch!, 1, this.config.qkRopeHeadDim],
745
+ );
746
+ qRopeHeadView.dispose();
747
+
748
+ // Wk is [qk_nope, latent], so q_nope @ Wk produces latent-space q.
749
+ const keyWeights = kvWeights.slice(
750
+ [row, 0],
751
+ [row + this.config.qkNopeHeadDim, this.config.kvLoraRank],
752
+ );
753
+ const latentQuery = ops.matmul(qNopeHead, keyWeights);
754
+ const contentScores = ops.matmul(latentQuery, latentTranspose);
755
+ const ropeScores = ops.matmul(qRopeHead, ropeTranspose);
756
+ const combinedScores = ops.add(contentScores, ropeScores);
757
+ const scaledScores = ops.mulScalar(
758
+ combinedScores,
759
+ 1 / Math.sqrt(this.config.qkHeadDim),
760
+ );
761
+ const probabilities = ops.softmaxAxis(scaledScores, -1, true);
762
+ const latentContext = ops.matmul(probabilities, activeLatent);
763
+
764
+ // Wv is [value, latent], so the latent context multiplies Wv^T.
765
+ const valueWeights = kvWeights.slice(
766
+ [row + this.config.qkNopeHeadDim, 0],
767
+ [row + kvWidth, this.config.kvLoraRank],
768
+ );
769
+ const valueWeightsTranspose = ops.transposeAxes(valueWeights, [1, 0]);
770
+ const headOutput = ops.matmul(latentContext, valueWeightsTranspose);
771
+ headOutputs.push(headOutput);
772
+ for (const temporary of [
773
+ qNopeHead,
774
+ qRopeHead,
775
+ keyWeights,
776
+ latentQuery,
777
+ contentScores,
778
+ ropeScores,
779
+ combinedScores,
780
+ scaledScores,
781
+ probabilities,
782
+ latentContext,
783
+ valueWeights,
784
+ valueWeightsTranspose,
785
+ ]) {
786
+ temporary.dispose();
787
+ }
788
+ }
789
+ const context = headOutputs.length === 1
790
+ ? headOutputs[0]!
791
+ : ops.concatAxis(headOutputs, 2);
792
+ const output = this.weights.linear(
793
+ context,
794
+ `${this.prefix}.o_proj.weight`,
795
+ this.config.hiddenSize,
796
+ heads * this.config.vHeadDim,
797
+ );
798
+ if (headOutputs.length > 1) context.dispose();
799
+ return output;
800
+ } finally {
801
+ for (const output of headOutputs) output.dispose();
802
+ kvWeights.dispose();
803
+ latentTranspose.dispose();
804
+ ropeTranspose.dispose();
805
+ if (activeLatent !== state.latent) activeLatent.dispose();
806
+ if (activeRope !== state.rope) activeRope.dispose();
807
+ }
808
+ }
809
+ }
810
+
811
+ import { Glm52MlaWeightSource } from "../../contracts/mlx/glm52-weights";
812
+ export { type Glm52MlaWeightSource } from "../../contracts/mlx/glm52-weights";