mlx-bun 0.2.0 → 0.3.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 (364) hide show
  1. package/README.md +71 -588
  2. package/docs/README.md +91 -0
  3. package/docs/{design → archive/investigations}/audio-input-plan.md +4 -4
  4. package/docs/{design → archive/investigations}/diffusion-gemma-port.md +3 -3
  5. package/docs/{design → archive/investigations}/faithful-l1-consolidation.md +4 -4
  6. package/docs/{design → archive/investigations}/minicpm5-decode-megakernel.md +11 -11
  7. package/docs/{design → archive/investigations}/optimization_plan.md +1 -1
  8. package/docs/{design → archive/investigations}/pre-colibri-stabilization.md +5 -5
  9. package/docs/{design → archive/investigations}/the-dreaming-handoff.md +2 -2
  10. package/docs/design/batching.md +569 -0
  11. package/docs/design/decode-speed-program.md +3178 -169
  12. package/docs/design/dreaming-nightly-pipeline.md +675 -282
  13. package/docs/design/generic-model-support.md +579 -334
  14. package/docs/design/kv-cache.md +680 -0
  15. package/docs/design/orpo-training.md +673 -1239
  16. package/docs/design/speculative-decoding.md +1627 -0
  17. package/docs/design/turboquant.md +2102 -0
  18. package/docs/design/unified-engine-frontier-plan.md +1831 -727
  19. package/docs/design/web-chat-redesign.md +666 -1083
  20. package/docs/planning/PRODUCT_ROADMAP.md +34 -1
  21. package/docs/planning/release-notes-v0.3.0.md +35 -0
  22. package/docs/planning/video-series.md +2 -2
  23. package/docs/reference/benchmarks.md +1162 -0
  24. package/docs/reference/cli.md +394 -159
  25. package/docs/reference/distribution.md +321 -182
  26. package/docs/reference/environment.md +348 -0
  27. package/docs/reference/glossary.md +39 -0
  28. package/docs/reference/library-api.md +566 -168
  29. package/docs/reference/memory.md +227 -64
  30. package/docs/reference/models.md +104 -6
  31. package/docs/reference/server-api.md +886 -425
  32. package/docs/reference/server-config.md +638 -330
  33. package/docs/reference/training.md +393 -254
  34. package/docs/reference/troubleshooting.md +166 -0
  35. package/package.json +9 -4
  36. package/src/audio/decode.ts +2 -2
  37. package/src/audio/features.ts +2 -2
  38. package/src/backends/mlx/autoregressive.ts +108 -0
  39. package/src/backends/mlx/batch-group.ts +1646 -0
  40. package/src/backends/mlx/diffusion.ts +39 -0
  41. package/src/backends/mlx/draft-target.ts +47 -0
  42. package/src/backends/mlx/evaluation.ts +105 -0
  43. package/src/backends/mlx/fill-append.ts +45 -0
  44. package/src/backends/mlx/gateway-binding.ts +71 -0
  45. package/src/backends/mlx/graph.ts +38 -0
  46. package/src/backends/mlx/memory-client.ts +189 -0
  47. package/src/backends/mlx/memory-guard.ts +40 -0
  48. package/src/backends/mlx/methods.ts +90 -0
  49. package/src/backends/mlx/model-prompt.ts +164 -0
  50. package/src/backends/mlx/model-serving.ts +79 -0
  51. package/src/backends/mlx/prefill.ts +33 -0
  52. package/src/backends/mlx/request-policy.ts +20 -0
  53. package/src/backends/mlx/rollback.ts +51 -0
  54. package/src/backends/mlx/serial-executor.ts +273 -0
  55. package/src/backends/mlx/speculative.ts +114 -0
  56. package/src/backends/mlx/state-views.ts +26 -0
  57. package/src/backends/mlx/text-engine.ts +73 -0
  58. package/src/cli.ts +58 -25
  59. package/src/client.ts +66 -0
  60. package/src/config.ts +21 -4
  61. package/src/contracts/completion.ts +16 -0
  62. package/src/contracts/execution.ts +49 -0
  63. package/src/contracts/generation.ts +103 -0
  64. package/src/contracts/host.ts +7 -0
  65. package/src/contracts/jobs.ts +66 -0
  66. package/src/contracts/pi-provider.ts +30 -0
  67. package/src/contracts/pi.ts +259 -0
  68. package/src/contracts/resources.ts +18 -0
  69. package/src/contracts/scheduling.ts +27 -0
  70. package/src/contracts/tsconfig.json +14 -0
  71. package/src/diffusion/diffusion-generate.ts +218 -153
  72. package/src/embed.ts +1 -1
  73. package/src/engine/admission.ts +59 -0
  74. package/src/engine/cancellation.ts +38 -0
  75. package/src/engine/engine.ts +56 -0
  76. package/src/engine/execution-coordinator.ts +70 -0
  77. package/src/engine/execution-plan.ts +51 -0
  78. package/src/engine/resources.ts +42 -0
  79. package/src/engine/scheduler.ts +67 -0
  80. package/src/engine/session.ts +247 -0
  81. package/src/eval/README.md +44 -1
  82. package/src/eval/kl.ts +236 -3
  83. package/src/eval/reference-logits.ts +207 -0
  84. package/src/eval/runner.ts +8 -96
  85. package/src/fill/echo-index.ts +188 -0
  86. package/src/fill/fill-session.ts +452 -0
  87. package/src/fill/proposal.ts +68 -0
  88. package/src/fill/schema-rows.ts +506 -0
  89. package/src/fill/tool-boundary.ts +103 -0
  90. package/src/fit.ts +1 -1
  91. package/src/generate.ts +656 -268
  92. package/src/grammar.ts +12 -7
  93. package/src/harness-pi.ts +14 -33
  94. package/src/hub-rest.ts +4 -4
  95. package/src/index.ts +7 -0
  96. package/src/inference/denoising.ts +17 -0
  97. package/src/inference/graph.ts +33 -0
  98. package/src/inference/prefill.ts +39 -0
  99. package/src/inference/rollback.ts +7 -0
  100. package/src/jobs/db.ts +4 -0
  101. package/src/jobs/index.ts +2 -0
  102. package/src/jobs/job-entry.ts +12 -12
  103. package/src/jobs/runner.ts +98 -13
  104. package/src/jobs/task-client.ts +19 -0
  105. package/src/jobs/types.ts +2 -58
  106. package/src/kv-store.ts +109 -49
  107. package/src/lab/README.md +18 -0
  108. package/src/{assets → lab/curve}/curve-designer.html +2 -2
  109. package/src/{curve-sampler.ts → lab/curve/curve-sampler.ts} +2 -2
  110. package/src/{expert-trace.ts → lab/expert-trace/expert-trace.ts} +5 -5
  111. package/src/{model → lab/paged-kv}/paged-kv.ts +9 -6
  112. package/src/library.ts +40 -0
  113. package/src/lora.ts +18 -0
  114. package/src/media-fetch.ts +1 -1
  115. package/src/memory/article.ts +1 -1
  116. package/src/memory/chunk.ts +1 -1
  117. package/src/memory/model.ts +30 -169
  118. package/src/memory/prompts.ts +1 -1
  119. package/src/memory/rest.ts +1 -1
  120. package/src/memory/schedule.ts +1 -1
  121. package/src/memory/stages.ts +4 -1
  122. package/src/memory/vault.ts +1 -3
  123. package/src/mlx/array.ts +9 -10
  124. package/src/mlx/compile.ts +12 -2
  125. package/src/mlx/ffi.ts +58 -11
  126. package/src/mlx/materialize.ts +23 -0
  127. package/src/mlx/metal-kernel.ts +10 -11
  128. package/src/mlx/ops.ts +33 -13
  129. package/src/mlx/quantized-rows.ts +36 -0
  130. package/src/mlx/token-bitmask.ts +52 -0
  131. package/src/mlx/turboquant-kv-decode.ts +82 -0
  132. package/src/mlx/turboquant-kv-inverse.ts +122 -0
  133. package/src/mlx/turboquant-ops.ts +33 -3
  134. package/src/mlx/turboquant-tables.ts +2 -2
  135. package/src/model/batched-mask.ts +8 -2
  136. package/src/model/batched-quant.ts +1 -1
  137. package/src/model/batched-rotating-quant.ts +2 -2
  138. package/src/model/batched-rotating.ts +1 -1
  139. package/src/model/compiled-decode.ts +1 -1
  140. package/src/model/diffusion-gemma.ts +8 -8
  141. package/src/model/factory.ts +76 -33
  142. package/src/model/fingerprint.ts +1 -1
  143. package/src/model/flash-attention.ts +3 -3
  144. package/src/model/gemma4-base.ts +26 -9
  145. package/src/model/gemma4.ts +6 -6
  146. package/src/model/generated/gemma4-12b.ts +2 -2
  147. package/src/model/generated/gemma4-26b.ts +2 -2
  148. package/src/model/generated/gemma4-e4b.ts +2 -2
  149. package/src/model/glm52-dsa.ts +13 -9
  150. package/src/model/glm52-memory.ts +1 -1
  151. package/src/model/implementation.ts +69 -0
  152. package/src/model/minicpm5.ts +6 -8
  153. package/src/model/profile.ts +12 -2
  154. package/src/model/qwen-append.ts +12 -0
  155. package/src/model/qwen-conv.ts +52 -0
  156. package/src/model/qwen3_5-checkpoint.ts +164 -0
  157. package/src/model/qwen3_5.ts +140 -56
  158. package/src/model/support.ts +1 -1
  159. package/src/model/trellis-balanced-scatter.ts +51 -0
  160. package/src/model/trellis-linear.ts +672 -0
  161. package/src/model/trellis-shared-m.ts +151 -0
  162. package/src/model/trellis-shared-scatter.ts +144 -0
  163. package/src/model/trellis-splitk-prefill.ts +158 -0
  164. package/src/model/trellis-tiled-prefill.ts +136 -0
  165. package/src/model/trellis-vector-expand.ts +62 -0
  166. package/src/model/trellis-wide-prefill.ts +94 -0
  167. package/src/model/universal/dense.ts +1 -1
  168. package/src/model/universal/modules.ts +8 -1
  169. package/src/native-pack.ts +6 -5
  170. package/src/pi-provider.ts +6 -64
  171. package/src/pi-web.ts +8 -315
  172. package/src/prompt-cache.ts +96 -53
  173. package/src/quantize/allocator.ts +1 -1
  174. package/src/quantize/config-writer.ts +10 -3
  175. package/src/quantize/rotate.ts +4 -4
  176. package/src/quantize/trellis.ts +586 -0
  177. package/src/responses.ts +28 -1
  178. package/src/runtime-config.ts +18 -4
  179. package/src/sampler.ts +4 -4
  180. package/src/serve/batch-scheduler.ts +8 -1671
  181. package/src/serve/chat-request.ts +391 -0
  182. package/src/serve/chat-stage.ts +195 -0
  183. package/src/serve/checkpoint-identity.ts +64 -0
  184. package/src/serve/completion-executor.ts +15 -7
  185. package/src/serve/discovery-routes.ts +12 -13
  186. package/src/serve/generation-gateway.ts +154 -193
  187. package/src/serve/http.ts +159 -0
  188. package/src/serve/inference-request.ts +79 -0
  189. package/src/serve/isolate.ts +297 -105
  190. package/src/serve/lab-routes.ts +3 -0
  191. package/src/serve/lane-registry.ts +2 -1
  192. package/src/serve/model-admin-routes.ts +7 -5
  193. package/src/serve/model-binding.ts +43 -0
  194. package/src/serve/model-host.ts +512 -0
  195. package/src/serve/openai-wire.ts +103 -0
  196. package/src/serve/parent-application.ts +56 -0
  197. package/src/serve/pipeline.ts +33 -0
  198. package/src/serve/preparation.ts +33 -0
  199. package/src/serve/request-plan.ts +19 -15
  200. package/src/serve/request-prep.ts +346 -0
  201. package/src/serve/response-lease.ts +24 -0
  202. package/src/serve/responses-client.ts +69 -0
  203. package/src/serve/session-completion-engine.ts +110 -0
  204. package/src/serve/session-search.ts +1 -1
  205. package/src/serve/static-routes.ts +5 -5
  206. package/src/serve/text-completion-stage.ts +104 -0
  207. package/src/serve/token-streams.ts +519 -0
  208. package/src/server.ts +317 -2649
  209. package/src/spec/assistant-source.ts +10 -44
  210. package/src/spec/deepspec-source.ts +6 -6
  211. package/src/spec/dflash-source.ts +8 -8
  212. package/src/spec/dspark/ab-stats.ts +3 -3
  213. package/src/spec/dspark/calibration.ts +2 -2
  214. package/src/spec/dspark/deepspec-module.ts +2 -2
  215. package/src/spec/dspark/generate-dflash.ts +2 -2
  216. package/src/spec/dspark/generate.ts +2 -2
  217. package/src/spec/dspark/loader.ts +1 -1
  218. package/src/spec/dspark/module-dflash.ts +4 -3
  219. package/src/spec/dspark/module.ts +3 -3
  220. package/src/spec/dspark/quantize-drafter.ts +1 -1
  221. package/src/spec/generate.ts +1 -1
  222. package/src/spec/glm52-mtp-source.ts +1 -1
  223. package/src/spec/ngram-source.ts +1 -1
  224. package/src/spec/prefix-state.ts +45 -0
  225. package/src/spec/qwen-mtp-source.ts +166 -69
  226. package/src/spec/serve-loop.ts +154 -156
  227. package/src/spec/source.ts +42 -10
  228. package/src/spec/two-model.ts +1 -1
  229. package/src/ssd-cache.ts +80 -12
  230. package/src/ssd-durability.ts +25 -13
  231. package/src/tool-call.ts +51 -14
  232. package/src/train/flash-cce.ts +2 -2
  233. package/src/train/forward.ts +6 -0
  234. package/src/train/loss.ts +1 -1
  235. package/src/train/prefix-shared.ts +5 -1
  236. package/src/train/segmented.ts +5 -3
  237. package/src/train/trainer.ts +6 -6
  238. package/src/vision/prompt.ts +1 -1
  239. package/src/vision/qwen3vl-tower.ts +52 -10
  240. package/src/web/app.html +1 -1
  241. package/src/web/app.js +5 -3
  242. package/src/web/src/adapters-panel.ts +3 -3
  243. package/src/web/src/assistant.ts +2 -36
  244. package/src/web/src/chat.ts +4 -4
  245. package/src/web/src/composer.ts +4 -4
  246. package/src/web/src/hub.ts +1 -1
  247. package/src/web/src/markdown.ts +0 -0
  248. package/src/web/src/memory-panel.ts +1 -1
  249. package/src/web/src/model-picker.ts +1 -1
  250. package/src/web/src/palette.ts +1 -1
  251. package/src/web/src/protocol.ts +3 -39
  252. package/src/web/src/sessions.ts +1 -1
  253. package/src/web/src/ui-catalog.ts +4 -18
  254. package/src/web-assets.ts +47 -0
  255. package/src/weights.ts +55 -9
  256. package/docs/archive/investigations/curve-runs/bisector-route.json +0 -98
  257. package/docs/archive/investigations/curve-runs/bisector-trace.json +0 -1393
  258. package/docs/archive/investigations/curve-runs/curve-terrain.json +0 -1179
  259. package/docs/archive/investigations/curve-runs/curve-terrain.svg +0 -42
  260. package/docs/archive/investigations/hlg-ab.html +0 -34
  261. package/docs/archive/investigations/hlg-explorer.html +0 -238
  262. package/docs/archive/investigations/hlg-figs/cliff.svg +0 -1
  263. package/docs/archive/investigations/hlg-figs/cross.svg +0 -1
  264. package/docs/archive/investigations/hlg-figs/edges.svg +0 -1
  265. package/docs/archive/investigations/hlg-figs/k20-grids.svg +0 -1
  266. package/docs/archive/investigations/hlg-figs/k20-peaks.svg +0 -1
  267. package/docs/archive/investigations/hlg-figs/lw-curve.svg +0 -1
  268. package/docs/archive/investigations/hlg-figs/terrain.svg +0 -1
  269. package/docs/archive/investigations/hlg-report.html +0 -262
  270. package/docs/archive/investigations/hlg-runs/hlg-ab.json +0 -149
  271. package/docs/archive/investigations/hlg-runs/hlg-blind-prompts.md +0 -87
  272. package/docs/archive/investigations/hlg-runs/hlg-blind.json +0 -107
  273. package/docs/archive/investigations/hlg-runs/hlg-cliff.json +0 -197
  274. package/docs/archive/investigations/hlg-runs/hlg-cross.json +0 -505
  275. package/docs/archive/investigations/hlg-runs/hlg-edge.json +0 -388
  276. package/docs/archive/investigations/hlg-runs/hlg-eval1.json +0 -25
  277. package/docs/archive/investigations/hlg-runs/hlg-frontier.json +0 -86
  278. package/docs/archive/investigations/hlg-runs/hlg-grid.json +0 -242
  279. package/docs/archive/investigations/hlg-runs/hlg-grid2.json +0 -343
  280. package/docs/archive/investigations/hlg-runs/hlg-lw-coarse.json +0 -110
  281. package/docs/archive/investigations/hlg-runs/hlg-map.json +0 -383
  282. package/docs/archive/investigations/hlg-runs/run0-original.log +0 -159
  283. package/docs/archive/investigations/hlg-runs/runA-recommended.log +0 -131
  284. package/docs/archive/investigations/hlg-runs/runAA-cliff-cpm5.log +0 -26
  285. package/docs/archive/investigations/hlg-runs/runAB-grid2-e4b.log +0 -18
  286. package/docs/archive/investigations/hlg-runs/runAC-peak-e4b-partial.log +0 -25
  287. package/docs/archive/investigations/hlg-runs/runAD-ab-samples.log +0 -8
  288. package/docs/archive/investigations/hlg-runs/runAE-edge-e4b.log +0 -51
  289. package/docs/archive/investigations/hlg-runs/runAF-logit-calibration.log +0 -10
  290. package/docs/archive/investigations/hlg-runs/runAG-eval-config2.log +0 -16
  291. package/docs/archive/investigations/hlg-runs/runB-gain-variants.log +0 -192
  292. package/docs/archive/investigations/hlg-runs/runC-pivots.log +0 -162
  293. package/docs/archive/investigations/hlg-runs/runD-oetf.log +0 -132
  294. package/docs/archive/investigations/hlg-runs/runE-eotf.log +0 -162
  295. package/docs/archive/investigations/hlg-runs/runF-shaper-working.log +0 -86
  296. package/docs/archive/investigations/hlg-runs/runG-shaper-sweep.log +0 -101
  297. package/docs/archive/investigations/hlg-runs/runH-wsweep.log +0 -176
  298. package/docs/archive/investigations/hlg-runs/runI-asweep.log +0 -176
  299. package/docs/archive/investigations/hlg-runs/runJ-knobmap.log +0 -49
  300. package/docs/archive/investigations/hlg-runs/runK-frontier.log +0 -19
  301. package/docs/archive/investigations/hlg-runs/runL-widemap.log +0 -65
  302. package/docs/archive/investigations/hlg-runs/runM-frontier-emb.log +0 -15
  303. package/docs/archive/investigations/hlg-runs/runN-verify.log +0 -116
  304. package/docs/archive/investigations/hlg-runs/runO-frontier-seed5000.log +0 -15
  305. package/docs/archive/investigations/hlg-runs/runP-grid-AxSm.log +0 -15
  306. package/docs/archive/investigations/hlg-runs/runQ-grid-tgW.log +0 -15
  307. package/docs/archive/investigations/hlg-runs/runR-blind-transcript.log +0 -79
  308. package/docs/archive/investigations/hlg-runs/runS-cross-coherence.log +0 -33
  309. package/docs/archive/investigations/hlg-runs/runT-cross-cpm5.log +0 -33
  310. package/docs/archive/investigations/hlg-runs/runU-cross-12b.log +0 -33
  311. package/docs/archive/investigations/hlg-runs/runV-lw-e4b.log +0 -22
  312. package/docs/archive/investigations/hlg-runs/runW-lw-12b.log +0 -22
  313. package/docs/archive/investigations/hlg-runs/runX-lw-cpm5.log +0 -22
  314. package/docs/archive/investigations/hlg-runs/runY-cliff-e4b.log +0 -26
  315. package/docs/archive/investigations/hlg-runs/runZ-cliff-12b.log +0 -26
  316. package/docs/archive/mlx-bun-lab-report.html +0 -275
  317. package/docs/archive/wwdc-mlx-bun.html +0 -848
  318. package/docs/design/adapters-end-to-end.md +0 -205
  319. package/docs/design/batching-perf-path.md +0 -255
  320. package/docs/design/batching-v2-plan.md +0 -529
  321. package/docs/design/bucketing-stage.md +0 -197
  322. package/docs/design/compat-cli-surface-design.md +0 -230
  323. package/docs/design/docs-reorg-plan.md +0 -165
  324. package/docs/design/dspark-serving-program.md +0 -371
  325. package/docs/design/dspark-speculative-decoding.md +0 -318
  326. package/docs/design/grammar-spec-batching-integration.md +0 -301
  327. package/docs/design/memory-inference-path.md +0 -422
  328. package/docs/design/memory-synthesis.md +0 -287
  329. package/docs/design/memory-system.md +0 -237
  330. package/docs/design/mlx-lm-serving-execution-seam.md +0 -81
  331. package/docs/design/mlx-lm-tool-parity-plan.md +0 -545
  332. package/docs/design/omlx-adoption-map.md +0 -98
  333. package/docs/design/orpo-dynamic-lambda.md +0 -220
  334. package/docs/design/orpo-future-enhancements.md +0 -340
  335. package/docs/design/paged-kv-cache.md +0 -214
  336. package/docs/design/parallel-slots.md +0 -567
  337. package/docs/design/parity-tier-dag.md +0 -161
  338. package/docs/design/repo-cleanup-plan.md +0 -231
  339. package/docs/design/runtime-isolation.md +0 -263
  340. package/docs/design/segmented-backward-training.md +0 -543
  341. package/docs/design/spec-decode-larger-targets.md +0 -229
  342. package/docs/design/ssd-kv-cold-tier.md +0 -415
  343. package/docs/design/structured-output.md +0 -484
  344. package/docs/design/tauri-desktop-app.md +0 -127
  345. package/docs/design/the-dreaming-master-plan.md +0 -445
  346. package/docs/design/turboquant-kv.md +0 -235
  347. package/docs/design/turboquant-weights.md +0 -467
  348. package/docs/design/w4a16-compute-precision-spike.md +0 -232
  349. package/docs/design/web-chat-beat-matrix.md +0 -315
  350. package/docs/design/write-pipeline-entity.md +0 -468
  351. package/docs/planning/IDEAS.md +0 -69
  352. package/docs/reference/embedding.md +0 -197
  353. package/docs/reference/features-matrix.md +0 -101
  354. package/docs/reference/orpo-quickstart.md +0 -135
  355. package/src/flags.ts +0 -11
  356. /package/docs/{design → archive/investigations}/colibri-glm52-port.md +0 -0
  357. /package/docs/{planning → archive/planning}/chunk-task-roadmap.md +0 -0
  358. /package/docs/{planning → archive/planning}/curve-sampler-research-plan.md +0 -0
  359. /package/docs/{planning → archive/planning}/memory-docs-and-dag-plan.md +0 -0
  360. /package/docs/{planning → archive/planning}/pi-integration-review.md +0 -0
  361. /package/docs/{planning → archive/planning}/release-notes-v0.2.0.md +0 -0
  362. /package/src/{memory-plan.ts → admission-plan.ts} +0 -0
  363. /package/{docs/archive/investigations → src/serve/assets}/curve-terrain.html +0 -0
  364. /package/{docs/dag → src/serve/assets}/training-inference-map.html +0 -0
@@ -1,384 +1,629 @@
1
- # Generic model support — run anything mlx-lm runs
1
+ ---
2
+ status: active
3
+ axis: ON
4
+ canonical-for: model-support
5
+ plan-anchor: "Serving architecture consolidation `[~]` (opened 2026-08-21)"
6
+ last-verified: 2026-08-23
7
+ ---
8
+
9
+ # Model support — run anything mlx-lm runs, and the ports we target
2
10
 
3
- **Status: Phase 1 + 1.5 BUILT (2026-07-01, commit 9bd9f1b)** — `src/model/universal/`
4
- (UniversalDense + descriptor table for 11 archs, rope factory bit-exact vs oracle
5
- fixtures, DenseLinear/bf16 checkpoints, weight audit, dispatch ladder in
6
- factory/support, manifest-driven parity harness). Parity gates light per-model
7
- once the small checkpoints are downloaded (`scripts/regen-universal-goldens.ts` →
8
- `MLX_BUN_TEST_UNIVERSAL=1`). Phases 2 (UniversalMoE), 3 (SSM/MLA one-offs),
9
- 4 (`mlx-bun port` scaffold) remain design.
11
+ **This is the one design doc for model support.** New model ports become
12
+ SECTIONS under §6 (Ports) of this file, not new files in `docs/design/`.
13
+ Per-port phase logs, gate results, and dates belong in PLAN.md (anchors are
14
+ named per section); this doc keeps the design and the oracle each port is
15
+ held to.
10
16
 
11
17
  Direction (Josh): *"we should be able to run any model that mlx-lm can run.
12
18
  Run any model generically; for the specific models we target, optimize them
13
19
  further — push the quality/performance border outwards. Like the gemma
14
20
  generator methodology."*
15
21
 
16
- The architecture already embodies the two-tier idea for one family:
17
- `src/model/gemma4-base.ts` (shared machinery) + `gemma4.ts` (config-driven
18
- monolith) + `src/model/generated/*.ts` (codegen'd specializations via
19
- `scripts/gen-model.ts`, gated bit-exact by `tests/generated-parity.test.ts`).
20
- This doc generalizes that shape to the whole mlx-lm zoo:
22
+ Two tiers, one contract:
21
23
 
22
24
  - **Tier-0 GENERIC** — one config-driven "universal" module per architecture
23
- *family* (dense llama-family first, MoE second). Runs any covered checkpoint
24
- at **L1 parity** (bit-exact vs mlx-lm on this machine's GPU). Monolith path
25
- only: no compiled decode, no kv-quant. Slow, never broken.
26
- - **Tier-1 TARGETED** — a model *graduates*: dedicated/generated forward,
27
- optiq-quant **L2** parity, Lab experiments where warranted, benchmark
28
- entries. Exactly what gemma4 / minicpm5 / qwen3_5 already are.
29
-
30
- The fidelity contract is unchanged (three-level tree): **L1 = mlx-lm
31
- bit-exact is the gate for ANY new model**, generic or targeted. A model the
32
- generic tier cannot hold to L1 parity is not "supported with caveats" — it is
33
- unsupported until fixed.
25
+ *family*. Runs any covered checkpoint at **L1 parity** (bit-exact vs mlx-lm
26
+ on this machine's GPU). Monolith path only: no compiled decode, no
27
+ kv-quant. Slow, never broken. Built: `src/model/universal/` (dense
28
+ llama-family). Design only: UniversalMoE.
29
+ - **Tier-1 TARGETED** — a model *graduates*: dedicated or generated forward,
30
+ L2 (mlx-optiq) composition where an optiq artifact exists, Lab experiments
31
+ where warranted, benchmark entries. The ports in §6.
32
+
33
+ **L1 = mlx-lm bit-exact is the gate for ANY new model**, generic or targeted.
34
+ Where no mlx-lm arm exists (audio, vision towers, DiffusionGemma, GLM-5.2's
35
+ Colibri container) the oracle is whoever ships the capability — optiq's
36
+ internal machinery, mlx-vlm, or the pinned Colibri engine — and the profile
37
+ declares that fidelity honestly (§3). A model the generic tier cannot hold to
38
+ L1 parity is not "supported with caveats"; it is unsupported until fixed.
34
39
 
35
40
  ---
36
41
 
37
- ## 1. The mlx-lm model zoo, inventoried
42
+ ## 1. The mlx-lm zoo, inventoried (mlx-lm 0.31.3, the oracle venv)
38
43
 
39
- Source: `mlx-lm 0.31.3` (the oracle venv),
40
- `site-packages/mlx_lm/models/*.py`. **108 model files** after excluding
44
+ 108 model files in `site-packages/mlx_lm/models/` after excluding
41
45
  infrastructure (`base`, `cache`, `rope_utils`, `activations`,
42
46
  `switch_layers`, `ssm`, `gated_delta`, `mla`, `bitlinear_layers`,
43
- `pipeline`). `utils.py MODEL_REMAPPING` adds free aliases on top
44
- (`mistral→llama`, `kimi_k2→deepseek_v3`, `qwen2_5_vl→qwen2_vl`,
45
- `minimax_m2→minimax`, `iquestcoder→llama`, `falcon_mamba→mamba`, …).
46
-
47
- ### Buckets (by reading llama.py in full + feature-grep + sampling ~20 files)
48
-
49
- **A. Dense llama-family — ~36 files, one parameterized module covers them.**
50
- All are: embed → N × (norm → GQA-attention(+rope) → residual → norm →
51
- MLP → residual) → norm → head. Every file is a ~170–240-line permutation of
52
- the same dozen knobs (verified deltas listed in §3.1):
53
-
54
- > llama (+mistral, iquestcoder via remap), qwen2, qwen3, gemma, gemma2,
55
- > helium, olmo2, olmo3, smollm3, starcoder2, stablelm, phi3, internlm2,
56
- > internlm3, granite, exaone, exaone4, glm, glm4, nemotron, minicpm, mimo,
57
- > ministral3, seed_oss, hunyuan_v1_dense, ernie4_5, cohere, cohere2,
58
- > apertus, nanochat, lille-130m, telechat3, solar_open, olmo, phi, plamo
59
-
60
- (`smollm3` is literally a 75-line subclass of `llama` that swaps some
61
- layers' rope for a no-op; `solar_open` is 38 lines. mlx-lm itself treats
62
- this family as one module with per-file ModelArgs.)
63
-
64
- **B. MoE llama-family (SwitchGLU experts) — ~17 files, a second module.**
65
- Same block, MLP replaced by router + `SwitchGLU` (+ optional shared expert):
66
-
67
- > mixtral, qwen2_moe, qwen3_moe, olmoe, phimoe, dots1, granitemoe,
68
- > ernie4_5_moe, glm4_moe, hunyuan, Klear, bailing_moe, exaone_moe, deepseek
69
- > (v1: MoE without MLA), step3p5, mimo_v2_flash, afmoe
70
- > (llama4_text / gpt_oss are near-family but carry extra deltas —
71
- > chunked-attention/NoPE, attention sinks.)
72
-
73
- **C. MLA lineage (DeepSeek latent attention, usually + MoE + yarn) — ~11:**
74
- > deepseek_v2, deepseek_v3 (+kimi_k2 remap), deepseek_v32, glm4_moe_lite,
75
- > glm_moe_dsa, kimi_k25, kimi_vl, longcat_flash(+ngram), minicpm3, youtu_llm
76
-
77
- **D. SSM / hybrid / linear-attention — ~17, individually shaped:**
78
- > mamba, mamba2 (+falcon_mamba remap), falcon_h1, jamba, granitemoehybrid,
79
- > nemotron_h, plamo2, lfm2, lfm2_moe, qwen3_next, kimi_linear, minimax,
80
- > recurrent_gemma, rwkv7, baichuan_m1, **qwen3_5 (already ported)**,
81
- > qwen3_5_moe
82
-
83
- **E. Legacy / exotic one-offs — ~13, deprioritized:**
84
- > gpt2, gpt_bigcode, gpt_neox, qwen (v1), openelm (per-layer varying dims),
85
- > phi3small (blocksparse attn), phixtral, dbrx, nemotron-nas (variable
86
- > blocks), bitnet (bitlinear), afm7, baichuan_m1 (also in D), lille-130m-ish
87
- > oddities
88
-
89
- **F. Vision/text wrappers — ~10, ~free once the text base exists:**
90
- > gemma3, gemma4 (ours already), qwen2_vl (+qwen2_5_vl), qwen3_vl,
91
- > qwen3_vl_moe, kimi_vl, lfm2-vl, mistral3 (+llava remap), pixtral, llama4
92
- > — mlx-lm runs only their *text* stack; each wrapper is ~50–120 lines of
93
- > config unwrapping + weight-prefix stripping.
94
-
95
- ### Coverage math
96
-
97
- | Bucket | Files | Unlocked by |
98
- |---|---|---|
99
- | A. dense llama-family | ~36 (33%) | **one** UniversalDense module (Phase 1) |
100
- | B. MoE llama-family | ~17 (16%) | **one** UniversalMoE module (Phase 2) |
101
- | F. vision wrappers | ~10 (9%) | thin config/prefix shims over A/B (Phase 1–2) |
102
- | C. MLA lineage | ~11 (10%) | one MLA attention module (Phase 3, demand-driven — most are >24 GB even at 4-bit) |
103
- | D. SSM/hybrid | ~17 (16%) | individual ports, qwen3_5 pattern (Phase 3) |
104
- | E. legacy/exotic | ~13 (12%) | individual ports, likely never |
105
- | already in mlx-bun | 5 | gemma4(+text), qwen3, qwen3_5, minicpm5(llama-typed), diffusion_gemma |
106
-
107
- **Two generic modules + the wrapper shims ≈ 60% of the zoo by file count,
108
- and >90% of what actually gets downloaded** (Llama 3.x, Mistral/Ministral,
109
- Qwen 2.5/3 dense+MoE, Gemma 1–3, Phi-3/4, SmolLM3, OLMo-2/3, GLM-4,
110
- Granite, Mixtral, OLMoE are all in A/B/F).
47
+ `pipeline`). `utils.py MODEL_REMAPPING` adds free aliases (`mistral→llama`,
48
+ `kimi_k2→deepseek_v3`, `qwen2_5_vl→qwen2_vl`, `falcon_mamba→mamba`, …).
49
+
50
+ | Bucket | ~Files | Shape | Unlocked by |
51
+ |---|---|---|---|
52
+ | A. dense llama-family | ~36 | embed → N × (norm → GQA attn(+rope) → residual → norm → MLP → residual) → norm → head; every file a permutation of a dozen knobs | **UniversalDense** (built) |
53
+ | B. MoE llama-family | ~17 | same block, MLP → router + SwitchGLU (+ shared expert) | **UniversalMoE** (design) |
54
+ | F. vision/text wrappers | ~10 | mlx-lm runs only the text stack; ~50–120 lines of config unwrapping + prefix stripping | thin shims over A/B |
55
+ | C. MLA lineage | ~11 | DeepSeek latent attention, usually + MoE + yarn | one MLA module, demand-driven (most exceed local RAM at 4-bit) |
56
+ | D. SSM / hybrid / linear-attention | ~17 | individually shaped (mamba, jamba, lfm2, qwen3_next, rwkv7, qwen3_5 …) | individual ports, the qwen3_5 pattern |
57
+ | E. legacy / exotic | ~13 | gpt2, openelm, phi3small, dbrx, bitnet … | individual ports, likely never |
58
+
59
+ Two generic modules + the wrapper shims cover roughly 60% of the zoo by
60
+ file count and the large majority of what actually gets downloaded (Llama
61
+ 3.x, Mistral/Ministral, Qwen 2.5/3 dense+MoE, Gemma 1–3, Phi-3/4, SmolLM3,
62
+ OLMo-2/3, GLM-4, Granite, Mixtral, OLMoE are all in A/B/F).
111
63
 
112
64
  ---
113
65
 
114
- ## 2. What mlx-bun already has to build on
115
-
116
- Audited 2026-07-01; file references are load-bearing.
66
+ ## 2. Building blocks (what every tier is made of)
117
67
 
118
- - **Config parsing** — `src/config.ts loadModelConfig`: already a *normalized*
119
- config (`TextConfig`) with per-model_type quirk handling inline (gemma
120
- nested `text_config`, qwen3_5 flat rope dict, llama defaults, diffusion
121
- backfill). Already carries: layerTypes/slidingWindow, headDim fallback,
122
- tie, softcap, partialRotaryFactor, `ropeParameters` map, per-layer quant
123
- map (`quantFor`), kv_config. **This is 70% of the normalization layer;**
124
- it needs the delta fields from §3.1 and a table-driven (not if-chain)
125
- quirk mechanism before it drowns.
68
+ - **Config parsing** — `src/config.ts loadModelConfig` produces a normalized
69
+ `TextConfig` (layerTypes/slidingWindow, headDim fallback, tie, softcap,
70
+ partialRotaryFactor, `ropeParameters`, per-layer quant map via `quantFor`,
71
+ kv_config, sidecar detection). Per-arch quirks for the generic tier live in
72
+ the descriptor table (§4.1), not in this if-chain.
126
73
  - **Primitives** — `src/model/gemma4-base.ts`: `QuantizedLinear` (+LoRA
127
74
  residual), `QuantizedEmbedding` (+tied `asLinear`), `RMSNorm`,
128
- `QuantizedSwitchLinear` (MoE experts — already used by 26B),
129
- `KVCache`/`RotatingKVCache`(+quantized variants), mask machinery,
130
- `logitSoftcap`, `disposing`. `src/model/qwen3.ts` (276 lines) is the
131
- proof-of-shape: a complete dense port using only these primitives.
132
- `minicpm5.ts`/`qwen3_5.ts` show the "dedicated module conforming to
133
- RuntimeModel" pattern.
134
- - **Gating/dispatch** — `src/model/support.ts` (allowlist by model_type +
135
- config predicates), `src/model/factory.ts` (specific classes →
136
- fingerprint-matched generated class → monolith; *rejects* unknown types),
137
- `src/model/fingerprint.ts` (structural hash for generated dispatch).
138
- - **Generator** — `scripts/gen-model.ts`: emits unrolled, branch-resolved
139
- `forwardLayers` overrides *of Gemma4Model specifically* (reads the shard
140
- index for layout facts, folds kv-quant constants, guards cache signature,
141
- falls back to monolith). Gated by `tests/generated-parity.test.ts`
142
- (bit-exact vs monolith, fingerprint asserted, fast-path-use asserted).
143
- - **Parity harness** — the `scripts/regen-*-goldens.ts` +
144
- `tests/*-parity.test.ts` pattern (e.g. `regen-qwen-parity-goldens.ts`):
145
- oracle venv emits `<prefix>-parity.json` (explicit prompt ids + greedy
146
- ids) + per-step raw logit `.bin`s; the test replays ids (no cross-stack
147
- tokenizer dependency) and compares bit-exact on the same GPU. Plus
148
- `scripts/check-minicpm5-port.ts` (hard, no-skip port check) and
149
- `scripts/inspect-model.ts` (tensor name/shape/dtype dump — the seed of
150
- the weight audit).
151
- - **Tokenizer** — `src/tokenizer.ts`: `@huggingface/tokenizers` (pure JS)
152
- reads stock `tokenizer.json` + `tokenizer_config.json`; already
153
- generic across BPE/SentencePiece-as-tokenizer.json. Known landmine class:
154
- BOS conventions (the double-BOS bug cost a debug session — see memory);
155
- chat-template rendering rides Pi, with `/v1` as the boundary.
156
-
157
- ### Gaps the generic tier must close (found in this audit)
158
-
159
- 1. **No unquantized Linear.** `QuantizedLinear.load` throws without a
160
- `.scales` tensor (`gemma4-base.ts:94`). Tier-0 v1 can inherit this
161
- constraint (target mlx-community 4/8-bit checkpoints — what users pull
162
- anyway), but a plain `DenseLinear` (bf16/fp16 `ops.matmul`) is a required
163
- fast-follow: many small models ship only fp16.
164
- 2. **No additive linear bias.** `QuantizedLinear`'s `biases` are quant
165
- zero-points, not a bias *term*. qwen2 (attention qkv bias), stablelm,
166
- starcoder2, phi et al. need an optional post-matmul `+ bias` in the
167
- linear primitive.
168
- 3. **Rope variants.** Only default/proportional exist in the runtime.
169
- mlx-lm `rope_utils.py` also ships: `linear`, `llama3` (Llama3RoPE),
170
- `yarn` (3 spellings), `longrope`/`su` (SuScaledRoPE), `mrope`. llama3 +
171
- yarn + longrope are hard requirements for bucket A (Llama 3.x, Phi-3,
172
- DeepSeek-lineage, various long-context finetunes).
173
- 4. **Attention-score softcap** (gemma2): needs the manual
174
- (q·kᵀ → tanh-cap → softmax) path — `ops.sdpa` can't express it.
175
- 5. **factory.ts rejects unknown types** rather than attempting a generic
176
- fallback (by design today; changes in §3.3).
75
+ `QuantizedSwitchLinear` (MoE experts), `KVCache`/`RotatingKVCache` (+
76
+ quantized and TurboQuant variants), mask machinery, `logitSoftcap`,
77
+ `disposing`. `src/model/universal/modules.ts` adds the dense
78
+ (unquantized) `nn.Linear`/`nn.Embedding` ports and the load-time
79
+ `WeightAudit`.
80
+ - **Generator** — `scripts/gen-model.ts` emits unrolled, branch-resolved
81
+ `forwardLayers` overrides of `Gemma4Model` into `src/model/generated/`
82
+ (e4b, 12B, 26B), dispatched by config fingerprint, gated bit-exact vs the
83
+ monolith by `tests/generated-parity.test.ts`.
84
+ - **Parity harness** — `scripts/regen.ts <job>` (jobs: `parity`,
85
+ `parity-26b`, `kvq`, `rotating-kvq`, `mixed-kv`, `lora`, `fused-sdpa`,
86
+ `universal`, `qwen-parity`, `minicpm5`, `minicpm5-kv`, `turboquant`,
87
+ `audio-fixtures`) drives the oracle venv
88
+ (`/Users/joshrossi/Code/mlx-lm/.venv/bin/python`) to emit
89
+ `<prefix>-parity.json` (explicit prompt ids + greedy ids) plus per-step raw
90
+ logit `.bin`s; `tests/*-parity.test.ts` replay the ids (no cross-stack
91
+ tokenizer dependency) and compare bit-exact on the same GPU. Manifests are
92
+ tracked; `.bin` blobs are machine-specific and untracked
93
+ (goldens/README.md).
94
+ - **Tokenizer** — `src/tokenizer.ts` (`@huggingface/tokenizers`, pure JS)
95
+ reads stock `tokenizer.json` + `tokenizer_config.json`. Known landmine:
96
+ BOS conventions (the double-BOS bug). Chat-template rendering rides Pi;
97
+ `/v1` is the boundary.
177
98
 
178
99
  ---
179
100
 
180
- ## 3. Design
181
-
182
- ### 3.1 Tier-0: `UniversalDense` (src/model/universal/)
101
+ ## 3. Model profiles — capability declaration (`src/model/profile.ts`)
183
102
 
184
- One module, shaped exactly like `qwen3.ts` but every delta a config field.
185
- **The normalized config is the union of mlx-lm's per-arch ModelArgs**, and a
186
- declarative **arch descriptor table** supplies per-model_type defaults +
187
- config-key aliases — mirroring mlx-lm's structure (shared base module,
188
- per-arch args) without one file per arch:
103
+ Model loading resolves ONE immutable profile before weights are opened.
104
+ A profile declares **construction only**: request-level methods (MTP, KV
105
+ schemes, adapters, grammar, sampling) are resolved elsewhere and cannot be
106
+ rewritten by profile selection.
189
107
 
190
108
  ```ts
191
- // src/model/universal/archs.ts — ~15–25 lines per arch vs mlx-lm's ~200
192
- export interface ArchDescriptor {
193
- // block structure
194
- block: "sequential" | "sandwich" | "parallel"; // llama | gemma2/glm4 | cohere/phi
195
- norm: { kind: "rmsnorm" | "layernorm"; plusOne?: boolean; bias?: boolean };
196
- qkNorm: "none" | "head-rms" | "layernorm-2d"; // qwen3/olmo2 | cohere
197
- mlp: { kind: "gated" | "plain"; act: "silu" | "gelu" | "gelu_tanh" | "relu2" };
198
- fusedQkv?: boolean; fusedGateUp?: boolean; // phi3 — split in sanitize
199
- // scalars (all read from config.json when present; descriptor = default)
200
- attnLogitSoftcap?: number; // gemma2
201
- logitScale?: ConfigPath; // cohere (multiply), granite logits_scaling (divide)
202
- embedScale?: "sqrt-hidden" | "mup" | null; // gemma family, minicpm
203
- residualMultiplier?: ConfigPath; // granite
204
- bias: { qkv?: boolean; o?: boolean; mlp?: boolean };
205
- nope?: "interval" | "list"; // smollm3, llama4-style no-rope layers
206
- // weights
207
- weightMap?: Partial<Record<CanonicalName, string>>; // starcoder2 c_fc/c_proj, gpt2 h.N.*
208
- sanitize?: (names: string[]) => Rename[]; // fused-proj splits, inv_freq drops
209
- configAliases?: Record<string, string>; // norm_epsilon→rms_norm_eps etc.
109
+ interface ModelProfile {
110
+ id: string;
111
+ artifactFingerprint?: string; // exact external identity (hf:<repo>@<rev>); family profiles omit
112
+ configFingerprint?: string; // structural guard for an exact artifact declaration
113
+ fidelity: FidelityTarget; // {l1, mlx-lm, bit-exact} | {l2, mlx-optiq, bit-exact} | {l3, null, measured}
114
+ requiredCapabilities: EngineCapability[];
115
+ execution: { loader: "safetensors" | "colibri";
116
+ graph: "gemma4" | "minicpm5" | "qwen3.5" | "qwen3" | "qwen3-moe"
117
+ | "diffusion-gemma" | "glm5.2" | "universal-dense";
118
+ loop: "autoregressive" | "diffusion";
119
+ specialization: "artifact" | "dedicated" | "generated" | "generic" };
210
120
  }
211
- export const ARCHS: Record<string /* model_type */, ArchDescriptor> = { ... };
212
121
  ```
213
122
 
123
+ Engine capabilities (`ENGINE_CAPABILITIES`): `autoregressive`,
124
+ `colibri-container`, `diffusion`, `diffusion-gemma-graph`, `gemma4-graph`,
125
+ `generated-graph`, `glm5.2-graph`, `minicpm5-graph`, `mixed-precision-kv`,
126
+ `native-mtp`, `qwen3-graph`, `qwen3-moe-graph`, `qwen3.5-graph`,
127
+ `recurrent-state`, `safetensors`, `streamed-experts`,
128
+ `universal-dense-graph`, `vision-sidecar`.
129
+
130
+ Resolution (`resolveModelProfile`):
131
+
132
+ 1. **Exact artifact profiles outrank family profiles.** The external
133
+ fingerprint is derived only from an immutable HF snapshot path
134
+ (`models--<org>--<repo>/snapshots/<40–64 hex>`); relocation keeps the
135
+ identity, mutable aliases and arbitrary local dirs return null. A matched
136
+ exact profile whose config fingerprint or graph acceptance mismatches
137
+ REFUSES — it never falls back. Built-in exact declarations today:
138
+ `qwen3.8-27b-optiq-4bit` (L1) and `glm5.2-colibri-int4-int8-mtp` (L3).
139
+ 2. **Family profile** by config predicate (`src/model/support.ts`), in this
140
+ order: `glm_moe_dsa` → `diffusion_gemma` → MiniCPM5 (exact-shape `llama`
141
+ predicate) → `qwen3_5`/`qwen3_5_text` (dense hybrid only; the MoE variant
142
+ is excluded) → `qwen3_moe` → `qwen3` → `gemma4*` (generated class if
143
+ `GENERATED.has(fingerprint)`, else monolith) → universal-dense if
144
+ `genericArgsFor(config)` resolves → throw naming the targeted list and
145
+ the generic descriptor keys.
146
+ 3. `validateProfile` enforces the fidelity/oracle pairing, loader↔graph
147
+ (`glm5.2` ⇒ colibri), loop↔graph (`diffusion-gemma` ⇒ diffusion), and that
148
+ every capability the execution composition implies is declared. Missing
149
+ engine capabilities refuse; nothing downgrades.
150
+
151
+ **Factory** (`src/model/factory.ts`): `openModel(dir)` resolves the profile,
152
+ routes `loader: "colibri"` through `openGlm52RuntimeModel` (header-only
153
+ memory plan first, then `Glm52Model.openStreamed`; the Colibri snapshot has no
154
+ `model.safetensors.index.json` so it bypasses `Weights.open`), and otherwise
155
+ `createModel(weights, config, profile)` switches on `execution.graph`.
156
+ `RuntimeModel` = `Gemma4Model | MiniCPM5Model | Qwen35Model | Qwen3Model |
157
+ Qwen3MoeModel | DiffusionGemmaModel | Glm52Model | UniversalDenseModel`.
158
+
159
+ **Support tiers in UX** (`supportTier(modelType, repoId)` →
160
+ `"targeted" | "generic" | null`): consumed by `mlx-bun ls`
161
+ (src/cli.ts), `src/hub-rest.ts`, and the serve discovery routes. Drafters
162
+ (`*_assistant`) are never selectable on their own. Note the MiniCPM5
163
+ *record*-level check keys on the repo id containing
164
+ `minicpm5-1b-optiq-4bit`; the config-level check is the exact-shape predicate.
165
+
166
+ Reference mirror of this section: docs/reference/models.md "Declared model
167
+ profiles" (keep in sync).
168
+
169
+ ---
170
+
171
+ ## 4. Tier-0 design
172
+
173
+ ### 4.1 `UniversalDense` (`src/model/universal/`)
174
+
175
+ One module (`dense.ts`), shaped like `qwen3.ts`, with every arch delta
176
+ selected by a `UniversalArgs` descriptor. `archs.ts` holds an explicit
177
+ table keyed by `model_type` (mlx-lm's `MODEL_REMAPPING` vendored in) — no
178
+ config sniffing to *identify* an arch; fingerprints remain a generated-
179
+ dispatch guard only. Descriptors in the table today (11):
180
+ **llama, smollm3, qwen2, qwen3, gemma, gemma2, phi3, olmo2, glm4, granite,
181
+ starcoder2**. `GENERIC_MODEL_TYPES` = the table's keys.
182
+
214
183
  Key choices:
215
184
 
216
- - **Descriptor keyed by `model_type` + mlx-lm's MODEL_REMAPPING table
217
- vendored in.** No config sniffing/fingerprinting to *identify* an arch —
218
- fingerprints stay what they are today: a dispatch guard for generated
219
- specializations. Explicit table = explicit support surface = honest
220
- `mlx-bun models` output.
221
- - **Transcribe mlx-lm op-for-op** inside the universal forward, per
222
- descriptor branch — same discipline as every existing port (q/k-norm
223
- before vs after transpose, rope order, cast points). The descriptor
224
- selects branches; it never approximates.
225
- - **Rope factory** `src/model/universal/rope.ts` ports
226
- `rope_utils.initialize_rope` verbatim: default/linear (native
227
- `ops.rope` with scale), llama3 (precomputed freqs), yarn, longrope/su.
228
- This is the single riskiest chunk of Phase 1 — rope bugs are
229
- off-by-a-little and only caught by logit goldens, which is why the parity
230
- gate feeds explicit ids and compares raw logits per step.
231
- - **KV cache**: reuse `KVCache`/`RotatingKVCache` unchanged;
232
- `layer_types`/`sliding_window` already normalized. No kv-quant at Tier-0
233
- (that's an L2/graduation concern).
234
- - **Quantization**: v1 requires MLX-quantized checkpoints (`.scales`
235
- present), same as the rest of the runtime; `DenseLinear` for
236
- fp16/bf16 checkpoints is Phase 1.5. Add the additive-bias term to both.
237
- - **RuntimeModel surface**: implement the same minimal contract
238
- `Qwen3Model` does (config, weightsBytes, loraState, makeCache,
239
- forwardHidden, logitsFromHidden, forward, generate, loraTargets) so
240
- serving/eval/LoRA hot-swap work day one. `forwardEmbeddings` throws
241
- (vision is per-arch work).
242
-
243
- ### 3.2 Tier-0.5: `UniversalMoE`
185
+ - **Transcribe mlx-lm op-for-op** inside each descriptor branch (q/k-norm
186
+ before vs after reshape, rope order, scale points, softcap, mask kinds).
187
+ The descriptor selects branches; it never approximates.
188
+ - **Rope factory** (`rope.ts`) — faithful port of `rope_utils.initialize_rope`:
189
+ default / linear / llama3 / yarn / longrope (SuScaledRoPE) / proportional.
190
+ Frequency tables are computed with the same mlx float32 ops the oracle uses
191
+ at module init so the bytes match; host double math only where the oracle
192
+ uses python floats. Gated by `tests/universal-rope.test.ts` against
193
+ machine-keyed oracle fixtures (`tests/fixtures/universal-rope/`).
194
+ - **Dense checkpoints** (`modules.ts`) — verbatim `mlx.nn.Linear`
195
+ (`addmm(bias, x, W.T)` / `x @ W.T`) and `nn.Embedding`; quantized paths
196
+ delegate to gemma4-base. Additive bias is a first-class field (qwen2 qkv,
197
+ starcoder2 all-bias).
198
+ - **Weight audit at load** — `WeightAudit.finish` diffs consumed tensors
199
+ against the shard index minus the arch's sanitize-drop rules; unconsumed
200
+ or missing tensors are a LOAD ERROR naming them, never a silently-wrong
201
+ model.
202
+ - **KV cache** — `KVCache`/`RotatingKVCache` unchanged; `layer_types` /
203
+ `sliding_window` already normalized. No kv-quant at Tier-0.
204
+ - **RuntimeModel surface** — the minimal contract `Qwen3Model` implements
205
+ (config, weightsBytes, loraState, makeCache, forwardHidden,
206
+ logitsFromHidden, forward, generate, loraTargets) so serving/eval/LoRA
207
+ hot-swap work day one. `forwardEmbeddings` is a stub (vision is per-arch).
208
+
209
+ ### 4.2 The parity gate for a new generic arch
210
+
211
+ `tests/universal-manifest.ts` — one entry per launch arch pointing at the
212
+ SMALLEST mlx-community 4-bit checkpoint of that arch, skip-if-absent, with
213
+ `expectClass: "universal" | "dedicated"` (the `qwen3` entry expects
214
+ `Qwen3Model` — generic never shadows targeted, the gate still runs the
215
+ oracle bar). Flow: user downloads → `bun scripts/regen.ts universal [<prefix>|all]`
216
+ → `MLX_BUN_TEST_UNIVERSAL=1 bun test tests/universal-parity.test.ts`.
217
+ Asserts: fingerprint-free construction, weight audit clean, greedy
218
+ trajectory match, per-step logits bit-exact.
219
+
220
+ **Definition of supported-generic = manifest entry green on the current
221
+ machine.** Adding a descriptor without its manifest goldens is the one
222
+ anti-pattern this design forbids.
223
+
224
+ ### 4.3 `UniversalMoE` (design only)
244
225
 
245
226
  Same block with the MLP swapped for router + `QuantizedSwitchLinear`
246
- (already exists and is serving 26B-A4B). Deltas to parameterize: top-k,
227
+ (already serving 26B-A4B and Qwen3-MoE). Deltas to parameterize: top-k,
247
228
  `norm_topk_probs`, routing score fn (softmax/sigmoid), routing scale,
248
229
  shared-expert branch (+ optional shared-expert gate — qwen2_moe),
249
230
  first-k-dense-replace (deepseek/glm4_moe layouts), grouped routing
250
231
  (deepseek_v3-style, defer). gpt_oss (attention sinks) and llama4 (chunked
251
- attention) are *not* claimed by the generic MoE — they graduate to
252
- one-offs if wanted.
253
-
254
- Memory reality check on the reference machines (24–32 GB): Mixtral-8x7B
255
- 4-bit ≈ 25 GB — no. OLMoE-1B-7B, Qwen3-30B-A3B (≈17 GB @4bit),
256
- GLM-4 lite variants, granitemoe small — yes. The generic MoE tier is
257
- worth building, but expectations belong in `mlx-bun models` output
258
- (weightsBytes vs RAM), not in marketing.
259
-
260
- ### 3.3 Dispatch changes (support.ts / factory.ts)
261
-
262
- Order matters — **generic never shadows a dedicated port**:
232
+ attention) are not claimed by the generic MoE. RAM honesty belongs in the
233
+ `ls`/fit output (weightsBytes vs RAM), not in marketing.
234
+
235
+ ### 4.4 Graduation to Tier-1
236
+
237
+ Criteria (all three): (a) L1 generic parity green and stable across real
238
+ use; (b) it is a daily driver / benchmark / product target; (c) a measured
239
+ perf or memory gap vs a dedicated path worth the maintenance (number in
240
+ docs/reference/benchmarks.md).
241
+
242
+ Process, in order, stop at any step: (1) L2 — optiq goldens
243
+ (`mixed-kv` pattern) if an optiq artifact/kv_config exists; (2) dedicated
244
+ or generated forward — extend `gen-model.ts` if gemma4-base-shaped,
245
+ otherwise a hand port (`qwen3_5.ts` pattern; hand ports are proven and
246
+ cheap); (3) perf levers behind their existing flags (compiled decode,
247
+ kv-quant, fused sdpa) — output-changing experiments are Lab items
248
+ (unified-engine-frontier-plan.md §6); (4) benchmark + docs entries, tier
249
+ label flips to `targeted`. Graduation never deletes the generic path — it
250
+ stays the permanent correctness fallback, as the monolith does for generated
251
+ gemma classes.
252
+
253
+ ### 4.5 Remaining phases
254
+
255
+ | Phase | Scope | State |
256
+ |---|---|---|
257
+ | 1 | UniversalDense + descriptor table + rope factory + bias + manifest harness | built |
258
+ | 1.5 | dense (bf16/fp16) checkpoints, no `.scales` | built (`modules.ts`) |
259
+ | 2 | UniversalMoE: mixtral, qwen2_moe, olmoe, glm4_moe, dots1, granitemoe, ernie4_5_moe, phimoe (qwen3_moe is already a dedicated port) | design |
260
+ | 3 | demand-driven one-offs (SSM/hybrid: lfm2, qwen3_next, mamba2, nemotron_h; MLA if a runnable target appears) — each a real port with its own goldens and cache types | design |
261
+ | 4 | `mlx-bun port <hf-repo>`: download smallest snapshot → descriptor coverage check (config keys + weight-audit dry run) → auto-regen goldens vs the oracle venv → run the gate → print verdict + manifest entry. Optionally generalize `gen-model.ts` to emit from UniversalDense | design (no CLI command exists) |
263
262
 
264
- ```
265
- createModel(weights, config):
266
- 1. dedicated classes (diffusion, minicpm5, qwen3_5, qwen3) — unchanged
267
- 2. gemma4*: GENERATED.get(fingerprint) ?? Gemma4Model — unchanged
268
- 3. ARCHS[remap(config.modelType)] → new UniversalDense/MoE(...) — NEW
269
- 4. throw "unsupported model_type X (nearest: …)" — unchanged shape
270
- ```
263
+ ---
271
264
 
272
- - `support.ts isSupportedModelRecord`: add `GENERIC_MODEL_TYPES` (the
273
- descriptor table's keys, post-remap). Keep the drafter exclusion.
274
- `mlx-bun models` labels the tier: `supported (targeted)` vs
275
- `supported (generic)` — sets perf expectations and doubles as the
276
- graduation worklist.
277
- - The current hard throw for non-MiniCPM5 `llama` configs is *replaced* by
278
- the generic path (that throw exists only because nothing generic existed).
279
- `isMiniCPM5Config`'s exact-shape predicate keeps MiniCPM5 on its
280
- dedicated class ahead of the generic.
281
-
282
- ### 3.4 Weight-name mapping + load audit
283
-
284
- Bucket A is already ~uniform in HF land
285
- (`model.layers.N.self_attn.{q,k,v,o}_proj`, `mlp.{gate,up,down}_proj`,
286
- `input_layernorm`, `post_attention_layernorm`, `model.norm`,
287
- `model.embed_tokens`, optional `lm_head`). The descriptor handles the
288
- exceptions: rename maps (starcoder2 `c_fc/c_proj`), fused-proj split rules
289
- (phi3 `qkv_proj`, `gate_up_proj` — split at load into views/copies),
290
- prefix strips (vision wrappers `language_model.model.*` — gemma4 already
291
- does this via `prefixBase`), and drop rules (`rotary_emb.inv_freq`,
292
- mlx-lm `sanitize`).
293
-
294
- **Every generic load runs a weight audit** (grow `scripts/inspect-model.ts`
295
- into `src/model/universal/audit.ts`): after module construction, diff
296
- {tensors in shard index} vs {tensors consumed}. Unconsumed or missing
297
- tensors ⇒ load error naming them, not a silent wrong-answer model. This is
298
- the cheap tripwire that catches descriptor mistakes before parity does
299
- (the "verify the foundation" rule, mechanized).
300
-
301
- ### 3.5 The parity gate for a NEW arch (cost: ~2–4 h/arch, mostly waiting)
302
-
303
- Pattern already proven three times (gemma/minicpm5/qwen). Parameterize it
304
- once:
305
-
306
- 1. `scripts/regen-generic-goldens.ts <snapshot> <prefix>` — oracle venv
307
- (`/Users/joshrossi/Code/mlx-lm/.venv/bin/python`, mlx-lm `load()` +
308
- `make_prompt_cache`) emits `<prefix>-parity.json` (explicit prompt ids,
309
- greedy ids) + 12 step-logit `.bin`s. Same-machine rule applies (logit
310
- goldens are GPU-specific — label goldens with host).
311
- 2. `tests/generic-parity.test.ts` — iterates a manifest
312
- (`tests/generic-manifest.ts`: prefix → snapshot path → model_type),
313
- skipIf-gated on snapshot presence like every parity test. Asserts:
314
- fingerprint-free construction, weight audit clean, greedy trajectory
315
- match, per-step logits bit-exact.
316
- 3. Checkpoint choice: **smallest mlx-community 4-bit of the arch**
317
- (0.5B–3B exists for nearly all of bucket A) — keeps the gate runnable
318
- and the download small. Downloads stay a user action (ground rules).
319
-
320
- Definition of supported-generic = manifest entry green on the current
321
- machine. No entry, no support claim.
322
-
323
- ### 3.6 Graduation to Tier-1 (targeted)
324
-
325
- **Criteria** (all three): (a) L1 generic parity green and stable across a
326
- few sessions of real use; (b) it's a daily driver / benchmark / product
327
- target (e.g. the Lucien pipeline models); (c) measured perf or memory gap
328
- vs a dedicated path that's worth the maintenance (number in
329
- `benchmarks/RESULTS.md`, per the every-perf-claim-gets-a-number rule).
330
-
331
- **Process** (the gemma methodology, in order — stop at any step):
332
- 1. **L2**: optiq-quant goldens (`regen-*-kv-goldens` pattern, mixed-KV vs
333
- the optiq oracle) if an optiq checkpoint/kv_config exists for it.
334
- 2. **Dedicated or generated forward**: if the arch is gemma4-base-shaped,
335
- extend `gen-model.ts`; otherwise hand-port a dedicated module
336
- (`qwen3_5.ts` pattern). Note: today's `gen-model.ts` subclasses
337
- `Gemma4Model` specifically — generalizing it to emit from
338
- `UniversalDense` is Phase 4 work, and *optional*: hand ports are proven
339
- and cheap ("faithful port took ~1 hr, not the huge effort claimed").
340
- 3. **Perf levers**: compiled decode / kv-quant / fused-sdpa, each behind its
341
- existing flag. (The former "L3 kernels" step — fused decode / flash paths
342
- with frozen-oracle gates — was deleted 2026-07-05; output-changing
343
- experiments are now Lab items, see
344
- [unified-engine-frontier-plan.md §6](unified-engine-frontier-plan.md).)
345
- 4. RESULTS.md + docs entries; `mlx-bun models` label flips to `targeted`.
346
-
347
- Nothing about graduation removes the generic path — it stays as the
348
- always-correct fallback, exactly like the monolith does for generated
349
- gemma classes today.
265
+ ## 5. Multimodal seams shared by the ports
266
+
267
+ Every tower (vision or audio) returns language-space soft tokens
268
+ `[1, soft, textHidden]` pre-divided by `embed_scale` (the language model
269
+ re-multiplies), splices into the prompt through
270
+ `src/vision/prompt.ts buildMultimodalPrompt` (images + audio in document
271
+ order; `buildVisionPrompt` is a byte-identical wrapper), and enters the model
272
+ through `forwardEmbeddings` (merged embeddings; per-layer-input models take
273
+ a `multimodal` zeroing mask decoupled from the `bidir` attention overlay).
274
+ Media requests route to the **serial lane**, skip the prompt cache and spec
275
+ decode, and capability flags (`visionCapable`, `audioCapable` in
276
+ `src/registry.ts`) check TENSORS, not just config. Non-WAV audio transcodes
277
+ via macOS `afconvert`; video decodes via the AVFoundation sidecar
278
+ `mlx-bun-frame-extract` — zero vendored codecs.
350
279
 
351
280
  ---
352
281
 
353
- ## 4. Phased rollout
282
+ ## 6. Ports
283
+
284
+ One subsection per family. Each states the graph, its oracle, and the
285
+ design-relevant facts; phase logs live in PLAN.md at the named anchor.
286
+
287
+ ### 6.1 Gemma-4 (e4b / 12B / 26B-A4B) — `src/model/gemma4.ts`, `gemma4-base.ts`, `generated/`
288
+
289
+ - **Graph**: line-for-line port of mlx-lm `gemma4_text.py` covering 12B
290
+ (dense), e4b (per-layer-input embeddings + KV-shared layers), 26B-A4B
291
+ (MoE: router + gather_qmm experts). SDPA scale 1.0 (q/k normalized);
292
+ full-attention layers use global_head_dim 512, one global KV head,
293
+ `attention_k_eq_v`; ProportionalRoPE rotates only
294
+ `partial_rotary_factor` dims; python-float scalars promote weakly; mlx
295
+ helper implementations replicated exactly (`x**3` is `mx.power`).
296
+ Sliding layers use a plain cache + window masks (numerically identical to
297
+ mlx-lm's RotatingKVCache).
298
+ - **Specialization**: `scripts/gen-model.ts` → `generated/gemma4-{e4b,12b,26b}.ts`,
299
+ fingerprint-dispatched, bit-exact vs the monolith
300
+ (`tests/generated-parity.test.ts`).
301
+ - **Oracle**: L1 mlx-lm (`goldens/parity.json` on the 12B snapshot,
302
+ `tests/parity.test.ts`; 26B `tests/parity-26b.test.ts`); L2 mlx-optiq for
303
+ the mixed-KV composition (26B `logits-26b-kvmix.bin`, `mixed-kv` job).
304
+ Profile: `gemma4-generated` / `gemma4-dedicated`, L1.
305
+ - **Vision**: two towers behind one merge contract — `src/vision/siglip.ts`
306
+ (full SigLIP encoder shipped by e2b/e4b/26B/31B; port of optiq's vendored
307
+ mlx-vlm gemma4 encoder from the bf16 `optiq_vision.safetensors` sidecar)
308
+ and `src/vision/embedder.ts` (12B `gemma4_unified` encoder-free patch
309
+ embedder). Oracle = optiq internals (`goldens/e4b-vision.json`,
310
+ `tests/e4b-vision.test.ts`, `tests/vision.test.ts`): spliced ids and
311
+ soft-token count EXACT, greedy PREFIX match; full bit-exact greedy is not
312
+ asserted because the 16-layer bf16 encoder composition drifts sub-bf16 and
313
+ the scale-1.0 softmax amplifies it (siglip.ts header).
314
+ - **Audio**: §6.6.
315
+ - PLAN anchors: Phase 12 (SigLIP), Phase 2 findings (parity notes).
316
+
317
+ ### 6.2 MiniCPM5-1B — `src/model/minicpm5.ts`
318
+
319
+ - **Graph**: `model_type: llama` (plain Llama block — no MiniCPM residual
320
+ scaling), 24 layers, hidden 1536, 16/2 heads @128, vocab 130560, untied,
321
+ `rope_theta` from `ropeParameters.full_attention`. Port target mlx-lm
322
+ `models/llama.py`; the expert swiglu is mx.compile-compiled like the
323
+ oracle's `activations.py` (`compiledSwiglu`, kill-switch
324
+ `MLX_BUN_COMPILED_SWIGLU=0`) — the one fusion that matched the oracle's
325
+ kernel set and brought CPM5 decode to parity with mlx-lm
326
+ (decode-speed-program.md).
327
+ - **Dispatch**: the exact-shape `isMiniCPM5Config` predicate keeps it on
328
+ the dedicated class ahead of the generic `llama` descriptor.
329
+ - **Oracle**: L1 mlx-lm (`goldens/minicpm5-parity.json`,
330
+ `tests/minicpm5-parity.test.ts`); L2 mlx-optiq for per-layer quantized KV
331
+ (`goldens/minicpm5-kv-parity.json`, `tests/minicpm5-kv-parity.test.ts`,
332
+ bit-exact via `QuantizedKVCache` + `quantizedSdpaUnfused`). Also the
333
+ TurboQuant KV testbed (`TurboQuantKVCache`). Profile
334
+ `minicpm5-dedicated`, L1 (the L2 composition is a request-level KV scheme,
335
+ not a profile property).
336
+ - Training reference model for segmented backward and ORPO
337
+ (docs/design/orpo-training.md, orpo-training.md).
338
+
339
+ ### 6.3 Qwen3 / Qwen3-MoE / Qwen3.5-family (3.6, 3.8) — `qwen3.ts`, `qwen3-moe.ts`, `qwen3_5.ts`
340
+
341
+ - **Qwen3** (`model_type: qwen3`): dense GQA with per-head q/k RMSNorm
342
+ before RoPE, theta 1e6, tied embeddings. Primary use is the
343
+ text-embedding backbone (`embedPooled`: last-token hidden, L2-normalized).
344
+ Oracle L1 mlx-lm (`tests/qwen3-embed-parity.test.ts`). Shadows the
345
+ generic `qwen3` descriptor.
346
+ - **Qwen3-MoE** (`qwen3_moe`): straight op-for-op transcription of
347
+ mlx-lm `qwen3_moe.py` + `switch_layers.py` — gate Linear → precise
348
+ softmax over all experts → argpartition top-k → renormalize → SwitchGLU
349
+ with compiled swiglu; `decoder_sparse_step` / `mlp_only_layers` dense
350
+ layers handled. Oracle L1 mlx-lm on the 30B-A3B
351
+ (`tests/qwen3-moe-parity.test.ts`; the cached checkpoint has no
352
+ kv_config, so no L2 bar).
353
+ - **Qwen3.5 hybrid** (`qwen3_5` / `qwen3_5_text`; the `qwen3_5_moe` variant
354
+ is excluded by `isQwen35Config`): 64 layers, every
355
+ `full_attention_interval`-th (4) a gated softmax-attention layer (q-gate
356
+ split, per-head q/k norm, partial RoPE 0.25 @ base 1e7, GQA 24/4 @256,
357
+ `o_proj(out·σ(gate))`), the rest **gated DeltaNet** linear attention
358
+ (`in_proj_qkv/z/b/a`, depthwise causal conv1d k=4, `A_log`/`dt_bias`
359
+ float, `RMSNormGated`). The recurrence is the reference's custom Metal
360
+ kernel `gated_delta_step` ported VERBATIM (`src/model/qwen3-delta.ts`) —
361
+ bit-exact parity requires it; the ops fallback reduces in a different
362
+ order. `SSMCache` (conv + recurrent state) implements `Cache` and the
363
+ spec-round contract (`specRoundBegin/Commit/Rollback`: free snapshot,
364
+ replay of the kept window on partial reject — bit-exact by construction
365
+ because the kernel's per-thread loop is serial). Weights under
366
+ `language_model.`; `output_gate_type` other than `"swish"`/absent throws.
367
+ **Qwen3.8-27B is byte-for-byte the 3.6-27B text geometry** — zero new
368
+ graph work; the exact profile `qwen3.8-27b-optiq-4bit` pins the validated
369
+ revision.
370
+ - **Oracles**: L1 mlx-lm `qwen3_5.py` with bf16 KV
371
+ (`goldens/qwen38-parity.json`, `tests/qwen-parity.test.ts`; earlier 4B/3.6
372
+ goldens via `regen.ts qwen-parity`); L2 mlx-optiq mixed-KV on the 3.5/3.6
373
+ OptiQ artifacts (`install_mixed_kv` per-layer bits keyed by cache index ≡
374
+ our `maybeQuantizeKv`). Qwen3.8's OptiQ repo ships no mixed-KV config →
375
+ no L2 bar; mixed-KV serving there is Lab-tier.
376
+ - **Vision + video** (`src/vision/qwen3vl-{preprocess,tower,prompt}.ts`,
377
+ `video-frames.ts`, `src/model/qwen3-mrope.ts`): mlx-vlm `qwen3_vl`
378
+ vision tower at the qwen3_5 configuration (depth 27, hidden 1152, heads
379
+ 16 @72, patch 16, temporal 2, merge 2, deepstack empty) from the bf16
380
+ sidecar `optiq/optiq_vision.safetensors`; PIL-exact fixed-point bicubic
381
+ smart_resize; conv3d patch embed (new `mlx_conv3d` binding);
382
+ `ensure_fused_sdpa`'s 72→80 head-dim pad replicated; **interleaved
383
+ mRoPE** via the reference's verbatim Metal apply kernel with
384
+ `get_rope_index` positions + delta exact, installed per request on the
385
+ serial lane (text-only keeps the untouched fast-rope path — equal
386
+ position streams degenerate mRoPE to plain RoPE). Video: AVFoundation
387
+ sidecar samples at 2 fps (max 768 frames), temporal-pair patchify, gridT>1
388
+ tower, transformers' `<t seconds>` per-frame-group prompt format with a
389
+ python-`%.1f` port. Oracle = **mlx-vlm 0.6.14 pinned to mlx 0.31.2**
390
+ (independent of mlx-lm, so no composite bit-match target exists): tower
391
+ bit-exact at small grids, calibrated envelope at 2304 patches; e2e greedy
392
+ token-exact on 2/3 image fixtures, step-0 argmax exact on all
393
+ (`tests/qwen38-vision*.test.ts`).
394
+ - **Native MTP** (`src/spec/qwen-mtp-source.ts`, `--draft-kind mtp`): the
395
+ separately published `Qwen3.8-27B-MTP-*` head (`fc` over
396
+ concat(rms(embed(next)), rms(target_hidden)) → one full-attention layer
397
+ with its own KVCache → target lm_head, block_size 3, DeepSeek-V3 shape)
398
+ reads the target's PRE-final-norm hidden via `captureLayer`. Gate:
399
+ greedy speculation token-identical to non-spec greedy
400
+ (`tests/qwen38-mtp.test.ts`). Durable negative perf verdict on the quiet
401
+ M1 Max (MTP-on slower than off at γ=2 despite 61% acceptance) — stays
402
+ opt-in. Drafter logits verified against the mlx-vlm reference fed the
403
+ oracle's captured hidden grid.
404
+ - Training: `loraTargets()` wired; the DeltaNet kernel is inference-only —
405
+ training needs mlx-lm's pure-mlx `gated_delta_ops` scan under training
406
+ (open).
407
+ - PLAN anchors: Phase 14 (bring-up, 14g MTP, 14v vision, 14w video, 14y
408
+ YaRN open, 14z TurboQuant weights).
409
+
410
+ ### 6.4 GLM-5.2 (`glm_moe_dsa`) — the Colibri hierarchy, `src/model/glm52*.ts`
411
+
412
+ Summary of docs/archive/investigations/colibri-glm52-port.md (frozen);
413
+ full phase record G0–G8 in git (`git show 3199c75:PLAN-archive.md`,
414
+ Phase 21, closed 2026-08-17). GLM-5.3+ would extend THIS section.
415
+
416
+ - **Decision**: a native `Glm52Model` over the PUBLIC Colibri container in
417
+ place (no second copy, no Colibri child process, no `--backend`); the
418
+ pinned Colibri build (`JustVugg/colibri` @ `44e489b`) is an external
419
+ executable oracle for formats, logits/tokens, routing traces, memory and
420
+ speed. Its policy code is studied and reproduced, not shipped.
421
+ - **Model**: 78 main layers (3 dense FFN + 75 sparse MoE, 256 routed
422
+ experts, top-8) + an MTP row with its own 256 experts (19,456 expert
423
+ instances). The dense spine (embeddings, lm-head, attention, norms, first
424
+ dense FFNs, routers, shared experts) stays resident at int4 (~9.9 GB); the
425
+ ~370 GB routed-expert pool stays on disk.
426
+ - **Graph** (`glm52.ts`, `glm52-mla.ts`, `glm52-dsa.ts`, `glm52-moe.ts`):
427
+ MLA — q-a → norm → q-b; kv-a → latent norm; **compressed cache** of the
428
+ 512-wide latent + 64 RoPE values per token (`MLACache`, ~57× smaller than
429
+ reconstructed K/V at this geometry), weight-absorption decode,
430
+ reconstructed-prefill fallback, partial interleaved RoPE. **DSA indexer**
431
+ — q/k/projection/norm, per-layer full/shared schedule, exact dense
432
+ fallback when the selected set covers the prefix (`DSAIndexCache` only on
433
+ layers that own it). MoE — sigmoid scores + correction bias for
434
+ selection, un-biased gate weights, true top-8, routed scale, shared expert
435
+ in parallel. Colibri arithmetic (offset-binary int4, per-row int8 scale
436
+ applied once, RMSNorm with multiplicative weight and double-accumulated
437
+ reduction) is mirrored in `glm52-reference.ts`. G2 favors an auditable
438
+ dequantize→f32 path; the Metal streamed kernel (`glm52-streamed-metal.ts`)
439
+ replaces the expert resolver without changing the graph.
440
+ - **Expert residency / streaming** (`glm52-residency.ts`,
441
+ `glm52-streamed-experts.ts`, `glm52-expert-layout.ts`, `glm52-pilot.ts`,
442
+ `glm52-coupling.ts`, `glm52-atlas.ts`): per-layer bounded LRU slots
443
+ derived from the whole-process memory equation, pinned hot-store seeded
444
+ from persistent usage, LFRU repinning with hysteresis, batch-union of
445
+ unique (layer, expert) across rows so each expert is read once per
446
+ layer/step, `pread` worker pool with slot generations and completion
447
+ fences (no reuse while a lazy MLX command can retain the buffer), PILOT
448
+ (next-layer router on the current state) and COUPLE (cross-layer route
449
+ pairs) prefetch, RSS feedback shrinking only the evictable tier. Unified
450
+ memory removes a copy, not the 370 GB: the meaningful states are disk /
451
+ resident / pinned / loading / in-flight / evictable.
452
+ - **Fixed-context admission** (`glm52-memory.ts`, `factory.ts
453
+ openGlm52RuntimeModel`): a header-only resource plan runs BEFORE any
454
+ resident tensor or expert slab is opened — process limit (default 25 GiB
455
+ preset or physical RAM, whichever is smaller), fixed context
456
+ (default 4096 tokens, 128 generation), allocator/Bun/safety reserves — and
457
+ refuses an impossible placement; the opened runtime's planned bytes must
458
+ equal the preflight equation or load fails. Serving clamps `max_tokens`
459
+ to the room under the fixed context (STATUS: "GLM fixed-context admission
460
+ correction", generalized to every model in v0.2.0).
461
+ - **Native MTP** (`src/spec/glm52-mtp-source.ts`): the checkpoint's int8 MTP
462
+ row as an in-process draft source sharing the target; serial only —
463
+ batched rows decode single tokens (Colibri keeps MTP out of its mux path
464
+ too). Batched caches land via `makeEmptyBatch/mergeRows/extractRow/
465
+ filterRows/projectedBytes` on right-justified latent/RoPE/DSA tensors —
466
+ no full K/V at merge, admission, extraction or cancellation.
467
+ - **Oracle / fidelity**: profile `glm5.2-colibri` / exact
468
+ `glm5.2-colibri-int4-int8-mtp`, **L3 (measured)** — Colibri is neither
469
+ mlx-lm nor mlx-optiq, so the tier system cannot call it bit-exact even
470
+ though the G2 gate is a teacher-forced trajectory from the pinned engine
471
+ on a tiny converted artifact (`tests/glm52-teacher-forcing.test.ts`,
472
+ `fixtures/colibri-glm52/`). Curated numbers live in code as operator
473
+ guidance (`GLM52_G5_MEASURED_WARM_DECODE_TPS`,
474
+ `GLM52_G5_DIRECT_ORACLE_WARM_DECODE_TPS`, M1 Max 32 GB, MTP on) and in
475
+ docs/reference/models.md "GLM-5.2 on a 32 GB Mac". The 24 GB M4 Pro is
476
+ below the one-slot-per-layer floor and out of scope.
477
+ - **Not claimed**: embeddings, image/audio input, LoRA, training, adapters
478
+ — advertised false, never faked (archive N8).
479
+ - PLAN anchor: Phase 21 (closed 2026-08-17); probes, gate tests, and
480
+ lab spikes for the port surface retired afterwards (commit fbafe44).
481
+
482
+ ### 6.5 DiffusionGemma-26B-A4B — `src/model/diffusion-gemma.ts`, `src/diffusion/`, `src/vision/diffusion-vision.ts`
483
+
484
+ Summary of docs/design/generic-model-support.md (the D0 reference dossier
485
+ there stays the porting contract; do not extend it).
486
+
487
+ - **What it is**: the first non-autoregressive model — an encoder prefill
488
+ over the prompt builds a KV cache, then a decoder pass over a fixed
489
+ 256-token canvas (initialized to uniform-random ids, NOT a mask token) is
490
+ denoised over up to 48 steps. 30 layers, hidden 2816, 16 heads; each
491
+ layer runs a parallel dense MLP + 128-expert top-8 MoE summed through 7
492
+ RMSNorms and a `layer_scalar`; attention scale 1.0, no attention softcap
493
+ (final-logit softcap 30 in fp32), QK/V-norm post-proj/pre-RoPE, full
494
+ layers (5,11,17,23,29) reuse k as v at hd 512 / kv 2 with partial rotary
495
+ 0.25; tied 4-bit head via `QuantizedEmbedding.asLinear`; a
496
+ `SelfConditioning` MLP feeds soft embeddings from the previous step.
497
+ Bidirectional decoder masks (`_make_decoder_masks`) are the crux.
498
+ - **Engine** (`diffusion-generate.ts`): linear temperature schedule
499
+ (`t_min 0.4 → t_max 0.8`, DIVIDES logits), both samplers
500
+ (confidence-threshold = optiq public default, entropy-bound = engine
501
+ default), self-conditioning feedback, stability/EOS, block loop. RNG
502
+ parity via bound `mlx_random_randint`/`mlx_random_seed` threading the
503
+ global key in the reference's draw order. Parity traps recorded in the
504
+ source doc: division not reciprocal-multiply; independent history copies;
505
+ the as-loaded oracle has `generation_config = None` (stable-stop OFF,
506
+ eos {1, 106}).
507
+ - **Vision**: a DEDICATED tower (`diffusion-vision.ts`), not the e4b SigLIP
508
+ — inline-quantized `input_proj` truncates `2*(x-0.5)` patches to integers
509
+ (a trained-in quirk), head_dim 72 padded to 80 like the reference, plain
510
+ bf16 `down_proj`.
511
+ - **Oracle**: **mlx-optiq IS the oracle** (stock mlx-lm cannot load it);
512
+ the mixed-precision quant is the native weight format, so matching optiq
513
+ = L2 = the floor. Profile `diffusion-gemma-dedicated`, L2. Gates:
514
+ single forward bit-exact (`tests/diffusion-parity.test.ts`), generation
515
+ token-for-token for both samplers (`diffusion-gen-parity`), image
516
+ token-for-token (`diffusion-vision`), serving (`diffusion-serving`), LoRA
517
+ with the denoising objective (`diffusion-lora`,
518
+ `src/train/diffusion-lora.ts`; MoE backward by stop-gradient on router
519
+ indices). All `MLX_BUN_TEST_DIFFUSION=1`.
520
+ - **Entry points — honest state**: `src/generate.ts` routes by
521
+ `instanceof DiffusionGemmaModel` to `generateDiffusionInner`, and
522
+ `src/server.ts` has a diffusion vision lane; **`src/cli.ts` contains no
523
+ diffusion reference** (`grep -c diffusion src/cli.ts` = 0). There is no
524
+ diffusion-specific CLI surface; the model is reachable only through the
525
+ generic `serve` / `generate` path by profile + instanceof dispatch, and
526
+ streaming yields tokens after the engine completes. D4 (performance,
527
+ measured) is open.
528
+
529
+ ### 6.6 Audio input (gemma-4 audio tower) — `src/audio/`
530
+
531
+ Summary of docs/design/generic-model-support.md. Scope: audio-in, text-out
532
+ through the chat API; TTS/STT endpoints, streaming audio, >30 s, batched
533
+ audio prefill, and 26B-A4B/DiffusionGemma audio (no `audio_config`) are
534
+ non-goals.
535
+
536
+ - **Oracle**: mlx-lm strips `audio_tower`/`embed_audio` at load and rejects
537
+ non-text content; optiq's internal gemma4 machinery is complete but its
538
+ serve frontend never exposes audio → the oracle is the optiq internal
539
+ model driven directly (`scripts/oracle/gen-e4b-audio-golden.py`,
540
+ `goldens/e4b-audio.json`). The e4b OptiQ-4bit sidecar already holds all
541
+ 752 audio tensors (bf16, incl. clipped-linear min/max stats) plus
542
+ `audio_config` and token ids (`boa 256000`, `audio 258881`,
543
+ `eoa 258883`) — no download.
544
+ - **Pipeline**: `decode.ts` (RIFF/WAVE PCM16/24/32/f32, mean mixdown, linear
545
+ resample to 16 kHz, PCM16/32768 like the oracle; >30 s truncates at
546
+ 480 000 samples BEFORE features, the oracle extractor's default) →
547
+ `transcode.ts` (`afconvert` for non-WAV, content-sniffed) →
548
+ `features.ts` (USM log-mel, frame 320 / hop 160 / rfft 512 in f64, 128 HTK
549
+ bins, `log(x+1e-3)`, the oracle's exact f32 Hann values baked in because
550
+ numpy's vectorized f32 cos differs by 1 ulp and log-amplifies) →
551
+ `conformer.ts` (SSCP two Conv2d stride-2 stages (128, 32) → Linear 1024 →
552
+ 12 Conformer blocks with chunked local attention (chunk 12, left 13,
553
+ softcap 50), depthwise Conv1d k=5, clipped linears → output_proj 1536 →
554
+ `embed_audio` RMSNormNoScale → Linear → /embed_scale). **Dtype contract
555
+ differs from siglip**: the oracle feeds f32 mel into bf16 weights and mlx
556
+ promotes, so activations stay f32 end-to-end — do not cast to weight
557
+ dtype. Clipped-linear stats are load-bearing (off diverges 90%).
558
+ - **LM semantics (resolved against the oracle)**: audio tokens are strictly
559
+ CAUSAL; any audio in the prompt disables the vision bidir overlay
560
+ entirely; per-layer-input zeroing uses the union multimodal mask; USM
561
+ parameters are fixed constructor defaults (no per-bin normalization);
562
+ `<|audio|>` → `boa + audio×n + eoa` with `n = min(ceil(ms/40), 750)` is
563
+ splice-side. The merge divides by embed_scale AFTER the bf16 cast (weak
564
+ scalar) — `features(preDivide=false)` + astype + div mirrors it.
565
+ - **Serve**: OpenAI `input_audio` plus optiq `audio`/`audio_url` parts;
566
+ lazy `getAudioTower` from the same sidecar, zero new flags; a request
567
+ with audio on a towerless model is an explicit 400; Anthropic
568
+ `/v1/messages` rejects audio blocks with a pointer to the OpenAI
569
+ endpoint; `audio` capability flag on `/v1/models`, `/library`, and the pi
570
+ `ready` frame (requires `audio_config` AND tower tensors — the 12B stub
571
+ sidecar reads false).
572
+ - **Gates**: T0 mel model-free (`tests/audio-features.test.ts`, 1e-5),
573
+ T1 tower rel-RMSE (`e4b-audio-tower`, bit-exact result), T2 e2e greedy +
574
+ decoded text EXACT for both fixtures (`e4b-audio`), T3 HTTP
575
+ (`e4b-audio-serve`, `MLX_BUN_TEST_AUDIO_SERVE=1`).
576
+ - **Open (A5)**: bench cells (tower ms, TTFT delta, RSS delta) into
577
+ docs/reference/benchmarks.md; 12B audio cell needs a sidecar rebuild via
578
+ optiq `build_vision_sidecar` (local 12B sidecar holds 1 audio tensor);
579
+ features-matrix note audio×batching = serial.
580
+ - PLAN anchor: "Phase: audio input — gemma-4 audio tower, e4b first".
354
581
 
355
- | Phase | Scope | Unlocks | Effort | Key risks |
356
- |---|---|---|---|---|
357
- | **1** | `UniversalDense` + descriptor table + rope factory (llama3/yarn/longrope) + bias term + goldens harness. Launch archs: **llama (⇒ Llama 3.x + Mistral + Ministral), qwen2, qwen3(fold-in), gemma, gemma2, smollm3, olmo2, olmo3, phi3, glm4, granite, starcoder2** | ~20 files at launch; the rest of bucket A (~36 total) as S-sized descriptor+golden follow-ons; F-wrappers for covered bases | **L** (module M, rope M, harness S, 12×goldens S-each) | rope variants (llama3/yarn/longrope math), gemma2 softcap manual-attn path, phi3 fused-proj split, BOS/tokenizer conventions per family (the double-BOS class of bug), qwen2 qkv bias |
358
- | **1.5** | `DenseLinear` (bf16/fp16 checkpoints, no `.scales`) | fp16-only small models; removes the "MLX-quantized only" constraint | **S–M** | dtype/cast points vs mlx-lm (bf16 matmul accumulation — see e4b length-sensitivity memory) |
359
- | **2** | `UniversalMoE` (router + SwitchGLU + shared experts + first-k-dense) : **mixtral, qwen2_moe, qwen3_moe, olmoe, glm4_moe, dots1, granitemoe, ernie4_5_moe, phimoe** | ~12–15 of bucket B (gpt_oss/llama4 excluded) | **M** (SwitchGLU exists; routing variants are config) | routing-score parity (softmax vs sigmoid + norm_topk), RAM honesty on 24 GB |
360
- | **3** | Demand-driven one-offs, qwen3_5 pattern: SSM/hybrid (**lfm2, qwen3_next, mamba2, nemotron_h**…), MLA module if a runnable target appears (**minicpm3**; deepseek-lineage mostly exceeds local RAM) | bucket C/D picks, ~1 model per effort unit | **M–L each** (cumulatively XL; never a blocker for A/B) | new cache types (conv/ssm state) touch the Cache interface + serving slots; each is a real port with its own goldens |
361
- | **4** | Tooling: **`mlx-bun port <hf-repo>`** — download smallest snapshot → descriptor coverage check (config keys + weight-audit dry run) → auto-regen goldens vs oracle venv → run parity gate → print verdict + manifest entry. Optionally: generalize `gen-model.ts` to emit from UniversalDense for graduation | turns "new arch" into a ~1-command, ~1-hour operation; makes the coverage claim self-verifying | **M** (scaffold) + **L** (generator generalization, optional) | oracle-venv coupling (mlx-lm version drift changes goldens — pin + record versions in parity.json, already the pattern) |
362
-
363
- Sequencing note: Phase 1's descriptor table should be *born* with only the
364
- 12 launch archs actually gated green — adding an arch without its manifest
365
- goldens is the one anti-pattern this design forbids.
582
+ ---
366
583
 
367
- ## 5. Top design decisions (summary)
584
+ ## 7. Top design decisions
368
585
 
369
586
  1. **Explicit descriptor table keyed by model_type (+ vendored
370
587
  MODEL_REMAPPING), not config sniffing** — support is a declared,
371
588
  testable surface; fingerprints remain a generated-dispatch guard only.
372
- 2. **One normalized config** — extend the existing `TextConfig` (it's
373
- already 70% there) with the bucket-A delta union; move per-arch quirks
374
- out of `loadModelConfig`'s if-chain into descriptor `configAliases`.
589
+ 2. **One profile per load, resolved before weights open** — exact artifact
590
+ > family > generic; mismatches refuse, never downgrade; profiles declare
591
+ construction only.
375
592
  3. **L1 bit-exact is the gate for generic too** — manifest-driven golden
376
- harness (explicit ids, raw per-step logits, same-GPU), one regen script
377
- + one test file for all generic archs.
378
- 4. **Weight audit at load** — unconsumed/missing tensors are a load error,
379
- never a silently-wrong model.
380
- 5. **Generic never shadows targeted; graduation never deletes generic** —
381
- the dispatch ladder is dedicated → generated → generic → reject, and the
382
- generic path is the permanent correctness fallback.
383
- 6. **Honest tiering in UX** — `mlx-bun models` shows `targeted` vs
384
- `generic` (and RAM fit), which doubles as the graduation worklist.
593
+ harness (explicit ids, raw per-step logits, same GPU).
594
+ 4. **Weight audit at load** — unconsumed/missing tensors are a load error.
595
+ 5. **Generic never shadows targeted; graduation never deletes generic.**
596
+ 6. **The oracle for any capability is whoever ships it** — mlx-lm (L1),
597
+ mlx-optiq (L2), mlx-vlm / optiq internals / Colibri where those are the
598
+ only implementations — and the profile's fidelity label says which.
599
+ 7. **Honest tiering in UX** — `mlx-bun ls`, hub REST, and discovery routes
600
+ show `targeted` vs `generic`; media capability flags check tensors.
601
+ 8. **New ports are sections of this doc**, with their durable phase record
602
+ in PLAN.md.
603
+
604
+ ## History
605
+
606
+ - 2026-06-24 — MiniCPM5 decode megakernel shelved (persistent single-dispatch
607
+ decode kernel, ~0.94× baseline at best; L2 ceiling 93/100 from quant
608
+ discontinuity amplifying 1-ULP GEMV drift). Premise removed by the
609
+ bandwidth-wall finding (decode-speed-program.md: the baseline decode path
610
+ sits at the memory-bandwidth wall; the real CPM5 waste was the unfused
611
+ swiglu, fixed by `compiledSwiglu`). Code shelved to scripts/experiments
612
+ 2026-07-01 and deleted with that directory 2026-08-23 (git history); no
613
+ megakernel code remains in `src/`. The doc
614
+ `docs/design/generic-model-support.md` is superseded by this line.
615
+ - 2026-06-24 — DiffusionGemma D0–D3 + D5 landed (bit-exact forward,
616
+ token-for-token generation, image, LoRA); D4 open.
617
+ - 2026-06-15 → 2026-08-18 — Qwen3.5-family: 3.6 text parity both bars;
618
+ 3.8 retarget (identical geometry), native MTP, vision, video landed.
619
+ - 2026-07-01 — Generic Phase 1 + 1.5 built (commit 9bd9f1b):
620
+ UniversalDense, 11 descriptors, rope factory, dense checkpoints, weight
621
+ audit, manifest harness.
622
+ - 2026-07-07 — Audio A0–A4 landed (e4b, oracle-exact); A5 open.
623
+ - 2026-08-17 — GLM-5.2 Colibri port closed (Phase 21); surface retired,
624
+ design archived.
625
+ - 2026-08-21 — Model profiles (`src/model/profile.ts`) introduced; exact
626
+ artifact declarations for Qwen3.8 OptiQ and GLM-5.2 Colibri.
627
+ - 2026-08-23 — This doc consolidated: audio-input-plan,
628
+ diffusion-gemma-port, minicpm5-decode-megakernel, and the Colibri archive
629
+ summarized here; ports become sections.