@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,355 @@
1
+ import type { MlxCacheLayer, MlxMixedRange, MlxSequenceRange } from "../../contracts/mlx/graph";
2
+ // Concrete model graph for Qwen3-MoE (model_type `qwen3_moe`,
3
+ // Qwen3MoeForCausalLM). A STRAIGHT op-for-op transcription of the oracle
4
+ // (`mlx_lm/models/qwen3_moe.py` +
5
+ // switch_layers.py SwitchGLU + activations.py swiglu). Each op below carries
6
+ // the exact oracle line it copies — no abstraction, no reorder, no
7
+ // "optimization". The ONLY intentional deviation from a naive transcription is
8
+ // that the expert swiglu is mx.compile-COMPILED (like the oracle's
9
+ // activations.py `@partial(mx.compile) def swiglu`), matching both the greedy
10
+ // token stream AND the dispatched Metal kernel set.
11
+ //
12
+ // Attention is identical to plain qwen3 (per-head q/k RMSNorm over head_dim
13
+ // before RoPE, full-head RoPE, GQA, SDPA). The MoE block is
14
+ // Qwen3MoeSparseMoeBlock: gate Linear -> precise softmax over ALL experts ->
15
+ // argpartition top-k -> take_along_axis -> renormalize (norm_topk_prob) ->
16
+ // SwitchGLU(swiglu) experts -> weighted sum. Per decoder_sparse_step /
17
+ // mlp_only_layers a layer may instead be a dense MLP (MLP class); both are
18
+ // handled faithfully. Weights are untied (separate lm_head). Config: 48 layers,
19
+ // hidden 2048, 128 experts, top-8, moe_intermediate 768, rope_theta 1e7.
20
+
21
+ import type { ModelConfig } from "../../artifacts/config";
22
+ import type { Weights } from "../../artifacts/weights";
23
+ import { MlxArray } from "@mlx-bun/mlx/array";
24
+ import * as ops from "@mlx-bun/mlx/ops";
25
+ import { disposing } from "../../layers/helpers";
26
+ import { compiledSwiglu } from "../../layers/swiglu";
27
+ import { LoraState } from "../../layers/lora";
28
+ import type { GraphDeclaration } from "../../contracts/portable/graph";
29
+ import type { MlxRowTable, MlxCapturingForward, MlxDeclaredGraph, MlxHeadForward, MlxPhaseForward, MlxPromptRange, MlxTokenPhases, MlxVerifyForward } from "../../contracts/mlx/graph";
30
+ import { declareGraph } from "../capabilities";
31
+ import { DEFAULT_PHASE_ROWS, rowTableFrom, rowTable, type ReadPhase } from "../graph";
32
+ import { QuantizedEmbedding } from "../../layers/quantized-embedding";
33
+ import { QuantizedLinear, QuantizedLinearPlain } from "../../layers/quantized-linear";
34
+ import { QuantizedSwitchLinear } from "../../layers/quantized-switch-linear";
35
+ import { RMSNorm } from "../../layers/normalization";
36
+ import { type Cache } from "../../contracts/mlx/cache";
37
+ import { Qwen3Attention } from "./qwen3";
38
+
39
+ // ── qwen3_moe.py Attention (== plain qwen3, rope_theta default 1e7) ───────────
40
+ const MOE_ROPE_THETA = 10000000; // args.rope_theta
41
+
42
+ // ── qwen3_moe.py MLP (dense-layer FFN) ───────────────────────────────────────
43
+ // def __call__(self, x): return self.down_proj(swiglu(self.gate_proj(x), self.up_proj(x)))
44
+ class MLP {
45
+ readonly kind = "dense";
46
+ readonly gateProj: QuantizedLinear;
47
+ readonly upProj: QuantizedLinear;
48
+ readonly downProj: QuantizedLinear;
49
+
50
+ readonly #gate: QuantizedLinearPlain;
51
+ readonly #up: QuantizedLinearPlain;
52
+ readonly #down: QuantizedLinearPlain;
53
+
54
+ constructor(weights: Weights, config: ModelConfig, prefix: string) {
55
+ this.gateProj = QuantizedLinear.load(weights, `${prefix}.gate_proj`, config); // nn.Linear(dim, hidden_dim, bias=False)
56
+ this.upProj = QuantizedLinear.load(weights, `${prefix}.up_proj`, config);
57
+ this.downProj = QuantizedLinear.load(weights, `${prefix}.down_proj`, config);
58
+ this.#gate = new QuantizedLinearPlain(this.gateProj);
59
+ this.#up = new QuantizedLinearPlain(this.upProj);
60
+ this.#down = new QuantizedLinearPlain(this.downProj);
61
+ }
62
+
63
+ forward(x: MlxArray): MlxArray {
64
+ const g = this.#gate.forward(x); // self.gate_proj(x)
65
+ const u = this.#up.forward(x); // self.up_proj(x)
66
+ const s = compiledSwiglu(g, u); // swiglu(gate, x) (mx.compile)
67
+ g.dispose(); u.dispose();
68
+ const y = this.#down.forward(s); s.dispose(); // self.down_proj(...)
69
+ return y;
70
+ }
71
+ }
72
+
73
+ // ── switch_layers.py SwitchGLU (swiglu activation, quantized experts) ─────────
74
+ // The three per-expert projections are the stacked switch_mlp.{gate,up,down}_proj
75
+ // tensors; the activation is the SAME @mx.compile swiglu as activations.py.
76
+ export class Qwen3SwitchWeights {
77
+ readonly gateProj: QuantizedSwitchLinear;
78
+ readonly upProj: QuantizedSwitchLinear;
79
+ readonly downProj: QuantizedSwitchLinear;
80
+
81
+ constructor(weights: Weights, config: ModelConfig, prefix: string) {
82
+ this.gateProj = QuantizedSwitchLinear.load(weights, `${prefix}.gate_proj`, config);
83
+ this.upProj = QuantizedSwitchLinear.load(weights, `${prefix}.up_proj`, config);
84
+ this.downProj = QuantizedSwitchLinear.load(weights, `${prefix}.down_proj`, config);
85
+ }
86
+ }
87
+
88
+ interface ExpertForward { forward(x: MlxArray, indices: MlxArray): MlxArray }
89
+
90
+ /** Gather projections over the original token order. */
91
+ export class Qwen3UnsortedExperts implements ExpertForward {
92
+ constructor(readonly weights: Qwen3SwitchWeights) {}
93
+ forward(x: MlxArray, indices: MlxArray): MlxArray {
94
+ using rows = ops.expandDims(x, -2);
95
+ using expanded = ops.expandDims(rows, -3);
96
+ using up = this.weights.upProj.forward(expanded, indices, false);
97
+ using gate = this.weights.gateProj.forward(expanded, indices, false);
98
+ using hidden = compiledSwiglu(gate, up);
99
+ const output = this.weights.downProj.forward(hidden, indices, false);
100
+ const shape = output.shape;
101
+ shape.splice(shape.length - 2, 1);
102
+ return disposing(output, ops.reshape(output, shape));
103
+ }
104
+ }
105
+
106
+ /** Sorted gather followed by the reference inverse permutation. */
107
+ export class Qwen3SortedExperts implements ExpertForward {
108
+ constructor(readonly weights: Qwen3SwitchWeights) {}
109
+ forward(x: MlxArray, indices: MlxArray): MlxArray {
110
+ const topK = indices.shape[indices.ndim - 1]!;
111
+ using rows = ops.expandDims(x, -2);
112
+ using expanded = ops.expandDims(rows, -3);
113
+ using flatIndices = ops.reshape(indices, [indices.size]);
114
+ using order = ops.argsortAxis(flatIndices, 0);
115
+ using inverse = ops.argsortAxis(order, 0);
116
+ using count = ops.scalarLike(topK, order);
117
+ using rowIndices = ops.floorDivide(order, count);
118
+ const width = expanded.shape[4]!;
119
+ using flat = ops.reshape(expanded, [-1, 1, width]);
120
+ using sortedRows = ops.takeAxis(flat, rowIndices, 0);
121
+ using sortedIndices = ops.takeAxis(flatIndices, order, 0);
122
+ using up = this.weights.upProj.forward(sortedRows, sortedIndices, true);
123
+ using gate = this.weights.gateProj.forward(sortedRows, sortedIndices, true);
124
+ using hidden = compiledSwiglu(gate, up);
125
+ using sortedOutput = this.weights.downProj.forward(hidden, sortedIndices, true);
126
+ using output = ops.takeAxis(sortedOutput, inverse, 0);
127
+ const [batch, length, selected] = indices.shape;
128
+ const outputWidth = output.shape[output.ndim - 1]!;
129
+ using unflattened = ops.reshape(output, [batch!, length!, selected!, 1, outputWidth]);
130
+ const shape = unflattened.shape;
131
+ shape.splice(shape.length - 2, 1);
132
+ return ops.reshape(unflattened, shape);
133
+ }
134
+ }
135
+
136
+ // ── qwen3_moe.py Qwen3MoeSparseMoeBlock ──────────────────────────────────────
137
+ class Qwen3MoeSparseMoeBlock {
138
+ readonly kind = "moe";
139
+ readonly gate: QuantizedLinear;
140
+ readonly switchMlp: Qwen3SwitchWeights;
141
+ readonly numExperts: number;
142
+ readonly topK: number;
143
+ readonly normTopkProb: boolean;
144
+ readonly #gate: QuantizedLinearPlain;
145
+ readonly #normalize: (scores: MlxArray) => MlxArray;
146
+
147
+ constructor(weights: Weights, config: ModelConfig, prefix: string) {
148
+ const t = config.text;
149
+ this.numExperts = t.numExperts; // self.num_experts
150
+ this.topK = t.topKExperts; // self.top_k
151
+ this.normTopkProb = t.normTopkProb; // self.norm_topk_prob
152
+ this.gate = QuantizedLinear.load(weights, `${prefix}.gate`, config); // nn.Linear(dim, num_experts, bias=False)
153
+ this.switchMlp = new Qwen3SwitchWeights(weights, config, `${prefix}.switch_mlp`); // SwitchGLU(dim, moe_inter, num_experts)
154
+ this.#gate = new QuantizedLinearPlain(this.gate);
155
+ this.#normalize = this.normTopkProb ? scores => {
156
+ using denominator = ops.sumAxis(scores, -1, true);
157
+ return disposing(scores, ops.div(scores, denominator));
158
+ } : scores => scores;
159
+ }
160
+
161
+ forward(x: MlxArray, experts: ExpertForward): MlxArray {
162
+ let gates = this.#gate.forward(x); // gates = self.gate(x)
163
+ gates = disposing(gates, ops.softmaxAxis(gates, -1, true)); // gates = mx.softmax(gates, axis=-1, precise=True)
164
+
165
+ const k = this.topK;
166
+ const [B, L] = gates.shape as [number, number, number];
167
+ // inds = mx.argpartition(gates, kth=-k, axis=-1)[..., -k:]
168
+ const part = ops.argpartitionAxis(gates, this.numExperts - k, -1); // argpartition(..., kth=-k)
169
+ const inds = part.slice([0, 0, this.numExperts - k], [B, L, this.numExperts]);
170
+ part.dispose();
171
+
172
+ let scores = ops.takeAlongAxis(gates, inds, -1); // scores = mx.take_along_axis(gates, inds, axis=-1)
173
+ gates.dispose();
174
+ scores = this.#normalize(scores);
175
+
176
+ const y = experts.forward(x, inds); // y = self.switch_mlp(x, inds)
177
+ inds.dispose();
178
+ // y = (y * scores[..., None]).sum(axis=-2)
179
+ const sExp = ops.expandDims(scores, -1); // scores[..., None]
180
+ scores.dispose();
181
+ const wy = ops.mul(y, sExp); // y * scores[..., None]
182
+ y.dispose(); sExp.dispose();
183
+ return disposing(wy, ops.sumAxis(wy, -2, false)); // .sum(axis=-2)
184
+ }
185
+ }
186
+
187
+ // ── qwen3_moe.py Qwen3MoeDecoderLayer ────────────────────────────────────────
188
+ class Qwen3MoeDecoderLayer {
189
+ readonly selfAttn: Qwen3Attention;
190
+ readonly mlp: Qwen3MoeSparseMoeBlock | MLP;
191
+ readonly inputLayernorm: RMSNorm;
192
+ readonly postAttentionLayernorm: RMSNorm;
193
+
194
+ constructor(weights: Weights, config: ModelConfig, prefix: string, layerIdx: number) {
195
+ const t = config.text;
196
+ this.selfAttn = new Qwen3Attention(weights, config, `${prefix}.self_attn`, MOE_ROPE_THETA);
197
+ this.inputLayernorm = new RMSNorm(weights.tensor(`${prefix}.input_layernorm.weight`), t.rmsNormEps);
198
+ this.postAttentionLayernorm = new RMSNorm(weights.tensor(`${prefix}.post_attention_layernorm.weight`), t.rmsNormEps);
199
+ // if (layer_idx not in mlp_only_layers) and (num_experts > 0 and
200
+ // (layer_idx + 1) % decoder_sparse_step == 0): Qwen3MoeSparseMoeBlock else MLP
201
+ const isMoe =
202
+ !t.mlpOnlyLayers.includes(layerIdx) &&
203
+ t.numExperts > 0 &&
204
+ (layerIdx + 1) % t.decoderSparseStep === 0;
205
+ this.mlp = isMoe
206
+ ? new Qwen3MoeSparseMoeBlock(weights, config, `${prefix}.mlp`)
207
+ : new MLP(weights, config, `${prefix}.mlp`);
208
+ }
209
+ }
210
+
211
+ class FixedMoeBlock {
212
+ constructor(readonly weights: Qwen3MoeSparseMoeBlock, readonly experts: ExpertForward) {}
213
+ forward(x: MlxArray): MlxArray { return this.weights.forward(x, this.experts); }
214
+ }
215
+ class Qwen3MoeLayerView {
216
+ readonly #mlp: MLP | FixedMoeBlock;
217
+ constructor(readonly base: Qwen3MoeDecoderLayer, Experts: new (weights: Qwen3SwitchWeights) => ExpertForward) {
218
+ this.#mlp = base.mlp.kind === "dense" ? base.mlp : new FixedMoeBlock(base.mlp, new Experts(base.mlp.switchMlp));
219
+ }
220
+ forward(x: MlxArray, cache: Cache, phase: ReadPhase): MlxArray {
221
+ const xn = this.base.inputLayernorm.forward(x); // self.input_layernorm(x)
222
+ const r1 = this.base.selfAttn.forward(xn, cache, phase); // r = self.self_attn(..., mask, cache)
223
+ xn.dispose();
224
+ const h = ops.add(x, r1); r1.dispose(); // h = x + r
225
+ const hn = this.base.postAttentionLayernorm.forward(h); // self.post_attention_layernorm(h)
226
+ const r2 = this.#mlp.forward(hn); hn.dispose(); // r = self.mlp(...)
227
+ const out = ops.add(h, r2); h.dispose(); r2.dispose(); // out = h + r
228
+ return out;
229
+ }
230
+ }
231
+
232
+ // ── qwen3_moe.py Qwen3MoeModel + Model ───────────────────────────────────────
233
+ export class Qwen3MoeModel implements MlxDeclaredGraph, MlxTokenPhases {
234
+ readonly config: ModelConfig;
235
+ readonly weightsBytes: number;
236
+ readonly prefixBase = "model";
237
+ readonly loraState = new LoraState();
238
+ /** An ordinary autoregressive graph: every method over every cache lego. */
239
+ get graphCapabilities(): GraphDeclaration { return declareGraph(); }
240
+ readonly embed: QuantizedEmbedding;
241
+ readonly layers: Qwen3MoeDecoderLayer[];
242
+ readonly finalNorm: RMSNorm;
243
+ /** Untied lm_head (tie_word_embeddings=false for this family). */
244
+ readonly lmHead: QuantizedLinear | null;
245
+ readonly #head: (hidden: MlxArray) => MlxArray;
246
+
247
+ constructor(weights: Weights, config: ModelConfig, maxPhaseRows = DEFAULT_PHASE_ROWS) {
248
+ this.config = config;
249
+ this.weightsBytes = [...weights.shards.files.values()]
250
+ .reduce((a, f) => a + f.mmap.size, 0);
251
+ this.embed = QuantizedEmbedding.load(weights, "model.embed_tokens", config); // nn.Embedding
252
+ this.layers = Array.from(
253
+ { length: config.text.numHiddenLayers },
254
+ (_, i) => new Qwen3MoeDecoderLayer(weights, config, `model.layers.${i}`, i),
255
+ );
256
+ this.finalNorm = new RMSNorm(weights.tensor("model.norm.weight"), config.text.rmsNormEps); // self.norm
257
+ this.lmHead = config.text.tieWordEmbeddings
258
+ ? null
259
+ : QuantizedLinear.load(weights, "lm_head", config); // self.lm_head
260
+
261
+ const head = this.lmHead && new QuantizedLinearPlain(this.lmHead);
262
+ this.#head = head ? hidden => head.forward(hidden) : hidden => this.embed.asLinear(hidden);
263
+ const unsorted = this.layers.map(layer => new Qwen3MoeLayerView(layer, Qwen3UnsortedExperts));
264
+ const sorted = this.layers.map(layer => new Qwen3MoeLayerView(layer, Qwen3SortedExperts));
265
+ const sortFrom = Math.ceil(64 / config.text.topKExperts);
266
+ const decodeUnsorted = (ids: MlxArray, cache: Cache[]) => this.forwardLayers(this.embed.encode(ids), cache, "decode", unsorted);
267
+ const decodeSorted = (ids: MlxArray, cache: Cache[]) => this.forwardLayers(this.embed.encode(ids), cache, "decode", sorted);
268
+ const windowUnsorted = (ids: MlxArray, cache: Cache[]) => this.forwardLayers(this.embed.encode(ids), cache, "window", unsorted);
269
+ const windowSorted = (ids: MlxArray, cache: Cache[]) => this.forwardLayers(this.embed.encode(ids), cache, "window", sorted);
270
+ this.prompt = [{ from: 1, to: sortFrom - 1, operation: windowUnsorted }, { from: sortFrom, to: Infinity, operation: windowSorted }]
271
+ .filter(range => range.from <= range.to);
272
+ this.promptChunk = this.prompt;
273
+ this.multiSequencePrompt = this.prompt;
274
+ this.multiSequencePromptChunk = this.multiSequencePrompt;
275
+ this.decode = rowTableFrom(rows => rows < sortFrom ? decodeUnsorted : decodeSorted, maxPhaseRows);
276
+ const windows = rowTableFrom(rows => rows < sortFrom ? windowUnsorted : windowSorted, maxPhaseRows);
277
+ this.verify = windows;
278
+ this.logits = rowTable(hidden => this.#head(hidden), maxPhaseRows);
279
+ const sequence: MlxSequenceRange[] = [
280
+ { from: 1, to: sortFrom - 1, operation: (ids, cache) => windowUnsorted(ids, cache) },
281
+ { from: sortFrom, to: Infinity, operation: (ids, cache) => windowSorted(ids, cache) },
282
+ ];
283
+ this.sequence = sequence.filter(range => range.from <= range.to);
284
+ this.appendKnown = this.sequence;
285
+
286
+ this.cacheLayers = this.layers.map(() => ({kind:"full" as const,turboQuant:"dense" as const}));
287
+ }
288
+
289
+ loraTargets(): Map<string, QuantizedLinear> {
290
+ const out = new Map<string, QuantizedLinear>();
291
+ for (let i = 0; i < this.layers.length; i++) {
292
+ const l = this.layers[i]!;
293
+ const p = `model.layers.${i}`;
294
+ out.set(`${p}.self_attn.q_proj`, l.selfAttn.qProj);
295
+ out.set(`${p}.self_attn.k_proj`, l.selfAttn.kProj);
296
+ out.set(`${p}.self_attn.v_proj`, l.selfAttn.vProj);
297
+ out.set(`${p}.self_attn.o_proj`, l.selfAttn.oProj);
298
+ if (l.mlp.kind === "dense") {
299
+ out.set(`${p}.mlp.gate_proj`, l.mlp.gateProj);
300
+ out.set(`${p}.mlp.up_proj`, l.mlp.upProj);
301
+ out.set(`${p}.mlp.down_proj`, l.mlp.downProj);
302
+ }
303
+ else out.set(`${p}.mlp.gate`, l.mlp.gate);
304
+ // Stacked expert weights are separate from the two-dimensional router target.
305
+ }
306
+ return out;
307
+ }
308
+
309
+ /** Builds no constants and opens no weights: nothing to release. */
310
+ dispose(): void {}
311
+
312
+ /** One cache per layer, sharing one mask memo (create_attention_mask's one
313
+ * mask per forward, built by the caches). Their TurboQuant read is dense
314
+ * (`denseTurboQuantKernels`): the read decodes keys and values, then runs the
315
+ * fused SDPA. */
316
+ readonly cacheLayers: readonly MlxCacheLayer[];
317
+ readonly sequence: readonly MlxSequenceRange[];
318
+
319
+
320
+ /** One whole chunk of a prompt; rows are sequences times the chunk size. The GPU is this graph's one unit. */
321
+ readonly prompt: readonly MlxPromptRange[];
322
+ readonly promptChunk: typeof this.prompt;
323
+ readonly multiSequencePromptChunk: typeof this.multiSequencePrompt;
324
+ readonly multiSequencePrompt: readonly MlxPromptRange[];
325
+
326
+ /** A prompt's positions that are not a whole chunk: the window read at every width. */
327
+
328
+ /** Known tokens: this graph declares no committed spans, so they read as a window. */
329
+ readonly appendKnown: import("../../contracts/mlx/graph").MlxKnownOperations;
330
+
331
+ /** The one output head at every width. */
332
+ readonly logits: MlxRowTable<MlxHeadForward>;
333
+
334
+ /** One new token per sequence: `ids` [sequences, 1]. */
335
+ readonly decode: MlxRowTable<MlxCapturingForward>;
336
+
337
+ /** A speculative window of depth + 1 positions per sequence. This graph
338
+ * declares no hidden-layer taps, so `capture` is always null. */
339
+ readonly verify: MlxRowTable<MlxCapturingForward>;
340
+
341
+ /** The decoder over `h0` (consumed), every block reading its cache for `phase`. */
342
+ protected forwardLayers(h0: MlxArray, cache: Cache[], phase: ReadPhase, layers: readonly Qwen3MoeLayerView[]): MlxArray {
343
+ let cur = h0;
344
+ for (let i = 0; i < layers.length; i++) { // for layer, c in zip(self.layers, cache)
345
+ const next = layers[i]!.forward(cur, cache[i]!, phase); // h = layer(h, mask, c)
346
+ cur.dispose();
347
+ cur = next;
348
+ }
349
+ return disposing(cur, this.finalNorm.forward(cur)); // return self.norm(h)
350
+ }
351
+
352
+ logitsFromHidden(h: MlxArray): MlxArray {
353
+ return this.#head(h); // self.lm_head(out)
354
+ }
355
+ }
@@ -0,0 +1,323 @@
1
+ import type { MlxCacheLayer, MlxMixedRange, MlxSequenceRange } from "../../contracts/mlx/graph";
2
+ // Concrete model graph for plain Qwen3 (Qwen3ForCausalLM).
3
+ // Port target: mlx_lm.models.qwen3. A standard dense decoder — GQA softmax
4
+ // attention with per-head q/k RMSNorm (applied over head_dim BEFORE RoPE),
5
+ // full-head RoPE (theta 1e6), SwiGLU MLP, RMSNorm, tied embeddings.
6
+ //
7
+ // Primary use here is as a TEXT-EMBEDDING backbone (mlx-community Qwen3-Embedding-*):
8
+ // `embedPooled` returns the last-token hidden, L2-normalized — the pooling
9
+ // Qwen3-Embedding was trained with (an <|endoftext|> token is appended by the
10
+ // caller; its hidden is the sentence vector). The full causal-LM surface
11
+ // (forward/generate via the tied head) is wired too so it conforms to RuntimeModel.
12
+ //
13
+ // Each attention block reads its cache through the cache contract for the
14
+ // phase its caller names (`ReadPhase`); the cache was composed with its read.
15
+
16
+ import type { ModelConfig } from "../../artifacts/config";
17
+ import type { Weights } from "../../artifacts/weights";
18
+ import { MlxArray } from "@mlx-bun/mlx/array";
19
+ import * as ops from "@mlx-bun/mlx/ops";
20
+ import { disposing } from "../../layers/helpers";
21
+ import { composedFullAttentionCache } from "../../state/bf16-first-kv";
22
+ import { AttentionMasks } from "../../state/attention-read";
23
+ import { denseTurboQuantKernels } from "../../state/turboquant-attention";
24
+ import { LoraState } from "../../layers/lora";
25
+ import type { GraphDeclaration } from "../../contracts/portable/graph";
26
+ import type { MlxRowTable, MlxCapturingForward, MlxDeclaredGraph, MlxHeadForward, MlxPhaseForward, MlxPromptRange, MlxTokenPhases, MlxVerifyForward } from "../../contracts/mlx/graph";
27
+ import { declareGraph } from "../capabilities";
28
+ import { DEFAULT_PHASE_ROWS, rowTable, type ReadPhase } from "../graph";
29
+ import { QuantizedEmbedding } from "../../layers/quantized-embedding";
30
+ import { QuantizedLinear, QuantizedLinearPlain } from "../../layers/quantized-linear";
31
+ import { RMSNorm } from "../../layers/normalization";
32
+ import { type Cache } from "../../contracts/mlx/cache";
33
+ import { compiledSwiglu } from "../../layers/swiglu";
34
+ import { cacheRead, type AttentionReadStrategy } from "./blocks";
35
+
36
+ /** Qwen3 attention (per-head q/k RMSNorm over head_dim before full-head RoPE,
37
+ * GQA). qwen3 and qwen3_moe share it; they differ only in the `rope_theta`
38
+ * default used when the config omits one. */
39
+ export class Qwen3Attention {
40
+ readonly qProj: QuantizedLinear;
41
+ readonly kProj: QuantizedLinear;
42
+ readonly vProj: QuantizedLinear;
43
+ readonly oProj: QuantizedLinear;
44
+ readonly qNorm: RMSNorm;
45
+ readonly kNorm: RMSNorm;
46
+ readonly nHeads: number;
47
+ readonly nKvHeads: number;
48
+ readonly headDim: number;
49
+ readonly scale: number;
50
+ readonly ropeBase: number;
51
+
52
+ readonly #q: QuantizedLinearPlain;
53
+ readonly #k: QuantizedLinearPlain;
54
+ readonly #v: QuantizedLinearPlain;
55
+ readonly #out: QuantizedLinearPlain;
56
+
57
+ /** `reads` is how the block reads its cache: the cache's own read, fixed when
58
+ * the block is built. */
59
+ constructor(weights: Weights, config: ModelConfig, prefix: string, defaultRopeBase = 1000000,
60
+ readonly reads: AttentionReadStrategy = cacheRead) {
61
+ const t = config.text;
62
+ this.nHeads = t.numAttentionHeads;
63
+ this.nKvHeads = t.numKeyValueHeads;
64
+ this.headDim = t.headDim;
65
+ this.scale = Math.pow(this.headDim, -0.5);
66
+ this.ropeBase = t.ropeParameters.full_attention?.ropeTheta ?? defaultRopeBase;
67
+ this.qProj = QuantizedLinear.load(weights, `${prefix}.q_proj`, config);
68
+ this.kProj = QuantizedLinear.load(weights, `${prefix}.k_proj`, config);
69
+ this.vProj = QuantizedLinear.load(weights, `${prefix}.v_proj`, config);
70
+ this.oProj = QuantizedLinear.load(weights, `${prefix}.o_proj`, config);
71
+ this.qNorm = new RMSNorm(weights.tensor(`${prefix}.q_norm.weight`), t.rmsNormEps);
72
+ this.kNorm = new RMSNorm(weights.tensor(`${prefix}.k_norm.weight`), t.rmsNormEps);
73
+ this.#q = new QuantizedLinearPlain(this.qProj);
74
+ this.#k = new QuantizedLinearPlain(this.kProj);
75
+ this.#v = new QuantizedLinearPlain(this.vProj);
76
+ this.#out = new QuantizedLinearPlain(this.oProj);
77
+ }
78
+
79
+ /** `x` [B, L, hidden] (borrowed) to an owned [B, L, hidden], appending to
80
+ * `cache` and attending through the read `phase` names. */
81
+ forward(x: MlxArray, cache: Cache, phase: ReadPhase): MlxArray {
82
+ const [B, L] = x.shape as [number, number, number];
83
+ let q = this.#q.forward(x);
84
+ let k = this.#k.forward(x);
85
+ let v = this.#v.forward(x);
86
+
87
+ // Reshape to heads, then q/k RMSNorm over head_dim BEFORE transpose+RoPE
88
+ // (mlx-lm qwen3 reference order).
89
+ q = disposing(q, ops.reshape(q, [B, L, this.nHeads, this.headDim]));
90
+ k = disposing(k, ops.reshape(k, [B, L, this.nKvHeads, this.headDim]));
91
+ v = disposing(v, ops.reshape(v, [B, L, this.nKvHeads, this.headDim]));
92
+ q = disposing(q, this.qNorm.forward(q));
93
+ k = disposing(k, this.kNorm.forward(k));
94
+
95
+ q = disposing(q, ops.transposeAxes(q, [0, 2, 1, 3]));
96
+ k = disposing(k, ops.transposeAxes(k, [0, 2, 1, 3]));
97
+ v = disposing(v, ops.transposeAxes(v, [0, 2, 1, 3]));
98
+
99
+ q = disposing(q, ops.rope(q, this.headDim, this.ropeBase, cache.offset, null));
100
+ k = disposing(k, ops.rope(k, this.headDim, this.ropeBase, cache.offset, null));
101
+
102
+ // The cache attends with the kernel it was composed with: mlx-lm's
103
+ // scaled_dot_product_attention reads quantized caches through quantized SDPA.
104
+ let attn: MlxArray;
105
+ try {
106
+ const read = this.reads.read(cache, phase, k, v);
107
+ try { attn = read.attend(q, this.scale); } finally { read.dispose(); }
108
+ } finally { k.dispose(); v.dispose(); q.dispose(); }
109
+
110
+ const attnT = ops.transposeAxes(attn, [0, 2, 1, 3]);
111
+ attn.dispose();
112
+ const merged = ops.reshape(attnT, [B, L, -1]);
113
+ attnT.dispose();
114
+ const out = this.#out.forward(merged);
115
+ merged.dispose();
116
+ return out;
117
+ }
118
+ }
119
+
120
+ export class Qwen3MLP {
121
+ readonly gate: QuantizedLinear;
122
+ readonly up: QuantizedLinear;
123
+ readonly down: QuantizedLinear;
124
+
125
+ readonly #gate: QuantizedLinearPlain;
126
+ readonly #up: QuantizedLinearPlain;
127
+ readonly #down: QuantizedLinearPlain;
128
+
129
+ constructor(weights: Weights, config: ModelConfig, prefix: string) {
130
+ this.gate = QuantizedLinear.load(weights, `${prefix}.gate_proj`, config);
131
+ this.up = QuantizedLinear.load(weights, `${prefix}.up_proj`, config);
132
+ this.down = QuantizedLinear.load(weights, `${prefix}.down_proj`, config);
133
+ this.#gate = new QuantizedLinearPlain(this.gate);
134
+ this.#up = new QuantizedLinearPlain(this.up);
135
+ this.#down = new QuantizedLinearPlain(this.down);
136
+ }
137
+
138
+ forward(x: MlxArray): MlxArray {
139
+ const gate = this.#gate.forward(x);
140
+ const up = this.#up.forward(x);
141
+ // Oracle (mlx_lm/models/qwen3.py MLP): down_proj(swiglu(gate_proj(x), up_proj(x)))
142
+ // where swiglu is the `@mx.compile` closure (silu(gate)*up). Compiled = the
143
+ // same libmlx kernel mlx-lm dispatches (L1-faithful) AND fuses the three
144
+ // standalone ops into one dispatch. Bit-identical to the old unfused path.
145
+ const hidden = compiledSwiglu(gate, up);
146
+ gate.dispose();
147
+ up.dispose();
148
+ const out = this.#down.forward(hidden);
149
+ hidden.dispose();
150
+ return out;
151
+ }
152
+ }
153
+
154
+ export class Qwen3Layer {
155
+ readonly attn: Qwen3Attention;
156
+ readonly mlp: Qwen3MLP;
157
+ readonly inputNorm: RMSNorm;
158
+ readonly postAttnNorm: RMSNorm;
159
+
160
+ constructor(weights: Weights, config: ModelConfig, prefix: string) {
161
+ this.attn = new Qwen3Attention(weights, config, `${prefix}.self_attn`);
162
+ this.mlp = new Qwen3MLP(weights, config, `${prefix}.mlp`);
163
+ this.inputNorm = new RMSNorm(weights.tensor(`${prefix}.input_layernorm.weight`), config.text.rmsNormEps);
164
+ this.postAttnNorm = new RMSNorm(weights.tensor(`${prefix}.post_attention_layernorm.weight`), config.text.rmsNormEps);
165
+ }
166
+
167
+ forward(x: MlxArray, cache: Cache, phase: ReadPhase): MlxArray {
168
+ const xn = this.inputNorm.forward(x);
169
+ const a = this.attn.forward(xn, cache, phase);
170
+ xn.dispose();
171
+ const h = ops.add(x, a);
172
+ a.dispose();
173
+ const hn = this.postAttnNorm.forward(h);
174
+ const m = this.mlp.forward(hn);
175
+ hn.dispose();
176
+ const out = ops.add(h, m);
177
+ h.dispose();
178
+ m.dispose();
179
+ return out;
180
+ }
181
+ }
182
+
183
+ export class Qwen3Model implements MlxDeclaredGraph, MlxTokenPhases {
184
+ readonly config: ModelConfig;
185
+ readonly weightsBytes: number;
186
+ readonly prefixBase = "model";
187
+ readonly loraState = new LoraState();
188
+ /** An ordinary autoregressive graph that also exposes the pooled embedding path. */
189
+ get graphCapabilities(): GraphDeclaration { return declareGraph(); }
190
+ readonly embed: QuantizedEmbedding;
191
+ readonly layers: Qwen3Layer[];
192
+ readonly finalNorm: RMSNorm;
193
+ /** Separate lm_head when NOT tied; otherwise the tied embedding's asLinear. */
194
+ readonly lmHead: QuantizedLinear | null;
195
+ readonly #head: (hidden: MlxArray) => MlxArray;
196
+
197
+ constructor(weights: Weights, config: ModelConfig, maxPhaseRows = DEFAULT_PHASE_ROWS) {
198
+ this.config = config;
199
+ this.weightsBytes = [...weights.shards.files.values()]
200
+ .reduce((a, f) => a + f.mmap.size, 0);
201
+ this.embed = QuantizedEmbedding.load(weights, "model.embed_tokens", config);
202
+ this.layers = Array.from(
203
+ { length: config.text.numHiddenLayers },
204
+ (_, i) => new Qwen3Layer(weights, config, `model.layers.${i}`),
205
+ );
206
+ this.finalNorm = new RMSNorm(weights.tensor("model.norm.weight"), config.text.rmsNormEps);
207
+ this.lmHead = config.text.tieWordEmbeddings
208
+ ? null
209
+ : QuantizedLinear.load(weights, "lm_head", config);
210
+ const head = this.lmHead && new QuantizedLinearPlain(this.lmHead);
211
+ this.#head = head ? hidden => head.forward(hidden) : hidden => this.embed.asLinear(hidden);
212
+
213
+ const multiSequencePrompt: MlxCapturingForward = (ids, cache) =>
214
+ this.forwardLayers(this.embed.encode(ids), cache, "window");
215
+ this.prompt = [{ from: 1, to: Infinity, operation: multiSequencePrompt }];
216
+ this.promptChunk = this.prompt;
217
+ this.multiSequencePrompt = this.prompt;
218
+ this.multiSequencePromptChunk = this.multiSequencePrompt;
219
+ this.appendKnown = [{from:1,to:Infinity,operation:(ids, cache) =>
220
+ this.forwardLayers(this.embed.encode(ids), cache, "window")}];
221
+ this.logits = rowTable<MlxHeadForward>(hidden => this.logitsFromHidden(hidden), maxPhaseRows);
222
+ this.decode = rowTable<MlxPhaseForward>((ids, cache) =>
223
+ this.forwardLayers(this.embed.encode(ids), cache, "decode"), maxPhaseRows);
224
+ this.verify = rowTable<MlxVerifyForward>((ids, cache) =>
225
+ this.forwardLayers(this.embed.encode(ids), cache, "window"), maxPhaseRows);
226
+
227
+
228
+ this.sequence = [{from:1,to:Infinity,operation:(ids,cache)=>this.sequenceHidden(ids,cache,1)}];
229
+
230
+ this.cacheLayers = this.layers.map(() => ({kind:"full" as const,turboQuant:"dense" as const}));
231
+ }
232
+
233
+ loraTargets(): Map<string, QuantizedLinear> {
234
+ const out = new Map<string, QuantizedLinear>();
235
+ for (let i = 0; i < this.layers.length; i++) {
236
+ const l = this.layers[i]!;
237
+ const p = `model.layers.${i}`;
238
+ out.set(`${p}.self_attn.q_proj`, l.attn.qProj);
239
+ out.set(`${p}.self_attn.k_proj`, l.attn.kProj);
240
+ out.set(`${p}.self_attn.v_proj`, l.attn.vProj);
241
+ out.set(`${p}.self_attn.o_proj`, l.attn.oProj);
242
+ out.set(`${p}.mlp.gate_proj`, l.mlp.gate);
243
+ out.set(`${p}.mlp.up_proj`, l.mlp.up);
244
+ out.set(`${p}.mlp.down_proj`, l.mlp.down);
245
+ }
246
+ return out;
247
+ }
248
+
249
+ /** Builds no constants and opens no weights: nothing to release. */
250
+ dispose(): void {}
251
+
252
+ /** One cache per layer, sharing one mask memo. Their TurboQuant read is dense
253
+ * (`denseTurboQuantKernels`): the read decodes keys and values, then runs the
254
+ * fused SDPA. */
255
+ readonly cacheLayers: readonly MlxCacheLayer[];
256
+ readonly sequence: readonly MlxSequenceRange[];
257
+
258
+
259
+ /** The scoring forward (`MlxSequenceForward`): the window read at every width. */
260
+ sequenceHidden(ids: MlxArray, cache: Cache[], _width: number): MlxArray {
261
+ return this.forwardLayers(this.embed.encode(ids), cache, "window");
262
+ }
263
+
264
+ /** One whole chunk of a prompt; rows are sequences times the chunk size. The GPU is this graph's one unit. */
265
+ readonly prompt: readonly MlxPromptRange[];
266
+ readonly promptChunk: typeof this.prompt;
267
+ readonly multiSequencePromptChunk: typeof this.multiSequencePrompt;
268
+ readonly multiSequencePrompt: readonly MlxPromptRange[];
269
+
270
+ /** A prompt's positions that are not a whole chunk: the window read at every width. */
271
+
272
+ /** Known tokens: this graph declares no committed spans, so they read as a window. */
273
+ readonly appendKnown: import("../../contracts/mlx/graph").MlxKnownOperations;
274
+
275
+ /** The one output head at every width. */
276
+ readonly logits: MlxRowTable<MlxHeadForward>;
277
+
278
+ /** One new token per sequence: `ids` [sequences, 1]. */
279
+ readonly decode: MlxRowTable<MlxCapturingForward>;
280
+
281
+ /** A speculative window of depth + 1 positions per sequence. This graph
282
+ * declares no hidden-layer taps, so `capture` is always null. */
283
+ readonly verify: MlxRowTable<MlxCapturingForward>;
284
+
285
+ /** The decoder over `h0` (consumed), every block reading its cache for `phase`. */
286
+ protected forwardLayers(h0: MlxArray, cache: Cache[], phase: ReadPhase): MlxArray {
287
+ let cur = h0;
288
+ for (let i = 0; i < this.layers.length; i++) {
289
+ const next = this.layers[i]!.forward(cur, cache[i]!, phase);
290
+ cur.dispose();
291
+ cur = next;
292
+ }
293
+ return disposing(cur, this.finalNorm.forward(cur));
294
+ }
295
+
296
+ /** Last-token pooled, L2-normalized sentence embedding for one sequence.
297
+ * ids [1, L] → vector [1, hidden]. The caller appends the EOS/eod token
298
+ * (Qwen3-Embedding's pooling token) to `ids`. */
299
+ embedPooled(this: Qwen3Model & { makeCache(): Cache[] }, ids: MlxArray): MlxArray {
300
+ const cache = this.makeCache();
301
+ try {
302
+ const h = this.forwardLayers(this.embed.encode(ids), cache, "window"); // [1, L, hidden]
303
+ const [B, L, H] = h.shape as [number, number, number];
304
+ const last = h.slice([0, L - 1, 0], [B, L, H]); // [1, 1, hidden]
305
+ h.dispose();
306
+ const vec = disposing(last, ops.reshape(last, [B, H])); // [1, hidden]
307
+ // L2 normalize: x / max(||x||_2, eps)
308
+ const sq = ops.square(vec);
309
+ const ss = disposing(sq, ops.sumAxis(sq, -1, true));
310
+ const norm = disposing(ss, ops.sqrt(ss));
311
+ const out = ops.div(vec, norm);
312
+ vec.dispose();
313
+ norm.dispose();
314
+ return out;
315
+ } finally {
316
+ for (const c of cache) c.dispose();
317
+ }
318
+ }
319
+
320
+ logitsFromHidden(h: MlxArray): MlxArray {
321
+ return this.#head(h);
322
+ }
323
+ }