@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,755 @@
1
+ // The Gemma 4 decoder blocks (port target: mlx-lm's gemma4_text.py and the
2
+ // switch_layers.py SwitchGLU it uses): the attention block with the read it was
3
+ // built with, the dense and mixture-of-experts feed-forward pieces, the
4
+ // per-layer-input pieces (e2b/e4b), the KV-sharing pieces (a donor layer's read
5
+ // and the donor each sharer attends) and the decoder layer composing them. The
6
+ // generic graph (`Gemma4Model`, model.ts) and the written graphs
7
+ // (gemma4-e4b.ts, gemma4-12b.ts, gemma4-26b.ts) compose these blocks.
8
+ // Each block is built once from the weights and the config and reads its cache
9
+ // through the read its caller's phase names: no block inspects its input to
10
+ // choose a phase, builds a mask or chooses an attention kernel. The training
11
+ // forward, which fetches plain keys and values from training caches under masks
12
+ // it builds, stays with the generic graph that declares training (model.ts).
13
+
14
+ import { MlxArray } from "@mlx-bun/mlx/array";
15
+ import { Dtype } from "@mlx-bun/mlx/ffi";
16
+ import * as ops from "@mlx-bun/mlx/ops";
17
+ import type { ModelConfig } from "../../artifacts/config";
18
+ import type { Weights } from "../../artifacts/weights";
19
+ import type { AttentionCache, AttentionRead, BidirectionalAttentionCache, Cache } from "../../contracts/mlx/cache";
20
+ import { geglu, GegluMLP } from "../../layers/geglu";
21
+ import { disposing } from "../../layers/helpers";
22
+ import { RMSNorm } from "../../layers/normalization";
23
+ import { QuantizedEmbedding } from "../../layers/quantized-embedding";
24
+ import { QuantizedLinear, QuantizedLinearPlain } from "../../layers/quantized-linear";
25
+ import { QuantizedSwitchLinear } from "../../layers/quantized-switch-linear";
26
+ import { isCompiledTrace } from "../../runtime/compiled-trace";
27
+ import { isExpertTracing, recordRouting } from "../../runtime/expert-trace";
28
+ import { captureRopeOffsets } from "../../state/attention-read";
29
+ import type { ReadPhase } from "../graph";
30
+
31
+ /** The phase a Gemma 4 forward runs, named by its caller: one new position per
32
+ * row (`decode`), a causal window of positions (`window`: prefill chunks and
33
+ * tails, speculative verify windows), or the first window of a prompt whose
34
+ * image tokens attend each other in both directions (`bidirectional`: a
35
+ * borrowed bool [L] flagging them, for every row). Each names a read of the
36
+ * cache: `AttentionCache.appendDecode`, `appendWindow`,
37
+ * `BidirectionalAttentionCache.appendBidirectional`. Gemma 4 composes no
38
+ * committed append. */
39
+ export type Gemma4Phase = Exclude<ReadPhase, "committed"> | { readonly bidirectional: MlxArray };
40
+
41
+ /** How an attention block reads its cache, fixed when the block is built.
42
+ * `read` appends `k` and `v` (borrowed) for `phase` and returns the read the
43
+ * block, and the layers sharing its keys and values, attend. */
44
+ export interface Gemma4ReadStrategy {
45
+ read(cache: Cache, phase: Gemma4Phase, k: MlxArray, v: MlxArray): AttentionRead;
46
+ }
47
+
48
+ const appendFor: Readonly<Record<Exclude<ReadPhase, "committed">, (cache: Cache, k: MlxArray, v: MlxArray) => AttentionRead>> = {
49
+ decode: (cache, k, v) => (cache as AttentionCache).appendDecode(k, v),
50
+ window: (cache, k, v) => (cache as AttentionCache).appendWindow(k, v),
51
+ };
52
+
53
+ /** The cache's own read for the phase: the default strategy. */
54
+ export const gemma4CacheRead: Gemma4ReadStrategy = Object.freeze({
55
+ read: (cache: Cache, phase: Gemma4Phase, k: MlxArray, v: MlxArray): AttentionRead => typeof phase === "string"
56
+ ? appendFor[phase](cache, k, v)
57
+ : (cache as BidirectionalAttentionCache).appendBidirectional(k, v, phase.bidirectional),
58
+ });
59
+
60
+ /** A donor layer's read for one forward and the positions its queries take: the
61
+ * donor cache's offset before the append and, where the cache carries per-row
62
+ * positions (a batched layout, a compiled-decode trace), an owned copy of them,
63
+ * held because advancing the cache may release its own array. The layers
64
+ * sharing the donor's keys and values attend the same read with their own
65
+ * queries (`AttentionRead.attend` is repeatable); the forward that made it
66
+ * disposes it once, after its last sharer. */
67
+ export interface DonorRead {
68
+ readonly read: AttentionRead;
69
+ readonly offset: number;
70
+ readonly offsetArr?: MlxArray;
71
+ dispose(): void;
72
+ }
73
+
74
+ /** The donor each layer's attention reads (reference Gemma4TextModel's
75
+ * `previous_kvs`): itself for the first `numDonors` layers, which own caches;
76
+ * for each later layer, the last donor of its own attention type, whose read
77
+ * that layer attends in the same forward. */
78
+ export function kvDonors(layerTypes: readonly string[], numDonors: number): number[] {
79
+ const donors = layerTypes.map((_, i) => i);
80
+ const lastByType: Record<string, number> = {};
81
+ for (let i = 0; i < numDonors; i++) lastByType[layerTypes[i]!] = i;
82
+ for (let j = numDonors; j < layerTypes.length; j++) {
83
+ const donor = lastByType[layerTypes[j]!];
84
+ if (donor === undefined) throw new Error(`no donor layer of type ${layerTypes[j]}`);
85
+ donors[j] = donor;
86
+ }
87
+ return donors;
88
+ }
89
+
90
+ /** The attention block of one layer: the query projection and norm, and for a
91
+ * layer that owns a cache the key and value projections and norms, RoPE on the
92
+ * layer type's frequencies, and the read it was built with (`reads`, the cache's
93
+ * own by default); a KV-sharing layer attends its donor's read instead. */
94
+ export class Attention {
95
+ readonly isSliding: boolean;
96
+ readonly windowSize: number;
97
+ readonly useKEqV: boolean;
98
+ readonly hasKv: boolean;
99
+ readonly headDim: number;
100
+ readonly nHeads: number;
101
+ readonly nKvHeads: number;
102
+ readonly ropeBase: number | null;
103
+ readonly ropeFreqs: MlxArray | null;
104
+ readonly qProj: QuantizedLinear;
105
+ readonly kProj: QuantizedLinear | null;
106
+ readonly vProj: QuantizedLinear | null;
107
+ readonly oProj: QuantizedLinear;
108
+ readonly #qPlain: QuantizedLinearPlain;
109
+ readonly #kPlain: QuantizedLinearPlain | null;
110
+ readonly #vPlain: QuantizedLinearPlain | null;
111
+ readonly #oPlain: QuantizedLinearPlain;
112
+ readonly qNorm: RMSNorm;
113
+ readonly kNorm: RMSNorm | null;
114
+ readonly vNorm: RMSNorm | null;
115
+
116
+ constructor(
117
+ weights: Weights, config: ModelConfig, prefix: string, layerType: string,
118
+ hasKv = true,
119
+ /** How a donor layer reads its cache (the cache's own read). */
120
+ readonly reads: Gemma4ReadStrategy = gemma4CacheRead,
121
+ kEqV?: boolean,
122
+ ) {
123
+ const t = config.text;
124
+ this.hasKv = hasKv;
125
+ this.isSliding = layerType === "sliding_attention";
126
+ this.windowSize = t.slidingWindow;
127
+ this.headDim = this.isSliding ? t.headDim : t.globalHeadDim;
128
+ this.nHeads = t.numAttentionHeads;
129
+ this.useKEqV = kEqV ?? (t.attentionKEqV && !this.isSliding);
130
+ this.nKvHeads = this.useKEqV ? t.numGlobalKeyValueHeads : t.numKeyValueHeads;
131
+
132
+ const rp = t.ropeParameters[this.isSliding ? "sliding_attention" : "full_attention"]!;
133
+ if (rp.ropeType === "default") {
134
+ this.ropeBase = rp.ropeTheta;
135
+ this.ropeFreqs = null;
136
+ } else if (rp.ropeType === "proportional") {
137
+ // Port of rope_utils.ProportionalRoPE.__init__. The rotated freqs
138
+ // MUST be computed on-device in f32 (arange/dims, then base**x)
139
+ // exactly like the reference — computing them host-side in f64 and
140
+ // rounding to f32 lands 17/64 of them 1 ulp off (f64 pow ≠ f32
141
+ // powf), a latent knife-edge that tiled values exposed
142
+ // as a 1-ulp q-rope divergence at layer 11 (porting rule:
143
+ // replicate the helper's IMPLEMENTATION, not its formula).
144
+ const rotated = Math.floor(this.headDim * rp.partialRotaryFactor);
145
+ const n = this.headDim / 2;
146
+ const exponentsRaw = ops.arange(0, rotated, 2, Dtype.float32);
147
+ const dims = ops.scalarLike(this.headDim, exponentsRaw);
148
+ const exponents = ops.div(exponentsRaw, dims);
149
+ const base = ops.scalarLike(rp.ropeTheta, exponents);
150
+ let rotFreqs = ops.pow(base, exponents);
151
+ if (rp.factor !== 1.0) {
152
+ const f = ops.scalarLike(rp.factor, rotFreqs);
153
+ rotFreqs = disposing(rotFreqs, ops.mul(f, rotFreqs));
154
+ f.dispose();
155
+ }
156
+ this.ropeBase = null;
157
+ const tailLen = n - rotated / 2;
158
+ if (tailLen > 0) {
159
+ const tail = MlxArray.fromFloat32(
160
+ new Float32Array(tailLen).fill(Infinity), [tailLen],
161
+ );
162
+ this.ropeFreqs = ops.concatAxis([rotFreqs, tail], 0);
163
+ tail.dispose();
164
+ } else {
165
+ this.ropeFreqs = rotFreqs;
166
+ }
167
+ for (const a of [exponentsRaw, dims, exponents, base])
168
+ a.dispose();
169
+ if (this.ropeFreqs !== rotFreqs) rotFreqs.dispose();
170
+ } else {
171
+ throw new Error(`unsupported rope_type ${rp.ropeType}`);
172
+ }
173
+
174
+ this.qProj = QuantizedLinear.load(weights, `${prefix}.q_proj`, config);
175
+ this.kProj = hasKv ? QuantizedLinear.load(weights, `${prefix}.k_proj`, config) : null;
176
+ this.vProj = hasKv && !this.useKEqV
177
+ ? QuantizedLinear.load(weights, `${prefix}.v_proj`, config) : null;
178
+ this.oProj = QuantizedLinear.load(weights, `${prefix}.o_proj`, config);
179
+ this.#qPlain = new QuantizedLinearPlain(this.qProj);
180
+ this.#kPlain = this.kProj ? new QuantizedLinearPlain(this.kProj) : null;
181
+ this.#vPlain = this.vProj ? new QuantizedLinearPlain(this.vProj) : null;
182
+ this.#oPlain = new QuantizedLinearPlain(this.oProj);
183
+ this.qNorm = new RMSNorm(weights.tensor(`${prefix}.q_norm.weight`), t.rmsNormEps);
184
+ this.kNorm = hasKv
185
+ ? new RMSNorm(weights.tensor(`${prefix}.k_norm.weight`), t.rmsNormEps) : null;
186
+ this.vNorm = hasKv ? new RMSNorm(null, t.rmsNormEps) : null;
187
+ }
188
+
189
+ /** A number offset takes the static fast::rope; an array offset (set by
190
+ * compiled-decode trace adapters) takes the dynamic variant — same
191
+ * kernel, offset read from the array (bit-exactness asserted in
192
+ * `02d723a:tests/unit/compile.test.ts`). */
193
+ rope(x: MlxArray, offset: number | MlxArray): MlxArray {
194
+ return typeof offset === "number"
195
+ ? ops.rope(x, this.headDim, this.ropeBase, offset, this.ropeFreqs)
196
+ : ops.ropeDynamic(x, this.headDim, this.ropeBase, offset, this.ropeFreqs);
197
+ }
198
+
199
+ /** `x` [B, L, hidden] (borrowed) to an owned [B, L, hidden]. A donor layer
200
+ * appends its keys and values to `cache` through the read `phase` names and
201
+ * returns that read (`donor`, owned by the caller, who keeps it for the
202
+ * layers that share it); a KV-sharing layer attends `donor`, the read its
203
+ * donor layer made in this forward, and returns null. */
204
+ forward(
205
+ x: MlxArray, cache: Cache | null, phase: Gemma4Phase, donor: DonorRead | null,
206
+ ): { out: MlxArray; donor: DonorRead | null } {
207
+ const q = this.queries(x);
208
+ const own = this.hasKv ? this.#append(x, cache!, phase) : null;
209
+ return this.attendQueries(q, own ?? donor!, own, x.shape[0]!, x.shape[1]!);
210
+ }
211
+
212
+ forwardSeparate(x: MlxArray, cache: Cache, phase: Gemma4Phase): { out: MlxArray; donor: DonorRead } {
213
+ const q = this.queries(x);
214
+ const [B, L] = x.shape;
215
+ let k = this.#kPlain!.forward(x);
216
+ k = disposing(k, ops.reshape(k, [B!, L!, this.nKvHeads, this.headDim]));
217
+ let v = this.#vPlain!.forward(x);
218
+ v = disposing(v, ops.reshape(v, [B!, L!, this.nKvHeads, this.headDim]));
219
+ const source = this.appendValues(k, v, cache, phase);
220
+ return this.attendQueries(q, source, source, B!, L!) as { out: MlxArray; donor: DonorRead };
221
+ }
222
+
223
+ forwardKEqV(x: MlxArray, cache: Cache, phase: Gemma4Phase): { out: MlxArray; donor: DonorRead } {
224
+ const q = this.queries(x);
225
+ const [B, L] = x.shape;
226
+ let k = this.#kPlain!.forward(x);
227
+ k = disposing(k, ops.reshape(k, [B!, L!, this.nKvHeads, this.headDim]));
228
+ const source = this.appendValues(k, k, cache, phase);
229
+ return this.attendQueries(q, source, source, B!, L!) as { out: MlxArray; donor: DonorRead };
230
+ }
231
+
232
+ forwardShared(x: MlxArray, donor: DonorRead): { out: MlxArray; donor: null } {
233
+ return this.attendQueries(this.queries(x), donor, null, x.shape[0]!, x.shape[1]!) as { out: MlxArray; donor: null };
234
+ }
235
+
236
+ private queries(x: MlxArray): MlxArray {
237
+ const [B, L] = x.shape;
238
+ let q = this.#qPlain.forward(x);
239
+ q = disposing(q, ops.reshape(q, [B!, L!, this.nHeads, this.headDim]));
240
+ return disposing(q, this.qNorm.forward(q));
241
+ }
242
+
243
+ private attendQueries(q: MlxArray, source: DonorRead, own: DonorRead | null, B: number, L: number): { out: MlxArray; donor: DonorRead | null } {
244
+ let attn: MlxArray;
245
+ try {
246
+ q = disposing(q, ops.transposeAxes(q, [0, 2, 1, 3]));
247
+ q = disposing(q, this.rope(q, source.offsetArr ?? source.offset));
248
+ attn = source.read.attend(q, 1.0);
249
+ } catch (error) {
250
+ own?.dispose();
251
+ throw error;
252
+ } finally { q.dispose(); }
253
+ const attnT = ops.transposeAxes(attn, [0, 2, 1, 3]);
254
+ attn.dispose();
255
+ const merged = ops.reshape(attnT, [B, L, -1]);
256
+ attnT.dispose();
257
+ const out = this.#oPlain.forward(merged);
258
+ merged.dispose();
259
+ return { out, donor: own };
260
+ }
261
+
262
+ /** The donor half of `forward`: this layer's keys and values, normalized and
263
+ * positioned at the cache's offset, appended through the read `phase`
264
+ * names. The caller owns the returned read. */
265
+ #append(x: MlxArray, cache: Cache, phase: Gemma4Phase): DonorRead {
266
+ const [B, L] = x.shape as [number, number, number];
267
+ let k = this.kProj!.forward(x);
268
+ k = disposing(k, ops.reshape(k, [B, L, this.nKvHeads, this.headDim]));
269
+ let v: MlxArray;
270
+ if (this.vProj) {
271
+ v = this.vProj.forward(x);
272
+ v = disposing(v, ops.reshape(v, [B, L, this.nKvHeads, this.headDim]));
273
+ } else {
274
+ v = k; // attention_k_eq_v: shared projection; norms differ below
275
+ }
276
+
277
+ return this.appendValues(k, v, cache, phase);
278
+ }
279
+
280
+ private appendValues(k: MlxArray, v: MlxArray, cache: Cache, phase: Gemma4Phase): DonorRead {
281
+ const offset = cache.offset;
282
+ // Own the pre-write positions through every query that attends this read.
283
+ // Advancing a row cache may replace and release its borrowed offset array
284
+ // before the queries' RoPE is built.
285
+ const offsetArr = captureRopeOffsets(cache);
286
+
287
+ const kNormed = this.kNorm!.forward(k);
288
+ const kT = ops.transposeAxes(kNormed, [0, 2, 1, 3]);
289
+ kNormed.dispose();
290
+ const kRoped = this.rope(kT, offsetArr ?? offset);
291
+ kT.dispose();
292
+
293
+ const vNormed = this.vNorm!.forward(v);
294
+ const vT = ops.transposeAxes(vNormed, [0, 2, 1, 3]);
295
+ vNormed.dispose();
296
+ if (v !== k) v.dispose();
297
+ k.dispose();
298
+
299
+ let read: AttentionRead;
300
+ try {
301
+ read = this.reads.read(cache, phase, kRoped, vT);
302
+ } catch (error) {
303
+ offsetArr?.dispose();
304
+ throw error;
305
+ } finally {
306
+ kRoped.dispose();
307
+ vT.dispose();
308
+ }
309
+ return { read, offset, offsetArr, dispose() { read.dispose(); offsetArr?.dispose(); } };
310
+ }
311
+ }
312
+
313
+ // --- MoE block (26B-A4B) — port of reference Router/Experts/SwitchGLU ----
314
+ // (gemma4_text.py + switch_layers.py in the oracle venv). The checkpoint
315
+ // ships pre-stacked switch_glu tensors [experts, out, in/packed]; only the
316
+ // quantized path exists here (all our targets are OptiQ quants).
317
+
318
+ /** Port of reference Router: norm -> scale -> project -> top-k -> renormalize. */
319
+ export class Router {
320
+ readonly proj: QuantizedLinear;
321
+ /** scale · hidden_size^-0.5, precomputed (identical ops to the per-call
322
+ * product in the reference: bf16 array × weak scalar). */
323
+ readonly normWeight: MlxArray;
324
+ readonly perExpertScale: MlxArray;
325
+ readonly eps: number;
326
+ readonly numExperts: number;
327
+ readonly topK: number;
328
+ /** Decoder layer index (parsed from the weight prefix) — for E0 tracing. */
329
+ readonly layerIdx: number;
330
+
331
+ constructor(weights: Weights, config: ModelConfig, prefix: string) {
332
+ const t = config.text;
333
+ this.layerIdx = Number(prefix.match(/layers\.(\d+)/)?.[1] ?? -1);
334
+ this.proj = QuantizedLinear.load(weights, `${prefix}.proj`, config);
335
+ // weights.tensor() returns the Weights-owned cached array — do NOT
336
+ // dispose it (a second model over the same Weights would get a dead
337
+ // handle; Weights.dispose() frees it at end of life)
338
+ const scale = weights.tensor(`${prefix}.scale`);
339
+ this.normWeight = ops.mulScalar(scale, Math.pow(t.hiddenSize, -0.5));
340
+ this.perExpertScale = weights.tensor(`${prefix}.per_expert_scale`);
341
+ this.eps = t.rmsNormEps;
342
+ this.numExperts = t.numExperts;
343
+ this.topK = t.topKExperts;
344
+ }
345
+
346
+ /** x [B, L, H] → { indices [B, L, k] (uint32), weights [B, L, k] }. */
347
+ forward(x: MlxArray): { indices: MlxArray; weights: MlxArray } {
348
+ const normed = ops.rmsNorm(x, this.normWeight, this.eps);
349
+ const scores = this.proj.forward(normed);
350
+ normed.dispose();
351
+
352
+ const part = ops.argpartitionAxis(scores, this.numExperts - this.topK, -1);
353
+ const [B, L] = scores.shape as [number, number, number];
354
+ let indices: MlxArray;
355
+ if (isCompiledTrace()) {
356
+ const start = ops.fromInt32([this.numExperts - this.topK], [1]);
357
+ indices = ops.sliceDynamic(part, start, [2], [B, L, this.topK]);
358
+ start.dispose();
359
+ } else {
360
+ indices = part.slice([0, 0, this.numExperts - this.topK], [B, L, this.numExperts]);
361
+ }
362
+ part.dispose();
363
+
364
+ let w = ops.takeAlongAxis(scores, indices, -1);
365
+ scores.dispose();
366
+ w = disposing(w, ops.softmaxAxis(w, -1, false));
367
+ const gathered = ops.takeAxis(this.perExpertScale, indices, 0);
368
+ w = disposing(w, ops.mul(w, gathered));
369
+ gathered.dispose();
370
+ if (isExpertTracing()) recordRouting(this.layerIdx, indices);
371
+ return { indices, weights: w };
372
+ }
373
+ }
374
+
375
+ /** Port of switch_layers.SwitchGLU (geglu activation), quantized path, with the
376
+ * activation form its owner chose at construction (`compiled`). */
377
+ export class SwitchGLU {
378
+ readonly gate: QuantizedSwitchLinear;
379
+ readonly up: QuantizedSwitchLinear;
380
+ readonly down: QuantizedSwitchLinear;
381
+
382
+ constructor(weights: Weights, config: ModelConfig, prefix: string, readonly compiled: boolean) {
383
+ this.gate = QuantizedSwitchLinear.load(weights, `${prefix}.gate_proj`, config);
384
+ this.up = QuantizedSwitchLinear.load(weights, `${prefix}.up_proj`, config);
385
+ this.down = QuantizedSwitchLinear.load(weights, `${prefix}.down_proj`, config);
386
+ }
387
+
388
+ /** x [B, L, H], indices [B, L, k] → [B, L, k, H]. */
389
+ forward(x: MlxArray, indices: MlxArray): MlxArray {
390
+ // x → [B, L, 1, 1, H] (reference expand_dims(x, (-2, -3)))
391
+ let h = ops.expandDims(x, -2);
392
+ h = disposing(h, ops.expandDims(h, -3));
393
+
394
+ // Sort tokens by expert when there are many rows (reference threshold).
395
+ const doSort = indices.size >= 64;
396
+ let idx = indices;
397
+ let invOrder: MlxArray | null = null;
398
+ let order: MlxArray | null = null;
399
+ if (doSort) {
400
+ // _gather_sort: flatten indices, argsort, gather rows by order // k
401
+ const k = indices.shape[indices.ndim - 1]!;
402
+ const idxFlat = ops.reshape(indices, [indices.size]);
403
+ order = ops.argsortAxis(idxFlat, 0);
404
+ invOrder = ops.argsortAxis(order, 0);
405
+ const kScalar = ops.scalarLike(k, order);
406
+ const rowIdx = ops.floorDivide(order, kScalar);
407
+ kScalar.dispose();
408
+ const [, , , , H] = h.shape as number[];
409
+ const flat = ops.reshape(h, [-1, 1, H!]);
410
+ h.dispose();
411
+ h = ops.takeAxis(flat, rowIdx, 0);
412
+ flat.dispose();
413
+ rowIdx.dispose();
414
+ idx = ops.takeAxis(idxFlat, order, 0);
415
+ idxFlat.dispose();
416
+ }
417
+
418
+ const xUp = this.up.forward(h, idx, doSort);
419
+ const xGate = this.gate.forward(h, idx, doSort);
420
+ h.dispose();
421
+ // GeGLU activation: gelu_approx(gate) · up. The oracle SwitchGLU uses the SAME
422
+ // @mx.compile'd geglu as the dense MLP (activation=GeGLU(), switch_layers.py).
423
+ const mid = geglu(xGate, xUp, this.compiled);
424
+ let y = this.down.forward(mid, idx, doSort);
425
+ mid.dispose();
426
+ if (idx !== indices) idx.dispose();
427
+
428
+ if (doSort) {
429
+ // _scatter_unsort: restore row order, unflatten to indices.shape
430
+ y = disposing(y, ops.takeAxis(y, invOrder!, 0));
431
+ invOrder!.dispose();
432
+ order!.dispose();
433
+ const [B, L, k] = indices.shape as number[];
434
+ const H = y.shape[y.ndim - 1]!;
435
+ y = disposing(y, ops.reshape(y, [B!, L!, k!, 1, H]));
436
+ }
437
+
438
+ // squeeze(-2)
439
+ const shape = y.shape;
440
+ shape.splice(shape.length - 2, 1);
441
+ return disposing(y, ops.reshape(y, shape));
442
+ }
443
+ }
444
+
445
+ /** Port of reference Experts: switch_glu then top-k weighted sum. */
446
+ export class Experts {
447
+ readonly switchGlu: SwitchGLU;
448
+
449
+ constructor(weights: Weights, config: ModelConfig, prefix: string, compiledGeglu: boolean) {
450
+ this.switchGlu = new SwitchGLU(weights, config, `${prefix}.switch_glu`, compiledGeglu);
451
+ }
452
+
453
+ forward(x: MlxArray, indices: MlxArray, weights: MlxArray): MlxArray {
454
+ const w = ops.expandDims(weights, -1);
455
+ const y = this.switchGlu.forward(x, indices);
456
+ const wy = ops.mul(w, y);
457
+ w.dispose();
458
+ y.dispose();
459
+ return disposing(wy, ops.sumAxis(wy, -2, false));
460
+ }
461
+ }
462
+
463
+ /** One decoder layer: the attention sub-block (`forwardAttn`) and the row-wise
464
+ * pieces after it (`denseFeedforward` or `moeFeedforward`, `perLayerGated`,
465
+ * `scaled`), which the generic graph composes through `forwardMlp` and a
466
+ * written graph composes as its layer table names them. */
467
+ export class DecoderLayer {
468
+ readonly attn: Attention;
469
+ readonly mlp: GegluMLP;
470
+ readonly inputNorm: RMSNorm;
471
+ readonly postAttnNorm: RMSNorm;
472
+ readonly preFfNorm: RMSNorm;
473
+ readonly postFfNorm: RMSNorm;
474
+ readonly layerScalar: MlxArray | null;
475
+ readonly layerType: string;
476
+ // 26B-A4B MoE block (dense MLP + routed experts in parallel branches)
477
+ readonly router: Router | null;
478
+ readonly experts: Experts | null;
479
+ readonly postFfNorm1: RMSNorm | null;
480
+ readonly postFfNorm2: RMSNorm | null;
481
+ readonly preFfNorm2: RMSNorm | null;
482
+ // e2b/e4b per-layer input gating
483
+ readonly perLayerGate: QuantizedLinear | null;
484
+ readonly perLayerProjection: QuantizedLinear | null;
485
+ readonly postPerLayerNorm: RMSNorm | null;
486
+
487
+ /** `compiledGeglu` is the graph's construction-time choice of the GeGLU
488
+ * activation form for the dense MLP and the experts (`layers/geglu.ts`). */
489
+ constructor(weights: Weights, config: ModelConfig, prefixBase: string, idx: number, hasKv: boolean, compiledGeglu: boolean) {
490
+ const prefix = `${prefixBase}.layers.${idx}`;
491
+ const t = config.text;
492
+ this.layerType = t.layerTypes[idx]!;
493
+ this.attn = new Attention(weights, config, `${prefix}.self_attn`, this.layerType, hasKv);
494
+ this.mlp = new GegluMLP(weights, config, `${prefix}.mlp`, compiledGeglu);
495
+ const norm = (n: string) => new RMSNorm(weights.tensor(`${prefix}.${n}.weight`), t.rmsNormEps);
496
+ this.inputNorm = norm("input_layernorm");
497
+ this.postAttnNorm = norm("post_attention_layernorm");
498
+ this.preFfNorm = norm("pre_feedforward_layernorm");
499
+ this.postFfNorm = norm("post_feedforward_layernorm");
500
+ this.layerScalar = weights.has(`${prefix}.layer_scalar`)
501
+ ? weights.tensor(`${prefix}.layer_scalar`)
502
+ : null;
503
+ if (t.enableMoeBlock) {
504
+ this.router = new Router(weights, config, `${prefix}.router`);
505
+ this.experts = new Experts(weights, config, `${prefix}.experts`, compiledGeglu);
506
+ this.postFfNorm1 = norm("post_feedforward_layernorm_1");
507
+ this.postFfNorm2 = norm("post_feedforward_layernorm_2");
508
+ this.preFfNorm2 = norm("pre_feedforward_layernorm_2");
509
+ } else {
510
+ this.router = this.experts = null;
511
+ this.postFfNorm1 = this.postFfNorm2 = this.preFfNorm2 = null;
512
+ }
513
+ if (t.hiddenSizePerLayerInput > 0) {
514
+ this.perLayerGate = QuantizedLinear.load(weights, `${prefix}.per_layer_input_gate`, config);
515
+ this.perLayerProjection = QuantizedLinear.load(weights, `${prefix}.per_layer_projection`, config);
516
+ this.postPerLayerNorm = norm("post_per_layer_input_norm");
517
+ } else {
518
+ this.perLayerGate = this.perLayerProjection = null;
519
+ this.postPerLayerNorm = null;
520
+ }
521
+ }
522
+
523
+ /** `x` [B, L, hidden] (borrowed) to an owned [B, L, hidden], the attention
524
+ * block reading `cache` for `phase` (a donor) or attending `donor` (a
525
+ * KV-sharing layer). Returns a donor layer's read, which the caller owns. */
526
+ forward(
527
+ x: MlxArray, cache: Cache | null, phase: Gemma4Phase,
528
+ donor: DonorRead | null, perLayerInput: MlxArray | null,
529
+ ): { h: MlxArray; donor: DonorRead | null } {
530
+ // Composed from the two sub-blocks (the intra-layer min-cut is the post-attn
531
+ // residual `hMid` [1,T,hidden]). `forwardMlp` borrows hMid.
532
+ const { h: hMid, donor: made } = this.forwardAttn(x, cache, phase, donor);
533
+ let h: MlxArray;
534
+ try { h = this.forwardMlp(hMid, perLayerInput); }
535
+ catch (error) { made?.dispose(); throw error; }
536
+ finally { hMid.dispose(); }
537
+ return { h, donor: made };
538
+ }
539
+
540
+ /** Attention sub-block: inputNorm → attn → postAttnNorm → residual add.
541
+ * Returns the post-attn residual stream `h` [B,T,hidden] (the intra-layer
542
+ * boundary) and a donor layer's read (null for a sharer). `x` is borrowed
543
+ * (never disposed). The e4b donor threading lives entirely here. */
544
+ forwardAttn(
545
+ x: MlxArray, cache: Cache | null, phase: Gemma4Phase, donor: DonorRead | null,
546
+ ): { h: MlxArray; donor: DonorRead | null } {
547
+ let h = this.inputNorm.forward(x);
548
+ const { out, donor: made } = this.attn.forward(h, cache, phase, donor);
549
+ h.dispose();
550
+ h = out;
551
+ h = disposing(h, this.postAttnNorm.forward(h));
552
+ h = disposing(h, ops.add(x, h));
553
+ return { h, donor: made };
554
+ }
555
+
556
+ /** MLP sub-block: FFN (dense or MoE) + postFfNorm + residual add, then the
557
+ * e4b per-layer-input gate and layer scalar. `hMid` (the attn sub-block's
558
+ * output) is BORROWED — the caller owns/disposes it (it is the checkpoint
559
+ * boundary leaf), so this never disposes it. The generic graph's composition
560
+ * of the pieces below; a written graph composes the ones its layer has. */
561
+ forwardMlp(hMid: MlxArray, perLayerInput: MlxArray | null): MlxArray {
562
+ let h = this.router && this.experts ? this.moeFeedforward(hMid) : this.denseFeedforward(hMid);
563
+ if (this.perLayerGate && this.perLayerProjection && this.postPerLayerNorm && perLayerInput)
564
+ h = this.perLayerGated(h, perLayerInput);
565
+ if (this.layerScalar) h = this.scaled(h);
566
+ return h;
567
+ }
568
+
569
+ /** The dense feed-forward with its residual: preFfNorm → GeGLU MLP →
570
+ * postFfNorm, added to `hMid` (borrowed). Returns an owned output. */
571
+ denseFeedforward(hMid: MlxArray): MlxArray {
572
+ let f = this.preFfNorm.forward(hMid);
573
+ f = disposing(f, this.mlp.forward(f));
574
+ f = disposing(f, this.postFfNorm.forward(f));
575
+ const h = ops.add(hMid, f); // hMid borrowed — NOT disposed here
576
+ f.dispose();
577
+ return h;
578
+ }
579
+
580
+ /** The MoE feed-forward with its residual (26B-A4B): the dense MLP and the
581
+ * routed experts as parallel branches off `hMid` (borrowed; reference
582
+ * DecoderLayer, enable_moe path), summed, postFfNorm, added to `hMid`.
583
+ * Returns an owned output. */
584
+ moeFeedforward(hMid: MlxArray): MlxArray {
585
+ let h1 = this.preFfNorm.forward(hMid);
586
+ h1 = disposing(h1, this.mlp.forward(h1));
587
+ h1 = disposing(h1, this.postFfNorm1!.forward(h1));
588
+
589
+ const { indices, weights: topKWeights } = this.router!.forward(hMid);
590
+ let h2 = this.preFfNorm2!.forward(hMid);
591
+ h2 = disposing(h2, this.experts!.forward(h2, indices, topKWeights));
592
+ h2 = disposing(h2, this.postFfNorm2!.forward(h2));
593
+ indices.dispose();
594
+ topKWeights.dispose();
595
+
596
+ let f = ops.add(h1, h2);
597
+ h1.dispose();
598
+ h2.dispose();
599
+ f = disposing(f, this.postFfNorm.forward(f));
600
+ const h = ops.add(hMid, f); // hMid borrowed — NOT disposed here
601
+ f.dispose();
602
+ return h;
603
+ }
604
+
605
+ /** The per-layer input gate (e2b/e4b; reference gemma4_text DecoderLayer):
606
+ * `h` (consumed) plus postPerLayerNorm(projection(gelu(gate(h)) ·
607
+ * `perLayerInput`)), the input borrowed. gelu(gate)·pli is the second GeGLU
608
+ * site — the bit-exact spelled-out gelu+mul. Returns an owned output. */
609
+ perLayerGated(h: MlxArray, perLayerInput: MlxArray): MlxArray {
610
+ let gate = this.perLayerGate!.forward(h);
611
+ gate = disposing(gate, ops.geluApprox(gate));
612
+ gate = disposing(gate, ops.mul(gate, perLayerInput));
613
+ gate = disposing(gate, this.perLayerProjection!.forward(gate));
614
+ gate = disposing(gate, this.postPerLayerNorm!.forward(gate));
615
+ const out = ops.add(h, gate);
616
+ h.dispose();
617
+ gate.dispose();
618
+ return out;
619
+ }
620
+
621
+ /** `h` (consumed) times the layer scalar. */
622
+ scaled(h: MlxArray): MlxArray {
623
+ return disposing(h, ops.mul(h, this.layerScalar!));
624
+ }
625
+ }
626
+
627
+ /** The per-layer inputs of a per-layer-input model (e2b/e4b; reference
628
+ * `_get_per_layer_inputs` + `_project_per_layer_inputs`): every layer's input
629
+ * from the token ids and the scaled embeddings, and each layer's slice. */
630
+ export class PerLayerInputs {
631
+ readonly embed: QuantizedEmbedding;
632
+ readonly modelProjection: QuantizedLinear;
633
+ readonly projectionNorm: RMSNorm;
634
+ /** Each layer's input width. */
635
+ readonly width: number;
636
+ readonly #layers: number;
637
+ readonly #hiddenSize: number;
638
+
639
+ constructor(weights: Weights, config: ModelConfig, prefixBase: string) {
640
+ const t = config.text;
641
+ this.embed = QuantizedEmbedding.load(weights, `${prefixBase}.embed_tokens_per_layer`, config);
642
+ this.modelProjection = QuantizedLinear.load(weights, `${prefixBase}.per_layer_model_projection`, config);
643
+ this.projectionNorm = new RMSNorm(weights.tensor(`${prefixBase}.per_layer_projection_norm.weight`), t.rmsNormEps);
644
+ this.width = t.hiddenSizePerLayerInput;
645
+ this.#layers = t.numHiddenLayers;
646
+ this.#hiddenSize = t.hiddenSize;
647
+ }
648
+
649
+ /** Every layer's input, [B, L, nLayers, width] (owned), from `ids` [B, L]
650
+ * and the scaled embeddings `hScaled` [B, L, hidden] (both borrowed). */
651
+ compute(ids: MlxArray, hScaled: MlxArray): MlxArray {
652
+ const B = ids.shape[0]!;
653
+ const L = ids.shape[1]!;
654
+ let pli = this.embed.encode(ids); // [B, L, nLayers*width]
655
+ pli = disposing(pli, ops.mulScalar(pli, Math.sqrt(this.width)));
656
+ pli = disposing(pli, ops.reshape(pli, [B, L, this.#layers, this.width]));
657
+
658
+ let proj = this.modelProjection.forward(hScaled);
659
+ proj = disposing(proj, ops.mulScalar(proj, 1 / Math.sqrt(this.#hiddenSize)));
660
+ proj = disposing(proj, ops.reshape(proj, [B, L, this.#layers, this.width]));
661
+ proj = disposing(proj, this.projectionNorm.forward(proj));
662
+
663
+ let combined = ops.add(proj, pli);
664
+ proj.dispose();
665
+ pli.dispose();
666
+ combined = disposing(combined, ops.mulScalar(combined, Math.SQRT1_2));
667
+ return combined;
668
+ }
669
+
670
+ /** Layer `i`'s input [B, L, width] (owned) from the [B, L, nLayers, width]
671
+ * tensor `compute` returned (borrowed). A compiled-decode trace slices at an
672
+ * array start, which replays at every step. */
673
+ ordinaryLayer(perLayer: MlxArray, i: number, B: number, L: number): MlxArray {
674
+ using slice = perLayer.slice([0, 0, i, 0], [B, L, i + 1, this.width]);
675
+ return ops.reshape(slice, [B, L, this.width]);
676
+ }
677
+
678
+ traceLayer(perLayer: MlxArray, i: number, B: number, L: number): MlxArray {
679
+ using start = ops.fromInt32([i], [1]);
680
+ using slice = ops.sliceDynamic(perLayer, start, [2], [B, L, 1, this.width]);
681
+ return ops.reshape(slice, [B, L, this.width]);
682
+ }
683
+
684
+ layer(perLayer: MlxArray, i: number, B: number, L: number): MlxArray {
685
+ let pls: MlxArray;
686
+ if (isCompiledTrace()) {
687
+ const start = ops.fromInt32([i], [1]);
688
+ pls = ops.sliceDynamic(perLayer, start, [2], [B, L, 1, this.width]);
689
+ start.dispose();
690
+ } else {
691
+ pls = perLayer.slice([0, 0, i, 0], [B, L, i + 1, this.width]);
692
+ }
693
+ const r = ops.reshape(pls, [B, L, this.width]);
694
+ pls.dispose();
695
+ return r;
696
+ }
697
+ }
698
+
699
+
700
+ /** Unsorted expert gather. The graph binds this below the reference threshold. */
701
+ export class UnsortedSwitchGLU {
702
+ constructor(readonly base: SwitchGLU,
703
+ readonly activation: { forward(gate: MlxArray, up: MlxArray): MlxArray }) {}
704
+ forward(x: MlxArray, indices: MlxArray): MlxArray {
705
+ using expanded = ops.expandDims(x, -2);
706
+ using h = ops.expandDims(expanded, -3);
707
+ const up = this.base.up.forward(h, indices, false);
708
+ const gate = this.base.gate.forward(h, indices, false);
709
+ using mid = this.activation.forward(gate, up);
710
+ using y = this.base.down.forward(mid, indices, false);
711
+ const shape = y.shape;
712
+ shape.splice(shape.length - 2, 1);
713
+ return ops.reshape(y, shape);
714
+ }
715
+ }
716
+
717
+ /** Sorted expert gather and inverse permutation. No token-count selector. */
718
+ export class SortedSwitchGLU {
719
+ constructor(readonly base: SwitchGLU,
720
+ readonly activation: { forward(gate: MlxArray, up: MlxArray): MlxArray }) {}
721
+ forward(x: MlxArray, indices: MlxArray): MlxArray {
722
+ using expanded = ops.expandDims(x, -2);
723
+ using h = ops.expandDims(expanded, -3);
724
+ const k = indices.shape[indices.ndim - 1]!;
725
+ using idxFlat = ops.reshape(indices, [indices.size]);
726
+ using order = ops.argsortAxis(idxFlat, 0);
727
+ using invOrder = ops.argsortAxis(order, 0);
728
+ using kScalar = ops.scalarLike(k, order);
729
+ using rowIdx = ops.floorDivide(order, kScalar);
730
+ using flat = ops.reshape(h, [-1, 1, h.shape[4]!]);
731
+ using sorted = ops.takeAxis(flat, rowIdx, 0);
732
+ using idx = ops.takeAxis(idxFlat, order, 0);
733
+ const up = this.base.up.forward(sorted, idx, true);
734
+ const gate = this.base.gate.forward(sorted, idx, true);
735
+ using mid = this.activation.forward(gate, up);
736
+ using projected = this.base.down.forward(mid, idx, true);
737
+ using unsorted = ops.takeAxis(projected, invOrder, 0);
738
+ const [B, L, topK] = indices.shape as number[];
739
+ using y = ops.reshape(unsorted, [B!, L!, topK!, 1, unsorted.shape.at(-1)!]);
740
+ const shape = y.shape;
741
+ shape.splice(shape.length - 2, 1);
742
+ return ops.reshape(y, shape);
743
+ }
744
+ }
745
+
746
+ /** The explicit expert operation, with top-k weighted sum in reference order. */
747
+ export class FixedExperts {
748
+ constructor(readonly switchGlu: { forward(x: MlxArray, indices: MlxArray): MlxArray }) {}
749
+ forward(x: MlxArray, indices: MlxArray, weights: MlxArray): MlxArray {
750
+ using w = ops.expandDims(weights, -1);
751
+ using y = this.switchGlu.forward(x, indices);
752
+ using wy = ops.mul(w, y);
753
+ return ops.sumAxis(wy, -2, false);
754
+ }
755
+ }