mlx-bun 0.0.13 → 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 (427) hide show
  1. package/README.md +71 -578
  2. package/bin/mlx-bun.mjs +1 -1
  3. package/docs/README.md +91 -0
  4. package/docs/{design → archive}/hlg-sampling.md +3 -3
  5. package/docs/{design → archive/investigations}/audio-input-plan.md +4 -4
  6. package/docs/{design → archive/investigations}/colibri-glm52-port.md +1 -1
  7. package/docs/{design → archive/investigations}/diffusion-gemma-port.md +3 -3
  8. package/docs/{design → archive/investigations}/faithful-l1-consolidation.md +4 -4
  9. package/docs/{investigations → archive/investigations}/hlg-sampling-investigation.md +1 -1
  10. package/docs/{design → archive/investigations}/minicpm5-decode-megakernel.md +11 -11
  11. package/docs/{design → archive/investigations}/optimization_plan.md +1 -1
  12. package/docs/archive/investigations/pareto-specialized-runtime-findings.md +92 -0
  13. package/docs/{design → archive/investigations}/pre-colibri-stabilization.md +5 -5
  14. package/docs/{design → archive/investigations}/the-dreaming-handoff.md +2 -2
  15. package/docs/{planning → archive/planning}/chat-ui-vision.md +1 -1
  16. package/docs/{planning → archive/planning}/chunk-task-roadmap.md +1 -1
  17. package/docs/{planning → archive/planning}/curve-sampler-research-plan.md +4 -4
  18. package/docs/{planning → archive/planning}/memory-docs-and-dag-plan.md +1 -1
  19. package/docs/{planning → archive/planning}/release-notes-v0.0.9.md +1 -1
  20. package/docs/archive/planning/release-notes-v0.2.0.md +49 -0
  21. package/docs/{planning → archive/planning}/website-readme-pass-plan.md +1 -1
  22. package/docs/design/batching.md +569 -0
  23. package/docs/design/decode-speed-program.md +3178 -169
  24. package/docs/design/dreaming-nightly-pipeline.md +675 -282
  25. package/docs/design/generic-model-support.md +579 -334
  26. package/docs/design/kv-cache.md +680 -0
  27. package/docs/design/orpo-training.md +673 -1239
  28. package/docs/design/speculative-decoding.md +1627 -0
  29. package/docs/design/turboquant.md +2102 -0
  30. package/docs/design/unified-engine-frontier-plan.md +1831 -727
  31. package/docs/design/web-chat-redesign.md +666 -1083
  32. package/docs/planning/PRODUCT_ROADMAP.md +34 -1
  33. package/docs/planning/release-notes-v0.3.0.md +35 -0
  34. package/docs/planning/video-series.md +421 -0
  35. package/docs/reference/benchmarks.md +1162 -0
  36. package/docs/reference/cli.md +395 -149
  37. package/docs/reference/distribution.md +321 -181
  38. package/docs/reference/environment.md +348 -0
  39. package/docs/reference/glossary.md +39 -0
  40. package/docs/reference/library-api.md +580 -142
  41. package/docs/reference/memory.md +227 -64
  42. package/docs/reference/models.md +137 -6
  43. package/docs/reference/server-api.md +938 -422
  44. package/docs/reference/server-config.md +652 -320
  45. package/docs/reference/training.md +393 -254
  46. package/docs/reference/troubleshooting.md +166 -0
  47. package/package.json +10 -5
  48. package/src/admission-plan.ts +18 -0
  49. package/src/anthropic.ts +92 -3
  50. package/src/audio/decode.ts +2 -2
  51. package/src/audio/features.ts +2 -2
  52. package/src/backends/mlx/autoregressive.ts +108 -0
  53. package/src/backends/mlx/batch-group.ts +1646 -0
  54. package/src/backends/mlx/diffusion.ts +39 -0
  55. package/src/backends/mlx/draft-target.ts +47 -0
  56. package/src/backends/mlx/evaluation.ts +105 -0
  57. package/src/backends/mlx/fill-append.ts +45 -0
  58. package/src/backends/mlx/gateway-binding.ts +71 -0
  59. package/src/backends/mlx/graph.ts +38 -0
  60. package/src/backends/mlx/memory-client.ts +189 -0
  61. package/src/backends/mlx/memory-guard.ts +40 -0
  62. package/src/backends/mlx/methods.ts +90 -0
  63. package/src/backends/mlx/model-prompt.ts +164 -0
  64. package/src/backends/mlx/model-serving.ts +79 -0
  65. package/src/backends/mlx/prefill.ts +33 -0
  66. package/src/backends/mlx/request-policy.ts +20 -0
  67. package/src/backends/mlx/rollback.ts +51 -0
  68. package/src/backends/mlx/serial-executor.ts +273 -0
  69. package/src/backends/mlx/speculative.ts +114 -0
  70. package/src/backends/mlx/state-views.ts +26 -0
  71. package/src/backends/mlx/text-engine.ts +73 -0
  72. package/src/cli.ts +189 -61
  73. package/src/client.ts +66 -0
  74. package/src/config.ts +21 -4
  75. package/src/contracts/completion.ts +16 -0
  76. package/src/contracts/execution.ts +49 -0
  77. package/src/contracts/generation.ts +103 -0
  78. package/src/contracts/host.ts +7 -0
  79. package/src/contracts/jobs.ts +66 -0
  80. package/src/contracts/pi-provider.ts +30 -0
  81. package/src/contracts/pi.ts +259 -0
  82. package/src/contracts/resources.ts +18 -0
  83. package/src/contracts/scheduling.ts +27 -0
  84. package/src/contracts/tsconfig.json +14 -0
  85. package/src/diffusion/diffusion-generate.ts +218 -153
  86. package/src/embed.ts +1 -1
  87. package/src/engine/admission.ts +59 -0
  88. package/src/engine/cancellation.ts +38 -0
  89. package/src/engine/engine.ts +56 -0
  90. package/src/engine/execution-coordinator.ts +70 -0
  91. package/src/engine/execution-plan.ts +51 -0
  92. package/src/engine/resources.ts +42 -0
  93. package/src/engine/scheduler.ts +67 -0
  94. package/src/engine/session.ts +247 -0
  95. package/src/eval/README.md +44 -1
  96. package/src/eval/kl.ts +263 -23
  97. package/src/eval/reference-logits.ts +207 -0
  98. package/src/eval/runner.ts +10 -79
  99. package/src/eval/tasks/bfcl.ts +2 -1
  100. package/src/eval/tasks/gsm8k.ts +2 -1
  101. package/src/eval/tasks/hashhop.ts +2 -1
  102. package/src/eval/tasks/humaneval.ts +5 -4
  103. package/src/eval/tasks/ifeval.ts +2 -1
  104. package/src/eval/tasks/mmlu.ts +2 -1
  105. package/src/expert-io.ts +3 -1
  106. package/src/expert-offload.ts +3 -1
  107. package/src/fill/echo-index.ts +188 -0
  108. package/src/fill/fill-session.ts +452 -0
  109. package/src/fill/proposal.ts +68 -0
  110. package/src/fill/schema-rows.ts +506 -0
  111. package/src/fill/tool-boundary.ts +103 -0
  112. package/src/fit.ts +24 -43
  113. package/src/generate.ts +740 -337
  114. package/src/grammar.ts +14 -8
  115. package/src/harness-pi.ts +15 -34
  116. package/src/hub-rest.ts +4 -4
  117. package/src/index.ts +27 -0
  118. package/src/inference/denoising.ts +17 -0
  119. package/src/inference/graph.ts +33 -0
  120. package/src/inference/prefill.ts +39 -0
  121. package/src/inference/rollback.ts +7 -0
  122. package/src/jobs/db.ts +4 -0
  123. package/src/jobs/index.ts +2 -0
  124. package/src/jobs/job-entry.ts +15 -14
  125. package/src/jobs/runner.ts +98 -13
  126. package/src/jobs/task-client.ts +19 -0
  127. package/src/jobs/types.ts +2 -58
  128. package/src/kv-scheme.ts +199 -0
  129. package/src/kv-store.ts +459 -234
  130. package/src/lab/README.md +18 -0
  131. package/src/{assets → lab/curve}/curve-designer.html +2 -2
  132. package/src/{curve-sampler.ts → lab/curve/curve-sampler.ts} +2 -2
  133. package/src/{expert-trace.ts → lab/expert-trace/expert-trace.ts} +7 -5
  134. package/src/{model → lab/paged-kv}/paged-kv.ts +9 -6
  135. package/src/library.ts +40 -0
  136. package/src/lora.ts +18 -0
  137. package/src/media-fetch.ts +20 -4
  138. package/src/memory/article.ts +1 -1
  139. package/src/memory/chunk.ts +1 -1
  140. package/src/memory/model.ts +32 -170
  141. package/src/memory/prompts.ts +1 -1
  142. package/src/memory/rest.ts +1 -1
  143. package/src/memory/schedule.ts +1 -1
  144. package/src/memory/stages.ts +4 -1
  145. package/src/memory/vault.ts +3 -4
  146. package/src/mlx/array.ts +9 -10
  147. package/src/mlx/compile.ts +12 -2
  148. package/src/mlx/ffi.ts +66 -22
  149. package/src/mlx/materialize.ts +23 -0
  150. package/src/mlx/metal-kernel.ts +10 -11
  151. package/src/mlx/ops.ts +64 -19
  152. package/src/mlx/quantized-rows.ts +36 -0
  153. package/src/mlx/token-bitmask.ts +52 -0
  154. package/src/mlx/turboquant-kv-decode.ts +82 -0
  155. package/src/mlx/turboquant-kv-inverse.ts +122 -0
  156. package/src/mlx/turboquant-ops.ts +33 -3
  157. package/src/mlx/turboquant-tables.ts +2 -2
  158. package/src/model/batched-mask.ts +9 -3
  159. package/src/model/batched-quant.ts +2 -2
  160. package/src/model/batched-rotating-quant.ts +45 -99
  161. package/src/model/batched-rotating-state.ts +73 -0
  162. package/src/model/batched-rotating.ts +57 -106
  163. package/src/model/batched-row-storage.ts +127 -0
  164. package/src/model/compiled-decode.ts +4 -3
  165. package/src/model/diffusion-gemma.ts +8 -8
  166. package/src/model/factory.ts +87 -49
  167. package/src/model/fingerprint.ts +1 -1
  168. package/src/model/flash-attention.ts +3 -3
  169. package/src/model/gemma4-base.ts +187 -17
  170. package/src/model/gemma4.ts +6 -6
  171. package/src/model/generated/gemma4-12b.ts +52 -148
  172. package/src/model/generated/gemma4-26b.ts +34 -94
  173. package/src/model/generated/gemma4-e4b.ts +28 -76
  174. package/src/model/glm52-cache.ts +19 -9
  175. package/src/model/glm52-dsa.ts +13 -9
  176. package/src/model/glm52-memory.ts +28 -1
  177. package/src/model/implementation.ts +69 -0
  178. package/src/model/minicpm5.ts +7 -8
  179. package/src/model/profile.ts +443 -0
  180. package/src/model/qwen-append.ts +12 -0
  181. package/src/model/qwen-conv.ts +52 -0
  182. package/src/model/qwen3-delta.ts +98 -2
  183. package/src/model/qwen3-mrope.ts +252 -0
  184. package/src/model/qwen3_5-checkpoint.ts +164 -0
  185. package/src/model/qwen3_5.ts +336 -60
  186. package/src/model/support.ts +1 -1
  187. package/src/model/trellis-balanced-scatter.ts +51 -0
  188. package/src/model/trellis-linear.ts +672 -0
  189. package/src/model/trellis-shared-m.ts +151 -0
  190. package/src/model/trellis-shared-scatter.ts +144 -0
  191. package/src/model/trellis-splitk-prefill.ts +158 -0
  192. package/src/model/trellis-tiled-prefill.ts +136 -0
  193. package/src/model/trellis-vector-expand.ts +62 -0
  194. package/src/model/trellis-wide-prefill.ts +94 -0
  195. package/src/model/universal/dense.ts +1 -1
  196. package/src/model/universal/modules.ts +8 -1
  197. package/src/native/frame_extract.swift +140 -0
  198. package/src/native-pack.ts +15 -7
  199. package/src/pi-provider.ts +6 -64
  200. package/src/pi-web.ts +14 -320
  201. package/src/prompt-cache.ts +97 -54
  202. package/src/quantize/allocator.ts +1 -1
  203. package/src/quantize/atomic-output.ts +38 -0
  204. package/src/quantize/calibration.ts +2 -1
  205. package/src/quantize/config-writer.ts +16 -3
  206. package/src/quantize/index.ts +28 -1
  207. package/src/quantize/job.ts +11 -0
  208. package/src/quantize/quantizer.ts +265 -100
  209. package/src/quantize/rotate.ts +603 -0
  210. package/src/quantize/safetensors-writer.ts +79 -1
  211. package/src/quantize/sensitivity.ts +41 -59
  212. package/src/quantize/trellis.ts +586 -0
  213. package/src/quantize/weight-transform.ts +292 -0
  214. package/src/responses.ts +130 -3
  215. package/src/runtime-config.ts +81 -0
  216. package/src/sampler.ts +276 -4
  217. package/src/serve/admin-routes.ts +184 -0
  218. package/src/serve/aux-routes.ts +132 -0
  219. package/src/serve/batch-scheduler.ts +8 -1558
  220. package/src/serve/chat-request.ts +391 -0
  221. package/src/serve/chat-stage.ts +195 -0
  222. package/src/serve/checkpoint-identity.ts +64 -0
  223. package/src/serve/completion-executor.ts +354 -0
  224. package/src/serve/completion-sink.ts +157 -0
  225. package/src/serve/discovery-routes.ts +204 -0
  226. package/src/serve/generation-gateway.ts +211 -243
  227. package/src/serve/http.ts +159 -0
  228. package/src/serve/inference-request.ts +79 -0
  229. package/src/serve/isolate.ts +297 -105
  230. package/src/serve/kv-budget.ts +14 -0
  231. package/src/serve/lab-routes.ts +341 -0
  232. package/src/serve/lane-registry.ts +2 -1
  233. package/src/serve/model-admin-routes.ts +155 -0
  234. package/src/serve/model-binding.ts +43 -0
  235. package/src/serve/model-host.ts +512 -0
  236. package/src/serve/openai-wire.ts +103 -0
  237. package/src/serve/parent-application.ts +56 -0
  238. package/src/serve/pipeline.ts +33 -0
  239. package/src/serve/preparation.ts +33 -0
  240. package/src/serve/prompt-response-trace.ts +147 -0
  241. package/src/serve/request-plan.ts +162 -0
  242. package/src/serve/request-prep.ts +346 -0
  243. package/src/serve/response-lease.ts +24 -0
  244. package/src/serve/responses-client.ts +69 -0
  245. package/src/serve/session-completion-engine.ts +110 -0
  246. package/src/serve/session-search.ts +1 -1
  247. package/src/serve/static-routes.ts +101 -0
  248. package/src/serve/text-completion-stage.ts +104 -0
  249. package/src/serve/token-streams.ts +519 -0
  250. package/src/server.ts +523 -3551
  251. package/src/spec/assistant-source.ts +10 -44
  252. package/src/spec/deepspec-source.ts +6 -6
  253. package/src/spec/dflash-source.ts +9 -8
  254. package/src/spec/dspark/ab-stats.ts +3 -3
  255. package/src/spec/dspark/calibration.ts +2 -2
  256. package/src/spec/dspark/deepspec-module.ts +2 -2
  257. package/src/spec/dspark/generate-dflash.ts +2 -2
  258. package/src/spec/dspark/generate.ts +2 -2
  259. package/src/spec/dspark/loader.ts +1 -1
  260. package/src/spec/dspark/module-dflash.ts +5 -4
  261. package/src/spec/dspark/module.ts +3 -3
  262. package/src/spec/dspark/quantize-drafter.ts +1 -1
  263. package/src/spec/generate.ts +1 -1
  264. package/src/spec/glm52-mtp-source.ts +9 -1
  265. package/src/spec/ngram-source.ts +1 -1
  266. package/src/spec/prefix-state.ts +45 -0
  267. package/src/spec/qwen-mtp-source.ts +528 -0
  268. package/src/spec/serve-loop.ts +179 -213
  269. package/src/spec/source.ts +42 -10
  270. package/src/spec/two-model.ts +1 -1
  271. package/src/ssd-cache.ts +95 -24
  272. package/src/ssd-durability.ts +207 -0
  273. package/src/tool-call.ts +51 -14
  274. package/src/train/flash-cce.ts +14 -13
  275. package/src/train/forward.ts +38 -1
  276. package/src/train/loss.ts +3 -2
  277. package/src/train/prefix-shared.ts +5 -1
  278. package/src/train/segmented.ts +9 -6
  279. package/src/train/trainer.ts +9 -8
  280. package/src/vision/preprocess.ts +1 -1
  281. package/src/vision/prompt.ts +48 -2
  282. package/src/vision/qwen3vl-preprocess.ts +409 -0
  283. package/src/vision/qwen3vl-prompt.ts +196 -0
  284. package/src/vision/qwen3vl-tower.ts +555 -0
  285. package/src/vision/video-frames.ts +148 -0
  286. package/src/web/app.html +1 -1
  287. package/src/web/app.js +15 -6
  288. package/src/web/src/adapters-panel.ts +3 -3
  289. package/src/web/src/assistant.ts +2 -36
  290. package/src/web/src/chat.ts +16 -9
  291. package/src/web/src/composer.ts +4 -4
  292. package/src/web/src/hub.ts +1 -1
  293. package/src/web/src/markdown.ts +0 -0
  294. package/src/web/src/memory-panel.ts +1 -1
  295. package/src/web/src/model-picker.ts +1 -1
  296. package/src/web/src/palette.ts +1 -1
  297. package/src/web/src/protocol.ts +3 -39
  298. package/src/web/src/sessions.ts +1 -1
  299. package/src/web/src/ui-catalog.ts +4 -18
  300. package/src/web-assets.ts +47 -0
  301. package/src/weights.ts +134 -33
  302. package/docs/archive/mlx-bun-lab-report.html +0 -275
  303. package/docs/archive/wwdc-mlx-bun.html +0 -848
  304. package/docs/design/adapters-end-to-end.md +0 -205
  305. package/docs/design/batching-perf-path.md +0 -255
  306. package/docs/design/batching-v2-plan.md +0 -529
  307. package/docs/design/bucketing-stage.md +0 -197
  308. package/docs/design/compat-cli-surface-design.md +0 -230
  309. package/docs/design/docs-reorg-plan.md +0 -165
  310. package/docs/design/dspark-serving-program.md +0 -371
  311. package/docs/design/dspark-speculative-decoding.md +0 -268
  312. package/docs/design/grammar-spec-batching-integration.md +0 -301
  313. package/docs/design/memory-inference-path.md +0 -422
  314. package/docs/design/memory-synthesis.md +0 -287
  315. package/docs/design/memory-system.md +0 -237
  316. package/docs/design/mlx-lm-tool-parity-plan.md +0 -545
  317. package/docs/design/omlx-adoption-map.md +0 -98
  318. package/docs/design/orpo-dynamic-lambda.md +0 -220
  319. package/docs/design/orpo-future-enhancements.md +0 -340
  320. package/docs/design/paged-kv-cache.md +0 -214
  321. package/docs/design/parallel-slots.md +0 -567
  322. package/docs/design/parity-tier-dag.md +0 -161
  323. package/docs/design/repo-cleanup-plan.md +0 -189
  324. package/docs/design/runtime-isolation.md +0 -263
  325. package/docs/design/segmented-backward-training.md +0 -543
  326. package/docs/design/spec-decode-larger-targets.md +0 -229
  327. package/docs/design/ssd-kv-cold-tier.md +0 -288
  328. package/docs/design/structured-output.md +0 -484
  329. package/docs/design/tauri-desktop-app.md +0 -127
  330. package/docs/design/the-dreaming-master-plan.md +0 -445
  331. package/docs/design/turboquant-kv.md +0 -235
  332. package/docs/design/w4a16-compute-precision-spike.md +0 -232
  333. package/docs/design/web-chat-beat-matrix.md +0 -315
  334. package/docs/design/write-pipeline-entity.md +0 -468
  335. package/docs/investigations/curve-runs/bisector-route.json +0 -98
  336. package/docs/investigations/curve-runs/bisector-trace.json +0 -1393
  337. package/docs/investigations/curve-runs/curve-terrain.json +0 -1179
  338. package/docs/investigations/curve-runs/curve-terrain.svg +0 -42
  339. package/docs/investigations/hlg-ab.html +0 -34
  340. package/docs/investigations/hlg-explorer.html +0 -238
  341. package/docs/investigations/hlg-figs/cliff.svg +0 -1
  342. package/docs/investigations/hlg-figs/cross.svg +0 -1
  343. package/docs/investigations/hlg-figs/edges.svg +0 -1
  344. package/docs/investigations/hlg-figs/k20-grids.svg +0 -1
  345. package/docs/investigations/hlg-figs/k20-peaks.svg +0 -1
  346. package/docs/investigations/hlg-figs/lw-curve.svg +0 -1
  347. package/docs/investigations/hlg-figs/terrain.svg +0 -1
  348. package/docs/investigations/hlg-report.html +0 -262
  349. package/docs/investigations/hlg-runs/hlg-ab.json +0 -149
  350. package/docs/investigations/hlg-runs/hlg-blind-prompts.md +0 -87
  351. package/docs/investigations/hlg-runs/hlg-blind.json +0 -107
  352. package/docs/investigations/hlg-runs/hlg-cliff.json +0 -197
  353. package/docs/investigations/hlg-runs/hlg-cross.json +0 -505
  354. package/docs/investigations/hlg-runs/hlg-edge.json +0 -388
  355. package/docs/investigations/hlg-runs/hlg-eval1.json +0 -25
  356. package/docs/investigations/hlg-runs/hlg-frontier.json +0 -86
  357. package/docs/investigations/hlg-runs/hlg-grid.json +0 -242
  358. package/docs/investigations/hlg-runs/hlg-grid2.json +0 -343
  359. package/docs/investigations/hlg-runs/hlg-lw-coarse.json +0 -110
  360. package/docs/investigations/hlg-runs/hlg-map.json +0 -383
  361. package/docs/investigations/hlg-runs/run0-original.log +0 -159
  362. package/docs/investigations/hlg-runs/runA-recommended.log +0 -131
  363. package/docs/investigations/hlg-runs/runAA-cliff-cpm5.log +0 -26
  364. package/docs/investigations/hlg-runs/runAB-grid2-e4b.log +0 -18
  365. package/docs/investigations/hlg-runs/runAC-peak-e4b-partial.log +0 -25
  366. package/docs/investigations/hlg-runs/runAD-ab-samples.log +0 -8
  367. package/docs/investigations/hlg-runs/runAE-edge-e4b.log +0 -51
  368. package/docs/investigations/hlg-runs/runAF-logit-calibration.log +0 -10
  369. package/docs/investigations/hlg-runs/runAG-eval-config2.log +0 -16
  370. package/docs/investigations/hlg-runs/runB-gain-variants.log +0 -192
  371. package/docs/investigations/hlg-runs/runC-pivots.log +0 -162
  372. package/docs/investigations/hlg-runs/runD-oetf.log +0 -132
  373. package/docs/investigations/hlg-runs/runE-eotf.log +0 -162
  374. package/docs/investigations/hlg-runs/runF-shaper-working.log +0 -86
  375. package/docs/investigations/hlg-runs/runG-shaper-sweep.log +0 -101
  376. package/docs/investigations/hlg-runs/runH-wsweep.log +0 -176
  377. package/docs/investigations/hlg-runs/runI-asweep.log +0 -176
  378. package/docs/investigations/hlg-runs/runJ-knobmap.log +0 -49
  379. package/docs/investigations/hlg-runs/runK-frontier.log +0 -19
  380. package/docs/investigations/hlg-runs/runL-widemap.log +0 -65
  381. package/docs/investigations/hlg-runs/runM-frontier-emb.log +0 -15
  382. package/docs/investigations/hlg-runs/runN-verify.log +0 -116
  383. package/docs/investigations/hlg-runs/runO-frontier-seed5000.log +0 -15
  384. package/docs/investigations/hlg-runs/runP-grid-AxSm.log +0 -15
  385. package/docs/investigations/hlg-runs/runQ-grid-tgW.log +0 -15
  386. package/docs/investigations/hlg-runs/runR-blind-transcript.log +0 -79
  387. package/docs/investigations/hlg-runs/runS-cross-coherence.log +0 -33
  388. package/docs/investigations/hlg-runs/runT-cross-cpm5.log +0 -33
  389. package/docs/investigations/hlg-runs/runU-cross-12b.log +0 -33
  390. package/docs/investigations/hlg-runs/runV-lw-e4b.log +0 -22
  391. package/docs/investigations/hlg-runs/runW-lw-12b.log +0 -22
  392. package/docs/investigations/hlg-runs/runX-lw-cpm5.log +0 -22
  393. package/docs/investigations/hlg-runs/runY-cliff-e4b.log +0 -26
  394. package/docs/investigations/hlg-runs/runZ-cliff-12b.log +0 -26
  395. package/docs/planning/IDEAS.md +0 -69
  396. package/docs/reference/embedding.md +0 -197
  397. package/docs/reference/features-matrix.md +0 -100
  398. package/docs/reference/orpo-quickstart.md +0 -135
  399. package/src/flags.ts +0 -13
  400. /package/docs/{investigations → archive/investigations}/chunk-finetune-experiment.md +0 -0
  401. /package/docs/{investigations → archive/investigations}/colibri-oracle-pin.md +0 -0
  402. /package/docs/{investigations → archive/investigations}/curve-bisector-routing.md +0 -0
  403. /package/docs/{investigations → archive/investigations}/decode-roofline-lookagain.md +0 -0
  404. /package/docs/{investigations → archive/investigations}/dspark-handoff.md +0 -0
  405. /package/docs/{investigations → archive/investigations}/expert-offload-single-user-moe.md +0 -0
  406. /package/docs/{investigations → archive/investigations}/indexshare-performance-spike.md +0 -0
  407. /package/docs/{investigations → archive/investigations}/kernel-perf-review-2026-07.md +0 -0
  408. /package/docs/{investigations → archive/investigations}/lab-build-journal.md +0 -0
  409. /package/docs/{investigations → archive/investigations}/openwiki-evaluation.md +0 -0
  410. /package/docs/{investigations → archive/investigations}/orpo-base-uf-experiment-and-directions.md +0 -0
  411. /package/docs/{investigations → archive/investigations}/orpo-flash-cce-pin-leak.md +0 -0
  412. /package/docs/{investigations → archive/investigations}/orpo-uf-testing-handoff.md +0 -0
  413. /package/docs/{investigations → archive/investigations}/pi-builtin-investigation.md +0 -0
  414. /package/docs/{investigations → archive/investigations}/segmented-backward-handoff.md +0 -0
  415. /package/docs/{investigations → archive/investigations}/starter-model-port-handoff.md +0 -0
  416. /package/docs/{investigations → archive/investigations}/steel-flash-cce-handoff.md +0 -0
  417. /package/docs/{investigations → archive/investigations}/trainer-validation-experiment.md +0 -0
  418. /package/docs/{planning → archive/planning}/chat-ui-vision-memory-first.md +0 -0
  419. /package/docs/{planning → archive/planning}/journal.md +0 -0
  420. /package/docs/{planning → archive/planning}/pi-integration-review.md +0 -0
  421. /package/docs/{planning → archive/planning}/release-notes-v0.0.10.md +0 -0
  422. /package/docs/{planning → archive/planning}/release-notes-v0.0.11.md +0 -0
  423. /package/docs/{planning → archive/planning}/release-notes-v0.0.12.md +0 -0
  424. /package/docs/{planning → archive/planning}/release-notes-v0.0.13.md +0 -0
  425. /package/docs/{planning → archive/planning}/web-ui-pass-plan.md +0 -0
  426. /package/{docs/investigations → src/serve/assets}/curve-terrain.html +0 -0
  427. /package/{docs/dag → src/serve/assets}/training-inference-map.html +0 -0
@@ -20,11 +20,17 @@
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";
27
27
  import { RotatingKVCache, type Cache, type Mask } from "./gemma4-base";
28
+ import { BatchedRotatingState } from "./batched-rotating-state";
29
+ import {
30
+ mergeStorageRows,
31
+ plainRowStorage,
32
+ temporalStorageView,
33
+ } from "./batched-row-storage";
28
34
 
29
35
  const STEP = 256;
30
36
 
@@ -78,39 +84,46 @@ export function buildBatchedRotatingMask(
78
84
  export class BatchedRotatingCache implements Cache {
79
85
  keys: MlxArray | null = null;
80
86
  values: MlxArray | null = null;
81
- /** Per-row absolute position (drives RoPE) and buffer padding. */
82
- offsetArr: number[];
83
- leftPad: number[];
84
- #idx = 0;
85
- #offset = 0; // scalar total tokens processed (mlx-lm _offset)
86
- #rotated = false;
87
+ readonly #rows: BatchedRotatingState;
87
88
  #ropeArr: MlxArray | null = null;
88
89
  #ropeForOffset = -1;
89
90
  readonly maxSize: number;
90
91
 
91
92
  constructor(maxSize: number, leftPad: number[]) {
92
93
  this.maxSize = maxSize;
93
- this.leftPad = [...leftPad];
94
- this.offsetArr = leftPad.map((l) => -l);
94
+ this.#rows = new BatchedRotatingState(maxSize, leftPad);
95
95
  }
96
96
 
97
+ /** Same signature as the serial RotatingKVCache — the scheduler's merge
98
+ * guards recognize a batched ring as "rotating-plain + RowBatchCache"
99
+ * (batch-scheduler #mergeJoiner prevRot branch); without this the next
100
+ * join sees "unknown", skips the prevRot rows, and replaces the batch's
101
+ * sliding-layer KV with a one-row cache (B≥3 collapse). Guard order
102
+ * everywhere is isRowBatchCache FIRST, so the shared string never
103
+ * misroutes a batched cache into a serial-only path. */
104
+ signature(): string { return "kv:rotating-plain"; }
105
+
106
+ get offsetArr(): number[] { return this.#rows.offsets; }
107
+ get leftPad(): number[] { return this.#rows.leftPad; }
108
+ get batchSize(): number { return this.#rows.batchSize; }
109
+
97
110
  /** Current batch size — tracks filter() (which shrinks the per-row arrays). */
98
111
  get #B(): number {
99
- return this.leftPad.length;
112
+ return this.#rows.batchSize;
100
113
  }
101
114
 
102
115
  /** mlx-lm uses `cache.offset` (the per-row array) for the scalar interface
103
116
  * too; we expose the scalar total as `offset` and the per-row positions via
104
117
  * `ropeOffsetArr` (the model's per-row RoPE path). */
105
118
  get offset(): number {
106
- return this.#offset;
119
+ return this.#rows.totalOffset;
107
120
  }
108
121
 
109
122
  get ropeOffsetArr(): MlxArray {
110
- if (this.#ropeArr && this.#ropeForOffset === this.#offset) return this.#ropeArr;
123
+ if (this.#ropeArr && this.#ropeForOffset === this.offset) return this.#ropeArr;
111
124
  this.#ropeArr?.dispose();
112
125
  this.#ropeArr = MlxArray.fromInt32(Int32Array.from(this.offsetArr), [this.#B]);
113
- this.#ropeForOffset = this.#offset;
126
+ this.#ropeForOffset = this.offset;
114
127
  return this.#ropeArr;
115
128
  }
116
129
 
@@ -120,7 +133,7 @@ export class BatchedRotatingCache implements Cache {
120
133
  mode: "array",
121
134
  arr: buildBatchedRotatingMask(
122
135
  this.#B, N, this.leftPad, this.maxSize, window,
123
- this.#idx, this.#offset, this.#rotated,
136
+ this.#rows.ringIndex, this.offset, this.#rows.rotated,
124
137
  ),
125
138
  };
126
139
  }
@@ -131,7 +144,7 @@ export class BatchedRotatingCache implements Cache {
131
144
  const vD = v.shape[3]!;
132
145
  if (S !== 1)
133
146
  throw new Error("BatchedRotatingCache supports N=1 decode updates only (solo-prefill then merge)");
134
- const prev = this.#offset;
147
+ const prev = this.offset;
135
148
 
136
149
  // Grow the buffer (in STEP chunks) until it reaches maxSize.
137
150
  if (!this.keys || (prev >= this.keys.shape[2]! && this.keys.shape[2]! < this.maxSize)) {
@@ -148,7 +161,7 @@ export class BatchedRotatingCache implements Cache {
148
161
  this.keys = newK;
149
162
  this.values = newV;
150
163
  }
151
- this.#idx = prev;
164
+ this.#rows.markGrown(prev);
152
165
  }
153
166
 
154
167
  // Trim any overshoot past maxSize (decrements left padding persistently).
@@ -160,34 +173,27 @@ export class BatchedRotatingCache implements Cache {
160
173
  this.values!.dispose();
161
174
  this.keys = tk;
162
175
  this.values = tv;
163
- this.#idx = this.maxSize;
164
- this.leftPad = this.leftPad.map((x) => x - trimSize);
176
+ this.#rows.trimOvershoot(trimSize);
165
177
  }
166
178
 
167
179
  // Rotate when the write head reaches the end of the ring.
168
- if (this.#idx === this.maxSize) {
169
- this.#rotated = true;
170
- this.#idx = 0;
171
- }
172
- if (this.#rotated) this.leftPad = this.leftPad.map((x) => x - S);
180
+ const writeIndex = this.#rows.beginWrite(S);
173
181
 
174
182
  // Write the new K/V at the ring head.
175
183
  const [, , SK, DK] = this.keys!.shape as [number, number, number, number];
176
- const k2 = ops.sliceUpdate(this.keys!, k, [0, 0, this.#idx, 0], [B, H, this.#idx + S, DK]);
177
- const v2 = ops.sliceUpdate(this.values!, v, [0, 0, this.#idx, 0], [B, H, this.#idx + S, vD]);
184
+ const k2 = ops.sliceUpdate(this.keys!, k, [0, 0, writeIndex, 0], [B, H, writeIndex + S, DK]);
185
+ const v2 = ops.sliceUpdate(this.values!, v, [0, 0, writeIndex, 0], [B, H, writeIndex + S, vD]);
178
186
  this.keys!.dispose();
179
187
  this.values!.dispose();
180
188
  this.keys = k2;
181
189
  this.values = v2;
182
- this.#offset += S;
183
- this.offsetArr = this.offsetArr.map((x) => x + S);
184
- this.#idx += S;
190
+ this.#rows.commitWrite(S);
185
191
 
186
192
  // Return the populated prefix (ring not yet full) or the whole buffer.
187
- if (this.#offset < this.maxSize) {
193
+ if (this.offset < this.maxSize) {
188
194
  return [
189
- this.keys.slice([0, 0, 0, 0], [B, H, this.#offset, DK]),
190
- this.values.slice([0, 0, 0, 0], [B, H, this.#offset, vD]),
195
+ this.keys.slice([0, 0, 0, 0], [B, H, this.offset, DK]),
196
+ this.values.slice([0, 0, 0, 0], [B, H, this.offset, vD]),
191
197
  ];
192
198
  }
193
199
  return [
@@ -206,27 +212,10 @@ export class BatchedRotatingCache implements Cache {
206
212
  /** Ring contents in temporal order, cut to the valid length (extract). */
207
213
  temporalView(): [MlxArray, MlxArray] {
208
214
  if (!this.keys || !this.values) throw new Error("cache is empty");
209
- const valid = Math.min(this.#offset, this.maxSize);
210
- const order = (a: MlxArray): MlxArray => {
211
- const [B, H, Sbuf, D] = a.shape as [number, number, number, number];
212
- let t: MlxArray;
213
- if (this.#idx === Sbuf) {
214
- t = a.slice([0, 0, 0, 0], [B, H, Sbuf, D]);
215
- } else if (this.#idx < this.#offset) {
216
- const tail = a.slice([0, 0, this.#idx, 0], [B, H, Sbuf, D]);
217
- const head = a.slice([0, 0, 0, 0], [B, H, this.#idx, D]);
218
- t = ops.concatAxis([tail, head], 2);
219
- tail.dispose();
220
- head.dispose();
221
- } else {
222
- t = a.slice([0, 0, 0, 0], [B, H, this.#idx, D]);
223
- }
224
- const [, , , Dd] = t.shape as [number, number, number, number];
225
- const cut = t.slice([0, 0, 0, 0], [B, H, valid, Dd]);
226
- t.dispose();
227
- return cut;
228
- };
229
- return [order(this.keys), order(this.values)];
215
+ return [
216
+ temporalStorageView(plainRowStorage, this.keys, this.#rows),
217
+ temporalStorageView(plainRowStorage, this.values, this.#rows),
218
+ ];
230
219
  }
231
220
 
232
221
  /** mlx-lm `BatchRotatingKVCache.extract` (models/cache.py:1417): row `i`
@@ -242,34 +231,13 @@ export class BatchedRotatingCache implements Cache {
242
231
  extractRow(i: number): RotatingKVCache | null {
243
232
  if (!this.keys || !this.values) return null;
244
233
  const pad = Math.max(0, this.leftPad[i]!);
245
- const valid = Math.min(this.#offset, this.maxSize);
246
- const cut = (a: MlxArray): MlxArray => {
247
- const [, H, Sbuf, D] = a.shape as [number, number, number, number];
248
- const row = a.slice([i, 0, 0, 0], [i + 1, H, Sbuf, D]);
249
- // temporal order — same three cases as temporalView, single row.
250
- let t: MlxArray;
251
- if (this.#idx === Sbuf) {
252
- t = row;
253
- } else if (this.#idx < this.#offset) {
254
- const tail = row.slice([0, 0, this.#idx, 0], [1, H, Sbuf, D]);
255
- const head = row.slice([0, 0, 0, 0], [1, H, this.#idx, D]);
256
- t = ops.concatAxis([tail, head], 2);
257
- tail.dispose();
258
- head.dispose();
259
- row.dispose();
260
- } else {
261
- t = row.slice([0, 0, 0, 0], [1, H, this.#idx, D]);
262
- row.dispose();
263
- }
264
- const cutV = t.slice([0, 0, pad, 0], [1, H, valid, D]);
265
- t.dispose();
266
- const own = ops.contiguous(cutV);
267
- cutV.dispose();
268
- return own;
269
- };
270
234
  const c = new RotatingKVCache(this.maxSize);
271
- const k = cut(this.keys);
272
- const v = cut(this.values);
235
+ const k = temporalStorageView(plainRowStorage, this.keys, this.#rows, {
236
+ row: i, from: pad, copy: true,
237
+ });
238
+ const v = temporalStorageView(plainRowStorage, this.values, this.#rows, {
239
+ row: i, from: pad, copy: true,
240
+ });
273
241
  c.restoreState(k, v, this.offsetArr[i]!, k.shape[2]!);
274
242
  return c;
275
243
  }
@@ -277,31 +245,27 @@ export class BatchedRotatingCache implements Cache {
277
245
  /** Keep only `keep` rows along the batch axis (eviction). */
278
246
  filter(keep: number[]): void {
279
247
  if (this.keys && this.values) {
280
- const idxArr = MlxArray.fromInt32(Int32Array.from(keep), [keep.length]);
281
- const k = ops.takeAxis(this.keys, idxArr, 0);
282
- const v = ops.takeAxis(this.values, idxArr, 0);
283
- idxArr.dispose();
248
+ const k = plainRowStorage.takeRows(this.keys, keep);
249
+ const v = plainRowStorage.takeRows(this.values, keep);
284
250
  this.keys.dispose();
285
251
  this.values.dispose();
286
252
  this.keys = k;
287
253
  this.values = v;
288
254
  }
289
- this.offsetArr = keep.map((i) => this.offsetArr[i]!);
290
- this.leftPad = keep.map((i) => this.leftPad[i]!);
255
+ this.#rows.filter(keep);
291
256
  this.releaseRopeArr();
292
257
  }
293
258
 
259
+ filterRows(keep: readonly number[]): void { this.filter([...keep]); }
260
+
294
261
  state(): MlxArray[] {
295
262
  return this.keys && this.values ? [this.keys, this.values] : [];
296
263
  }
297
264
  isTrimmable(): boolean {
298
- return this.#offset < this.maxSize;
265
+ return this.#rows.trimmable;
299
266
  }
300
267
  trim(n: number): void {
301
- const k = Math.min(this.#offset, n);
302
- this.#offset -= k;
303
- this.#idx -= k;
304
- this.offsetArr = this.offsetArr.map((x) => x - k);
268
+ this.#rows.trim(n);
305
269
  }
306
270
  dispose(): void {
307
271
  this.keys?.dispose();
@@ -322,24 +286,11 @@ export class BatchedRotatingCache implements Cache {
322
286
  const width = Math.max(...lens, 0);
323
287
  const leftPad = lens.map((l) => width - l);
324
288
  const cache = new BatchedRotatingCache(maxSize, leftPad);
325
- cache.offsetArr = [...offsets];
289
+ cache.#rows.restoreMerged(width, offsets);
326
290
  if (width === 0) return cache;
327
291
 
328
- const padTo = (a: MlxArray, pad: number): MlxArray => {
329
- if (pad === 0) return a.slice([0, 0, 0, 0], a.shape as number[]);
330
- const [B, H, , D] = a.shape as [number, number, number, number];
331
- const z = ops.zeros([B, H, pad, D], a.dtype);
332
- const out = ops.concatAxis([z, a], 2);
333
- z.dispose();
334
- return out;
335
- };
336
- const ks = rows.map((r, i) => padTo(r.keys, leftPad[i]!));
337
- const vs = rows.map((r, i) => padTo(r.values, leftPad[i]!));
338
- cache.keys = ops.concatAxis(ks, 0);
339
- cache.values = ops.concatAxis(vs, 0);
340
- for (const a of [...ks, ...vs]) a.dispose();
341
- cache.#idx = width;
342
- cache.#offset = width;
292
+ cache.keys = mergeStorageRows(plainRowStorage, rows.map((row) => row.keys), leftPad);
293
+ cache.values = mergeStorageRows(plainRowStorage, rows.map((row) => row.values), leftPad);
343
294
  return cache;
344
295
  }
345
296
  }
@@ -0,0 +1,127 @@
1
+ import { MlxArray } from "../mlx/array";
2
+ import * as ops from "../mlx/ops";
3
+ import { BatchedRotatingState } from "./batched-rotating-state";
4
+
5
+ export interface RowStorage<T> {
6
+ shape(value: T): readonly [number, number, number, number];
7
+ slice(value: T, batchFrom: number, batchTo: number, tokenFrom: number, tokenTo: number): T;
8
+ concatTokens(values: readonly T[]): T;
9
+ concatRows(values: readonly T[]): T;
10
+ padLeft(value: T, tokens: number): T;
11
+ takeRows(value: T, keep: readonly number[]): T;
12
+ copy(value: T): T;
13
+ dispose(value: T): void;
14
+ }
15
+
16
+ export const plainRowStorage: RowStorage<MlxArray> = {
17
+ shape: (value) => value.shape as [number, number, number, number],
18
+ slice(value, batchFrom, batchTo, tokenFrom, tokenTo) {
19
+ const [, H, , D] = value.shape as [number, number, number, number];
20
+ return value.slice([batchFrom, 0, tokenFrom, 0], [batchTo, H, tokenTo, D]);
21
+ },
22
+ concatTokens: (values) => ops.concatAxis([...values], 2),
23
+ concatRows: (values) => ops.concatAxis([...values], 0),
24
+ padLeft(value, tokens) {
25
+ if (tokens === 0) return value.slice([0, 0, 0, 0], value.shape as number[]);
26
+ const [B, H, , D] = value.shape as [number, number, number, number];
27
+ const zeros = ops.zeros([B, H, tokens, D], value.dtype);
28
+ const out = ops.concatAxis([zeros, value], 2);
29
+ zeros.dispose();
30
+ return out;
31
+ },
32
+ takeRows(value, keep) {
33
+ const indices = MlxArray.fromInt32(Int32Array.from(keep), [keep.length]);
34
+ const out = ops.takeAxis(value, indices, 0);
35
+ indices.dispose();
36
+ return out;
37
+ },
38
+ copy: (value) => ops.copyOf(value),
39
+ dispose: (value) => value.dispose(),
40
+ };
41
+
42
+ export const quantizedRowStorage: RowStorage<ops.QuantizedTensor> = {
43
+ shape: (value) => value.packed.shape as [number, number, number, number],
44
+ slice(value, batchFrom, batchTo, tokenFrom, tokenTo) {
45
+ const cut = (array: MlxArray): MlxArray => {
46
+ const [, H, , D] = array.shape as [number, number, number, number];
47
+ return array.slice([batchFrom, 0, tokenFrom, 0], [batchTo, H, tokenTo, D]);
48
+ };
49
+ return { packed: cut(value.packed), scales: cut(value.scales), biases: cut(value.biases) };
50
+ },
51
+ concatTokens(values) {
52
+ return {
53
+ packed: ops.concatAxis(values.map((value) => value.packed), 2),
54
+ scales: ops.concatAxis(values.map((value) => value.scales), 2),
55
+ biases: ops.concatAxis(values.map((value) => value.biases), 2),
56
+ };
57
+ },
58
+ concatRows(values) {
59
+ return {
60
+ packed: ops.concatAxis(values.map((value) => value.packed), 0),
61
+ scales: ops.concatAxis(values.map((value) => value.scales), 0),
62
+ biases: ops.concatAxis(values.map((value) => value.biases), 0),
63
+ };
64
+ },
65
+ padLeft(value, tokens) {
66
+ const pad = (array: MlxArray): MlxArray => plainRowStorage.padLeft(array, tokens);
67
+ return { packed: pad(value.packed), scales: pad(value.scales), biases: pad(value.biases) };
68
+ },
69
+ takeRows(value, keep) {
70
+ const take = (array: MlxArray): MlxArray => plainRowStorage.takeRows(array, keep);
71
+ return { packed: take(value.packed), scales: take(value.scales), biases: take(value.biases) };
72
+ },
73
+ copy(value) {
74
+ return {
75
+ packed: ops.copyOf(value.packed),
76
+ scales: ops.copyOf(value.scales),
77
+ biases: ops.copyOf(value.biases),
78
+ };
79
+ },
80
+ dispose(value) {
81
+ value.packed.dispose();
82
+ value.scales.dispose();
83
+ value.biases.dispose();
84
+ },
85
+ };
86
+
87
+ export function mergeStorageRows<T>(
88
+ storage: RowStorage<T>,
89
+ rows: readonly T[],
90
+ leftPad: readonly number[],
91
+ ): T {
92
+ const padded = rows.map((row, index) => storage.padLeft(row, leftPad[index]!));
93
+ try {
94
+ return storage.concatRows(padded);
95
+ } finally {
96
+ for (const row of padded) storage.dispose(row);
97
+ }
98
+ }
99
+
100
+ /** De-roll storage into temporal order, then cut one row or the whole batch. */
101
+ export function temporalStorageView<T>(
102
+ storage: RowStorage<T>,
103
+ value: T,
104
+ state: BatchedRotatingState,
105
+ options: { row?: number; from?: number; to?: number; copy?: boolean } = {},
106
+ ): T {
107
+ const [batch, , tokens] = storage.shape(value);
108
+ const batchFrom = options.row ?? 0;
109
+ const batchTo = options.row === undefined ? batch : options.row + 1;
110
+ const parts = state.temporalRanges(tokens).map(([from, to]) =>
111
+ storage.slice(value, batchFrom, batchTo, from, to)
112
+ );
113
+ const ordered = parts.length === 1 ? parts[0]! : storage.concatTokens(parts);
114
+ if (parts.length > 1) for (const part of parts) storage.dispose(part);
115
+ const cut = storage.slice(
116
+ ordered,
117
+ 0,
118
+ batchTo - batchFrom,
119
+ options.from ?? 0,
120
+ options.to ?? state.validLength,
121
+ );
122
+ storage.dispose(ordered);
123
+ if (!options.copy) return cut;
124
+ const owned = storage.copy(cut);
125
+ storage.dispose(cut);
126
+ return owned;
127
+ }
@@ -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
@@ -33,6 +33,7 @@
33
33
  import { MlxArray } from "../mlx/array";
34
34
  import { CompiledFunction } from "../mlx/compile";
35
35
  import * as ops from "../mlx/ops";
36
+ import { runtimeValue } from "../runtime-config";
36
37
  import {
37
38
  Gemma4Model,
38
39
  KVCache,
@@ -207,7 +208,7 @@ function slotDesc(c: AnyCache, plan: DecodeStepPlan): SlotDesc {
207
208
  * inside quantizedSdpa's dispatch). Offsets/capacities don't — they're
208
209
  * array values or shapeless dims. */
209
210
  function closureKey(descs: SlotDesc[]): string {
210
- const flags = `nf=${process.env.MLX_BUN_NO_FUSED_SDPA === "1" ? 1 : 0}`;
211
+ const flags = `nf=${runtimeValue("MLX_BUN_NO_FUSED_SDPA") === "1" ? 1 : 0}`;
211
212
  return descs.map((d) => `${d.kind}:${d.groupSize}:${d.bits}`).join(",") + "|" + flags;
212
213
  }
213
214
 
@@ -555,7 +556,7 @@ export class CompiledDecode {
555
556
  anyCaches
556
557
  .map((c, i) => (phases[i] === "ring" ? `${slotDesc(c, plans[i]!).kind}:${slotDesc(c, plans[i]!).groupSize}:${slotDesc(c, plans[i]!).bits}` : "js"))
557
558
  .join(",") +
558
- `|nf=${process.env.MLX_BUN_NO_FUSED_SDPA === "1" ? 1 : 0}`;
559
+ `|nf=${runtimeValue("MLX_BUN_NO_FUSED_SDPA") === "1" ? 1 : 0}`;
559
560
  if (this.#broken.has(key)) throw new Error(`compiled decode: known-broken closure ${key}`);
560
561
 
561
562
  let closures = this.#segClosures.get(key);
@@ -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
@@ -1,8 +1,7 @@
1
- // Model construction with generated-specialization dispatch
2
- // (docs/design/optimization_plan.md Phase C): pick the generated class whose config
3
- // fingerprint matches, else the monolith. Generated classes subclass
4
- // Gemma4Model and only override forwardLayers (with their own
5
- // cache-signature guard), so the choice is always safe.
1
+ // Model construction consumes one declared profile. Exact artifact profiles
2
+ // outrank family profiles; dedicated/generated graphs outrank the universal
3
+ // fallback. Request-level methods are resolved elsewhere and are never changed
4
+ // by this module.
6
5
 
7
6
  import { loadModelConfig, type ModelConfig } from "../config";
8
7
  import { totalmem } from "node:os";
@@ -24,13 +23,56 @@ import {
24
23
  type Glm52MemoryPlan,
25
24
  } from "./glm52-memory";
26
25
  import { UniversalDenseModel } from "./universal/dense";
27
- import { genericArgsFor, GENERIC_MODEL_TYPES, remapModelType } from "./universal/archs";
28
- import { isDiffusionGemmaConfig, isGlm52Config, isMiniCPM5Config, isQwen35Config, isQwen3Config, isQwen3MoeConfig } from "./support";
26
+ import { genericArgsFor } from "./universal/archs";
27
+ import {
28
+ ModelImplementationRegistry,
29
+ type ModelImplementation,
30
+ type ModelImplementationProvider,
31
+ } from "./implementation";
32
+ import {
33
+ assertResolvedModelProfile,
34
+ resolveModelProfile,
35
+ type ResolvedModelProfile,
36
+ type ResolveModelProfileOptions,
37
+ type ModelGraph,
38
+ } from "./profile";
29
39
 
30
40
  export type RuntimeModel =
31
41
  | Gemma4Model | MiniCPM5Model | Qwen35Model | Qwen3Model | Qwen3MoeModel
32
42
  | DiffusionGemmaModel | Glm52Model | UniversalDenseModel;
33
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
+
34
76
  export interface Glm52RuntimeOpenOptions {
35
77
  /** Whole-process ceiling. Defaults to the smaller of the validated 25 GiB
36
78
  * preset and physical RAM. */
@@ -97,55 +139,51 @@ export async function openGlm52RuntimeModel(
97
139
  * model.safetensors.index.json, so it must bypass Weights.open and use its
98
140
  * dedicated header catalog/tensor source.
99
141
  */
100
- export async function openModel(
142
+ export function openModel<Model>(
101
143
  modelDir: string,
102
- options: Glm52RuntimeOpenOptions = {},
103
- ): 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> {
104
151
  const config = await loadModelConfig(modelDir);
105
- if (isGlm52Config(config))
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");
106
156
  return (await openGlm52RuntimeModel(modelDir, options)).model;
107
- return createModel(await Weights.open(modelDir), config);
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; }
108
163
  }
109
164
 
110
- /** Dispatch ladder (docs/design/generic-model-support.md §3.3):
111
- * dedicated → generated/monolith (gemma4*) → generic (Tier-0 universal)
112
- * → reject with a helpful error. Generic never shadows a dedicated port. */
113
- export function createModel(weights: Weights, config: ModelConfig): RuntimeModel {
114
- // 1. Dedicated classes.
115
- if (isGlm52Config(config)) {
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;
172
+ export function createModel(
173
+ weights: Weights, config: ModelConfig, resolved?: ResolvedModelProfile,
174
+ implementations?: ModelImplementationProvider<Weights, RuntimeModel>,
175
+ ): RuntimeModel;
176
+ export function createModel<Model>(
177
+ weights: Weights,
178
+ config: ModelConfig,
179
+ resolved: ResolvedModelProfile = resolveModelProfile(config),
180
+ implementations: ModelImplementationProvider<Weights, Model> | ModelImplementationProvider<Weights, RuntimeModel> = MLX_MODEL_IMPLEMENTATIONS,
181
+ ): Model | RuntimeModel {
182
+ assertResolvedModelProfile(config, resolved);
183
+ if (resolved.profile.execution.loader === "colibri")
116
184
  throw new Error(
117
185
  "glm_moe_dsa uses the direct Colibri container; construct it with " +
118
186
  "openModel(modelDir) or Glm52Model.open(modelDir)",
119
187
  );
120
- }
121
- // DiffusionGemma is non-autoregressive — generate() detects it and routes to
122
- // the denoising engine instead of the AR decode loop. It exposes the shared
123
- // RuntimeModel surface (config/weightsBytes/loraState/makeCache) with the
124
- // AR-only methods as throwing stubs (never called on this model).
125
- if (isDiffusionGemmaConfig(config)) return new DiffusionGemmaModel(weights, config);
126
- if (isMiniCPM5Config(config)) return new MiniCPM5Model(weights, config);
127
- if (isQwen35Config(config)) return new Qwen35Model(weights, config);
128
- if (isQwen3MoeConfig(config)) return new Qwen3MoeModel(weights, config);
129
- if (isQwen3Config(config)) return new Qwen3Model(weights, config);
130
- // 2. gemma4*: fingerprint-matched generated specialization, else monolith.
131
- // Always prefer the model-specific generated file when its config
132
- // fingerprint matches — the parity regime is selected by the OUTPUT-changing
133
- // flags (kv-quant), which the generated forward already gates, NOT by
134
- // detouring an optimized model through the generic monolith.
135
- if (config.modelType.startsWith("gemma4")) {
136
- const cls = GENERATED.get(configFingerprint(config)) ?? Gemma4Model;
137
- return new cls(weights, config);
138
- }
139
- // 3. Generic Tier-0 fallback: the universal-dense descriptor table
140
- // (replaces the old hard "llama = MiniCPM5 only" / unknown-type throws).
141
- const generic = genericArgsFor(config);
142
- if (generic) return new UniversalDenseModel(weights, config, generic);
143
- // 4. Reject, naming the arch (post-remap) and the declared surface.
144
- const arch = remapModelType(config.modelType);
145
- throw new Error(
146
- `unsupported model_type "${config.modelType}"` +
147
- (arch !== config.modelType ? ` (mlx-lm remaps it to "${arch}")` : "") +
148
- ` — targeted: gemma4*, diffusion_gemma, glm_moe_dsa, qwen3_5, qwen3, qwen3_moe, MiniCPM5;` +
149
- ` generic (Tier-0): ${[...GENERIC_MODEL_TYPES].sort().join(", ")}`,
150
- );
188
+ return implementations.select(config, resolved).create(weights, config, resolved);
151
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