@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,900 @@
1
+ import type { MlxCacheLayer, MlxDenoisingPrefillRange, MlxDenoisingDecodeRange, MlxDenoisingAppendRange } from "../../contracts/mlx/graph";
2
+ import { cleanupFailure, disposeResources } from "../../runtime/resources";
3
+ // DiffusionGemma-26B-A4B-it (model_type "diffusion_gemma") — the first
4
+ // NON-autoregressive model in the codebase. Instead of a left-to-right AR loop
5
+ // it runs an ENCODER prefill over the prompt (builds a KV cache) and then a
6
+ // DECODER pass over a fixed 256-token "canvas" that it denoises over a few
7
+ // steps. This file is the D1 static graph: one full forward
8
+ // encoder(prompt) -> cache ; decoder(canvas, cache) -> hidden -> tied head
9
+ // held bit-exact (KL/argmax where the quantized GEMV is 1-ULP off) against the
10
+ // mlx-optiq reference (optiq IS the oracle here — stock mlx-lm can't load it).
11
+ //
12
+ // Ported verbatim from optiq/vlm/_mlxvlm/models/diffusion_gemma/language.py.
13
+ // Reuses mlx-bun's quantized primitives (QuantizedLinear/Embedding/SwitchLinear,
14
+ // RMSNorm, the composed cache legos) from packages/inference/src/layers/ and
15
+ // packages/inference/src/state/. Architecture deltas vs
16
+ // gemma4 (verified against the checkpoint, see `02d723a:docs/design/generic-model-support.md` §6.5):
17
+ // - attention scale=1.0, NO attention softcap (only final-logit softcap 30 fp32)
18
+ // - QK/V-norm POST-proj / PRE-RoPE; v_norm is RMSNormNoScale; V gets no RoPE
19
+ // - full-attention layers (5,11,17,23,29) reuse k as v (no v_proj), hd=512 kv=2,
20
+ // partial-rotary 0.25; sliding layers hd=256 kv=8
21
+ // - per layer: parallel dense-MLP + 128-expert top-8 MoE, summed via 7 RMSNorms
22
+ // and a per-layer `layer_scalar`
23
+ // - experts use a FUSED gate_up_proj SwitchLinear (split at moe_intermediate)
24
+ // - plain nn.RMSNorm everywhere (NO Gemma 1+weight offset) — weights used as-is
25
+ // - tied LM head via the 4-bit QuantizedEmbedding.asLinear
26
+ // - SelfConditioning MLP wraps the canvas embeddings every step (zero signal on
27
+ // the first step still applies its RMSNormNoScale post-norm)
28
+
29
+ import type { ModelConfig } from "../../artifacts/config";
30
+ import type { Weights } from "../../artifacts/weights";
31
+ import { MlxArray } from "@mlx-bun/mlx/array";
32
+ import { Dtype } from "@mlx-bun/mlx/ffi";
33
+ import * as ops from "@mlx-bun/mlx/ops";
34
+ import { disposing } from "../../layers/helpers";
35
+ import { LoraState } from "../../layers/lora";
36
+ import type { GraphDeclaration } from "../../contracts/portable/graph";
37
+ import type { MlxDeclaredGraph, MlxDenoisingOperations } from "../../contracts/mlx/graph";
38
+ import type { TrainableGraph } from "../../contracts/mlx/trainable";
39
+ import { declareGraph } from "../capabilities";
40
+ import type { PixelInput } from "../../contracts/mlx/media";
41
+ import { geglu, GegluMLP, CompiledGeglu, CompiledGegluMLP } from "../../layers/geglu";
42
+ import { SortedDiffExperts, UnsortedDiffExperts } from "./experts";
43
+ import { compiledLogitSoftcap } from "../../kernels/softcap";
44
+ import { QuantizedEmbedding } from "../../layers/quantized-embedding";
45
+ import { QuantizedLinear } from "../../layers/quantized-linear";
46
+ import { QuantizedSwitchLinear } from "../../layers/quantized-switch-linear";
47
+ import { RMSNorm } from "../../layers/normalization";
48
+ import { AttentionMasks } from "../../state/attention-read";
49
+ import { composedFullAttentionCache, composedSlidingAttentionCache } from "../../state/bf16-first-kv";
50
+ import { maybePageKv } from "../../state/request-policy";
51
+ import { deferredTurboQuantKernels } from "../../state/turboquant-attention";
52
+ import type { AttentionRead, BlockAttentionCache, Cache, EncoderAttentionCache } from "../../contracts/mlx/cache";
53
+ import { DiffusionVisionTower, spliceImageTokens } from "./vision";
54
+
55
+ /** The caches `makeCache` composes: each serves the window read (encoder
56
+ * prefill over prompt tokens or an accepted canvas block), the block read (the
57
+ * decoder canvas pass) and the encoder window read (the vision prompt). */
58
+ type DiffusionCache = BlockAttentionCache & EncoderAttentionCache;
59
+
60
+ /** The read one pass makes on a layer's cache with that layer's keys and
61
+ * values. The pass names it; no layer inspects its input or its cache. */
62
+ type PassRead = (cache: Cache, k: MlxArray, v: MlxArray) => AttentionRead;
63
+
64
+ /** Encoder prefill over tokens: append, causal, limited to the layer's sliding window. */
65
+ const windowRead: PassRead = (cache, k, v) => (cache as DiffusionCache).appendWindow(k, v);
66
+ /** Decoder canvas pass: the canvas attends the stored encoder positions (the
67
+ * newest sliding_window - 1 on sliding layers) and itself, storing nothing. */
68
+ const blockRead: PassRead = (cache, k, v) => (cache as DiffusionCache).readBlock(k, v);
69
+ /** Vision encoder prefill: append, causal over the whole prompt with no sliding
70
+ * window, image positions (`media`, bool [L]) attending each other both ways. */
71
+ const encoderRead = (media: MlxArray): PassRead =>
72
+ (cache, k, v) => (cache as DiffusionCache).appendEncoderWindow(k, v, media);
73
+
74
+ /** Router: pre-projection RMSNorm (no-scale) * scale * hidden**-0.5, top-8
75
+ * argpartition, softmax(precise) * per_expert_scale[idx]. Local, not gemma4's Router:
76
+ * the reference applies scale and hidden**-0.5 as separate bf16 ops after a no-scale
77
+ * norm (gemma4's folds them into the norm weight, which rounds differently), softmaxes
78
+ * in precise mode, and this one stops gradient on the indices. */
79
+ class DiffRouter {
80
+ readonly proj: QuantizedLinear;
81
+ readonly scale: MlxArray; // per-hidden-dim scale (applied AFTER the no-scale norm)
82
+ readonly rootSize: number; // hidden**-0.5
83
+ readonly perExpertScale: MlxArray;
84
+ readonly eps: number;
85
+ readonly numExperts: number;
86
+ readonly topK: number;
87
+ constructor(weights: Weights, config: ModelConfig, prefix: string) {
88
+ const t = config.text;
89
+ this.proj = QuantizedLinear.load(weights, `${prefix}.proj`, config);
90
+ this.scale = weights.tensor(`${prefix}.scale`);
91
+ this.rootSize = Math.pow(t.hiddenSize, -0.5);
92
+ this.perExpertScale = weights.tensor(`${prefix}.per_expert_scale`);
93
+ this.eps = t.rmsNormEps;
94
+ this.numExperts = t.numExperts;
95
+ this.topK = t.topKExperts;
96
+ }
97
+ /** x: [T, H] -> { indices: [T, k], weights: [T, k] }. */
98
+ forward(x: MlxArray): { indices: MlxArray; weights: MlxArray } {
99
+ // Match the reference EXACTLY (do NOT fold the post-scale into the norm
100
+ // weight — that changes the bf16 rounding and shifts the softmax weights):
101
+ // x = rms_norm(x, None, eps); x = x * scale * hidden**-0.5
102
+ let normed = ops.rmsNorm(x, null, this.eps);
103
+ normed = disposing(normed, ops.mul(normed, this.scale));
104
+ normed = disposing(normed, ops.mulScalar(normed, this.rootSize));
105
+ const scores = this.proj.forward(normed);
106
+ normed.dispose();
107
+ const part = ops.argpartitionAxis(scores, this.numExperts - this.topK, -1);
108
+ const [T] = scores.shape as [number, number];
109
+ const sliced = part.slice([0, this.numExperts - this.topK], [T, this.numExperts]);
110
+ part.dispose();
111
+ // The top-k indices derive from `scores`, so under value_and_grad they sit
112
+ // on the tape and MLX's gather VJP refuses a cotangent for indices
113
+ // ("Cannot calculate VJP with respect to indices"). Integer indices carry
114
+ // no gradient; stopping them here keeps the forward bit-identical and lets
115
+ // the value gather still backpropagate into `scores`. (Main's copy of this
116
+ // router lacks the stop and fails the same way under MLX 0.32.2.)
117
+ const indices = ops.stopGradient(sliced);
118
+ sliced.dispose();
119
+ let w = ops.takeAlongAxis(scores, indices, -1);
120
+ scores.dispose();
121
+ w = disposing(w, ops.softmaxAxis(w, -1, true));
122
+ const gathered = ops.takeAxis(this.perExpertScale, indices, 0);
123
+ w = disposing(w, ops.mul(w, gathered));
124
+ gathered.dispose();
125
+ return { indices, weights: w };
126
+ }
127
+ }
128
+
129
+ /** 128-expert top-8 MoE with a FUSED gate_up SwitchLinear (split at
130
+ * moe_intermediate_size) + a down SwitchLinear (gemma4's SwitchGLU holds separate
131
+ * gate and up projections; the geglu between them is the shared one). _gather_sort / _scatter_unsort
132
+ * exactly as mlx_lm.models.switch_layers (sort threshold idx.size >= 64). */
133
+ class DiffExperts {
134
+ readonly gateUp: QuantizedSwitchLinear;
135
+ readonly down: QuantizedSwitchLinear;
136
+ readonly hiddenDims: number; // moe_intermediate_size (split point)
137
+ constructor(weights: Weights, config: ModelConfig, prefix: string, readonly compiledGeglu: boolean) {
138
+ this.gateUp = QuantizedSwitchLinear.load(weights, `${prefix}.gate_up_proj`, config);
139
+ this.down = QuantizedSwitchLinear.load(weights, `${prefix}.down_proj`, config);
140
+ this.hiddenDims = config.text.moeIntermediateSize;
141
+ }
142
+ /** x: [T, H], indices/weights: [T, k] -> [T, H]. */
143
+ forward(x: MlxArray, indices: MlxArray, weights: MlxArray): MlxArray {
144
+ let h = ops.expandDims(x, -2); // [T, 1, H]
145
+ h = disposing(h, ops.expandDims(h, -3)); // [T, 1, 1, H]
146
+ const doSort = indices.size >= 64;
147
+ let idx = indices;
148
+ let order: MlxArray | null = null;
149
+ let invOrder: MlxArray | null = null;
150
+ if (doSort) {
151
+ const k = indices.shape[indices.ndim - 1]!;
152
+ const idxFlat = ops.reshape(indices, [indices.size]);
153
+ order = ops.argsortAxis(idxFlat, 0);
154
+ invOrder = ops.argsortAxis(order, 0);
155
+ const kS = ops.scalarLike(k, order);
156
+ const rowIdx = ops.floorDivide(order, kS);
157
+ kS.dispose();
158
+ const H = h.shape[h.ndim - 1]!;
159
+ const flat = ops.reshape(h, [-1, 1, H]);
160
+ h.dispose();
161
+ h = ops.takeAxis(flat, rowIdx, 0);
162
+ flat.dispose();
163
+ rowIdx.dispose();
164
+ idx = ops.takeAxis(idxFlat, order, 0);
165
+ idxFlat.dispose();
166
+ }
167
+
168
+ const gateUp = this.gateUp.forward(h, idx, doSort);
169
+ h.dispose();
170
+ const parts = ops.split(gateUp, [this.hiddenDims], -1);
171
+ gateUp.dispose();
172
+ const gate = parts[0]!;
173
+ const up = parts[1]!;
174
+ const mid = geglu(gate, up, this.compiledGeglu);
175
+ let y = this.down.forward(mid, idx, doSort);
176
+ mid.dispose();
177
+ if (idx !== indices) idx.dispose();
178
+
179
+ if (doSort) {
180
+ y = disposing(y, ops.takeAxis(y, invOrder!, 0));
181
+ invOrder!.dispose();
182
+ order!.dispose();
183
+ const shape = [...indices.shape, 1, y.shape[y.ndim - 1]!]; // [T, k, 1, H]
184
+ y = disposing(y, ops.reshape(y, shape));
185
+ }
186
+ // squeeze(-2): [T, k, 1, H] -> [T, k, H]
187
+ {
188
+ const shape = y.shape;
189
+ shape.splice(shape.length - 2, 1);
190
+ y = disposing(y, ops.reshape(y, shape));
191
+ }
192
+ // (y * weights[..., None]).sum(axis=-2) -> [T, H]
193
+ const w = ops.expandDims(weights, -1);
194
+ const wy = ops.mul(y, w);
195
+ w.dispose();
196
+ y.dispose();
197
+ return disposing(wy, ops.sumAxis(wy, -2, false));
198
+ }
199
+ }
200
+
201
+ /** SelfConditioning: post_norm(inputs_embeds + down(geglu(gate(pre_norm(signal)),
202
+ * up(pre_norm(signal))))). pre_norm is RMSNorm, post_norm is RMSNormNoScale. */
203
+ class SelfConditioning {
204
+ readonly activation = new CompiledGeglu();
205
+ readonly preNorm: RMSNorm;
206
+ readonly postNorm: RMSNorm; // RMSNormNoScale (null weight)
207
+ readonly gate: QuantizedLinear;
208
+ readonly up: QuantizedLinear;
209
+ readonly down: QuantizedLinear;
210
+ constructor(weights: Weights, config: ModelConfig, prefix: string, readonly compiledGeglu: boolean) {
211
+ const eps = config.text.rmsNormEps;
212
+ this.preNorm = new RMSNorm(weights.tensor(`${prefix}.pre_norm.weight`), eps);
213
+ this.postNorm = new RMSNorm(null, eps);
214
+ this.gate = QuantizedLinear.load(weights, `${prefix}.gate_proj`, config);
215
+ this.up = QuantizedLinear.load(weights, `${prefix}.up_proj`, config);
216
+ this.down = QuantizedLinear.load(weights, `${prefix}.down_proj`, config);
217
+ }
218
+ forward(inputsEmbeds: MlxArray, signal: MlxArray): MlxArray {
219
+ const normed = this.preNorm.forward(signal);
220
+ const m = this.activation.forward(this.gate.forward(normed), this.up.forward(normed));
221
+ normed.dispose();
222
+ const s = this.down.forward(m);
223
+ m.dispose();
224
+ const sum = ops.add(inputsEmbeds, s);
225
+ s.dispose();
226
+ const out = this.postNorm.forward(sum);
227
+ sum.dispose();
228
+ return out;
229
+ }
230
+ }
231
+
232
+ /** Attention through the read its pass names: the encoder passes append to the
233
+ * cache, the decoder canvas pass reads the stored encoder K/V with the canvas
234
+ * K/V without storing them. The cache builds the mask. scale=1.0, no softcap. */
235
+ class DiffAttention {
236
+ readonly isSliding: boolean;
237
+ readonly headDim: number;
238
+ readonly nHeads: number;
239
+ readonly nKvHeads: number;
240
+ readonly qProj: QuantizedLinear;
241
+ readonly kProj: QuantizedLinear;
242
+ readonly vProj: QuantizedLinear | null;
243
+ readonly oProj: QuantizedLinear;
244
+ readonly qNorm: RMSNorm;
245
+ readonly kNorm: RMSNorm;
246
+ readonly vNorm: RMSNorm; // RMSNormNoScale (null weight)
247
+ readonly ropeBase: number | null;
248
+ readonly ropeFreqs: MlxArray | null;
249
+
250
+ constructor(weights: Weights, config: ModelConfig, prefix: string, layerType: string) {
251
+ const t = config.text;
252
+ this.isSliding = layerType === "sliding_attention";
253
+ this.headDim = this.isSliding ? t.headDim : t.globalHeadDim;
254
+ this.nHeads = t.numAttentionHeads;
255
+ this.nKvHeads = this.isSliding ? t.numKeyValueHeads : t.numGlobalKeyValueHeads;
256
+
257
+ this.qProj = QuantizedLinear.load(weights, `${prefix}.q_proj`, config);
258
+ this.kProj = QuantizedLinear.load(weights, `${prefix}.k_proj`, config);
259
+ // Full-attention layers have no v_proj — they reuse the (pre-norm) k tensor.
260
+ this.vProj = this.isSliding
261
+ ? QuantizedLinear.load(weights, `${prefix}.v_proj`, config)
262
+ : null;
263
+ this.oProj = QuantizedLinear.load(weights, `${prefix}.o_proj`, config);
264
+ this.qNorm = new RMSNorm(weights.tensor(`${prefix}.q_norm.weight`), t.rmsNormEps);
265
+ this.kNorm = new RMSNorm(weights.tensor(`${prefix}.k_norm.weight`), t.rmsNormEps);
266
+ this.vNorm = new RMSNorm(null, t.rmsNormEps);
267
+
268
+ // RoPE: default (sliding, theta 1e4) or proportional (full, partial 0.25,
269
+ // theta 1e6). Proportional rotates only floor(head_dim*factor) dims; the
270
+ // unrotated tail is freq=Infinity (cos=1, sin=0 → identity). Mirrors
271
+ // packages/inference/src/models/gemma4/model.ts initialize_rope.
272
+ const rp = t.ropeParameters[this.isSliding ? "sliding_attention" : "full_attention"]!;
273
+ if (rp.ropeType === "default") {
274
+ this.ropeBase = rp.ropeTheta;
275
+ this.ropeFreqs = null;
276
+ } else {
277
+ const rotated = Math.floor(this.headDim * rp.partialRotaryFactor);
278
+ const n = this.headDim / 2;
279
+ const exponentsRaw = ops.arange(0, rotated, 2, Dtype.float32);
280
+ const dims = ops.scalarLike(this.headDim, exponentsRaw);
281
+ const exponents = ops.div(exponentsRaw, dims);
282
+ exponentsRaw.dispose();
283
+ dims.dispose();
284
+ const base = ops.scalarLike(rp.ropeTheta, exponents);
285
+ let rotFreqs = ops.pow(base, exponents);
286
+ base.dispose();
287
+ exponents.dispose();
288
+ if (rp.factor !== 1.0) {
289
+ const f = ops.scalarLike(rp.factor, rotFreqs);
290
+ rotFreqs = disposing(rotFreqs, ops.mul(f, rotFreqs));
291
+ f.dispose();
292
+ }
293
+ this.ropeBase = null;
294
+ const tailLen = n - rotated / 2;
295
+ if (tailLen > 0) {
296
+ const tail = MlxArray.fromFloat32(new Float32Array(tailLen).fill(Infinity), [tailLen]);
297
+ this.ropeFreqs = ops.concatAxis([rotFreqs, tail], 0);
298
+ rotFreqs.dispose();
299
+ tail.dispose();
300
+ } else {
301
+ this.ropeFreqs = rotFreqs;
302
+ }
303
+ }
304
+ }
305
+
306
+ #rope(x: MlxArray, offset: number): MlxArray {
307
+ return ops.rope(x, this.headDim, this.ropeBase, offset, this.ropeFreqs);
308
+ }
309
+
310
+ /** `offset` is the RoPE position of `x`'s first token, which the pass chooses
311
+ * (the reference encoder ropes every pass from 0). */
312
+ forward(x: MlxArray, cache: Cache, read: PassRead, offset: number): MlxArray {
313
+ const [B, L] = x.shape as [number, number, number];
314
+
315
+ const qFlat = this.qProj.forward(x);
316
+ let q = ops.reshape(qFlat, [B, L, this.nHeads, this.headDim]);
317
+ qFlat.dispose();
318
+ q = disposing(q, this.qNorm.forward(q));
319
+ q = disposing(q, ops.transposeAxes(q, [0, 2, 1, 3]));
320
+ q = disposing(q, this.#rope(q, offset));
321
+
322
+ const kFlat = this.kProj.forward(x);
323
+ const kReshaped = ops.reshape(kFlat, [B, L, this.nKvHeads, this.headDim]);
324
+ kFlat.dispose();
325
+ // v reuses the pre-norm k tensor on full layers (v_proj is None there).
326
+ let vReshaped: MlxArray;
327
+ if (this.vProj) {
328
+ const vFlat = this.vProj.forward(x);
329
+ vReshaped = ops.reshape(vFlat, [B, L, this.nKvHeads, this.headDim]);
330
+ vFlat.dispose();
331
+ } else {
332
+ vReshaped = kReshaped;
333
+ }
334
+
335
+ let k = this.kNorm.forward(kReshaped);
336
+ k = disposing(k, ops.transposeAxes(k, [0, 2, 1, 3]));
337
+ k = disposing(k, this.#rope(k, offset));
338
+ let v = this.vNorm.forward(vReshaped); // no RoPE on V
339
+ v = disposing(v, ops.transposeAxes(v, [0, 2, 1, 3]));
340
+ if (vReshaped !== kReshaped) vReshaped.dispose();
341
+ kReshaped.dispose();
342
+
343
+ let attended: AttentionRead;
344
+ try { attended = read(cache, k, v); } finally { k.dispose(); v.dispose(); }
345
+ let attn: MlxArray;
346
+ try { attn = attended.attend(q, 1.0); } finally { attended.dispose(); q.dispose(); }
347
+
348
+ let out = ops.transposeAxes(attn, [0, 2, 1, 3]);
349
+ attn.dispose();
350
+ out = disposing(out, ops.reshape(out, [B, L, this.nHeads * this.headDim]));
351
+ const o = this.oProj.forward(out);
352
+ out.dispose();
353
+ return o;
354
+ }
355
+ }
356
+
357
+ /** One decoder layer: attention residual + a PARALLEL dense-MLP + MoE branch,
358
+ * combined through 7 RMSNorms and scaled by `layer_scalar` (the encoder pass
359
+ * overrides it with the per-layer encoder scalar). */
360
+ class DiffDecoderLayer {
361
+ readonly layerType: string;
362
+ readonly attn: DiffAttention;
363
+ readonly mlp: GegluMLP;
364
+ readonly router: DiffRouter;
365
+ readonly experts: DiffExperts;
366
+ readonly inputNorm: RMSNorm;
367
+ readonly postAttnNorm: RMSNorm;
368
+ readonly preFfNorm: RMSNorm;
369
+ readonly postFfNorm: RMSNorm;
370
+ readonly postFfNorm1: RMSNorm;
371
+ readonly postFfNorm2: RMSNorm;
372
+ readonly preFfNorm2: RMSNorm;
373
+ readonly layerScalar: MlxArray;
374
+
375
+ constructor(weights: Weights, config: ModelConfig, idx: number, compiledGeglu: boolean) {
376
+ const t = config.text;
377
+ const prefix = `model.decoder.layers.${idx}`;
378
+ this.layerType = t.layerTypes[idx]!;
379
+ const norm = (n: string) => new RMSNorm(weights.tensor(`${prefix}.${n}.weight`), t.rmsNormEps);
380
+ this.attn = new DiffAttention(weights, config, `${prefix}.self_attn`, this.layerType);
381
+ this.mlp = new GegluMLP(weights, config, `${prefix}.mlp`, compiledGeglu);
382
+ this.router = new DiffRouter(weights, config, `${prefix}.router`);
383
+ this.experts = new DiffExperts(weights, config, `${prefix}.experts`, compiledGeglu);
384
+ this.inputNorm = norm("input_layernorm");
385
+ this.postAttnNorm = norm("post_attention_layernorm");
386
+ this.preFfNorm = norm("pre_feedforward_layernorm");
387
+ this.postFfNorm = norm("post_feedforward_layernorm");
388
+ this.postFfNorm1 = norm("post_feedforward_layernorm_1");
389
+ this.postFfNorm2 = norm("post_feedforward_layernorm_2");
390
+ this.preFfNorm2 = norm("pre_feedforward_layernorm_2");
391
+ this.layerScalar = weights.tensor(`${prefix}.layer_scalar`);
392
+ }
393
+
394
+ forward(
395
+ x: MlxArray,
396
+ cache: Cache,
397
+ read: PassRead,
398
+ offset: number,
399
+ layerScalar: MlxArray | null,
400
+ ): MlxArray { return this.forwardWith(x,cache,read,offset,layerScalar,this.mlp,this.experts); }
401
+
402
+ forwardWith(x: MlxArray, cache: Cache, read: PassRead, offset: number, layerScalar: MlxArray | null,
403
+ mlp: {forward(x:MlxArray):MlxArray}, experts: {forward(x:MlxArray,indices:MlxArray,weights:MlxArray):MlxArray}): MlxArray {
404
+ // attention
405
+ let residual = x;
406
+ let h = this.inputNorm.forward(x);
407
+ h = disposing(h, this.attn.forward(h, cache, read, offset));
408
+ h = disposing(h, this.postAttnNorm.forward(h));
409
+ h = disposing(h, ops.add(residual, h));
410
+
411
+ // parallel dense-MLP + MoE feedforward
412
+ residual = h;
413
+ const dense = this.preFfNorm.forward(h);
414
+ let h1 = mlp.forward(dense);
415
+ dense.dispose();
416
+ h1 = disposing(h1, this.postFfNorm1.forward(h1));
417
+
418
+ const shape = residual.shape;
419
+ const hidden = shape[shape.length - 1]!;
420
+ const flat = ops.reshape(residual, [-1, hidden]);
421
+ const { indices: rawIdx, weights: topw } = this.router.forward(flat);
422
+ // stop_gradient the routing indices so the MoE backward (gather_qmm vjp)
423
+ // doesn't try to differentiate the non-differentiable top-k indices (the
424
+ // reference does this in training mode). Identity in the forward, so D1/D2
425
+ // stay bit-exact; required for D5 LoRA grads to flow through the experts.
426
+ const indices = ops.stopGradient(rawIdx);
427
+ rawIdx.dispose();
428
+ let h2 = this.preFfNorm2.forward(flat);
429
+ flat.dispose();
430
+ h2 = disposing(h2, experts.forward(h2, indices, topw));
431
+ indices.dispose();
432
+ topw.dispose();
433
+ h2 = disposing(h2, ops.reshape(h2, shape));
434
+ h2 = disposing(h2, this.postFfNorm2.forward(h2));
435
+
436
+ const sum = ops.add(h1, h2);
437
+ h1.dispose();
438
+ h2.dispose();
439
+ h = this.postFfNorm.forward(sum);
440
+ sum.dispose();
441
+ h = disposing(h, ops.add(residual, h));
442
+ residual.dispose();
443
+
444
+ const scalar = layerScalar ?? this.layerScalar;
445
+ h = disposing(h, ops.mul(h, scalar));
446
+ return h;
447
+ }
448
+ }
449
+
450
+ export class DiffusionGemmaModel implements MlxDeclaredGraph, MlxDenoisingOperations {
451
+ readonly config: ModelConfig;
452
+ readonly weightsBytes: number;
453
+ readonly embed: QuantizedEmbedding;
454
+ readonly embedScale: number;
455
+ readonly layers: DiffDecoderLayer[];
456
+ readonly finalNorm: RMSNorm;
457
+ readonly selfConditioning: SelfConditioning;
458
+ readonly encoderLayerScalars: MlxArray[];
459
+ readonly canvasLength: number;
460
+ readonly softcap: number;
461
+ readonly slidingWindow: number;
462
+ // Shared RuntimeModel surface. LoRA mounts onto the (quantized) decoder
463
+ // linears like the AR models; the diffusion engine activates it per request.
464
+ readonly loraState = new LoraState();
465
+ /** Denoising rows interleave through their own grouped method; images enter as
466
+ * the method's pixel prefill input. */
467
+ get graphCapabilities(): GraphDeclaration {
468
+ return declareGraph({ method: "denoising", media: { input: "pixels", video: false } });
469
+ }
470
+ /** The denoising objective: LoRA on the decoder blocks (the encoder reuses them, weight-tied). */
471
+ get trainable(): TrainableGraph {
472
+ return {
473
+ denoising: {
474
+ vocabSize: this.config.text.vocabSize,
475
+ layerModulePaths: this.layers.map((_, i) => `model.decoder.layers.${i}`),
476
+ canvasLogits: (promptIds, canvas) => (this as unknown as DiffusionGemmaModel & { makeCache(): Cache[] }).forwardCanvasLogitsArr(promptIds, canvas),
477
+ },
478
+ };
479
+ }
480
+ pixelInput(): PixelInput | null {
481
+ const tower = this.visionTower;
482
+ return tower ? { preprocess: bytes => tower.preprocess(bytes), spliceTokens: (ids, softTokens, tokenIds) => spliceImageTokens(ids, [softTokens], tokenIds) } : null;
483
+ }
484
+ /** Dedicated DiffusionGemma SigLIP vision tower (image-text-to-text). */
485
+ readonly visionTower: DiffusionVisionTower | null;
486
+ readonly imageTokenId: number;
487
+
488
+ /** The graph binds compiled GeGLU and softcap closures, matching the
489
+ * reference's mx.compile'd operations. */
490
+ readonly makeCache!: () => Cache[];
491
+ readonly denoisingDecode: readonly MlxDenoisingDecodeRange[];
492
+ readonly denoisingAppend: readonly MlxDenoisingAppendRange[];
493
+ readonly denoisingPrefill: readonly MlxDenoisingPrefillRange[];
494
+ readonly #canvasLayers: readonly {forward: DiffDecoderLayer["forward"]}[];
495
+ readonly #unsortedLayers: readonly {forward: DiffDecoderLayer["forward"]}[];
496
+ readonly #sortedLayers: readonly {forward: DiffDecoderLayer["forward"]}[];
497
+ /** The composition this graph was built for: its KV scheme and paging are its caches' (`makeCache`). */
498
+
499
+ constructor(weights: Weights, config: ModelConfig) {
500
+ const t = config.text;
501
+ this.config = config;
502
+
503
+ this.weightsBytes = [...weights.shards.files.values()].reduce((a, f) => a + f.mmap.size, 0);
504
+ this.embed = QuantizedEmbedding.load(weights, "model.decoder.embed_tokens", config);
505
+ this.embedScale = Math.pow(t.hiddenSize, 0.5);
506
+ this.layers = Array.from(
507
+ { length: t.numHiddenLayers },
508
+ (_, i) => new DiffDecoderLayer(weights, config, i, true),
509
+ );
510
+ this.finalNorm = new RMSNorm(weights.tensor("model.decoder.norm.weight"), t.rmsNormEps);
511
+ this.selfConditioning = new SelfConditioning(weights, config, "model.decoder.self_conditioning", true);
512
+ this.encoderLayerScalars = this.layers.map((_, i) =>
513
+ weights.tensor(`model.encoder.language_model.layers.${i}.layer_scalar`),
514
+ );
515
+ this.canvasLength = t.canvasLength ?? 256;
516
+ this.#unsortedLayers = this.layers.map(layer=>{
517
+ const mlp = new CompiledGegluMLP(layer.mlp), experts = new UnsortedDiffExperts(layer.experts);
518
+ return {forward:(x:MlxArray,cache:Cache,read:PassRead,offset:number,scalar:MlxArray|null)=>layer.forwardWith(x,cache,read,offset,scalar,mlp,experts)};
519
+ });
520
+ this.#sortedLayers = this.layers.map(layer=>{
521
+ const mlp = new CompiledGegluMLP(layer.mlp), experts = new SortedDiffExperts(layer.experts);
522
+ return {forward:(x:MlxArray,cache:Cache,read:PassRead,offset:number,scalar:MlxArray|null)=>layer.forwardWith(x,cache,read,offset,scalar,mlp,experts)};
523
+ });
524
+ this.#canvasLayers = this.canvasLength * t.topKExperts < 64 ? this.#unsortedLayers : this.#sortedLayers;
525
+ const boundary = Math.ceil(64/t.topKExperts);
526
+ const ranges = [{from:1,to:boundary-1,layers:this.#unsortedLayers},{from:boundary,to:Infinity,layers:this.#sortedLayers}].filter(range=>range.from<=range.to);
527
+ this.denoisingDecode = ranges.map(range=>({from:range.from,to:range.to,operation:(canvas,cache,feedback)=>this.decoderLogits(canvas,cache,feedback,range.layers)}));
528
+ this.denoisingAppend = ranges.map(range=>({from:range.from,to:range.to,operation:(tokens,cache)=>this.extendPrefill(tokens,cache,range.layers)}));
529
+ this.denoisingPrefill = ranges.map(range=>({from:range.from,to:range.to,operation:(ids,pixels)=>{
530
+ const cache = this.makeCache();
531
+ try {
532
+ if (pixels) this.encodeVisionPrefill(ids,pixels,cache,range.layers);
533
+ else this.#encodePrompt(ids,cache,range.layers);
534
+ return cache;
535
+ } catch(error) { cleanupFailure(error,()=>disposeResources(cache)); }
536
+ }}));
537
+ this.softcap = t.finalLogitSoftcapping ?? 30.0;
538
+ this.slidingWindow = t.slidingWindow;
539
+ const raw = (config as unknown as { raw: Record<string, any> }).raw;
540
+ this.imageTokenId = (raw.image_token_id as number) ?? 258880;
541
+ this.visionTower = raw.vision_config ? new DiffusionVisionTower(weights, config) : null;
542
+
543
+
544
+ this.cacheLayers = this.layers.map(layer=>layer.layerType === "sliding_attention" ? {kind:"sliding" as const,window:this.slidingWindow}:{kind:"full" as const,turboQuant:"deferred" as const});
545
+ }
546
+
547
+ /** Build the merged prompt embeddings for an image+text turn: embed the text
548
+ * ids (image positions → pad) * embed_scale, then scatter the SigLIP vision
549
+ * features in at the contiguous <|image|> run. `pixels` is channel-first
550
+ * [1,3,H,W]. Reference EncoderModel._embed_inputs + get_image_features. */
551
+ #embedInputsVision(splicedIds: number[], pixels: MlxArray): MlxArray {
552
+ if (!this.visionTower) throw new Error("this checkpoint has no vision tower");
553
+ const padId = 0; // pad_token_id (reference where(vision_mask, pad, ids))
554
+ const llm = splicedIds.map((id) => (id === this.imageTokenId ? padId : id));
555
+ const ids = MlxArray.fromInt32(Int32Array.from(llm), [1, llm.length]);
556
+ let embeds = this.embed.encode(ids);
557
+ ids.dispose();
558
+ embeds = disposing(embeds, ops.mulScalar(embeds, this.embedScale)); // [1, L, hidden]
559
+
560
+ const feats = this.visionTower.getImageFeatures(pixels); // [1, numSoft, hidden]
561
+ const featsCast = feats.astype(embeds.dtype);
562
+ feats.dispose();
563
+ // image tokens are one contiguous run (boi + image*N + eoi)
564
+ const start = splicedIds.indexOf(this.imageTokenId);
565
+ const count = splicedIds.filter((id) => id === this.imageTokenId).length;
566
+ const hidden = embeds.shape[embeds.shape.length - 1]!;
567
+ embeds = disposing(
568
+ embeds,
569
+ ops.sliceUpdate(embeds, featsCast, [0, start, 0], [1, start + count, hidden]),
570
+ );
571
+ featsCast.dispose();
572
+ return embeds;
573
+ }
574
+
575
+ /** The vision prompt's image positions, bool [N]: every <|image|> token. The
576
+ * encoder window read lets them attend each other both ways (reference
577
+ * _vision_block_overlay). */
578
+ #imagePositions(splicedIds: number[]): MlxArray {
579
+ const flags = splicedIds.map((id) => (id === this.imageTokenId ? 1 : 0));
580
+ using ints = MlxArray.fromInt32(Int32Array.from(flags), [flags.length]);
581
+ return ints.astype(Dtype.bool);
582
+ }
583
+
584
+ /** Image-text-to-text encoder prefill: merged embeddings through all layers,
585
+ * each reading through the encoder window (causal over the whole prompt, no
586
+ * sliding window, image positions both ways; reference _make_encoder_masks +
587
+ * _vision_block_overlay, one mask for every layer), filling `cache`. The
588
+ * canvas decoder then reads it like the text path. */
589
+ encodeVisionPrefill(splicedIds: number[], pixels: MlxArray, cache: Cache[], layers: readonly {forward:DiffDecoderLayer["forward"]}[] = this.layers): void {
590
+ let h = this.#embedInputsVision(splicedIds, pixels); // [1, L, hidden] (*embed_scale)
591
+ using media = this.#imagePositions(splicedIds);
592
+ const read = encoderRead(media);
593
+ for (let i = 0; i < this.layers.length; i++) {
594
+ const layer = layers[i]!;
595
+ const next = layer.forward(h, cache[i]!, read, 0, this.encoderLayerScalars[i]!);
596
+ h.dispose();
597
+ h = next;
598
+ }
599
+ h.dispose();
600
+ }
601
+
602
+ /** Releases the rope frequency tables its layers built; the weights are its owner's. */
603
+ dispose(): void {
604
+ disposeResources(this.layers.flatMap(layer => layer.attn.ropeFreqs ? [layer.attn.ropeFreqs] : []));
605
+ }
606
+
607
+ /** One cache per layer, the lego the composition's KV scheme names for it
608
+ * (bf16, bf16-first affine, per listed layer under a per-layer scheme, or
609
+ * TurboQuant on full-attention layers, read deferred as Gemma 4 reads it), a
610
+ * ring of the sliding window on sliding layers, all sharing one mask memo;
611
+ * under the composition's paging the full-attention layers hold pages of
612
+ * what the scheme names (`maybePageKv`, growing from one block). The encoder
613
+ * prefill fills them, the decoder canvas pass reads them. */
614
+ readonly cachePaging = true;
615
+ readonly cacheLayers: readonly MlxCacheLayer[];
616
+
617
+
618
+ /** Encoder prefill: run all layers over the prompt through the window read,
619
+ * filling the cache. Discards the hidden. */
620
+ encodePrompt(promptIds: number[], cache: Cache[]): void { this.#encodePrompt(promptIds,cache,this.layers); }
621
+
622
+ #encodePrompt(promptIds: number[], cache: Cache[], layers: readonly {forward:DiffDecoderLayer["forward"]}[]): void {
623
+ const ids = MlxArray.fromInt32(Int32Array.from(promptIds), [1, promptIds.length]);
624
+ let h = this.embed.encode(ids);
625
+ ids.dispose();
626
+ h = disposing(h, ops.mulScalar(h, this.embedScale));
627
+ for (let i = 0; i < this.layers.length; i++) {
628
+ const next = layers[i]!.forward(h, cache[i]!, windowRead, 0, this.encoderLayerScalars[i]!);
629
+ h.dispose();
630
+ h = next;
631
+ }
632
+ h.dispose();
633
+ }
634
+
635
+ /** Diagnostic: run the encoder, invoking `onLayer(i, h)` after each layer
636
+ * (h is the raw layer output, pre-final-norm) and `onEmbed(h)` on the
637
+ * embedding. Returns the final pre-norm hidden. Fills `cache`. */
638
+ encoderTrace(
639
+ promptIds: number[],
640
+ cache: Cache[],
641
+ onEmbed: (h: MlxArray) => void,
642
+ onLayer: (i: number, h: MlxArray) => void,
643
+ ): MlxArray {
644
+ const ids = MlxArray.fromInt32(Int32Array.from(promptIds), [1, promptIds.length]);
645
+ let h = this.embed.encode(ids);
646
+ ids.dispose();
647
+ h = disposing(h, ops.mulScalar(h, this.embedScale));
648
+ onEmbed(h);
649
+ for (let i = 0; i < this.layers.length; i++) {
650
+ const next = this.layers[i]!.forward(h, cache[i]!, windowRead, 0, this.encoderLayerScalars[i]!);
651
+ h.dispose();
652
+ h = next;
653
+ onLayer(i, h);
654
+ }
655
+ return h;
656
+ }
657
+
658
+ /** Diagnostic: run encoder layer 0 sub-components (attention vs parallel
659
+ * dense/MoE) and dump each via `cb(name, h)`. */
660
+ diagLayer0(promptIds: number[], cache: Cache[], cb: (name: string, h: MlxArray) => void): void {
661
+ const ids = MlxArray.fromInt32(Int32Array.from(promptIds), [1, promptIds.length]);
662
+ let emb = this.embed.encode(ids);
663
+ ids.dispose();
664
+ emb = disposing(emb, ops.mulScalar(emb, this.embedScale));
665
+ const layer = this.layers[0]!;
666
+ const ls0 = this.encoderLayerScalars[0]!;
667
+
668
+ const residual = emb;
669
+ const hn = layer.inputNorm.forward(emb);
670
+ const attnOut = layer.attn.forward(hn, cache[0]!, windowRead, 0);
671
+ hn.dispose();
672
+ cb("attn_out", attnOut);
673
+ const postAttn = layer.postAttnNorm.forward(attnOut);
674
+ attnOut.dispose();
675
+ const hMid = ops.add(residual, postAttn);
676
+ postAttn.dispose();
677
+ cb("h_mid", hMid);
678
+
679
+ const dense = layer.preFfNorm.forward(hMid);
680
+ let h1 = layer.mlp.forward(dense);
681
+ dense.dispose();
682
+ h1 = disposing(h1, layer.postFfNorm1.forward(h1));
683
+ cb("h1", h1);
684
+ h1.dispose();
685
+
686
+ const shape = hMid.shape;
687
+ const hidden = shape[shape.length - 1]!;
688
+ const flat = ops.reshape(hMid, [-1, hidden]);
689
+ const { indices, weights } = layer.router.forward(flat);
690
+ cb("router_idx", indices.astype(Dtype.float32));
691
+ cb("router_w", weights.astype(Dtype.float32));
692
+ let h2 = layer.preFfNorm2.forward(flat);
693
+ flat.dispose();
694
+ h2 = disposing(h2, layer.experts.forward(h2, indices, weights));
695
+ indices.dispose();
696
+ weights.dispose();
697
+ h2 = disposing(h2, ops.reshape(h2, shape));
698
+ cb("experts_raw", h2);
699
+ h2 = disposing(h2, layer.postFfNorm2.forward(h2));
700
+ cb("h2", h2);
701
+ h2.dispose();
702
+ void ls0;
703
+ emb.dispose();
704
+ hMid.dispose();
705
+ }
706
+
707
+ /** Diagnostic: encoder prefill then finalNorm(h) — matches the reference
708
+ * EncoderModel.__call__ output (decoder.norm(h)) for per-stage localization. */
709
+ encodePromptHidden(promptIds: number[], cache: Cache[]): MlxArray {
710
+ const ids = MlxArray.fromInt32(Int32Array.from(promptIds), [1, promptIds.length]);
711
+ let h = this.embed.encode(ids);
712
+ ids.dispose();
713
+ h = disposing(h, ops.mulScalar(h, this.embedScale));
714
+ for (let i = 0; i < this.layers.length; i++) {
715
+ const next = this.layers[i]!.forward(h, cache[i]!, windowRead, 0, this.encoderLayerScalars[i]!);
716
+ h.dispose();
717
+ h = next;
718
+ }
719
+ return disposing(h, this.finalNorm.forward(h));
720
+ }
721
+
722
+ /** Embed the canvas + self-conditioning. For the first step both signals are
723
+ * None → zero signal (which still applies SelfConditioning's post RMSNorm). */
724
+ #embedCanvas(
725
+ canvasIds: MlxArray,
726
+ scLogits: MlxArray | null,
727
+ scEmbeddings: MlxArray | null,
728
+ ): MlxArray {
729
+ let inputsEmbeds = this.embed.encode(canvasIds);
730
+ inputsEmbeds = disposing(inputsEmbeds, ops.mulScalar(inputsEmbeds, this.embedScale));
731
+ let soft: MlxArray;
732
+ let softOwned = true;
733
+ if (scEmbeddings) {
734
+ soft = scEmbeddings.astype(inputsEmbeds.dtype);
735
+ } else if (scLogits === null) {
736
+ soft = ops.zeros(inputsEmbeds.shape, inputsEmbeds.dtype);
737
+ } else {
738
+ // probs @ embed_tokens.weight (quantized, transpose=false), * embed_scale.
739
+ const probs = ops.softmaxAxis(scLogits, -1, true);
740
+ const probsCast = probs.astype(inputsEmbeds.dtype);
741
+ probs.dispose();
742
+ const se = ops.quantizedMatmul(
743
+ probsCast,
744
+ this.embed.w,
745
+ this.embed.scales,
746
+ this.embed.biases,
747
+ this.embed.spec,
748
+ false,
749
+ );
750
+ probsCast.dispose();
751
+ soft = ops.mulScalar(se, this.embedScale);
752
+ se.dispose();
753
+ }
754
+ const out = this.selfConditioning.forward(inputsEmbeds, soft);
755
+ inputsEmbeds.dispose();
756
+ if (softOwned) soft.dispose();
757
+ return out;
758
+ }
759
+
760
+ /** Decoder canvas pass over an on-device canvas: embed canvas -> 30 layers,
761
+ * each reading its cache through the block read (the cache builds the
762
+ * reference _make_decoder_masks sliding mask and keeps the newest
763
+ * sliding_window - 1 encoder positions) -> final norm. Returns hidden
764
+ * [1, canvasLength, hidden]. The D2 engine drives this per denoising step
765
+ * (canvas stays on-device). */
766
+ decodeCanvasArr(
767
+ canvasArr: MlxArray,
768
+ cache: Cache[],
769
+ scLogits: MlxArray | null = null,
770
+ scEmbeddings: MlxArray | null = null,
771
+ layers: readonly {forward:DiffDecoderLayer["forward"]}[] = this.layers,
772
+ ): MlxArray {
773
+ let h = this.#embedCanvas(canvasArr, scLogits, scEmbeddings);
774
+ const offset = cache[0]!.offset;
775
+ for (let i = 0; i < this.layers.length; i++) {
776
+ const next = layers[i]!.forward(h, cache[i]!, blockRead, offset, null);
777
+ h.dispose();
778
+ h = next;
779
+ }
780
+ h = disposing(h, this.finalNorm.forward(h));
781
+ return h;
782
+ }
783
+
784
+ /** Decoder canvas pass from host ids (D1 path). */
785
+ decodeCanvas(
786
+ canvasIds: number[],
787
+ cache: Cache[],
788
+ scLogits: MlxArray | null = null,
789
+ scEmbeddings: MlxArray | null = null,
790
+ ): MlxArray {
791
+ const ids = MlxArray.fromInt32(Int32Array.from(canvasIds), [1, canvasIds.length]);
792
+ const h = this.decodeCanvasArr(ids, cache, scLogits, scEmbeddings);
793
+ ids.dispose();
794
+ return h;
795
+ }
796
+
797
+ // ---- D2 denoising-engine surface ----
798
+
799
+ /** One denoising-step decoder pass -> softcapped logits [1, canvas, vocab]. */
800
+ decoderLogits(canvasArr: MlxArray, cache: Cache[], scEmbeddings: MlxArray | null, layers = this.#canvasLayers): MlxArray {
801
+ const h = this.decodeCanvasArr(canvasArr, cache, null, scEmbeddings,layers);
802
+ try { return this.logitsFromHidden(h); }
803
+ finally { h.dispose(); }
804
+ }
805
+
806
+ /** Dequantized embedding table (once per call), for self-conditioning soft
807
+ * embeddings — matches optiq's `_diffusion_soft_embedding_weight`. */
808
+ dequantEmbedWeight(): MlxArray {
809
+ return ops.dequantize(this.embed.w, this.embed.scales, this.embed.biases, this.embed.spec);
810
+ }
811
+
812
+ /** Self-conditioning soft embeddings for the NEXT step (confidence sampler):
813
+ * softmax(processed, precise) @ dequant(embed) * embed_scale. */
814
+ softEmbeddings(processedLogits: MlxArray, dequantWeight: MlxArray): MlxArray {
815
+ const probs = ops.softmaxAxis(processedLogits, -1, true);
816
+ const probsCast = probs.astype(dequantWeight.dtype);
817
+ probs.dispose();
818
+ const se = ops.matmul(probsCast, dequantWeight);
819
+ probsCast.dispose();
820
+ const cast = se.astype(dequantWeight.dtype);
821
+ se.dispose();
822
+ const out = ops.mulScalar(cast, this.embedScale);
823
+ cast.dispose();
824
+ return out;
825
+ }
826
+
827
+ /** Re-prefill the encoder over an accepted canvas block (continuation), so the
828
+ * next block's decoder sees it as context. Appends to the existing cache. */
829
+ extendPrefill(canvasArr: MlxArray, cache: Cache[], layers: readonly {forward:DiffDecoderLayer["forward"]}[] = this.layers): void {
830
+ let h = this.embed.encode(canvasArr);
831
+ h = disposing(h, ops.mulScalar(h, this.embedScale));
832
+ for (let i = 0; i < this.layers.length; i++) {
833
+ const next = layers[i]!.forward(h, cache[i]!, windowRead, 0, this.encoderLayerScalars[i]!);
834
+ h.dispose();
835
+ h = next;
836
+ }
837
+ h.dispose();
838
+ }
839
+
840
+ /** Tied quantized head + fp32 softcap. hidden [1, L, hidden] -> [1, L, vocab]. */
841
+ logitsFromHidden(h: MlxArray): MlxArray {
842
+ const logits = this.embed.asLinear(h);
843
+ // fp32-upcast tanh softcap, fused like the reference's mx.compile'd make_compiled_softcap
844
+ // (stays f32; the golden is dumped f32). The flag-off arm spells out the same math.
845
+ const wide = logits.astype(Dtype.float32);
846
+ logits.dispose();
847
+ const capped = compiledLogitSoftcap(wide, this.softcap);
848
+ wide.dispose();
849
+ return capped;
850
+ }
851
+
852
+ /** D1 entry point: prompt + canvas ids -> softcapped logits [1, canvas, vocab].
853
+ * Builds a fresh cache, prefills the encoder, runs one decoder pass. */
854
+ forwardCanvasLogits(this: DiffusionGemmaModel & { makeCache(): Cache[] }, promptIds: number[], canvasIds: number[]): MlxArray {
855
+ const cache = this.makeCache();
856
+ try {
857
+ this.encodePrompt(promptIds, cache);
858
+ const h = this.decodeCanvas(canvasIds, cache);
859
+ const logits = this.logitsFromHidden(h);
860
+ h.dispose();
861
+ return logits;
862
+ } finally {
863
+ for (const c of cache) c.dispose();
864
+ }
865
+ }
866
+
867
+ /** Training forward over an on-device canvas (D5 LoRA): prompt + canvas array
868
+ * -> softcapped logits [1, L, vocab], differentiable w.r.t. mounted LoRA. */
869
+ forwardCanvasLogitsArr(this: DiffusionGemmaModel & { makeCache(): Cache[] }, promptIds: number[], canvasArr: MlxArray): MlxArray {
870
+ const cache = this.makeCache();
871
+ try {
872
+ this.encodePrompt(promptIds, cache);
873
+ const h = this.decodeCanvasArr(canvasArr, cache);
874
+ const logits = this.logitsFromHidden(h);
875
+ h.dispose();
876
+ return logits;
877
+ } finally {
878
+ for (const c of cache) c.dispose();
879
+ }
880
+ }
881
+
882
+ readonly prefixBase = "model.decoder";
883
+
884
+ /** LoRA-able decoder linears (the optiq diffusion DEFAULT_LORA_KEYS: attention
885
+ * q/k/v/o + dense MLP gate/up/down per layer; full layers have no v_proj). */
886
+ loraTargets(): Map<string, QuantizedLinear> {
887
+ const m = new Map<string, QuantizedLinear>();
888
+ this.layers.forEach((layer, i) => {
889
+ const p = `model.decoder.layers.${i}`;
890
+ m.set(`${p}.self_attn.q_proj`, layer.attn.qProj);
891
+ m.set(`${p}.self_attn.k_proj`, layer.attn.kProj);
892
+ if (layer.attn.vProj) m.set(`${p}.self_attn.v_proj`, layer.attn.vProj);
893
+ m.set(`${p}.self_attn.o_proj`, layer.attn.oProj);
894
+ m.set(`${p}.mlp.gate_proj`, layer.mlp.gate);
895
+ m.set(`${p}.mlp.up_proj`, layer.mlp.up);
896
+ m.set(`${p}.mlp.down_proj`, layer.mlp.down);
897
+ });
898
+ return m;
899
+ }
900
+ }