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,8 +1,8 @@
1
- // TurboQuant KV-cache codec — pure mlx-op composition, ported op-for-op
1
+ // TurboQuant KV-cache codec — reference operations ported op-for-op
2
2
  // from the vendored vllm-metal reference
3
3
  // (lab/repro/vllm-metal-turboquant/turboquant_reference.py). See
4
- // docs/design/turboquant-kv.md for the algorithm writeup and
5
- // goldens/turboquant.json (via tests/turboquant-ops.test.ts) for the
4
+ // docs/design/turboquant.md for the algorithm writeup and
5
+ // goldens/turboquant.json (via tests/research/turboquant-ops.test.ts) for the
6
6
  // bit-exactness oracle. Quantization groups run along the LAST axis
7
7
  // (head_dim) only — never the token axis, per the standing invariant.
8
8
  //
@@ -27,6 +27,10 @@ import {
27
27
  type TurboQuantHeadDim, type TurboQuantValueBits,
28
28
  } from "./turboquant-tables";
29
29
 
30
+ import { tryJointDecodePackedKv, type PackedKvArrays } from "./turboquant-kv-decode";
31
+ import { tryDecodePackedKvInverse256 } from "./turboquant-kv-inverse";
32
+ import { isShapelessTracing } from "./compile";
33
+
30
34
  type S = MlxHandle;
31
35
 
32
36
  const BLOCK_SIZE = 32;
@@ -421,6 +425,32 @@ export function decodeValuesRotated(
421
425
  return outBf16;
422
426
  }
423
427
 
428
+ /** Optional exact packed K/V decode. Inputs stay borrowed; the caller owns
429
+ * the returned pair. The eager path keeps the reference inverse rotation. */
430
+ export function tryDecodePackedKv(
431
+ inputs: PackedKvArrays, kBits: number, vBits: number, headDim: number,
432
+ deferV = false, s: S = gpuStream,
433
+ ): [MlxArray, MlxArray] | null {
434
+ // Reject unsupported value widths before requesting their centroid table.
435
+ if (![2, 3, 4, 5, 8].includes(vBits)) return null;
436
+ if (!deferV && headDim === 256 && kBits === 8 && vBits === 3 &&
437
+ s === gpuStream && !isShapelessTracing() && inputs[0].shape[2]! >= 8192) {
438
+ const decoded = tryDecodePackedKvInverse256(
439
+ inputs, centroidsFor(vBits), signArray(256), kBits, vBits, headDim, s,
440
+ );
441
+ if (decoded) return decoded;
442
+ }
443
+ const decoded = tryJointDecodePackedKv(inputs, centroidsFor(vBits), kBits, vBits, headDim, deferV, s);
444
+ if (!decoded || deferV) return decoded;
445
+ const [key, rotated] = decoded;
446
+ try {
447
+ const value = fwht(rotated, false, s);
448
+ try { return [key, value.astype(Dtype.bfloat16, s)]; }
449
+ finally { value.dispose(); }
450
+ } catch (error) { key.dispose(); throw error; }
451
+ finally { rotated.dispose(); }
452
+ }
453
+
424
454
  /** Undo the value rotation on an attention output computed against
425
455
  * decodeValuesRotated values. f32 through the transform (matching the
426
456
  * eager decode path's precision), returned in the input's dtype. */
@@ -1,5 +1,5 @@
1
- // GENERATED by scripts/regen-turboquant-goldens.ts — DO NOT EDIT.
2
- // Regenerate: bun scripts/regen-turboquant-goldens.ts
1
+ // GENERATED by scripts/regen.ts turboquant — DO NOT EDIT.
2
+ // Regenerate: bun scripts/regen.ts turboquant
3
3
  //
4
4
  // Source: lab/repro/vllm-metal-turboquant/turboquant_reference.py (vendored
5
5
  // vllm-metal oracle, see PROVENANCE.md in that directory), executed under
@@ -2,11 +2,11 @@
2
2
  //
3
3
  // Companion to src/train/forward.ts's buildBatchedPadMask, which builds the
4
4
  // PREFILL mask ([B,1,L,L], offset 0, right-padded — the training case, already
5
- // parity-proven by tests/train-batch-e2e.test.ts). This module covers the
5
+ // parity-proven by tests/parity/train-batch-e2e.test.ts). This module covers the
6
6
  // DECODE case for batched serving: left-padded rows sharing one growing
7
7
  // [B,H,S,D] KV buffer, where a step forwards N new query tokens at a nonzero
8
8
  // offset. (The two builders should be consolidated when batched prefill is
9
- // wired into the serving path — phase S1a, docs/design/parallel-slots.md.)
9
+ // wired into the serving path — phase S1a, docs/design/batching.md.)
10
10
  //
11
11
  // Why left-padding for decode: with rows right-aligned in the KV buffer, every
12
12
  // row's next write lands in the SAME column, so one advancing offset serves
@@ -15,6 +15,7 @@
15
15
  // thing the mask must encode is (a) causality, (b) each row's left padding,
16
16
  // and (c) the sliding window, if any.
17
17
 
18
+ import { cacheSignature } from "./gemma4-base";
18
19
  import { MlxArray } from "../mlx/array";
19
20
  import * as ops from "../mlx/ops";
20
21
  import { createCausalMask, KVCache, type Cache, type Mask } from "./gemma4-base";
@@ -92,6 +93,11 @@ export class BatchedDecodeMaskCache implements Cache {
92
93
  #ropeArr: MlxArray | null = null;
93
94
  #ropeForOffset = -1;
94
95
 
96
+ /** A decorator, not a storage kind: never claims the inner cache's
97
+ * identity (isPlainKvCache(wrapper) must stay false — it is not a
98
+ * KVCache), but records what it wraps for diagnostics/persistence. */
99
+ signature(): string { return `kv:batched-mask:${cacheSignature(this.inner)}`; }
100
+
95
101
  constructor(
96
102
  private readonly inner: Cache,
97
103
  private readonly B: number,
@@ -17,7 +17,7 @@
17
17
  // (maybeQuantizeKv at chunk boundaries — see BatchScheduler.#quantizeSolo),
18
18
  // so a row's quantized bytes are bit-exact vs serial `--kv-quant config` by
19
19
  // construction; this module only re-arranges those bytes across the batch
20
- // axis. Gates: tests/batched-kv-quant-parity.test.ts.
20
+ // axis. Gates: tests/parity/batched-kv-quant-parity.test.ts.
21
21
 
22
22
  import { MlxArray } from "../mlx/array";
23
23
  import * as ops from "../mlx/ops";
@@ -22,9 +22,9 @@
22
22
  // every step; it self-tracks per-row offset/leftPad and the scalar ring
23
23
  // state. N=1 decode updates only (rows solo-prefill serially, then merge).
24
24
  //
25
- // Gates: tests/batched-rotating-quant.test.ts (model-free byte-identity vs
25
+ // Gates: tests/unit/batched-rotating-quant.test.ts (model-free byte-identity vs
26
26
  // the serial oracle per row, through ring wrap, B=1 and B=2) + the gemma
27
- // scheduler gate in tests/batched-kv-quant-parity.test.ts.
27
+ // scheduler gate in tests/parity/batched-kv-quant-parity.test.ts.
28
28
 
29
29
  import { MlxArray } from "../mlx/array";
30
30
  import * as ops from "../mlx/ops";
@@ -20,7 +20,7 @@
20
20
  // temporal coordinates then ROLLED to the ring's physical layout — see
21
21
  // buildBatchedRotatingMask (a column-by-column port of mlx-lm make_mask,
22
22
  // including the `roll(shift=idx+1)`). Gated model-free against mlx-lm
23
- // (tests/batched-rotating.test.ts) + end-to-end vs a long-context Gemma oracle.
23
+ // (tests/unit/batched-rotating.test.ts) + end-to-end vs a long-context Gemma oracle.
24
24
 
25
25
  import { MlxArray } from "../mlx/array";
26
26
  import * as ops from "../mlx/ops";
@@ -1,4 +1,4 @@
1
- // Compiled decode step (Phase A of docs/design/optimization_plan.md).
1
+ // Compiled decode step (Phase A of docs/archive/investigations/optimization_plan.md).
2
2
  //
3
3
  // The single-token decode graph is identical token-to-token except for
4
4
  // integer state (RoPE offset, cache write position, active length). This
@@ -1,3 +1,4 @@
1
+ import { cleanupFailure, disposeResources } from "../engine/resources";
1
2
  // DiffusionGemma-26B-A4B-it (model_type "diffusion_gemma") — the first
2
3
  // NON-autoregressive model in the codebase. Instead of a left-to-right AR loop
3
4
  // it runs an ENCODER prefill over the prompt (builds a KV cache) and then a
@@ -10,7 +11,7 @@
10
11
  // Ported verbatim from optiq/vlm/_mlxvlm/models/diffusion_gemma/language.py.
11
12
  // Reuses mlx-bun's quantized primitives (QuantizedLinear/Embedding/SwitchLinear,
12
13
  // RMSNorm, KVCache/RotatingKVCache) from gemma4-base. Architecture deltas vs
13
- // gemma4 (verified against the checkpoint, see docs/design/diffusion-gemma-port.md):
14
+ // gemma4 (verified against the checkpoint, see docs/design/generic-model-support.md):
14
15
  // - attention scale=1.0, NO attention softcap (only final-logit softcap 30 fp32)
15
16
  // - QK/V-norm POST-proj / PRE-RoPE; v_norm is RMSNormNoScale; V gets no RoPE
16
17
  // - full-attention layers (5,11,17,23,29) reuse k as v (no v_proj), hd=512 kv=2,
@@ -626,8 +627,8 @@ export class DiffusionGemmaModel {
626
627
  /** Prefill from a pre-spliced image+text prompt → populated cache. */
627
628
  prefillVision(splicedIds: number[], pixels: MlxArray): Cache[] {
628
629
  const cache = this.makeCache();
629
- this.encodeVisionPrefill(splicedIds, pixels, cache);
630
- return cache;
630
+ try { this.encodeVisionPrefill(splicedIds, pixels, cache); return cache; }
631
+ catch (error) { cleanupFailure(error, () => disposeResources(cache)); }
631
632
  }
632
633
 
633
634
  /** One KVCache (full) / RotatingKVCache (sliding) per layer — the encoder
@@ -899,16 +900,15 @@ export class DiffusionGemmaModel {
899
900
  * loop reuses it across all steps of a canvas block). */
900
901
  prefill(promptIds: number[]): Cache[] {
901
902
  const cache = this.makeCache();
902
- this.encodePrompt(promptIds, cache);
903
- return cache;
903
+ try { this.encodePrompt(promptIds, cache); return cache; }
904
+ catch (error) { cleanupFailure(error, () => disposeResources(cache)); }
904
905
  }
905
906
 
906
907
  /** One denoising-step decoder pass -> softcapped logits [1, canvas, vocab]. */
907
908
  decoderLogits(canvasArr: MlxArray, cache: Cache[], scEmbeddings: MlxArray | null): MlxArray {
908
909
  const h = this.decodeCanvasArr(canvasArr, cache, null, scEmbeddings);
909
- const logits = this.logitsFromHidden(h);
910
- h.dispose();
911
- return logits;
910
+ try { return this.logitsFromHidden(h); }
911
+ finally { h.dispose(); }
912
912
  }
913
913
 
914
914
  /** Dequantized embedding table (once per call), for self-conditioning soft
@@ -24,16 +24,55 @@ import {
24
24
  } from "./glm52-memory";
25
25
  import { UniversalDenseModel } from "./universal/dense";
26
26
  import { genericArgsFor } from "./universal/archs";
27
+ import {
28
+ ModelImplementationRegistry,
29
+ type ModelImplementation,
30
+ type ModelImplementationProvider,
31
+ } from "./implementation";
27
32
  import {
28
33
  assertResolvedModelProfile,
29
34
  resolveModelProfile,
30
35
  type ResolvedModelProfile,
36
+ type ResolveModelProfileOptions,
37
+ type ModelGraph,
31
38
  } from "./profile";
32
39
 
33
40
  export type RuntimeModel =
34
41
  | Gemma4Model | MiniCPM5Model | Qwen35Model | Qwen3Model | Qwen3MoeModel
35
42
  | DiffusionGemmaModel | Glm52Model | UniversalDenseModel;
36
43
 
44
+ function residentImplementation(
45
+ id: string,
46
+ graph: ModelGraph,
47
+ create: ModelImplementation<Weights, RuntimeModel>["create"],
48
+ ): ModelImplementation<Weights, RuntimeModel> {
49
+ return { id, graph, loader: "safetensors",
50
+ loop: graph === "diffusion-gemma" ? "diffusion" : "autoregressive", create };
51
+ }
52
+
53
+ /** Engine-owned registrations. Exact quant profiles can name additional
54
+ * implementations in a composed registry, without changing sessions or files. */
55
+ export const MLX_MODEL_IMPLEMENTATIONS = new ModelImplementationRegistry<Weights, RuntimeModel>([
56
+ residentImplementation("diffusion-gemma", "diffusion-gemma", (weights, config) => new DiffusionGemmaModel(weights, config)),
57
+ residentImplementation("minicpm5", "minicpm5", (weights, config) => new MiniCPM5Model(weights, config)),
58
+ residentImplementation("qwen3.5", "qwen3.5", (weights, config) => new Qwen35Model(weights, config)),
59
+ residentImplementation("qwen3-moe", "qwen3-moe", (weights, config) => new Qwen3MoeModel(weights, config)),
60
+ residentImplementation("qwen3", "qwen3", (weights, config) => new Qwen3Model(weights, config)),
61
+ residentImplementation("gemma4", "gemma4", (weights, config) => new Gemma4Model(weights, config)),
62
+ residentImplementation("gemma4-generated", "gemma4", (weights, config) => {
63
+ const Graph = GENERATED.get(configFingerprint(config));
64
+ if (!Graph) throw new Error("no generated Gemma graph for this config; refusing to fall back");
65
+ return new Graph(weights, config);
66
+ }),
67
+ residentImplementation("universal-dense", "universal-dense", (weights, config) =>
68
+ new UniversalDenseModel(weights, config, genericArgsFor(config)!)),
69
+ ]);
70
+
71
+ export interface ModelOpenOptions<Model = RuntimeModel> extends Glm52RuntimeOpenOptions {
72
+ readonly profiles?: ResolveModelProfileOptions;
73
+ readonly implementations?: ModelImplementationProvider<Weights, Model>;
74
+ }
75
+
37
76
  export interface Glm52RuntimeOpenOptions {
38
77
  /** Whole-process ceiling. Defaults to the smaller of the validated 25 GiB
39
78
  * preset and physical RAM. */
@@ -100,47 +139,51 @@ export async function openGlm52RuntimeModel(
100
139
  * model.safetensors.index.json, so it must bypass Weights.open and use its
101
140
  * dedicated header catalog/tensor source.
102
141
  */
103
- export async function openModel(
142
+ export function openModel<Model>(
104
143
  modelDir: string,
105
- options: Glm52RuntimeOpenOptions = {},
106
- ): Promise<RuntimeModel> {
144
+ options: ModelOpenOptions<Model> & { readonly implementations: ModelImplementationProvider<Weights, Model> },
145
+ ): Promise<Model>;
146
+ export function openModel(modelDir: string, options?: ModelOpenOptions): Promise<RuntimeModel>;
147
+ export async function openModel<Model>(
148
+ modelDir: string,
149
+ options: ModelOpenOptions<Model> = {},
150
+ ): Promise<Model | RuntimeModel> {
107
151
  const config = await loadModelConfig(modelDir);
108
- const profile = resolveModelProfile(config);
109
- if (profile.profile.execution.loader === "colibri")
152
+ const profile = resolveModelProfile(config, options.profiles);
153
+ if (profile.profile.execution.loader === "colibri") {
154
+ if (options.implementations || profile.profile.execution.implementation !== undefined)
155
+ throw new Error("custom Colibri implementations require a streamed loader binding; refusing to fall back");
110
156
  return (await openGlm52RuntimeModel(modelDir, options)).model;
111
- return createModel(await Weights.open(modelDir), config, profile);
157
+ }
158
+ // Resolve before opening weights; missing or incompatible code allocates nothing.
159
+ const implementation = (options.implementations ?? MLX_MODEL_IMPLEMENTATIONS).select(config, profile);
160
+ const weights = await Weights.open(modelDir);
161
+ try { return implementation.create(weights, config, profile); }
162
+ catch (error) { weights.dispose(); throw error; }
112
163
  }
113
164
 
114
- /** Construct the graph named by a previously validated profile. */
165
+ /** Construct the binding named by a validated profile. A supplied registry can
166
+ * return any backend-owned interface; the compatibility default returns the
167
+ * legacy model union. The caller retains ownership of the supplied weights. */
168
+ export function createModel<Model>(
169
+ weights: Weights, config: ModelConfig, resolved: ResolvedModelProfile | undefined,
170
+ implementations: ModelImplementationProvider<Weights, Model>,
171
+ ): Model;
115
172
  export function createModel(
173
+ weights: Weights, config: ModelConfig, resolved?: ResolvedModelProfile,
174
+ implementations?: ModelImplementationProvider<Weights, RuntimeModel>,
175
+ ): RuntimeModel;
176
+ export function createModel<Model>(
116
177
  weights: Weights,
117
178
  config: ModelConfig,
118
179
  resolved: ResolvedModelProfile = resolveModelProfile(config),
119
- ): RuntimeModel {
180
+ implementations: ModelImplementationProvider<Weights, Model> | ModelImplementationProvider<Weights, RuntimeModel> = MLX_MODEL_IMPLEMENTATIONS,
181
+ ): Model | RuntimeModel {
120
182
  assertResolvedModelProfile(config, resolved);
121
- const execution = resolved.profile.execution;
122
- switch (execution.graph) {
123
- case "glm5.2":
124
- throw new Error(
125
- "glm_moe_dsa uses the direct Colibri container; construct it with " +
126
- "openModel(modelDir) or Glm52Model.open(modelDir)",
127
- );
128
- // DiffusionGemma is non-autoregressive. The profile pairs this graph with
129
- // the denoising loop; generate() recognizes the constructed model surface.
130
- case "diffusion-gemma": return new DiffusionGemmaModel(weights, config);
131
- case "minicpm5": return new MiniCPM5Model(weights, config);
132
- case "qwen3.5": return new Qwen35Model(weights, config);
133
- case "qwen3-moe": return new Qwen3MoeModel(weights, config);
134
- case "qwen3": return new Qwen3Model(weights, config);
135
- case "gemma4": {
136
- // Generated specializations remain exact config matches. Output-changing
137
- // request methods stay independently resolved and cannot be fallbacks.
138
- const cls = execution.specialization === "generated"
139
- ? GENERATED.get(configFingerprint(config))!
140
- : Gemma4Model;
141
- return new cls(weights, config);
142
- }
143
- case "universal-dense":
144
- return new UniversalDenseModel(weights, config, genericArgsFor(config)!);
145
- }
183
+ if (resolved.profile.execution.loader === "colibri")
184
+ throw new Error(
185
+ "glm_moe_dsa uses the direct Colibri container; construct it with " +
186
+ "openModel(modelDir) or Glm52Model.open(modelDir)",
187
+ );
188
+ return implementations.select(config, resolved).create(weights, config, resolved);
146
189
  }
@@ -1,4 +1,4 @@
1
- // Config fingerprint for generated-model dispatch (docs/design/optimization_plan.md
1
+ // Config fingerprint for generated-model dispatch (docs/archive/investigations/optimization_plan.md
2
2
  // Phase C): a stable hash over every config field that changes the
3
3
  // decode graph structure, plus the kv_config quant layout. A generated
4
4
  // specialization is used only when the fingerprint of the loaded config
@@ -545,7 +545,7 @@ export function flashBackward(
545
545
  // (dK_j[d] → [kv_row][d]); no post-transpose. (An earlier "fix" transposed
546
546
  // it on the false belief the buffer was swapped — that transpose is only the
547
547
  // identity at Tkv==D and corrupted dK for Tkv≠D; removed after
548
- // scripts/experiments/flash-dkv-debug.ts showed the raw output is correct.)
548
+ // flash-dkv-debug.ts (deleted 2026-08-23; git history) showed the raw output is correct.)
549
549
  return [dq[0]!, dkv[0]!, dkv[1]!];
550
550
  } finally {
551
551
  Dvec.dispose();
@@ -621,8 +621,8 @@ export function flashAttention(
621
621
  // - This flash kernel is the L2 path: a port of mlx-optiq's
622
622
  // flash_attention_metal (O(L) backward memory). Its parity oracle is OPTIQ,
623
623
  // not finite differences. Confirmed: optiq's flash dK == ops.sdpa to f16
624
- // (scripts/flash-optiq-check.py, rel 0.0%), and mlx-bun's flash == ops.sdpa
625
- // (tests/flash-attention.test.ts) ⟹ mlx-bun flash == optiq flash (L2 parity).
624
+ // (scripts/oracle/flash-optiq-check.py, rel 0.0%), and mlx-bun's flash == ops.sdpa
625
+ // (tests/unit/flash-attention.test.ts) ⟹ mlx-bun flash == optiq flash (L2 parity).
626
626
  //
627
627
  // Two real port bugs were fixed to reach that parity (the original diverged
628
628
  // ~100% from optiq on dK): a spurious dK transpose in flashBackward (corrupted
@@ -1,5 +1,5 @@
1
1
  // gemma4-base — the config-INDEPENDENT machinery shared by every
2
- // architecture variant (docs/design/optimization_plan.md Phase B: pure code
2
+ // architecture variant (docs/archive/investigations/optimization_plan.md Phase B: pure code
3
3
  // movement out of the gemma4.ts monolith; every definition here is
4
4
  // verbatim from it). Cache classes, quantized-KV SDPA, masks, the
5
5
  // quantized primitives + LoRA machinery, and small graph helpers live
@@ -13,6 +13,7 @@ import type { Weights } from "../weights";
13
13
  import { MlxArray } from "../mlx/array";
14
14
  import { Dtype } from "../mlx/ffi";
15
15
  import * as ops from "../mlx/ops";
16
+ import { quantizedMatmulRows } from "../mlx/quantized-rows";
16
17
  import { expertOffloadArray } from "../expert-offload";
17
18
  import * as tq from "../mlx/turboquant-ops";
18
19
  import { runtimeValue } from "../runtime-config";
@@ -155,8 +156,10 @@ export class QuantizedLinear {
155
156
  return this._scales.shape[0]!;
156
157
  }
157
158
 
158
- forward(x: MlxArray): MlxArray {
159
- let out = ops.quantizedMatmul(
159
+ forward(x: MlxArray, independentRows = false): MlxArray {
160
+ let out = independentRows ? quantizedMatmulRows(
161
+ x, this._w, this._scales, this._biases, this._spec,
162
+ ) : ops.quantizedMatmul(
160
163
  x,
161
164
  this._w,
162
165
  this._scales,
@@ -263,8 +266,13 @@ export type SharedKv =
263
266
 
264
267
  export interface Cache {
265
268
  offset: number;
266
- /** Stable storage identity for compatibility guards and persistence. */
267
- signature?(): string;
269
+ /** Stable storage identity for compatibility guards and persistence.
270
+ * REQUIRED: every capability predicate (isPlainKvCache, isRotating*,
271
+ * kv-store codecs) is a string compare on this value, so a missing
272
+ * override used to fail OPEN into wrong routing (PRs #42/#43:
273
+ * BatchedRotatingCache shipped without one and batched joins dropped
274
+ * running rows). Wrappers report their own kind plus the inner kind. */
275
+ signature(): string;
268
276
  /** Physical cache storage consumed by one additional token for one row.
269
277
  * Recurrent caches return 0 because their state does not grow with the
270
278
  * sequence. Optional for stateless/training adapters that are never
@@ -303,7 +311,7 @@ export interface Cache {
303
311
  }
304
312
 
305
313
  export function cacheSignature(cache: Cache | undefined): string {
306
- return cache?.signature?.() ?? "unknown";
314
+ return cache ? cache.signature() : "unknown";
307
315
  }
308
316
 
309
317
  export interface RowBatchCache extends Cache {
@@ -1499,7 +1507,7 @@ export class RotatingQuantizedKVCache implements Cache {
1499
1507
  /** One TurboQuant-encoded (K, V) storage tuple — the 5 arrays kv-store.ts
1500
1508
  * and toQuantized/fromKVCache pass around. Not `ops.QuantizedTensor`
1501
1509
  * (mlx's affine int4/int8 scheme): this is the rotation + Lloyd-Max
1502
- * layout (docs/design/turboquant-kv.md), asymmetric-affine for keys,
1510
+ * layout (docs/design/turboquant.md), asymmetric-affine for keys,
1503
1511
  * FWHT+Lloyd-Max for values. Field order is the kv-store.ts tensor-slot
1504
1512
  * contract — do not reorder without updating snapshotCache/loadKvCache. */
1505
1513
  export interface TurboQuantTensor {
@@ -1518,7 +1526,7 @@ export const disposeTurboQuant = (t: TurboQuantTensor): void => {
1518
1526
  t.vScales.dispose();
1519
1527
  };
1520
1528
 
1521
- /** TurboQuant KV cache — v1 (docs/design/turboquant-kv.md): dequantize-
1529
+ /** TurboQuant KV cache — v1 (docs/design/turboquant.md): dequantize-
1522
1530
  * on-fetch. Deliberately does NOT subclass KVCache/RotatingKVCache (or
1523
1531
  * QuantizedKVCache) — a novel class fails every generated-file
1524
1532
  * `#matches()` guard and every batching `instanceof` allow-list, so it
@@ -1548,6 +1556,8 @@ export class TurboQuantKVCache implements Cache {
1548
1556
  offset = 0;
1549
1557
  /** Validated lazily on first update, once head_dim is known. */
1550
1558
  #headDim: number | null = null;
1559
+ /** Experimental operation selection, captured once for this cache. */
1560
+ readonly #fusedDecode = process.env.MLX_BUN_TURBOQUANT_FUSED_DECODE === "1";
1551
1561
 
1552
1562
  constructor(readonly kBits: number, readonly vBits: number) {}
1553
1563
 
@@ -1626,6 +1636,13 @@ export class TurboQuantKVCache implements Cache {
1626
1636
  const [B, H, , D] = a.shape as [number, number, number, number];
1627
1637
  return a.slice([0, 0, 0, 0], [B, H, upTo, D]);
1628
1638
  };
1639
+ if (this.#fusedDecode) {
1640
+ const inputs = [cut(t.kIdx), cut(t.kScales), cut(t.kZeros), cut(t.vPacked), cut(t.vScales)] as const;
1641
+ try {
1642
+ const decoded = tq.tryDecodePackedKv(inputs, this.kBits, this.vBits, headDim, deferV);
1643
+ if (decoded) return decoded;
1644
+ } finally { for (const input of inputs) input.dispose(); }
1645
+ }
1629
1646
  const kIdxCut = cut(t.kIdx);
1630
1647
  const kIdxUnpacked = this.kBits < 8 ? tq.unpackBits(kIdxCut, this.kBits, headDim) : kIdxCut;
1631
1648
  if (kIdxUnpacked !== kIdxCut) kIdxCut.dispose();
@@ -2080,7 +2097,7 @@ export function quantizedSdpaTiled(
2080
2097
  function fusedSdpaSupported(q: MlxArray, mask: Mask, groupSize: number, bits: number): boolean {
2081
2098
  // Escape hatch mirroring optiq serve's --no-fused-kv: forces the
2082
2099
  // stock unfused path everywhere. Also the A/B lever for
2083
- // scripts/bench-fused-prefill.ts. Read per call (cheap next to the
2100
+ // scripts/bench-levers.ts fused-prefill. Read per call (cheap next to the
2084
2101
  // FFI work) so tests and paired A/B harnesses can flip it in-process.
2085
2102
  if (runtimeValue("MLX_BUN_NO_FUSED_SDPA") === "1") return false;
2086
2103
  if (bits !== 4 && bits !== 8) return false;
@@ -23,7 +23,7 @@ import type { Weights } from "../weights";
23
23
  import { MlxArray } from "../mlx/array";
24
24
  import { Dtype } from "../mlx/ffi";
25
25
  import * as ops from "../mlx/ops";
26
- import { isExpertTracing, recordRouting } from "../expert-trace";
26
+ import { isExpertTracing, recordRouting } from "../lab/expert-trace/expert-trace";
27
27
 
28
28
  // The shared, config-independent machinery lives in gemma4-base.ts
29
29
  // (Phase B extraction); this file keeps the architecture-specific
@@ -56,7 +56,7 @@ import { Checkpoint } from "../mlx/checkpoint";
56
56
  import { flashAttention, getTrainingAttn, flashSupported } from "./flash-attention";
57
57
  import { unrotateValues as tqUnrotateValues } from "../mlx/turboquant-ops";
58
58
  import { CompiledFunction } from "../mlx/compile";
59
- import { flagOn } from "../flags";
59
+ import { flagOn } from "../runtime-config";
60
60
 
61
61
  /** Verbatim port of mlx_lm/models/gemma4_text.py:
62
62
  * `@partial(mx.compile, shapeless=True) def geglu(gate, x): return nn.gelu_approx(gate) * x`
@@ -242,7 +242,7 @@ class Attention {
242
242
  /** A number offset takes the static fast::rope; an array offset (set by
243
243
  * compiled-decode trace adapters) takes the dynamic variant — same
244
244
  * kernel, offset read from the array (bit-exactness asserted in
245
- * tests/compile.test.ts). */
245
+ * tests/unit/compile.test.ts). */
246
246
  rope(x: MlxArray, offset: number | MlxArray): MlxArray {
247
247
  return typeof offset === "number"
248
248
  ? ops.rope(x, this.headDim, this.ropeBase, offset, this.ropeFreqs)
@@ -370,7 +370,7 @@ class Attention {
370
370
  // verified route: its dQ/dK/dV vjps are exact (finite-difference checked;
371
371
  // it's what mlx-lm's tuner differentiates). The two historical bugs were
372
372
  // in THIS flash kernel (spurious dK transpose, divergent dQ barrier),
373
- // both fixed and gated by tests/flash-attention.test.ts vs the ops.sdpa
373
+ // both fixed and gated by tests/unit/flash-attention.test.ts vs the ops.sdpa
374
374
  // oracle — see flash-attention.ts's routing comment. Flash also remains
375
375
  // Gemma-guarded in the trainer (e4b >=2K SIGTRAP not yet re-validated).
376
376
  // Full layers → window 0 (pure causal); sliding layers → their window.
@@ -885,7 +885,7 @@ export class Gemma4Model {
885
885
  * which attend bidirectionally among themselves (use_bidirectional_attention:
886
886
  * "vision" — text stays causal; audio prompts pass NO bidir mask, and a
887
887
  * mixed image+audio prompt drops the image overlay too, §3.3 Q1 of
888
- * docs/design/audio-input-plan.md). `ids` ([1, L], the spliced token ids)
888
+ * docs/design/generic-model-support.md). `ids` ([1, L], the spliced token ids)
889
889
  * is required for per-layer-input models (e2b/e4b): multimodal soft-token
890
890
  * positions get token 0's per-layer embedding, and their tower content
891
891
  * enters only through the projection term (the merged hidden). Matches
@@ -1015,7 +1015,7 @@ export class Gemma4Model {
1015
1015
  return h;
1016
1016
  }
1017
1017
 
1018
- // --- Segmented-backward support (docs/design/segmented-backward-training.md
1018
+ // --- Segmented-backward support (docs/design/orpo-training.md
1019
1019
  // §4 Phase B). These are ADDITIVE — forwardLayers is untouched. The segmented
1020
1020
  // driver builds masks + per-layer inputs ONCE, then drives runLayerRange per
1021
1021
  // segment, threading the KV-shared donor K/V across segment boundaries.
@@ -4,8 +4,8 @@
4
4
  // fingerprint: 9f812d2eb461fcbe
5
5
  //
6
6
  // Branch-resolved, unrolled forward pass for this architecture
7
- // (docs/design/optimization_plan.md Phase C). Bit-exactness vs the monolith is the
8
- // generator's gate: tests/generated-parity.test.ts. The cache-signature
7
+ // (docs/archive/investigations/optimization_plan.md Phase C). Bit-exactness vs the monolith is the
8
+ // generator's gate: tests/parity/generated-parity.test.ts. The cache-signature
9
9
  // guard falls back to the monolith for anything this file wasn't
10
10
  // generated for (bf16 compat runs, vision bidir prefill).
11
11
 
@@ -4,8 +4,8 @@
4
4
  // fingerprint: c9dd67ed5a525231
5
5
  //
6
6
  // Branch-resolved, unrolled forward pass for this architecture
7
- // (docs/design/optimization_plan.md Phase C). Bit-exactness vs the monolith is the
8
- // generator's gate: tests/generated-parity.test.ts. The cache-signature
7
+ // (docs/archive/investigations/optimization_plan.md Phase C). Bit-exactness vs the monolith is the
8
+ // generator's gate: tests/parity/generated-parity.test.ts. The cache-signature
9
9
  // guard falls back to the monolith for anything this file wasn't
10
10
  // generated for (bf16 compat runs, vision bidir prefill).
11
11
 
@@ -4,8 +4,8 @@
4
4
  // fingerprint: 418e9adc386ea67c
5
5
  //
6
6
  // Branch-resolved, unrolled forward pass for this architecture
7
- // (docs/design/optimization_plan.md Phase C). Bit-exactness vs the monolith is the
8
- // generator's gate: tests/generated-parity.test.ts. The cache-signature
7
+ // (docs/archive/investigations/optimization_plan.md Phase C). Bit-exactness vs the monolith is the
8
+ // generator's gate: tests/parity/generated-parity.test.ts. The cache-signature
9
9
  // guard falls back to the monolith for anything this file wasn't
10
10
  // generated for (bf16 compat runs, vision bidir prefill).
11
11
 
@@ -291,7 +291,7 @@ function getDsaRankKeyKernel(): MetalKernel {
291
291
  ? ~bits
292
292
  : (bits ^ 0x80000000u);
293
293
  const uint descending = ~ascending;
294
- keys[i] = (ulong(descending) << 32) | ulong(i);
294
+ keys[i] = descending;
295
295
  `,
296
296
  });
297
297
  }
@@ -308,7 +308,8 @@ function getDsaContractOrderKeyKernel(): MetalKernel {
308
308
  const uint i = thread_position_in_grid.x;
309
309
  const uint position = selected_positions[i];
310
310
  const uint threshold_class = selected_scores[i] > threshold ? 0u : 1u;
311
- keys[i] = (ulong(threshold_class) << 32) | ulong(position);
311
+ // MLX axis lengths are signed int32; positions leave the high bit free.
312
+ keys[i] = (threshold_class << 31) | position;
312
313
  `,
313
314
  });
314
315
  }
@@ -318,8 +319,8 @@ function getDsaContractOrderKeyKernel(): MetalKernel {
318
319
  /**
319
320
  * Deterministic on-device equivalent of `selectDsaThresholdTiesF32`.
320
321
  *
321
- * The first uint64 key sorts by score descending and then position ascending,
322
- * making the top-k set deterministic even at the threshold. The second key
322
+ * Stable sorting of the first uint32 key orders scores descending and keeps
323
+ * equal-score positions ascending, including threshold ties. The second key
323
324
  * restores Colibri's observable two-scan order: all scores strictly above the
324
325
  * threshold in position order, followed by threshold ties in position order.
325
326
  * The caller owns the returned arrays and must call `dispose()` once the last
@@ -340,18 +341,21 @@ export function selectGlm52DsaDevice(
340
341
  ? scores
341
342
  : scores.astype(Dtype.float32);
342
343
  const [rankKeys] = getDsaRankKeyKernel().apply([scoresF32], {
343
- outputs: [{ shape: [contextLength], dtype: Dtype.uint64 }],
344
+ outputs: [{ shape: [contextLength], dtype: Dtype.uint32 }],
344
345
  grid: [contextLength, 1, 1],
345
346
  threadGroup: [Math.min(256, contextLength), 1, 1],
346
347
  });
347
- const partition = ops.argpartitionAxis(rankKeys!, topK - 1, 0);
348
- const selectedUnordered = partition.slice([0], [topK]);
348
+ // Stable argsort retains ascending positions for equal score bits, so the
349
+ // ranking key needs no position field. MLX 0.31.2's GPU argpartition also
350
+ // sorts fully, but does not promise this tie order in its public contract.
351
+ const rankedPositions = ops.argsortAxis(rankKeys!, 0);
352
+ const selectedUnordered = rankedPositions.slice([0], [topK]);
349
353
  const selectedScores = ops.takeAlongAxis(scoresF32, selectedUnordered, 0);
350
354
  const threshold = ops.minAxis(selectedScores, 0, false);
351
355
  const [orderKeys] = getDsaContractOrderKeyKernel().apply(
352
356
  [selectedScores, selectedUnordered, threshold],
353
357
  {
354
- outputs: [{ shape: [topK], dtype: Dtype.uint64 }],
358
+ outputs: [{ shape: [topK], dtype: Dtype.uint32 }],
355
359
  grid: [topK, 1, 1],
356
360
  threadGroup: [Math.min(256, topK), 1, 1],
357
361
  },
@@ -360,7 +364,7 @@ export function selectGlm52DsaDevice(
360
364
  const positions = ops.takeAlongAxis(selectedUnordered, order, 0);
361
365
 
362
366
  rankKeys!.dispose();
363
- partition.dispose();
367
+ rankedPositions.dispose();
364
368
  selectedUnordered.dispose();
365
369
  selectedScores.dispose();
366
370
  orderKeys!.dispose();
@@ -4,7 +4,7 @@ import {
4
4
  type Glm52Config,
5
5
  } from "./glm52-config";
6
6
  import { ColibriGlm52Container } from "./glm52-container";
7
- import type { MemoryPlan } from "../memory-plan";
7
+ import type { MemoryPlan } from "../admission-plan";
8
8
 
9
9
  export const GLM52_G5_GIB = 1024 ** 3;
10
10
  export const GLM52_G5_DEFAULT_MACHINE_BYTES = 32 * GLM52_G5_GIB;