mlx-bun 0.2.0 → 0.4.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 (433) hide show
  1. package/README.md +71 -588
  2. package/docs/README.md +94 -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/archive/investigations/openjarvis-fit.md +198 -0
  8. package/docs/{design → archive/investigations}/optimization_plan.md +1 -1
  9. package/docs/{design → archive/investigations}/pre-colibri-stabilization.md +5 -5
  10. package/docs/archive/investigations/qwen38-27b-campaign-log.md +1092 -0
  11. package/docs/{design → archive/investigations}/the-dreaming-handoff.md +2 -2
  12. package/docs/archive/planning/release-notes-v0.3.0.md +35 -0
  13. package/docs/design/batching.md +1841 -0
  14. package/docs/design/decode-speed-program.md +2346 -169
  15. package/docs/design/dreaming-nightly-pipeline.md +675 -282
  16. package/docs/design/generic-model-support.md +655 -334
  17. package/docs/design/kv-cache.md +824 -0
  18. package/docs/design/orpo-training.md +673 -1239
  19. package/docs/design/speculative-decoding.md +1990 -0
  20. package/docs/design/turboquant.md +2212 -0
  21. package/docs/design/unified-engine-frontier-plan.md +1833 -727
  22. package/docs/design/web-chat-redesign.md +666 -1083
  23. package/docs/planning/PRODUCT_ROADMAP.md +50 -1
  24. package/docs/planning/release-notes-v0.4.0.md +45 -0
  25. package/docs/planning/video-series.md +2 -2
  26. package/docs/reference/benchmarks.md +3613 -0
  27. package/docs/reference/cli.md +434 -160
  28. package/docs/reference/distribution.md +328 -182
  29. package/docs/reference/environment.md +398 -0
  30. package/docs/reference/glossary.md +39 -0
  31. package/docs/reference/library-api.md +566 -168
  32. package/docs/reference/memory.md +227 -64
  33. package/docs/reference/models.md +113 -6
  34. package/docs/reference/server-api.md +944 -429
  35. package/docs/reference/server-config.md +740 -338
  36. package/docs/reference/training.md +393 -254
  37. package/docs/reference/troubleshooting.md +158 -0
  38. package/package.json +9 -4
  39. package/src/audio/decode.ts +2 -2
  40. package/src/audio/features.ts +2 -2
  41. package/src/backends/mlx/assistant-target.ts +46 -0
  42. package/src/backends/mlx/autoregressive-draft-rows.ts +120 -0
  43. package/src/backends/mlx/autoregressive.ts +108 -0
  44. package/src/backends/mlx/batch-group.ts +1790 -0
  45. package/src/backends/mlx/cache-attachment.ts +67 -0
  46. package/src/backends/mlx/cache-layout.ts +31 -0
  47. package/src/backends/mlx/checkpoint-state.ts +53 -0
  48. package/src/backends/mlx/continuation-persistence.ts +106 -0
  49. package/src/backends/mlx/continuation-request.ts +43 -0
  50. package/src/backends/mlx/continuation.ts +77 -0
  51. package/src/backends/mlx/diffusion.ts +39 -0
  52. package/src/backends/mlx/draft-target.ts +32 -0
  53. package/src/backends/mlx/evaluation.ts +105 -0
  54. package/src/backends/mlx/fill-append.ts +45 -0
  55. package/src/backends/mlx/full-kv-row-append.ts +32 -0
  56. package/src/backends/mlx/full-kv-row-donor.ts +54 -0
  57. package/src/backends/mlx/full-transitioning-kv-rows.ts +65 -0
  58. package/src/backends/mlx/gateway-binding.ts +130 -0
  59. package/src/backends/mlx/graph.ts +38 -0
  60. package/src/backends/mlx/kv-codec.ts +9 -0
  61. package/src/backends/mlx/kv-maintenance.ts +133 -0
  62. package/src/backends/mlx/kv-tensor-rows.ts +212 -0
  63. package/src/backends/mlx/memory-client.ts +189 -0
  64. package/src/backends/mlx/methods.ts +90 -0
  65. package/src/backends/mlx/model-prompt.ts +164 -0
  66. package/src/backends/mlx/model-serving.ts +79 -0
  67. package/src/backends/mlx/prefill-cohort.ts +89 -0
  68. package/src/backends/mlx/prefill-rows.ts +200 -0
  69. package/src/backends/mlx/prefill.ts +33 -0
  70. package/src/backends/mlx/projected-context-rows.ts +72 -0
  71. package/src/backends/mlx/request-policy.ts +20 -0
  72. package/src/backends/mlx/request-state-policy.ts +41 -0
  73. package/src/backends/mlx/rollback.ts +89 -0
  74. package/src/backends/mlx/row-sampling.ts +49 -0
  75. package/src/backends/mlx/serial-executor.ts +242 -0
  76. package/src/backends/mlx/speculative-group.ts +359 -0
  77. package/src/backends/mlx/speculative-round.ts +161 -0
  78. package/src/backends/mlx/speculative.ts +114 -0
  79. package/src/backends/mlx/state-rows.ts +104 -0
  80. package/src/backends/mlx/state-views.ts +46 -0
  81. package/src/backends/mlx/target-layout-capability.ts +35 -0
  82. package/src/backends/mlx/text-engine.ts +73 -0
  83. package/src/backends/mlx/transitioning-kv-rows.ts +165 -0
  84. package/src/cli.ts +66 -58
  85. package/src/client.ts +66 -0
  86. package/src/config.ts +21 -4
  87. package/src/contracts/completion.ts +16 -0
  88. package/src/contracts/execution.ts +60 -0
  89. package/src/contracts/generation.ts +103 -0
  90. package/src/contracts/host.ts +7 -0
  91. package/src/contracts/jobs.ts +66 -0
  92. package/src/contracts/pi-provider.ts +30 -0
  93. package/src/contracts/pi.ts +259 -0
  94. package/src/contracts/prefix-cache.ts +23 -0
  95. package/src/contracts/resources.ts +25 -0
  96. package/src/contracts/sampling.ts +23 -0
  97. package/src/contracts/scheduling.ts +45 -0
  98. package/src/contracts/tsconfig.json +14 -0
  99. package/src/diffusion/diffusion-generate.ts +218 -153
  100. package/src/embed.ts +1 -1
  101. package/src/engine/admission.ts +59 -0
  102. package/src/engine/cancellation.ts +38 -0
  103. package/src/engine/engine.ts +56 -0
  104. package/src/engine/execution-coordinator.ts +70 -0
  105. package/src/engine/execution-plan.ts +53 -0
  106. package/src/engine/resources.ts +53 -0
  107. package/src/engine/scheduler.ts +72 -0
  108. package/src/engine/session.ts +247 -0
  109. package/src/eval/README.md +44 -1
  110. package/src/eval/kl.ts +236 -3
  111. package/src/eval/reference-logits.ts +207 -0
  112. package/src/eval/runner.ts +8 -96
  113. package/src/fill/echo-index.ts +188 -0
  114. package/src/fill/fill-session.ts +452 -0
  115. package/src/fill/proposal.ts +68 -0
  116. package/src/fill/schema-rows.ts +506 -0
  117. package/src/fill/tool-boundary.ts +103 -0
  118. package/src/fit.ts +30 -16
  119. package/src/generate.ts +709 -417
  120. package/src/grammar.ts +12 -7
  121. package/src/harness-pi.ts +14 -33
  122. package/src/hub-rest.ts +4 -4
  123. package/src/index.ts +7 -0
  124. package/src/inference/denoising.ts +17 -0
  125. package/src/inference/draft-acceptance.ts +41 -0
  126. package/src/inference/draft-output.ts +49 -0
  127. package/src/inference/graph.ts +33 -0
  128. package/src/inference/prefill.ts +39 -0
  129. package/src/inference/rollback.ts +7 -0
  130. package/src/jobs/db.ts +4 -0
  131. package/src/jobs/index.ts +2 -0
  132. package/src/jobs/job-entry.ts +12 -12
  133. package/src/jobs/runner.ts +98 -13
  134. package/src/jobs/task-client.ts +19 -0
  135. package/src/jobs/types.ts +2 -58
  136. package/src/kv-scheme.ts +70 -12
  137. package/src/kv-store.ts +177 -63
  138. package/src/lab/README.md +18 -0
  139. package/src/{assets → lab/curve}/curve-designer.html +2 -2
  140. package/src/{curve-sampler.ts → lab/curve/curve-sampler.ts} +2 -2
  141. package/src/{expert-trace.ts → lab/expert-trace/expert-trace.ts} +5 -5
  142. package/src/lab/paged-kv/paged-kv-rows.ts +83 -0
  143. package/src/{model → lab/paged-kv}/paged-kv.ts +32 -15
  144. package/src/library.ts +40 -0
  145. package/src/lora.ts +18 -0
  146. package/src/media-fetch.ts +1 -1
  147. package/src/memory/article.ts +1 -1
  148. package/src/memory/chunk.ts +1 -1
  149. package/src/memory/model.ts +30 -169
  150. package/src/memory/prompts.ts +1 -1
  151. package/src/memory/rest.ts +1 -1
  152. package/src/memory/schedule.ts +1 -1
  153. package/src/memory/stages.ts +4 -1
  154. package/src/memory/vault.ts +1 -3
  155. package/src/mlx/array.ts +11 -11
  156. package/src/mlx/compile.ts +12 -2
  157. package/src/mlx/ffi.ts +58 -11
  158. package/src/mlx/materialize.ts +23 -0
  159. package/src/mlx/metal-kernel.ts +10 -11
  160. package/src/mlx/ops.ts +33 -13
  161. package/src/mlx/quantized-rows.ts +36 -0
  162. package/src/mlx/token-bitmask.ts +52 -0
  163. package/src/mlx/turboquant-kv-decode.ts +82 -0
  164. package/src/mlx/turboquant-kv-inverse.ts +122 -0
  165. package/src/mlx/turboquant-ops.ts +33 -3
  166. package/src/mlx/turboquant-tables.ts +2 -2
  167. package/src/model/artifact-identity.ts +73 -0
  168. package/src/model/batched-kv.ts +235 -0
  169. package/src/model/batched-mask.ts +20 -6
  170. package/src/model/batched-quant.ts +9 -3
  171. package/src/model/batched-quantized-kv.ts +144 -0
  172. package/src/model/batched-rotating-quant.ts +78 -17
  173. package/src/model/batched-rotating-state.ts +76 -0
  174. package/src/model/batched-rotating.ts +65 -23
  175. package/src/model/batched-row-storage.ts +52 -0
  176. package/src/model/batched-ssm.ts +52 -0
  177. package/src/model/batched-turboquant-kv.ts +93 -0
  178. package/src/model/compiled-decode.ts +8 -2
  179. package/src/model/decoded-kv-donor.ts +12 -0
  180. package/src/model/delayed-quantized-kv.ts +72 -0
  181. package/src/model/delayed-rotating-quantized-kv.ts +93 -0
  182. package/src/model/delayed-turboquant-kv.ts +55 -0
  183. package/src/model/diffusion-gemma.ts +8 -8
  184. package/src/model/factory.ts +76 -33
  185. package/src/model/fingerprint.ts +1 -1
  186. package/src/model/flash-attention.ts +3 -3
  187. package/src/model/full-prefill-padding.ts +56 -0
  188. package/src/model/full-prefill-row.ts +105 -0
  189. package/src/model/gemma4-base.ts +179 -80
  190. package/src/model/gemma4.ts +40 -32
  191. package/src/model/generated/gemma4-12b.ts +6 -3
  192. package/src/model/generated/gemma4-26b.ts +6 -3
  193. package/src/model/generated/gemma4-e4b.ts +6 -3
  194. package/src/model/glm52-cache.ts +80 -4
  195. package/src/model/glm52-dsa.ts +13 -9
  196. package/src/model/glm52-memory.ts +1 -1
  197. package/src/model/glm52-mla.ts +12 -6
  198. package/src/model/glm52.ts +19 -40
  199. package/src/model/implementation.ts +69 -0
  200. package/src/model/kv-attention-view.ts +67 -0
  201. package/src/model/minicpm5.ts +17 -15
  202. package/src/model/profile.ts +12 -2
  203. package/src/model/qwen-append.ts +12 -0
  204. package/src/model/qwen-conv.ts +52 -0
  205. package/src/model/qwen3-delta-state.ts +74 -0
  206. package/src/model/qwen3-delta.ts +59 -36
  207. package/src/model/qwen3_5-checkpoint.ts +164 -0
  208. package/src/model/qwen3_5.ts +202 -78
  209. package/src/model/rotating-kv-donor.ts +35 -0
  210. package/src/model/rotating-kv-layout.ts +262 -0
  211. package/src/model/rotating-row-transaction.ts +35 -0
  212. package/src/model/speculative-rotating-kv.ts +101 -0
  213. package/src/model/ssm-prefill-padding.ts +39 -0
  214. package/src/model/support.ts +1 -1
  215. package/src/model/trellis-balanced-scatter.ts +51 -0
  216. package/src/model/trellis-linear.ts +672 -0
  217. package/src/model/trellis-shared-m.ts +151 -0
  218. package/src/model/trellis-shared-scatter.ts +144 -0
  219. package/src/model/trellis-splitk-prefill.ts +158 -0
  220. package/src/model/trellis-tiled-prefill.ts +136 -0
  221. package/src/model/trellis-vector-expand.ts +62 -0
  222. package/src/model/trellis-wide-prefill.ts +94 -0
  223. package/src/model/turboquant-codec.ts +83 -0
  224. package/src/model/universal/dense.ts +15 -14
  225. package/src/model/universal/modules.ts +8 -1
  226. package/src/native-pack.ts +6 -5
  227. package/src/pi-provider.ts +6 -64
  228. package/src/pi-web.ts +8 -315
  229. package/src/preflight.ts +31 -15
  230. package/src/prompt-cache.ts +110 -69
  231. package/src/quantize/allocator.ts +1 -1
  232. package/src/quantize/config-writer.ts +10 -3
  233. package/src/quantize/rotate.ts +4 -4
  234. package/src/quantize/trellis.ts +586 -0
  235. package/src/responses.ts +28 -1
  236. package/src/runtime-config.ts +18 -4
  237. package/src/sampler.ts +40 -29
  238. package/src/serve/batch-scheduler.ts +8 -1671
  239. package/src/serve/chat-request.ts +391 -0
  240. package/src/serve/chat-stage.ts +195 -0
  241. package/src/serve/checkpoint-identity.ts +64 -0
  242. package/src/serve/completion-executor.ts +15 -7
  243. package/src/serve/discovery-routes.ts +12 -13
  244. package/src/serve/generation-gateway.ts +183 -213
  245. package/src/serve/http.ts +159 -0
  246. package/src/serve/inference-request.ts +79 -0
  247. package/src/serve/isolate.ts +297 -105
  248. package/src/serve/lab-routes.ts +3 -0
  249. package/src/serve/lane-registry.ts +2 -1
  250. package/src/serve/model-admin-routes.ts +7 -5
  251. package/src/serve/model-binding.ts +43 -0
  252. package/src/serve/model-host.ts +511 -0
  253. package/src/serve/openai-wire.ts +103 -0
  254. package/src/serve/parent-application.ts +56 -0
  255. package/src/serve/pipeline.ts +33 -0
  256. package/src/serve/preparation.ts +33 -0
  257. package/src/serve/request-plan.ts +23 -18
  258. package/src/serve/request-prep.ts +344 -0
  259. package/src/serve/response-lease.ts +24 -0
  260. package/src/serve/responses-client.ts +69 -0
  261. package/src/serve/session-completion-engine.ts +110 -0
  262. package/src/serve/session-search.ts +1 -1
  263. package/src/serve/static-routes.ts +5 -5
  264. package/src/serve/text-completion-stage.ts +104 -0
  265. package/src/serve/token-streams.ts +519 -0
  266. package/src/server.ts +335 -2711
  267. package/src/spec/assistant-rows.ts +92 -0
  268. package/src/spec/assistant-source.ts +31 -55
  269. package/src/spec/cache-identity.ts +22 -0
  270. package/src/spec/deepspec-rows.ts +29 -0
  271. package/src/spec/deepspec-source.ts +16 -14
  272. package/src/spec/dflash-source.ts +36 -12
  273. package/src/spec/drafter.ts +153 -142
  274. package/src/spec/dspark/ab-stats.ts +3 -3
  275. package/src/spec/dspark/calibration.ts +2 -2
  276. package/src/spec/dspark/deepspec-module.ts +97 -122
  277. package/src/spec/dspark/generate-dflash.ts +2 -2
  278. package/src/spec/dspark/generate.ts +2 -2
  279. package/src/spec/dspark/loader.ts +1 -1
  280. package/src/spec/dspark/module-dflash.ts +129 -172
  281. package/src/spec/dspark/module.ts +3 -3
  282. package/src/spec/dspark/quantize-drafter.ts +1 -1
  283. package/src/spec/generate.ts +1 -1
  284. package/src/spec/glm52-mtp-graph.ts +29 -0
  285. package/src/spec/glm52-mtp-rows.ts +132 -0
  286. package/src/spec/glm52-mtp-source.ts +54 -137
  287. package/src/spec/glm52-mtp-state.ts +33 -0
  288. package/src/spec/ngram-source.ts +93 -21
  289. package/src/spec/projected-draft-rows.ts +73 -0
  290. package/src/spec/qwen-mtp-module.ts +171 -0
  291. package/src/spec/qwen-mtp-rows.ts +219 -0
  292. package/src/spec/qwen-mtp-source.ts +147 -195
  293. package/src/spec/qwen-mtp-state.ts +30 -0
  294. package/src/spec/serve-loop.ts +201 -201
  295. package/src/spec/source.ts +110 -13
  296. package/src/spec/two-model-rows.ts +61 -0
  297. package/src/spec/two-model.ts +44 -102
  298. package/src/ssd-cache.ts +99 -19
  299. package/src/ssd-durability.ts +36 -20
  300. package/src/tool-call.ts +51 -14
  301. package/src/train/flash-cce.ts +2 -2
  302. package/src/train/forward.ts +6 -0
  303. package/src/train/loss.ts +1 -1
  304. package/src/train/prefix-shared.ts +5 -1
  305. package/src/train/segmented.ts +5 -3
  306. package/src/train/trainer.ts +6 -6
  307. package/src/vision/prompt.ts +1 -1
  308. package/src/vision/qwen3vl-tower.ts +52 -10
  309. package/src/web/app.html +1 -1
  310. package/src/web/app.js +62 -60
  311. package/src/web/src/adapters-panel.ts +3 -3
  312. package/src/web/src/assistant.ts +2 -36
  313. package/src/web/src/chat.ts +4 -4
  314. package/src/web/src/composer.ts +4 -4
  315. package/src/web/src/hub.ts +1 -1
  316. package/src/web/src/markdown.ts +0 -0
  317. package/src/web/src/memory-panel.ts +1 -1
  318. package/src/web/src/model-picker.ts +1 -1
  319. package/src/web/src/palette.ts +1 -1
  320. package/src/web/src/protocol.ts +3 -39
  321. package/src/web/src/sessions.ts +1 -1
  322. package/src/web/src/ui-catalog.ts +4 -18
  323. package/src/web-assets.ts +47 -0
  324. package/src/weights.ts +55 -9
  325. package/docs/archive/investigations/curve-runs/bisector-route.json +0 -98
  326. package/docs/archive/investigations/curve-runs/bisector-trace.json +0 -1393
  327. package/docs/archive/investigations/curve-runs/curve-terrain.json +0 -1179
  328. package/docs/archive/investigations/curve-runs/curve-terrain.svg +0 -42
  329. package/docs/archive/investigations/hlg-ab.html +0 -34
  330. package/docs/archive/investigations/hlg-explorer.html +0 -238
  331. package/docs/archive/investigations/hlg-figs/cliff.svg +0 -1
  332. package/docs/archive/investigations/hlg-figs/cross.svg +0 -1
  333. package/docs/archive/investigations/hlg-figs/edges.svg +0 -1
  334. package/docs/archive/investigations/hlg-figs/k20-grids.svg +0 -1
  335. package/docs/archive/investigations/hlg-figs/k20-peaks.svg +0 -1
  336. package/docs/archive/investigations/hlg-figs/lw-curve.svg +0 -1
  337. package/docs/archive/investigations/hlg-figs/terrain.svg +0 -1
  338. package/docs/archive/investigations/hlg-report.html +0 -262
  339. package/docs/archive/investigations/hlg-runs/hlg-ab.json +0 -149
  340. package/docs/archive/investigations/hlg-runs/hlg-blind-prompts.md +0 -87
  341. package/docs/archive/investigations/hlg-runs/hlg-blind.json +0 -107
  342. package/docs/archive/investigations/hlg-runs/hlg-cliff.json +0 -197
  343. package/docs/archive/investigations/hlg-runs/hlg-cross.json +0 -505
  344. package/docs/archive/investigations/hlg-runs/hlg-edge.json +0 -388
  345. package/docs/archive/investigations/hlg-runs/hlg-eval1.json +0 -25
  346. package/docs/archive/investigations/hlg-runs/hlg-frontier.json +0 -86
  347. package/docs/archive/investigations/hlg-runs/hlg-grid.json +0 -242
  348. package/docs/archive/investigations/hlg-runs/hlg-grid2.json +0 -343
  349. package/docs/archive/investigations/hlg-runs/hlg-lw-coarse.json +0 -110
  350. package/docs/archive/investigations/hlg-runs/hlg-map.json +0 -383
  351. package/docs/archive/investigations/hlg-runs/run0-original.log +0 -159
  352. package/docs/archive/investigations/hlg-runs/runA-recommended.log +0 -131
  353. package/docs/archive/investigations/hlg-runs/runAA-cliff-cpm5.log +0 -26
  354. package/docs/archive/investigations/hlg-runs/runAB-grid2-e4b.log +0 -18
  355. package/docs/archive/investigations/hlg-runs/runAC-peak-e4b-partial.log +0 -25
  356. package/docs/archive/investigations/hlg-runs/runAD-ab-samples.log +0 -8
  357. package/docs/archive/investigations/hlg-runs/runAE-edge-e4b.log +0 -51
  358. package/docs/archive/investigations/hlg-runs/runAF-logit-calibration.log +0 -10
  359. package/docs/archive/investigations/hlg-runs/runAG-eval-config2.log +0 -16
  360. package/docs/archive/investigations/hlg-runs/runB-gain-variants.log +0 -192
  361. package/docs/archive/investigations/hlg-runs/runC-pivots.log +0 -162
  362. package/docs/archive/investigations/hlg-runs/runD-oetf.log +0 -132
  363. package/docs/archive/investigations/hlg-runs/runE-eotf.log +0 -162
  364. package/docs/archive/investigations/hlg-runs/runF-shaper-working.log +0 -86
  365. package/docs/archive/investigations/hlg-runs/runG-shaper-sweep.log +0 -101
  366. package/docs/archive/investigations/hlg-runs/runH-wsweep.log +0 -176
  367. package/docs/archive/investigations/hlg-runs/runI-asweep.log +0 -176
  368. package/docs/archive/investigations/hlg-runs/runJ-knobmap.log +0 -49
  369. package/docs/archive/investigations/hlg-runs/runK-frontier.log +0 -19
  370. package/docs/archive/investigations/hlg-runs/runL-widemap.log +0 -65
  371. package/docs/archive/investigations/hlg-runs/runM-frontier-emb.log +0 -15
  372. package/docs/archive/investigations/hlg-runs/runN-verify.log +0 -116
  373. package/docs/archive/investigations/hlg-runs/runO-frontier-seed5000.log +0 -15
  374. package/docs/archive/investigations/hlg-runs/runP-grid-AxSm.log +0 -15
  375. package/docs/archive/investigations/hlg-runs/runQ-grid-tgW.log +0 -15
  376. package/docs/archive/investigations/hlg-runs/runR-blind-transcript.log +0 -79
  377. package/docs/archive/investigations/hlg-runs/runS-cross-coherence.log +0 -33
  378. package/docs/archive/investigations/hlg-runs/runT-cross-cpm5.log +0 -33
  379. package/docs/archive/investigations/hlg-runs/runU-cross-12b.log +0 -33
  380. package/docs/archive/investigations/hlg-runs/runV-lw-e4b.log +0 -22
  381. package/docs/archive/investigations/hlg-runs/runW-lw-12b.log +0 -22
  382. package/docs/archive/investigations/hlg-runs/runX-lw-cpm5.log +0 -22
  383. package/docs/archive/investigations/hlg-runs/runY-cliff-e4b.log +0 -26
  384. package/docs/archive/investigations/hlg-runs/runZ-cliff-12b.log +0 -26
  385. package/docs/archive/mlx-bun-lab-report.html +0 -275
  386. package/docs/archive/wwdc-mlx-bun.html +0 -848
  387. package/docs/design/adapters-end-to-end.md +0 -205
  388. package/docs/design/batching-perf-path.md +0 -255
  389. package/docs/design/batching-v2-plan.md +0 -529
  390. package/docs/design/bucketing-stage.md +0 -197
  391. package/docs/design/compat-cli-surface-design.md +0 -230
  392. package/docs/design/docs-reorg-plan.md +0 -165
  393. package/docs/design/dspark-serving-program.md +0 -371
  394. package/docs/design/dspark-speculative-decoding.md +0 -318
  395. package/docs/design/grammar-spec-batching-integration.md +0 -301
  396. package/docs/design/memory-inference-path.md +0 -422
  397. package/docs/design/memory-synthesis.md +0 -287
  398. package/docs/design/memory-system.md +0 -237
  399. package/docs/design/mlx-lm-serving-execution-seam.md +0 -81
  400. package/docs/design/mlx-lm-tool-parity-plan.md +0 -545
  401. package/docs/design/omlx-adoption-map.md +0 -98
  402. package/docs/design/orpo-dynamic-lambda.md +0 -220
  403. package/docs/design/orpo-future-enhancements.md +0 -340
  404. package/docs/design/paged-kv-cache.md +0 -214
  405. package/docs/design/parallel-slots.md +0 -567
  406. package/docs/design/parity-tier-dag.md +0 -161
  407. package/docs/design/repo-cleanup-plan.md +0 -231
  408. package/docs/design/runtime-isolation.md +0 -263
  409. package/docs/design/segmented-backward-training.md +0 -543
  410. package/docs/design/spec-decode-larger-targets.md +0 -229
  411. package/docs/design/ssd-kv-cold-tier.md +0 -415
  412. package/docs/design/structured-output.md +0 -484
  413. package/docs/design/tauri-desktop-app.md +0 -127
  414. package/docs/design/the-dreaming-master-plan.md +0 -445
  415. package/docs/design/turboquant-kv.md +0 -235
  416. package/docs/design/turboquant-weights.md +0 -467
  417. package/docs/design/w4a16-compute-precision-spike.md +0 -232
  418. package/docs/design/web-chat-beat-matrix.md +0 -315
  419. package/docs/design/write-pipeline-entity.md +0 -468
  420. package/docs/planning/IDEAS.md +0 -69
  421. package/docs/reference/embedding.md +0 -197
  422. package/docs/reference/features-matrix.md +0 -101
  423. package/docs/reference/orpo-quickstart.md +0 -135
  424. package/src/flags.ts +0 -11
  425. /package/docs/{design → archive/investigations}/colibri-glm52-port.md +0 -0
  426. /package/docs/{planning → archive/planning}/chunk-task-roadmap.md +0 -0
  427. /package/docs/{planning → archive/planning}/curve-sampler-research-plan.md +0 -0
  428. /package/docs/{planning → archive/planning}/memory-docs-and-dag-plan.md +0 -0
  429. /package/docs/{planning → archive/planning}/pi-integration-review.md +0 -0
  430. /package/docs/{planning → archive/planning}/release-notes-v0.2.0.md +0 -0
  431. /package/src/{memory-plan.ts → admission-plan.ts} +0 -0
  432. /package/{docs/archive/investigations → src/serve/assets}/curve-terrain.html +0 -0
  433. /package/{docs/dag → src/serve/assets}/training-inference-map.html +0 -0
@@ -0,0 +1,164 @@
1
+ // Qwen3.5 checkpoint-generation normalization — the load-seam port of
2
+ // mlx_lm.models.qwen3_5's `Model.sanitize` + `TextModel.sanitize`.
3
+ //
4
+ // Two checkpoint generations ship the same graph under different conventions:
5
+ //
6
+ // pre-5.8 (mlx-lm-converted) 5.8-family (transformers 5.8.0 export)
7
+ // ────────────────────────── ───────────────────────────────────────
8
+ // language_model.model.* model.language_model.*
9
+ // language_model.lm_head.* lm_head.* (top level)
10
+ // vision_tower.* model.visual.*
11
+ // (no mtp tensors in-repo) mtp.* (in-repo)
12
+ // conv1d.weight [C, K, 1] conv1d.weight [C, 1, K] (HF layout)
13
+ // RMSNorm gains stored as γ RMSNorm gains stored as γ−1
14
+ //
15
+ // Our graph speaks the FIRST column (`PREFIX = "language_model"` in
16
+ // qwen3_5.ts) — which is exactly mlx-lm's post-sanitize name space — so this
17
+ // module maps the artifact onto it instead of teaching every consumer two
18
+ // namings. The rules below are a line-for-line copy of the oracle; see
19
+ // mlx_lm/models/qwen3_5.py `Model.sanitize` (naming/drop) and
20
+ // `TextModel.sanitize` (γ shift, conv layout).
21
+ //
22
+ // The γ shift is the dangerous one: a spurious +1.0 silently corrupts an old
23
+ // artifact. The oracle's discriminator is NOT the naming — it is
24
+ // should_shift = any("mtp." in k) or any conv1d.weight with shape[-1] != 1
25
+ // evaluated over the whole (post-rename, pre-drop) weight map. Reproduced
26
+ // verbatim. Measured on the two artifacts in play (2026-09-01, M1 Max):
27
+ // • ~/.cache/…/models--mjriii--Qwen3.8-27B/snapshots/staged — no mtp.*,
28
+ // conv1d [10240,4,1] → no shift; layer-0 input_layernorm ≡ 1.0.
29
+ // • /Volumes/MLX-Models/models/mjriii/Qwen3.8-27B-{tqalloc-norot,rtn4-g64}
30
+ // — 31 mtp.* tensors, conv1d [10240,1,4] → shift; layer-0
31
+ // input_layernorm mean −0.0334 (= γ−1), byte-identical to the raw
32
+ // Qwen/Qwen3.8-27B 5.8 checkpoint (the quantizer copies norms verbatim).
33
+
34
+ import type { MlxArray } from "../mlx/array";
35
+ import * as ops from "../mlx/ops";
36
+ import type { Weights, WeightsView } from "../weights";
37
+
38
+ /** Header-level facts about one stored tensor (no bytes are read). */
39
+ export interface CheckpointTensor {
40
+ readonly name: string;
41
+ readonly shape: readonly number[];
42
+ }
43
+
44
+ export interface Qwen35Sanitized {
45
+ /** canonical (graph) name → the name the artifact actually stores. */
46
+ readonly names: Map<string, string>;
47
+ /** Canonical names stored as γ−1: a +1.0 is due at load. */
48
+ readonly normShift: Set<string>;
49
+ /** Canonical names in HF conv layout: a moveaxis(2, 1) is due at load. */
50
+ readonly convMoveaxis: Set<string>;
51
+ /** True when the artifact already IS our canonical layout — no view needed
52
+ * and the pre-existing load path runs untouched. */
53
+ readonly identity: boolean;
54
+ }
55
+
56
+ /** mlx-lm `TextModel.sanitize` norm_keys, verbatim and in order. */
57
+ const NORM_KEYS = [
58
+ ".input_layernorm.weight",
59
+ ".post_attention_layernorm.weight",
60
+ "model.norm.weight",
61
+ ".q_norm.weight",
62
+ ".k_norm.weight",
63
+ ] as const;
64
+
65
+ /** mlx-lm `Model.sanitize`: drop vision, fold every naming onto
66
+ * `language_model.…`. Returns null for a dropped tensor. */
67
+ function canonicalName(key: string): string | null {
68
+ if (key.startsWith("vision_tower") || key.startsWith("model.visual")) return null;
69
+ if (key.startsWith("model.language_model"))
70
+ return "language_model.model" + key.slice("model.language_model".length);
71
+ if (key.startsWith("language_model.")) return key;
72
+ return "language_model." + key;
73
+ }
74
+
75
+ /**
76
+ * Port of mlx_lm.models.qwen3_5 `Model.sanitize` ∘ `TextModel.sanitize`,
77
+ * name/shape half. Pure: takes header facts, returns the load plan.
78
+ */
79
+ export function sanitizeQwen35Checkpoint(
80
+ tensors: Iterable<CheckpointTensor>,
81
+ ): Qwen35Sanitized {
82
+ // Pass 1 — rename (vision dropped), exactly as Model.sanitize.
83
+ const renamed: { canonical: string; source: string; shape: readonly number[] }[] = [];
84
+ let sawForeignName = false;
85
+ for (const t of tensors) {
86
+ const canonical = canonicalName(t.name);
87
+ if (canonical === null) {
88
+ sawForeignName ||= t.name.startsWith("model.visual");
89
+ continue;
90
+ }
91
+ if (canonical !== t.name) sawForeignName = true;
92
+ renamed.push({ canonical, source: t.name, shape: t.shape });
93
+ }
94
+
95
+ // Pass 2 — TextModel.sanitize's two flags, over the renamed map BEFORE the
96
+ // mtp drop (the oracle computes them first, then filters).
97
+ const hasMtpWeights = renamed.some((t) => t.canonical.includes("mtp."));
98
+ const hasUnsanitizedConv1d = renamed.some(
99
+ (t) => t.canonical.includes("conv1d.weight") && t.shape[t.shape.length - 1] !== 1,
100
+ );
101
+ const shouldShiftNormWeights = hasMtpWeights || hasUnsanitizedConv1d;
102
+
103
+ const names = new Map<string, string>();
104
+ const normShift = new Set<string>();
105
+ const convMoveaxis = new Set<string>();
106
+ for (const t of renamed) {
107
+ if (t.canonical.includes("mtp.")) continue; // oracle drops these outright
108
+ names.set(t.canonical, t.source);
109
+ if (t.canonical.includes("conv1d.weight") && t.shape[t.shape.length - 1] !== 1)
110
+ convMoveaxis.add(t.canonical);
111
+ if (
112
+ shouldShiftNormWeights &&
113
+ NORM_KEYS.some((sfx) => t.canonical.endsWith(sfx)) &&
114
+ t.shape.length === 1
115
+ )
116
+ normShift.add(t.canonical);
117
+ }
118
+
119
+ return {
120
+ names,
121
+ normShift,
122
+ convMoveaxis,
123
+ identity: !sawForeignName && !hasMtpWeights && !shouldShiftNormWeights &&
124
+ convMoveaxis.size === 0,
125
+ };
126
+ }
127
+
128
+ /**
129
+ * The {@link WeightsView} for a qwen3.5 artifact, or null when the artifact is
130
+ * already canonical (then nothing is installed and the load path is bit-for-bit
131
+ * what it was before this seam existed).
132
+ */
133
+ export function qwen35WeightsView(weights: Weights): WeightsView | null {
134
+ const plan = sanitizeQwen35Checkpoint(
135
+ weights.tensorNames.map((name) => ({ name, shape: weights.info(name).shape })),
136
+ );
137
+ if (plan.identity) return null;
138
+ return {
139
+ names: plan.names,
140
+ fixup(canonical: string, arr: MlxArray): MlxArray | null {
141
+ // Oracle order: moveaxis first, then the γ shift (disjoint sets — a
142
+ // conv1d weight is 3-D and never a norm gain).
143
+ if (plan.convMoveaxis.has(canonical)) {
144
+ // `v.moveaxis(2, 1)` on [C, 1, K] → [C, K, 1]. Materialized: the
145
+ // conv1d kernel consumes it every forward, and the transposed view
146
+ // would otherwise pin the source shard buffer.
147
+ const moved = ops.transposeAxes(arr, [0, 2, 1]);
148
+ const out = ops.contiguous(moved);
149
+ moved.dispose();
150
+ return out;
151
+ }
152
+ if (plan.normShift.has(canonical)) {
153
+ // `v + 1.0` — a weak python scalar, so the add happens at the gain's
154
+ // own dtype (bf16 here); scalarLike builds the constant host-side at
155
+ // that dtype for the same single-kernel add the reference dispatches.
156
+ const one = ops.scalarLike(1, arr);
157
+ const out = ops.add(arr, one);
158
+ one.dispose();
159
+ return out;
160
+ }
161
+ return null;
162
+ },
163
+ };
164
+ }
@@ -10,18 +10,20 @@
10
10
  // shared maybeQuantizeKv path).
11
11
 
12
12
  import type { ModelConfig } from "../config";
13
+ import { gatedDeltaState } from "./qwen3-delta-state";
13
14
  import type { Weights } from "../weights";
14
15
  import { MlxArray } from "../mlx/array";
15
- import { Dtype } from "../mlx/ffi";
16
+ import { Dtype, deviceArchitecture } from "../mlx/ffi";
16
17
  import * as ops from "../mlx/ops";
17
18
  import { CompiledFunction } from "../mlx/compile";
19
+ import { qwenAppendChunkSize } from "./qwen-append";
20
+ import { TrellisLinear, fusedGateUpEligible, fusedGateUpSwiglu, TRELLIS_MATVEC_MAX_M } from "./trellis-linear";
18
21
  import {
19
22
  argmaxLastPosition,
20
23
  disposeTriple,
21
24
  disposing,
22
25
  KVCache,
23
26
  LoraState,
24
- QuantizedKVCache,
25
27
  QuantizedEmbedding,
26
28
  QuantizedLinear,
27
29
  quantizedSdpa,
@@ -29,7 +31,10 @@ import {
29
31
  type Cache,
30
32
  type Mask,
31
33
  } from "./gemma4-base";
34
+ import { qwen35WeightsView } from "./qwen3_5-checkpoint";
35
+ import { materializeCopy } from "../mlx/materialize";
32
36
  import { gatedDeltaUpdate, SSMCache } from "./qwen3-delta";
37
+ import type { QwenConvolution } from "./qwen-conv";
33
38
  import {
34
39
  activeMrope, applyInterleavedRope, buildMropePositions, mropeInvFreq,
35
40
  setActiveMrope, type MropeRequestState,
@@ -107,6 +112,10 @@ export function compiledSilu(x: MlxArray): MlxArray {
107
112
 
108
113
  /** Gated-DeltaNet linear-attention layer (mlx-lm GatedDeltaNet). */
109
114
  export class GatedDeltaNet {
115
+ /** Per-model experiment seam; null retains the oracle graph. The same
116
+ * implementation advances speculative rollback prefixes. Borrowed inputs,
117
+ * owned activation and independent state tail; no global runtime mutation. */
118
+ convolution: QwenConvolution | null = null;
110
119
  readonly inProjQkv: QuantizedLinear;
111
120
  readonly inProjZ: QuantizedLinear;
112
121
  readonly inProjB: QuantizedLinear;
@@ -146,7 +155,40 @@ export class GatedDeltaNet {
146
155
  this.normWeight = weights.tensor(`${prefix}.norm.weight`);
147
156
  }
148
157
 
149
- forward(x: MlxArray, cache: SSMCache): MlxArray {
158
+ #convolve(qkv: MlxArray, state: MlxArray, rowLengths?: readonly number[]): [MlxArray, MlxArray] {
159
+ if (this.convolution) {
160
+ const result = this.convolution(qkv, state, this.convWeight);
161
+ if (!rowLengths) return result;
162
+ using input = ops.concatAxis([state, qkv], 1);
163
+ const tail = this.#convTail(input, qkv.shape[1]!, rowLengths);
164
+ result[1].dispose();
165
+ return [result[0], tail];
166
+ }
167
+ const [, S, D] = qkv.shape as [number, number, number];
168
+ const input = ops.concatAxis([state, qkv], 1);
169
+ // MLX copy and contiguous can both alias the whole prefill buffer.
170
+ // Materialize only the tail so cache residency follows its logical size.
171
+ const tail = this.#convTail(input, S, rowLengths);
172
+ const conv = ops.conv1d(input, this.convWeight, 1, 0, 1, D);
173
+ input.dispose();
174
+ const out = compiledSilu(conv);
175
+ conv.dispose();
176
+ return [out, tail];
177
+ }
178
+
179
+ #convTail(input: MlxArray, processed: number, rowLengths?: readonly number[]): MlxArray {
180
+ const [B, , D] = input.shape as [number, number, number];
181
+ const nKeep = this.convKernel - 1;
182
+ if (rowLengths) {
183
+ using indices = ops.fromInt32(rowLengths.flatMap(length =>
184
+ Array.from({ length: nKeep }, (_, position) => length + position)), [B, nKeep, 1]);
185
+ return ops.takeAlongAxis(input, indices, 1);
186
+ }
187
+ using view = input.slice([0, processed, 0], [B, processed + nKeep, D]);
188
+ return materializeCopy(view);
189
+ }
190
+
191
+ forward(x: MlxArray, cache: SSMCache, independentRows = false, mask?: MlxArray | null): MlxArray {
150
192
  const [B, S] = x.shape as [number, number, number];
151
193
  const convDim = this.keyDim * 2 + this.valueDim;
152
194
  const nKeep = this.convKernel - 1;
@@ -168,38 +210,34 @@ export class GatedDeltaNet {
168
210
  }
169
211
 
170
212
  const t0 = prof ? performance.now() : 0;
171
- const qkv = this.inProjQkv.forward(x); // [B,S,convDim]
172
- let z = this.inProjZ.forward(x);
213
+ let qkv = this.inProjQkv.forward(x, independentRows); // [B,S,convDim]
214
+ let z = this.inProjZ.forward(x, independentRows);
173
215
  z = disposing(z, ops.reshape(z, [B, S, this.numVHeads, this.headVDim]));
174
- const b = this.inProjB.forward(x); // [B,S,numVHeads]
175
- const a = this.inProjA.forward(x);
216
+ const b = this.inProjB.forward(x, independentRows); // [B,S,numVHeads]
217
+ const a = this.inProjA.forward(x, independentRows);
176
218
  if (prof) { ops.evalAll([qkv, z, b, a]); prof.proj = (prof.proj ?? 0) + performance.now() - t0; }
177
219
  const tc = prof ? performance.now() : 0; // [B,S,numVHeads]
178
220
 
179
- // Causal depthwise conv with the conv-state prefix (B=1: no ssm mask).
221
+ // The model shares one padding mask across recurrent layers. Direct layer
222
+ // callers can request the same cache-owned mask without a model wrapper.
223
+ using localMask = mask === undefined ? cache.prefillPadding?.makeMask(S) ?? null : null;
224
+ const ssmMask = mask ?? localMask;
225
+ if (ssmMask) {
226
+ using expanded = ops.reshape(ssmMask, [B, S, 1]);
227
+ using zero = MlxArray.fromBytesCopy(new Uint8Array(qkv.dtype === Dtype.float32 ? 4 : 2), [], qkv.dtype);
228
+ qkv = disposing(qkv, ops.where(expanded, qkv, zero));
229
+ }
230
+ // Convolution keeps each row's last real-token tail, including empty rows.
180
231
  const convState =
181
232
  cache.conv ?? ops.zeros([B, nKeep, convDim], x.dtype);
182
- const convInput = ops.concatAxis([convState, qkv], 1); // [B,S+nKeep,convDim]
233
+ const [convOut, newConv] = this.#convolve(qkv, convState, cache.prefillPadding?.convolutionLengths(S));
183
234
  if (!cache.conv) convState.dispose();
184
235
  if (spec) spec.qkv = qkv;
185
236
  else qkv.dispose();
186
- // New conv state = last nKeep rows. MUST be a TRUE copy (mx.copy):
187
- // this tail slice is already row-contiguous at B=1, so ops.contiguous
188
- // is a no-op VIEW sharing convInput's buffer — caching it pinned the
189
- // whole [B, S+nKeep, convDim] chunk buffer (~42 MB/layer/chunk, 48
190
- // layers = the measured 2.0 GB-per-2048-chunk prefill leak; 46 GB
191
- // active at 32k, async-GPU-OOM on 24 GB — M4 2026-08-20).
192
- const newConvView = convInput.slice([0, S, 0], [B, S + nKeep, convDim]);
193
- const newConv = ops.copyOf(newConvView);
194
- newConvView.dispose();
195
237
  if (spec) spec.prevConv = cache.conv;
196
238
  else cache.conv?.dispose();
197
239
  cache.conv = newConv;
198
240
 
199
- const conv = ops.conv1d(convInput, this.convWeight, 1, 0, 1, convDim);
200
- convInput.dispose();
201
- const convOut = compiledSilu(conv); // nn.silu (mx.compile), matches mlx-lm
202
- conv.dispose();
203
241
  if (prof) { ops.evalAll([convOut]); prof.conv = (prof.conv ?? 0) + performance.now() - tc; }
204
242
  const tn = prof ? performance.now() : 0;
205
243
 
@@ -223,7 +261,7 @@ export class GatedDeltaNet {
223
261
  const tk = prof ? performance.now() : 0;
224
262
 
225
263
  const [out, newState] = gatedDeltaUpdate(
226
- q, k, v, a, b, this.aLog, this.dtBias, cache.recurrent,
264
+ q, k, v, a, b, this.aLog, this.dtBias, cache.recurrent, ssmMask,
227
265
  );
228
266
  if (prof) { ops.evalAll([out]); prof.kernel = (prof.kernel ?? 0) + performance.now() - tk; }
229
267
  const to = prof ? performance.now() : 0;
@@ -248,7 +286,7 @@ export class GatedDeltaNet {
248
286
  z.dispose();
249
287
  const merged = ops.reshape(gated, [B, S, this.valueDim]);
250
288
  gated.dispose();
251
- const result = this.outProj.forward(merged);
289
+ const result = this.outProj.forward(merged, independentRows);
252
290
  merged.dispose();
253
291
  if (prof) { ops.evalAll([result]); prof.out = (prof.out ?? 0) + performance.now() - to; }
254
292
  return result;
@@ -262,66 +300,69 @@ export class GatedDeltaNet {
262
300
  * recurrent state is BIT-EXACTLY what a forward over only the accepted
263
301
  * prefix would have produced. Output projections (z / out_proj) are state-
264
302
  * free and skipped: only the states matter here. */
265
- #replaySpecPrefix(cache: SSMCache, keep: number): void {
303
+ #replaySpecPrefix(cache: SSMCache, keep: number | readonly number[]): void {
266
304
  const r = cache.specRound;
267
- if (!r || !r.qkv || !r.a || !r.b) throw new Error("GatedDeltaNet replay without recorded round inputs");
305
+ if (!r || !r.qkv || !r.a || !r.b)
306
+ throw new Error("GatedDeltaNet replay without recorded round inputs");
268
307
  const B = r.qkv.shape[0]!;
308
+ const count = typeof keep === "number" ? keep : Math.max(...keep);
309
+ const rowLengths = typeof keep === "number" ? undefined : keep;
269
310
  const convDim = this.keyDim * 2 + this.valueDim;
270
311
  const nKeep = this.convKernel - 1;
271
312
 
272
- const qkvPfxView = r.qkv.slice([0, 0, 0], [B, keep, convDim]);
313
+ const qkvPfxView = r.qkv.slice([0, 0, 0], [B, count, convDim]);
273
314
  const qkvPfx = ops.contiguous(qkvPfxView);
274
315
  qkvPfxView.dispose();
275
316
  const convState = cache.conv ?? ops.zeros([B, nKeep, convDim], qkvPfx.dtype);
276
- const convInput = ops.concatAxis([convState, qkvPfx], 1);
317
+ const [convOut, newConv] = this.#convolve(qkvPfx, convState, rowLengths);
277
318
  if (!cache.conv) convState.dispose();
278
319
  qkvPfx.dispose();
279
- // TRUE copy, same reason as forward(): the contiguous tail slice would
280
- // otherwise be a view pinning the whole replay convInput buffer.
281
- const rConvView = convInput.slice([0, keep, 0], [B, keep + nKeep, convDim]);
282
- const newConv = ops.copyOf(rConvView);
283
- rConvView.dispose();
284
320
  cache.conv?.dispose();
285
321
  cache.conv = newConv;
286
322
 
287
- const conv = ops.conv1d(convInput, this.convWeight, 1, 0, 1, convDim);
288
- convInput.dispose();
289
- const convOut = compiledSilu(conv);
290
- conv.dispose();
291
323
  const [qFlat, kFlat, vFlat] = ops.split(
292
324
  convOut, [this.keyDim, 2 * this.keyDim], -1,
293
325
  ) as [MlxArray, MlxArray, MlxArray];
294
326
  convOut.dispose();
295
- let q = ops.reshape(qFlat, [B, keep, this.numKHeads, this.headKDim]);
327
+ let q = rowLengths ? null : ops.reshape(qFlat, [B, count, this.numKHeads, this.headKDim]);
296
328
  qFlat.dispose();
297
- let k = ops.reshape(kFlat, [B, keep, this.numKHeads, this.headKDim]);
329
+ let k = ops.reshape(kFlat, [B, count, this.numKHeads, this.headKDim]);
298
330
  kFlat.dispose();
299
- const v = disposing(vFlat, ops.reshape(vFlat, [B, keep, this.numVHeads, this.headVDim]));
331
+ const v = disposing(vFlat, ops.reshape(vFlat, [B, count, this.numVHeads, this.headVDim]));
300
332
  const invScale = Math.pow(this.headKDim, -0.5);
301
- q = disposing(q, ops.rmsNorm(q, null, 1e-6));
302
- q = disposing(q, ops.mulScalar(q, invScale * invScale));
333
+ if (q) {
334
+ q = disposing(q, ops.rmsNorm(q, null, 1e-6));
335
+ q = disposing(q, ops.mulScalar(q, invScale * invScale));
336
+ }
303
337
  k = disposing(k, ops.rmsNorm(k, null, 1e-6));
304
338
  k = disposing(k, ops.mulScalar(k, invScale));
305
339
 
306
- const aPfxView = r.a.slice([0, 0, 0], [B, keep, this.numVHeads]);
340
+ const aPfxView = r.a.slice([0, 0, 0], [B, count, this.numVHeads]);
307
341
  const aPfx = ops.contiguous(aPfxView);
308
342
  aPfxView.dispose();
309
- const bPfxView = r.b.slice([0, 0, 0], [B, keep, this.numVHeads]);
343
+ const bPfxView = r.b.slice([0, 0, 0], [B, count, this.numVHeads]);
310
344
  const bPfx = ops.contiguous(bPfxView);
311
345
  bPfxView.dispose();
312
346
 
313
- const [y, newState] = gatedDeltaUpdate(
314
- q, k, v, aPfx, bPfx, this.aLog, this.dtBias, cache.recurrent,
315
- );
316
- q.dispose();
347
+ let newState: MlxArray;
348
+ if (rowLengths) {
349
+ // Unequal prefixes need a row-length-aware recurrence. The state-only
350
+ // kernel omits query/output work. Uniform replay retains its measured
351
+ // kernel until the separate optimization demonstrates a serving win.
352
+ newState = gatedDeltaState(k, v, aPfx, bPfx, this.aLog, this.dtBias, cache.recurrent, rowLengths);
353
+ } else {
354
+ const [output, state] = gatedDeltaUpdate(q!, k, v, aPfx, bPfx, this.aLog, this.dtBias, cache.recurrent);
355
+ output.dispose(); newState = state;
356
+ }
357
+ q?.dispose();
317
358
  k.dispose();
318
359
  v.dispose();
319
360
  aPfx.dispose();
320
361
  bPfx.dispose();
321
- y.dispose(); // only the state advance matters on the rollback path
322
362
  cache.recurrent?.dispose();
323
363
  cache.recurrent = newState;
324
- cache.advance(keep);
364
+ if (typeof keep === "number") cache.advance(keep);
365
+ else cache.advanceRows(keep);
325
366
  }
326
367
 
327
368
  private rmsNormGated(hidden: MlxArray, gate: MlxArray): MlxArray {
@@ -365,18 +406,18 @@ export class Qwen3Attention {
365
406
  this.kNorm = new RMSNorm(weights.tensor(`${prefix}.k_norm.weight`), t.rmsNormEps);
366
407
  }
367
408
 
368
- forward(x: MlxArray, mask: Mask, cache: Cache): MlxArray {
409
+ forward(x: MlxArray, mask: Mask, cache: Cache, independentRows = false): MlxArray {
369
410
  const [B, L] = x.shape as [number, number, number];
370
411
 
371
412
  // q_proj emits 2× head_dim per head → split into queries + gate.
372
- const qp = this.qProj.forward(x);
413
+ const qp = this.qProj.forward(x, independentRows);
373
414
  const qpr = disposing(qp, ops.reshape(qp, [B, L, this.nHeads, this.headDim * 2]));
374
415
  const [qHeads, gateHeads] = ops.split(qpr, [this.headDim], -1) as [MlxArray, MlxArray];
375
416
  qpr.dispose();
376
417
  const gate = disposing(gateHeads, ops.reshape(gateHeads, [B, L, this.nHeads * this.headDim]));
377
418
 
378
- let k = this.kProj.forward(x);
379
- let v = this.vProj.forward(x);
419
+ let k = this.kProj.forward(x, independentRows);
420
+ let v = this.vProj.forward(x, independentRows);
380
421
 
381
422
  // q/k norm over head_dim BEFORE transpose (reference order).
382
423
  let q = this.qNorm.forward(qHeads);
@@ -391,7 +432,7 @@ export class Qwen3Attention {
391
432
  // Batched decode: the scheduler's mask wrapper exposes each row's REAL
392
433
  // position as ropeOffsetArr (rows have different prompt lengths); the
393
434
  // dynamic-offset kernel is the same fast::rope, bit-exact vs the static
394
- // form (tests/compile.test.ts). Serial lane: scalar offset, unchanged.
435
+ // form (tests/unit/compile.test.ts). Serial lane: scalar offset, unchanged.
395
436
  // Vision requests (serial lane only) install activeMrope for the current
396
437
  // forward: 3D interleaved positions via the manual apply — the reference
397
438
  // never uses the fused fast-rope kernel when position_ids are supplied,
@@ -411,11 +452,16 @@ export class Qwen3Attention {
411
452
  }
412
453
 
413
454
  let attn: MlxArray;
414
- if (cache instanceof QuantizedKVCache) {
415
- const [keys, values] = cache.updateAndFetchQuantized(k, v);
455
+ const quantized = cache.quantizedAttention;
456
+ if (cache.attentionState) {
457
+ const view = cache.attentionState.appendAndFetch(k, v);
458
+ k.dispose(); v.dispose();
459
+ try { attn = view.attend(q, this.scale, mask); } finally { view.dispose(); }
460
+ } else if (quantized) {
461
+ const [keys, values] = quantized.updateAndFetchQuantized(k, v);
416
462
  k.dispose();
417
463
  v.dispose();
418
- attn = quantizedSdpa(q, keys, values, this.scale, mask, cache.groupSize, cache.bits);
464
+ attn = quantizedSdpa(q, keys, values, this.scale, mask, quantized.groupSize, quantized.bits);
419
465
  disposeTriple(keys);
420
466
  disposeTriple(values);
421
467
  } else {
@@ -439,32 +485,56 @@ export class Qwen3Attention {
439
485
  const gated = ops.mul(merged, sig);
440
486
  merged.dispose();
441
487
  sig.dispose();
442
- const out = this.oProj.forward(gated);
488
+ const out = this.oProj.forward(gated, independentRows);
443
489
  gated.dispose();
444
490
  return out;
445
491
  }
446
492
  }
447
493
 
494
+ /** MLP projections are affine-quantized (QuantizedLinear) or, in a Q2b
495
+ * packed-trellis artifact, TrellisLinear — chosen per module by the config's
496
+ * quantization entry (`mode: "trellis"`). */
497
+ export type MlpLinear = QuantizedLinear | TrellisLinear;
498
+ function loadMlpLinear(weights: Weights, path: string, config: ModelConfig): MlpLinear {
499
+ if (!TrellisLinear.isTrellis(config, path)) return QuantizedLinear.load(weights, path, config);
500
+ // M4 Pro measurements support the 27B down projection at M3/4. The
501
+ // operation also checks variant, dtype and packed-code layout eligibility.
502
+ const useSharedScatterCodebook = config.text.hiddenSize === 5120 &&
503
+ config.text.intermediateSize === 17408 && path.endsWith(".down_proj");
504
+ return TrellisLinear.load(weights, path, config, useSharedScatterCodebook);
505
+ }
506
+
448
507
  export class Qwen3MLP {
449
- readonly gate: QuantizedLinear;
450
- readonly up: QuantizedLinear;
451
- readonly down: QuantizedLinear;
508
+ readonly gate: MlpLinear;
509
+ readonly up: MlpLinear;
510
+ readonly down: MlpLinear;
452
511
 
453
512
  constructor(weights: Weights, config: ModelConfig, prefix: string) {
454
- this.gate = QuantizedLinear.load(weights, `${prefix}.gate_proj`, config);
455
- this.up = QuantizedLinear.load(weights, `${prefix}.up_proj`, config);
456
- this.down = QuantizedLinear.load(weights, `${prefix}.down_proj`, config);
513
+ this.gate = loadMlpLinear(weights, `${prefix}.gate_proj`, config);
514
+ this.up = loadMlpLinear(weights, `${prefix}.up_proj`, config);
515
+ this.down = loadMlpLinear(weights, `${prefix}.down_proj`, config);
457
516
  }
458
517
 
459
- forward(x: MlxArray): MlxArray {
460
- const g = this.gate.forward(x);
461
- const u = this.up.forward(x);
518
+ forward(x: MlxArray, inputRowContiguous = false, independentRows = false): MlxArray {
519
+ // Packed-trellis decode (M ≤ 4): gate, up and the swiglu in ONE kernel —
520
+ // x read once, no gate/up vectors materialized. This Lab path retains
521
+ // the packed activation arithmetic documented in turboquant.md.
522
+ if (this.gate instanceof TrellisLinear && this.up instanceof TrellisLinear &&
523
+ fusedGateUpEligible(this.gate, this.up) &&
524
+ x.shape.slice(0, -1).reduce((a, b) => a * b, 1) <= TRELLIS_MATVEC_MAX_M) {
525
+ const hidden = fusedGateUpSwiglu(x, this.gate, this.up);
526
+ const out = this.down instanceof QuantizedLinear ? this.down.forward(hidden, independentRows) : this.down.forward(hidden);
527
+ hidden.dispose();
528
+ return out;
529
+ }
530
+ const g = this.gate instanceof TrellisLinear ? this.gate.forward(x, inputRowContiguous) : this.gate.forward(x, independentRows);
531
+ const u = this.up instanceof TrellisLinear ? this.up.forward(x, inputRowContiguous) : this.up.forward(x, independentRows);
462
532
  // Oracle: down_proj(swiglu(gate_proj(x), up_proj(x))) — swiglu ALWAYS compiled
463
533
  // (mlx-lm's @mx.compile swiglu; no unfused silu+mul, matches its kernel set).
464
534
  const hidden = compiledSwiglu(g, u);
465
535
  g.dispose();
466
536
  u.dispose();
467
- const out = this.down.forward(hidden);
537
+ const out = this.down instanceof QuantizedLinear ? this.down.forward(hidden, independentRows) : this.down.forward(hidden);
468
538
  hidden.dispose();
469
539
  return out;
470
540
  }
@@ -489,16 +559,18 @@ export class Qwen3Layer {
489
559
  this.postAttnNorm = new RMSNorm(weights.tensor(`${prefix}.post_attention_layernorm.weight`), config.text.rmsNormEps);
490
560
  }
491
561
 
492
- forward(x: MlxArray, faMask: Mask, cache: Cache): MlxArray {
562
+ forward(x: MlxArray, faMask: Mask, cache: Cache, independentRows = false, ssmMask?: MlxArray | null): MlxArray {
493
563
  const xn = this.inputNorm.forward(x);
494
564
  const r = this.isLinear
495
- ? this.linearAttn!.forward(xn, cache as SSMCache)
496
- : this.selfAttn!.forward(xn, faMask, cache);
565
+ ? this.linearAttn!.forward(xn, cache as SSMCache, independentRows, ssmMask)
566
+ : this.selfAttn!.forward(xn, faMask, cache, independentRows);
497
567
  xn.dispose();
498
568
  const h = ops.add(x, r);
499
569
  r.dispose();
500
570
  const hn = this.postAttnNorm.forward(h);
501
- const m = this.mlp.forward(hn);
571
+ // RMSNorm allocates an aligned row-contiguous output. Pass that layout
572
+ // proof so Trellis can match native small-prefill matmul without an eval.
573
+ const m = this.mlp.forward(hn, true, independentRows);
502
574
  hn.dispose();
503
575
  const out = ops.add(h, m);
504
576
  h.dispose();
@@ -522,6 +594,14 @@ export class Qwen35Model {
522
594
  readonly faIdx: number;
523
595
 
524
596
  constructor(weights: Weights, config: ModelConfig) {
597
+ // Checkpoint-generation normalization BEFORE the first tensor() call: the
598
+ // 5.8-family export names the trunk `model.language_model.*` with a
599
+ // top-level `lm_head`, ships `mtp.*` in-repo, and stores RMSNorm gains as
600
+ // γ−1 with HF-layout conv1d. The view maps all of that onto the names and
601
+ // values this graph reads (mlx-lm's post-sanitize space). No-op — nothing
602
+ // installed at all — for artifacts already in that space.
603
+ const view = qwen35WeightsView(weights);
604
+ if (view) weights.setView(view);
525
605
  this.config = config;
526
606
  this.tied = config.text.tieWordEmbeddings;
527
607
  this.weightsBytes = [...weights.shards.files.values()]
@@ -555,9 +635,10 @@ export class Qwen35Model {
555
635
  out.set(`${p}.linear_attn.in_proj_a`, l.linearAttn.inProjA);
556
636
  out.set(`${p}.linear_attn.out_proj`, l.linearAttn.outProj);
557
637
  }
558
- out.set(`${p}.mlp.gate_proj`, l.mlp.gate);
559
- out.set(`${p}.mlp.up_proj`, l.mlp.up);
560
- out.set(`${p}.mlp.down_proj`, l.mlp.down);
638
+ // Trellis-coded MLP tensors are not LoRA targets (no adapter seam yet).
639
+ if (l.mlp.gate instanceof QuantizedLinear) out.set(`${p}.mlp.gate_proj`, l.mlp.gate);
640
+ if (l.mlp.up instanceof QuantizedLinear) out.set(`${p}.mlp.up_proj`, l.mlp.up);
641
+ if (l.mlp.down instanceof QuantizedLinear) out.set(`${p}.mlp.down_proj`, l.mlp.down);
561
642
  }
562
643
  return out;
563
644
  }
@@ -566,6 +647,38 @@ export class Qwen35Model {
566
647
  return this.layers.map((l) => (l.isLinear ? new SSMCache() : new KVCache()));
567
648
  }
568
649
 
650
+ /** The 27B append path keeps the native M1 affine arithmetic while Trellis
651
+ * shares up to four rows. Other shapes retain single-token execution. */
652
+ createAppend(policy: { hasAdapters: boolean; pagedKv: boolean }) {
653
+ const t = this.config.text;
654
+ if (policy.hasAdapters || policy.pagedKv || this.loraState.active.length || this.mrope ||
655
+ t.hiddenSize !== 5120 || t.intermediateSize !== 17408 || this.layers.length !== 64 ||
656
+ t.headDim !== 256 || t.numAttentionHeads !== 24 || t.numKeyValueHeads !== 4)
657
+ return null;
658
+ if (deviceArchitecture() !== "applegpu_g16s") return null;
659
+ const qualified = (linear: QuantizedLinear | TrellisLinear) => {
660
+ if (linear instanceof TrellisLinear) return true;
661
+ const { mode, bits, groupSize } = linear.spec;
662
+ return mode === "affine" && [2, 3, 4, 6, 8].includes(bits) && [32, 64, 128].includes(groupSize);
663
+ };
664
+ for (const layer of this.layers) {
665
+ const a = layer.linearAttn, s = layer.selfAttn;
666
+ const attention = a ? [a.inProjQkv, a.inProjZ, a.inProjB, a.inProjA, a.outProj]
667
+ : [s!.qProj, s!.kProj, s!.vProj, s!.oProj];
668
+ if (!attention.every(qualified) || ![layer.mlp.gate, layer.mlp.up, layer.mlp.down].every(qualified))
669
+ return null;
670
+ }
671
+ return {
672
+ maxChunkSize: (state: readonly Cache[]) => qwenAppendChunkSize(state[0]!.offset),
673
+ forwardHidden: (ids: MlxArray, cache: Cache[]): MlxArray => {
674
+ if (ids.shape.length !== 2 || ids.shape[0] !== 1 || ids.shape[1]! > 4)
675
+ throw new Error("Qwen committed-token append supports one row and at most four positions");
676
+ const hidden = this.embed.encode(ids);
677
+ return this.forwardLayers(hidden, cache, true);
678
+ },
679
+ };
680
+ }
681
+
569
682
  forwardHidden(ids: MlxArray, cache: Cache[]): MlxArray {
570
683
  const h = this.embed.encode(ids);
571
684
  return this.forwardLayers(h, cache);
@@ -609,11 +722,12 @@ export class Qwen35Model {
609
722
  tap.captured.set(i, copy);
610
723
  }
611
724
 
612
- protected forwardLayers(h0: MlxArray, cache: Cache[]): MlxArray {
725
+ protected forwardLayers(h0: MlxArray, cache: Cache[], independentRows = false): MlxArray {
613
726
  const L = h0.shape[1]!;
614
727
  // One full-attention mask shared by all full layers (same offset); linear
615
728
  // layers see no ssm mask at B=1.
616
729
  const faMask = cache[this.faIdx]!.makeMask(L, null);
730
+ using ssmMask = (cache[0] as SSMCache).prefillPadding?.makeMask(L) ?? null;
617
731
  // Vision requests: one interleaved-mRoPE cos/sin table per forward,
618
732
  // shared by all 12 full-attention layers (positions are layer-invariant).
619
733
  let mropeFwd: ReturnType<typeof buildMropePositions> | null = null;
@@ -634,9 +748,19 @@ export class Qwen35Model {
634
748
  try {
635
749
  for (let i = 0; i < this.layers.length; i++) {
636
750
  const tl = prof ? performance.now() : 0;
637
- const next = this.layers[i]!.forward(h, faMask, cache[i]!);
751
+ const next = this.layers[i]!.forward(h, faMask, cache[i]!, independentRows, ssmMask);
638
752
  h.dispose();
639
753
  h = next;
754
+ if (L > TRELLIS_MATVEC_MAX_M) {
755
+ // Affine prefills also need bounded evaluation: a retained prefix
756
+ // history can leave too little room for the entire deferred graph.
757
+ // Materialize the cache outputs too. In particular, the tiny
758
+ // copied conv tail is not a dependency of `h`; leaving it lazy
759
+ // pins the whole prefill conv buffer until the chunk ends.
760
+ const state = cache[i]!.state();
761
+ try { ops.evalAll([h, ...state]); }
762
+ finally { if (cache[i]!.stateNeedsDispose) for (const a of state) a.dispose(); }
763
+ }
640
764
  if (prof) {
641
765
  ops.evalAll([h]);
642
766
  const key = this.layers[i]!.isLinear ? "layerLin" : "layerFull";