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
@@ -12,9 +12,11 @@
12
12
  import type { ModelConfig } from "../config";
13
13
  import type { Weights } from "../weights";
14
14
  import { MlxArray } from "../mlx/array";
15
- import { Dtype } from "../mlx/ffi";
15
+ import { Dtype, deviceArchitecture } from "../mlx/ffi";
16
16
  import * as ops from "../mlx/ops";
17
17
  import { CompiledFunction } from "../mlx/compile";
18
+ import { qwenAppendChunkSize } from "./qwen-append";
19
+ import { TrellisLinear, fusedGateUpEligible, fusedGateUpSwiglu, TRELLIS_MATVEC_MAX_M } from "./trellis-linear";
18
20
  import {
19
21
  argmaxLastPosition,
20
22
  disposeTriple,
@@ -29,7 +31,14 @@ 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";
38
+ import {
39
+ activeMrope, applyInterleavedRope, buildMropePositions, mropeInvFreq,
40
+ setActiveMrope, type MropeRequestState,
41
+ } from "./qwen3-mrope";
33
42
 
34
43
  const PREFIX = "language_model";
35
44
 
@@ -103,6 +112,10 @@ export function compiledSilu(x: MlxArray): MlxArray {
103
112
 
104
113
  /** Gated-DeltaNet linear-attention layer (mlx-lm GatedDeltaNet). */
105
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;
106
119
  readonly inProjQkv: QuantizedLinear;
107
120
  readonly inProjZ: QuantizedLinear;
108
121
  readonly inProjB: QuantizedLinear;
@@ -142,34 +155,66 @@ export class GatedDeltaNet {
142
155
  this.normWeight = weights.tensor(`${prefix}.norm.weight`);
143
156
  }
144
157
 
145
- forward(x: MlxArray, cache: SSMCache): MlxArray {
158
+ #convolve(qkv: MlxArray, state: MlxArray): [MlxArray, MlxArray] {
159
+ if (this.convolution) return this.convolution(qkv, state, this.convWeight);
160
+ const [B, S, D] = qkv.shape as [number, number, number];
161
+ const nKeep = this.convKernel - 1;
162
+ const input = ops.concatAxis([state, qkv], 1);
163
+ // MLX copy and contiguous can both alias the whole prefill buffer.
164
+ // Materialize only the tail so cache residency follows its logical size.
165
+ const view = input.slice([0, S, 0], [B, S + nKeep, D]);
166
+ const tail = materializeCopy(view);
167
+ view.dispose();
168
+ const conv = ops.conv1d(input, this.convWeight, 1, 0, 1, D);
169
+ input.dispose();
170
+ const out = compiledSilu(conv);
171
+ conv.dispose();
172
+ return [out, tail];
173
+ }
174
+
175
+ forward(x: MlxArray, cache: SSMCache, independentRows = false): MlxArray {
146
176
  const [B, S] = x.shape as [number, number, number];
147
177
  const convDim = this.keyDim * 2 + this.valueDim;
148
178
  const nKeep = this.convKernel - 1;
179
+ const prof = (globalThis as Record<string, unknown>).__deltaProf as
180
+ Record<string, number>
181
+ | undefined;
182
+
183
+ // Armed speculative verify round (serve loop, serial lane): this forward
184
+ // must be rewindable. Snapshot = hand the REPLACED state slots to the
185
+ // round instead of disposing (free — arrays are immutable); record the
186
+ // position-local kernel inputs (qkv/a/b) and install the prefix replay.
187
+ const spec = cache.specRound;
188
+ if (spec) {
189
+ if (!spec.armed)
190
+ throw new Error("GatedDeltaNet: spec round already recorded this round");
191
+ spec.armed = false;
192
+ spec.S = S;
193
+ spec.replay = (c, keep) => this.#replaySpecPrefix(c, keep);
194
+ }
149
195
 
150
- const qkv = this.inProjQkv.forward(x); // [B,S,convDim]
151
- let z = this.inProjZ.forward(x);
196
+ const t0 = prof ? performance.now() : 0;
197
+ const qkv = this.inProjQkv.forward(x, independentRows); // [B,S,convDim]
198
+ let z = this.inProjZ.forward(x, independentRows);
152
199
  z = disposing(z, ops.reshape(z, [B, S, this.numVHeads, this.headVDim]));
153
- const b = this.inProjB.forward(x); // [B,S,numVHeads]
154
- const a = this.inProjA.forward(x); // [B,S,numVHeads]
200
+ const b = this.inProjB.forward(x, independentRows); // [B,S,numVHeads]
201
+ const a = this.inProjA.forward(x, independentRows);
202
+ if (prof) { ops.evalAll([qkv, z, b, a]); prof.proj = (prof.proj ?? 0) + performance.now() - t0; }
203
+ const tc = prof ? performance.now() : 0; // [B,S,numVHeads]
155
204
 
156
205
  // Causal depthwise conv with the conv-state prefix (B=1: no ssm mask).
157
206
  const convState =
158
207
  cache.conv ?? ops.zeros([B, nKeep, convDim], x.dtype);
159
- const convInput = ops.concatAxis([convState, qkv], 1); // [B,S+nKeep,convDim]
208
+ const [convOut, newConv] = this.#convolve(qkv, convState);
160
209
  if (!cache.conv) convState.dispose();
161
- qkv.dispose();
162
- // New conv state = last nKeep rows (contiguous, the array is sliced).
163
- const newConv = ops.contiguous(
164
- convInput.slice([0, S, 0], [B, S + nKeep, convDim]),
165
- );
166
- cache.conv?.dispose();
210
+ if (spec) spec.qkv = qkv;
211
+ else qkv.dispose();
212
+ if (spec) spec.prevConv = cache.conv;
213
+ else cache.conv?.dispose();
167
214
  cache.conv = newConv;
168
215
 
169
- const conv = ops.conv1d(convInput, this.convWeight, 1, 0, 1, convDim);
170
- convInput.dispose();
171
- const convOut = compiledSilu(conv); // nn.silu (mx.compile), matches mlx-lm
172
- conv.dispose();
216
+ if (prof) { ops.evalAll([convOut]); prof.conv = (prof.conv ?? 0) + performance.now() - tc; }
217
+ const tn = prof ? performance.now() : 0;
173
218
 
174
219
  const [qFlat, kFlat, vFlat] = ops.split(
175
220
  convOut, [this.keyDim, 2 * this.keyDim], -1,
@@ -187,16 +232,26 @@ export class GatedDeltaNet {
187
232
  q = disposing(q, ops.mulScalar(q, invScale * invScale));
188
233
  k = disposing(k, ops.rmsNorm(k, null, 1e-6));
189
234
  k = disposing(k, ops.mulScalar(k, invScale));
235
+ if (prof) { ops.evalAll([q, k]); prof.norms = (prof.norms ?? 0) + performance.now() - tn; }
236
+ const tk = prof ? performance.now() : 0;
190
237
 
191
238
  const [out, newState] = gatedDeltaUpdate(
192
239
  q, k, v, a, b, this.aLog, this.dtBias, cache.recurrent,
193
240
  );
241
+ if (prof) { ops.evalAll([out]); prof.kernel = (prof.kernel ?? 0) + performance.now() - tk; }
242
+ const to = prof ? performance.now() : 0;
194
243
  q.dispose();
195
244
  k.dispose();
196
245
  v.dispose();
197
- a.dispose();
198
- b.dispose();
199
- cache.recurrent?.dispose();
246
+ if (spec) {
247
+ spec.a = a;
248
+ spec.b = b;
249
+ spec.prevRecurrent = cache.recurrent;
250
+ } else {
251
+ a.dispose();
252
+ b.dispose();
253
+ cache.recurrent?.dispose();
254
+ }
200
255
  cache.recurrent = newState;
201
256
  cache.advance(S);
202
257
 
@@ -206,11 +261,73 @@ export class GatedDeltaNet {
206
261
  z.dispose();
207
262
  const merged = ops.reshape(gated, [B, S, this.valueDim]);
208
263
  gated.dispose();
209
- const result = this.outProj.forward(merged);
264
+ const result = this.outProj.forward(merged, independentRows);
210
265
  merged.dispose();
266
+ if (prof) { ops.evalAll([result]); prof.out = (prof.out ?? 0) + performance.now() - to; }
211
267
  return result;
212
268
  }
213
269
 
270
+ /** Speculative rollback replay: with the pre-round snapshot RESTORED onto
271
+ * `cache`, re-advance the first `keep` window tokens from the recorded
272
+ * position-local inputs. Every op mirrors forward() on the same values —
273
+ * conv windows, silu, per-position norms, and the kernel's serial prefix
274
+ * are all independent of the rejected tail — so the resulting conv +
275
+ * recurrent state is BIT-EXACTLY what a forward over only the accepted
276
+ * prefix would have produced. Output projections (z / out_proj) are state-
277
+ * free and skipped: only the states matter here. */
278
+ #replaySpecPrefix(cache: SSMCache, keep: number): void {
279
+ const r = cache.specRound;
280
+ if (!r || !r.qkv || !r.a || !r.b) throw new Error("GatedDeltaNet replay without recorded round inputs");
281
+ const B = r.qkv.shape[0]!;
282
+ const convDim = this.keyDim * 2 + this.valueDim;
283
+ const nKeep = this.convKernel - 1;
284
+
285
+ const qkvPfxView = r.qkv.slice([0, 0, 0], [B, keep, convDim]);
286
+ const qkvPfx = ops.contiguous(qkvPfxView);
287
+ qkvPfxView.dispose();
288
+ const convState = cache.conv ?? ops.zeros([B, nKeep, convDim], qkvPfx.dtype);
289
+ const [convOut, newConv] = this.#convolve(qkvPfx, convState);
290
+ if (!cache.conv) convState.dispose();
291
+ qkvPfx.dispose();
292
+ cache.conv?.dispose();
293
+ cache.conv = newConv;
294
+
295
+ const [qFlat, kFlat, vFlat] = ops.split(
296
+ convOut, [this.keyDim, 2 * this.keyDim], -1,
297
+ ) as [MlxArray, MlxArray, MlxArray];
298
+ convOut.dispose();
299
+ let q = ops.reshape(qFlat, [B, keep, this.numKHeads, this.headKDim]);
300
+ qFlat.dispose();
301
+ let k = ops.reshape(kFlat, [B, keep, this.numKHeads, this.headKDim]);
302
+ kFlat.dispose();
303
+ const v = disposing(vFlat, ops.reshape(vFlat, [B, keep, this.numVHeads, this.headVDim]));
304
+ const invScale = Math.pow(this.headKDim, -0.5);
305
+ q = disposing(q, ops.rmsNorm(q, null, 1e-6));
306
+ q = disposing(q, ops.mulScalar(q, invScale * invScale));
307
+ k = disposing(k, ops.rmsNorm(k, null, 1e-6));
308
+ k = disposing(k, ops.mulScalar(k, invScale));
309
+
310
+ const aPfxView = r.a.slice([0, 0, 0], [B, keep, this.numVHeads]);
311
+ const aPfx = ops.contiguous(aPfxView);
312
+ aPfxView.dispose();
313
+ const bPfxView = r.b.slice([0, 0, 0], [B, keep, this.numVHeads]);
314
+ const bPfx = ops.contiguous(bPfxView);
315
+ bPfxView.dispose();
316
+
317
+ const [y, newState] = gatedDeltaUpdate(
318
+ q, k, v, aPfx, bPfx, this.aLog, this.dtBias, cache.recurrent,
319
+ );
320
+ q.dispose();
321
+ k.dispose();
322
+ v.dispose();
323
+ aPfx.dispose();
324
+ bPfx.dispose();
325
+ y.dispose(); // only the state advance matters on the rollback path
326
+ cache.recurrent?.dispose();
327
+ cache.recurrent = newState;
328
+ cache.advance(keep);
329
+ }
330
+
214
331
  private rmsNormGated(hidden: MlxArray, gate: MlxArray): MlxArray {
215
332
  const xn = ops.rmsNorm(hidden, this.normWeight, this.eps); // bf16
216
333
  // Oracle: _precise_swiglu(hidden, gate, xn) — ALWAYS one @mx.compile kernel
@@ -252,18 +369,18 @@ export class Qwen3Attention {
252
369
  this.kNorm = new RMSNorm(weights.tensor(`${prefix}.k_norm.weight`), t.rmsNormEps);
253
370
  }
254
371
 
255
- forward(x: MlxArray, mask: Mask, cache: Cache): MlxArray {
372
+ forward(x: MlxArray, mask: Mask, cache: Cache, independentRows = false): MlxArray {
256
373
  const [B, L] = x.shape as [number, number, number];
257
374
 
258
375
  // q_proj emits 2× head_dim per head → split into queries + gate.
259
- const qp = this.qProj.forward(x);
376
+ const qp = this.qProj.forward(x, independentRows);
260
377
  const qpr = disposing(qp, ops.reshape(qp, [B, L, this.nHeads, this.headDim * 2]));
261
378
  const [qHeads, gateHeads] = ops.split(qpr, [this.headDim], -1) as [MlxArray, MlxArray];
262
379
  qpr.dispose();
263
380
  const gate = disposing(gateHeads, ops.reshape(gateHeads, [B, L, this.nHeads * this.headDim]));
264
381
 
265
- let k = this.kProj.forward(x);
266
- let v = this.vProj.forward(x);
382
+ let k = this.kProj.forward(x, independentRows);
383
+ let v = this.vProj.forward(x, independentRows);
267
384
 
268
385
  // q/k norm over head_dim BEFORE transpose (reference order).
269
386
  let q = this.qNorm.forward(qHeads);
@@ -278,14 +395,24 @@ export class Qwen3Attention {
278
395
  // Batched decode: the scheduler's mask wrapper exposes each row's REAL
279
396
  // position as ropeOffsetArr (rows have different prompt lengths); the
280
397
  // dynamic-offset kernel is the same fast::rope, bit-exact vs the static
281
- // form (tests/compile.test.ts). Serial lane: scalar offset, unchanged.
398
+ // form (tests/unit/compile.test.ts). Serial lane: scalar offset, unchanged.
399
+ // Vision requests (serial lane only) install activeMrope for the current
400
+ // forward: 3D interleaved positions via the manual apply — the reference
401
+ // never uses the fused fast-rope kernel when position_ids are supplied,
402
+ // so this IS the oracle's own arithmetic. Text-only stays on ops.rope.
403
+ const mr = activeMrope;
282
404
  const offArr = (cache as { ropeOffsetArr?: MlxArray }).ropeOffsetArr;
283
- q = disposing(q, offArr
284
- ? ops.ropeDynamic(q, this.ropeDims, this.ropeBase, offArr, null)
285
- : ops.rope(q, this.ropeDims, this.ropeBase, cache.offset, null));
286
- k = disposing(k, offArr
287
- ? ops.ropeDynamic(k, this.ropeDims, this.ropeBase, offArr, null)
288
- : ops.rope(k, this.ropeDims, this.ropeBase, cache.offset, null));
405
+ if (mr) {
406
+ q = disposing(q, applyInterleavedRope(q, mr));
407
+ k = disposing(k, applyInterleavedRope(k, mr));
408
+ } else {
409
+ q = disposing(q, offArr
410
+ ? ops.ropeDynamic(q, this.ropeDims, this.ropeBase, offArr, null)
411
+ : ops.rope(q, this.ropeDims, this.ropeBase, cache.offset, null));
412
+ k = disposing(k, offArr
413
+ ? ops.ropeDynamic(k, this.ropeDims, this.ropeBase, offArr, null)
414
+ : ops.rope(k, this.ropeDims, this.ropeBase, cache.offset, null));
415
+ }
289
416
 
290
417
  let attn: MlxArray;
291
418
  if (cache instanceof QuantizedKVCache) {
@@ -316,32 +443,56 @@ export class Qwen3Attention {
316
443
  const gated = ops.mul(merged, sig);
317
444
  merged.dispose();
318
445
  sig.dispose();
319
- const out = this.oProj.forward(gated);
446
+ const out = this.oProj.forward(gated, independentRows);
320
447
  gated.dispose();
321
448
  return out;
322
449
  }
323
450
  }
324
451
 
452
+ /** MLP projections are affine-quantized (QuantizedLinear) or, in a Q2b
453
+ * packed-trellis artifact, TrellisLinear — chosen per module by the config's
454
+ * quantization entry (`mode: "trellis"`). */
455
+ export type MlpLinear = QuantizedLinear | TrellisLinear;
456
+ function loadMlpLinear(weights: Weights, path: string, config: ModelConfig): MlpLinear {
457
+ if (!TrellisLinear.isTrellis(config, path)) return QuantizedLinear.load(weights, path, config);
458
+ // M4 Pro measurements support the 27B down projection at M3/4. The
459
+ // operation also checks variant, dtype and packed-code layout eligibility.
460
+ const useSharedScatterCodebook = config.text.hiddenSize === 5120 &&
461
+ config.text.intermediateSize === 17408 && path.endsWith(".down_proj");
462
+ return TrellisLinear.load(weights, path, config, useSharedScatterCodebook);
463
+ }
464
+
325
465
  export class Qwen3MLP {
326
- readonly gate: QuantizedLinear;
327
- readonly up: QuantizedLinear;
328
- readonly down: QuantizedLinear;
466
+ readonly gate: MlpLinear;
467
+ readonly up: MlpLinear;
468
+ readonly down: MlpLinear;
329
469
 
330
470
  constructor(weights: Weights, config: ModelConfig, prefix: string) {
331
- this.gate = QuantizedLinear.load(weights, `${prefix}.gate_proj`, config);
332
- this.up = QuantizedLinear.load(weights, `${prefix}.up_proj`, config);
333
- this.down = QuantizedLinear.load(weights, `${prefix}.down_proj`, config);
471
+ this.gate = loadMlpLinear(weights, `${prefix}.gate_proj`, config);
472
+ this.up = loadMlpLinear(weights, `${prefix}.up_proj`, config);
473
+ this.down = loadMlpLinear(weights, `${prefix}.down_proj`, config);
334
474
  }
335
475
 
336
- forward(x: MlxArray): MlxArray {
337
- const g = this.gate.forward(x);
338
- const u = this.up.forward(x);
476
+ forward(x: MlxArray, inputRowContiguous = false, independentRows = false): MlxArray {
477
+ // Packed-trellis decode (M ≤ 4): gate, up and the swiglu in ONE kernel —
478
+ // x read once, no gate/up vectors materialized. This Lab path retains
479
+ // the packed activation arithmetic documented in turboquant.md.
480
+ if (this.gate instanceof TrellisLinear && this.up instanceof TrellisLinear &&
481
+ fusedGateUpEligible(this.gate, this.up) &&
482
+ x.shape.slice(0, -1).reduce((a, b) => a * b, 1) <= TRELLIS_MATVEC_MAX_M) {
483
+ const hidden = fusedGateUpSwiglu(x, this.gate, this.up);
484
+ const out = this.down instanceof QuantizedLinear ? this.down.forward(hidden, independentRows) : this.down.forward(hidden);
485
+ hidden.dispose();
486
+ return out;
487
+ }
488
+ const g = this.gate instanceof TrellisLinear ? this.gate.forward(x, inputRowContiguous) : this.gate.forward(x, independentRows);
489
+ const u = this.up instanceof TrellisLinear ? this.up.forward(x, inputRowContiguous) : this.up.forward(x, independentRows);
339
490
  // Oracle: down_proj(swiglu(gate_proj(x), up_proj(x))) — swiglu ALWAYS compiled
340
491
  // (mlx-lm's @mx.compile swiglu; no unfused silu+mul, matches its kernel set).
341
492
  const hidden = compiledSwiglu(g, u);
342
493
  g.dispose();
343
494
  u.dispose();
344
- const out = this.down.forward(hidden);
495
+ const out = this.down instanceof QuantizedLinear ? this.down.forward(hidden, independentRows) : this.down.forward(hidden);
345
496
  hidden.dispose();
346
497
  return out;
347
498
  }
@@ -366,16 +517,18 @@ export class Qwen3Layer {
366
517
  this.postAttnNorm = new RMSNorm(weights.tensor(`${prefix}.post_attention_layernorm.weight`), config.text.rmsNormEps);
367
518
  }
368
519
 
369
- forward(x: MlxArray, faMask: Mask, cache: Cache): MlxArray {
520
+ forward(x: MlxArray, faMask: Mask, cache: Cache, independentRows = false): MlxArray {
370
521
  const xn = this.inputNorm.forward(x);
371
522
  const r = this.isLinear
372
- ? this.linearAttn!.forward(xn, cache as SSMCache)
373
- : this.selfAttn!.forward(xn, faMask, cache);
523
+ ? this.linearAttn!.forward(xn, cache as SSMCache, independentRows)
524
+ : this.selfAttn!.forward(xn, faMask, cache, independentRows);
374
525
  xn.dispose();
375
526
  const h = ops.add(x, r);
376
527
  r.dispose();
377
528
  const hn = this.postAttnNorm.forward(h);
378
- const m = this.mlp.forward(hn);
529
+ // RMSNorm allocates an aligned row-contiguous output. Pass that layout
530
+ // proof so Trellis can match native small-prefill matmul without an eval.
531
+ const m = this.mlp.forward(hn, true, independentRows);
379
532
  hn.dispose();
380
533
  const out = ops.add(h, m);
381
534
  h.dispose();
@@ -399,6 +552,14 @@ export class Qwen35Model {
399
552
  readonly faIdx: number;
400
553
 
401
554
  constructor(weights: Weights, config: ModelConfig) {
555
+ // Checkpoint-generation normalization BEFORE the first tensor() call: the
556
+ // 5.8-family export names the trunk `model.language_model.*` with a
557
+ // top-level `lm_head`, ships `mtp.*` in-repo, and stores RMSNorm gains as
558
+ // γ−1 with HF-layout conv1d. The view maps all of that onto the names and
559
+ // values this graph reads (mlx-lm's post-sanitize space). No-op — nothing
560
+ // installed at all — for artifacts already in that space.
561
+ const view = qwen35WeightsView(weights);
562
+ if (view) weights.setView(view);
402
563
  this.config = config;
403
564
  this.tied = config.text.tieWordEmbeddings;
404
565
  this.weightsBytes = [...weights.shards.files.values()]
@@ -432,9 +593,10 @@ export class Qwen35Model {
432
593
  out.set(`${p}.linear_attn.in_proj_a`, l.linearAttn.inProjA);
433
594
  out.set(`${p}.linear_attn.out_proj`, l.linearAttn.outProj);
434
595
  }
435
- out.set(`${p}.mlp.gate_proj`, l.mlp.gate);
436
- out.set(`${p}.mlp.up_proj`, l.mlp.up);
437
- out.set(`${p}.mlp.down_proj`, l.mlp.down);
596
+ // Trellis-coded MLP tensors are not LoRA targets (no adapter seam yet).
597
+ if (l.mlp.gate instanceof QuantizedLinear) out.set(`${p}.mlp.gate_proj`, l.mlp.gate);
598
+ if (l.mlp.up instanceof QuantizedLinear) out.set(`${p}.mlp.up_proj`, l.mlp.up);
599
+ if (l.mlp.down instanceof QuantizedLinear) out.set(`${p}.mlp.down_proj`, l.mlp.down);
438
600
  }
439
601
  return out;
440
602
  }
@@ -443,28 +605,142 @@ export class Qwen35Model {
443
605
  return this.layers.map((l) => (l.isLinear ? new SSMCache() : new KVCache()));
444
606
  }
445
607
 
608
+ /** The 27B append path keeps the native M1 affine arithmetic while Trellis
609
+ * shares up to four rows. Other shapes retain single-token execution. */
610
+ createAppend(policy: { hasAdapters: boolean; pagedKv: boolean }) {
611
+ const t = this.config.text;
612
+ if (policy.hasAdapters || policy.pagedKv || this.loraState.active.length || this.mrope ||
613
+ t.hiddenSize !== 5120 || t.intermediateSize !== 17408 || this.layers.length !== 64 ||
614
+ t.headDim !== 256 || t.numAttentionHeads !== 24 || t.numKeyValueHeads !== 4)
615
+ return null;
616
+ if (deviceArchitecture() !== "applegpu_g16s") return null;
617
+ const qualified = (linear: QuantizedLinear | TrellisLinear) => {
618
+ if (linear instanceof TrellisLinear) return true;
619
+ const { mode, bits, groupSize } = linear.spec;
620
+ return mode === "affine" && [2, 3, 4, 6, 8].includes(bits) && [32, 64, 128].includes(groupSize);
621
+ };
622
+ for (const layer of this.layers) {
623
+ const a = layer.linearAttn, s = layer.selfAttn;
624
+ const attention = a ? [a.inProjQkv, a.inProjZ, a.inProjB, a.inProjA, a.outProj]
625
+ : [s!.qProj, s!.kProj, s!.vProj, s!.oProj];
626
+ if (!attention.every(qualified) || ![layer.mlp.gate, layer.mlp.up, layer.mlp.down].every(qualified))
627
+ return null;
628
+ }
629
+ return {
630
+ maxChunkSize: (state: readonly Cache[]) => qwenAppendChunkSize(state[0]!.offset),
631
+ forwardHidden: (ids: MlxArray, cache: Cache[]): MlxArray => {
632
+ if (ids.shape.length !== 2 || ids.shape[0] !== 1 || ids.shape[1]! > 4)
633
+ throw new Error("Qwen committed-token append supports one row and at most four positions");
634
+ const hidden = this.embed.encode(ids);
635
+ return this.forwardLayers(hidden, cache, true);
636
+ },
637
+ };
638
+ }
639
+
446
640
  forwardHidden(ids: MlxArray, cache: Cache[]): MlxArray {
447
641
  const h = this.embed.encode(ids);
448
642
  return this.forwardLayers(h, cache);
449
643
  }
450
644
 
451
- forwardEmbeddings(_embeds: MlxArray, _cache: Cache[], _bidir: MlxArray | null): MlxArray {
452
- throw new Error("qwen3_5 vision/input-embedding path is not supported");
645
+ /** Active vision mRoPE request state (serial lane; set by the generation
646
+ * gateway around a vision request's run, null for text-only — which keeps
647
+ * the bit-exact fast-rope path). While set, forwardLayers installs the
648
+ * per-forward interleaved cos/sin consumed by every full-attn layer. */
649
+ mrope: MropeRequestState | null = null;
650
+ /** Serving's memory-pressure check, scoped to the serial request. */
651
+ prefillMemoryGuard: (() => void) | null = null;
652
+ #mropeInvFreq: MlxArray | null = null;
653
+
654
+ /** Vision prefill: spliced input embeddings [1, L, H] (image features
655
+ * overwriting image-token rows — the caller builds them). Caller keeps
656
+ * ownership of `embeds`. bidir/ids/multimodal are the Gemma-shaped extras
657
+ * and are unused: Qwen3.5 vision attends fully causally. */
658
+ forwardEmbeddings(
659
+ embeds: MlxArray, cache: Cache[], _bidir: MlxArray | null,
660
+ _ids: MlxArray | null = null, _multimodal: MlxArray | null = null,
661
+ ): MlxArray {
662
+ const h = ops.contiguous(embeds); // fresh handle — the layer loop consumes it
663
+ return this.forwardLayers(h, cache);
664
+ }
665
+
666
+ /** Layer-output tap (same contract as Gemma4Model.hiddenTap): the serve
667
+ * loop's forwardMaybeTap sets it around prefill/verify forwards so
668
+ * KV-borrowing draft sources (native Qwen MTP) can read PRE-final-norm
669
+ * hiddens — layer 63's output stream, what mlx-vlm captures with
670
+ * skip_final_norm=True. No-op (and graph-identical) when null. */
671
+ hiddenTap: { layers: Set<number>; pos?: number; captured: Map<number, MlxArray> } | null = null;
672
+
673
+ /** Store layer `i`'s residual stream ([1,L,H]) if the tap requests it.
674
+ * Copies so the loop's h.dispose() can't free the capture. */
675
+ protected captureLayer(i: number, h: MlxArray): void {
676
+ const tap = this.hiddenTap;
677
+ if (!tap || !tap.layers.has(i)) return;
678
+ const H = h.shape[2]!;
679
+ const src = tap.pos !== undefined ? h.slice([0, tap.pos, 0], [1, tap.pos + 1, H]) : h;
680
+ const copy = ops.contiguous(src);
681
+ if (src !== h) src.dispose();
682
+ tap.captured.set(i, copy);
453
683
  }
454
684
 
455
- protected forwardLayers(h0: MlxArray, cache: Cache[]): MlxArray {
685
+ protected forwardLayers(h0: MlxArray, cache: Cache[], independentRows = false): MlxArray {
456
686
  const L = h0.shape[1]!;
457
687
  // One full-attention mask shared by all full layers (same offset); linear
458
688
  // layers see no ssm mask at B=1.
459
689
  const faMask = cache[this.faIdx]!.makeMask(L, null);
460
- let h = h0;
461
- for (let i = 0; i < this.layers.length; i++) {
462
- const next = this.layers[i]!.forward(h, faMask, cache[i]!);
463
- h.dispose();
464
- h = next;
690
+ // Vision requests: one interleaved-mRoPE cos/sin table per forward,
691
+ // shared by all 12 full-attention layers (positions are layer-invariant).
692
+ let mropeFwd: ReturnType<typeof buildMropePositions> | null = null;
693
+ if (this.mrope) {
694
+ const t = this.config.text;
695
+ const ropeDims = Math.trunc(t.headDim * t.partialRotaryFactor);
696
+ const base = t.ropeParameters.full_attention?.ropeTheta ?? 10000;
697
+ this.#mropeInvFreq ??= mropeInvFreq(ropeDims, base);
698
+ mropeFwd = buildMropePositions(
699
+ this.mrope, cache[this.faIdx]!.offset, L, this.#mropeInvFreq, ropeDims,
700
+ );
701
+ setActiveMrope(mropeFwd);
702
+ }
703
+ let h: MlxArray | null = h0;
704
+ const prof = (globalThis as Record<string, unknown>).__deltaProf as
705
+ Record<string, number>
706
+ | undefined;
707
+ try {
708
+ for (let i = 0; i < this.layers.length; i++) {
709
+ if (L > 1) this.prefillMemoryGuard?.();
710
+ const tl = prof ? performance.now() : 0;
711
+ const next = this.layers[i]!.forward(h, faMask, cache[i]!, independentRows);
712
+ h.dispose();
713
+ h = next;
714
+ const mlp = this.layers[i]!.mlp;
715
+ if (L > TRELLIS_MATVEC_MAX_M &&
716
+ [mlp.gate, mlp.up, mlp.down].some((p) => p instanceof TrellisLinear && !p.fallback)) {
717
+ // Materialize the cache outputs too. In particular, the tiny
718
+ // copied conv tail is not a dependency of `h`; leaving it lazy
719
+ // pins the whole prefill conv buffer until the chunk ends.
720
+ const state = cache[i]!.state();
721
+ try { ops.evalAll([h, ...state]); }
722
+ finally { if (cache[i]!.stateNeedsDispose) for (const a of state) a.dispose(); }
723
+ }
724
+ if (prof) {
725
+ ops.evalAll([h]);
726
+ const key = this.layers[i]!.isLinear ? "layerLin" : "layerFull";
727
+ prof[key] = (prof[key] ?? 0) + performance.now() - tl;
728
+ }
729
+ this.captureLayer(i, h); // native-MTP pre-final-norm tap (no-op unless set)
730
+ }
731
+ const out = disposing(h, this.finalNorm.forward(h));
732
+ h = null; // consumed — the finally must not double-free
733
+ return out;
734
+ } finally {
735
+ if (mropeFwd) {
736
+ setActiveMrope(null);
737
+ mropeFwd.posIds.dispose();
738
+ }
739
+ // A mid-loop layer throw must not strand the mask array or the
740
+ // in-flight [1,L,H] residual (2026-08-18 review).
741
+ faMask.arr?.dispose();
742
+ h?.dispose();
465
743
  }
466
- faMask.arr?.dispose();
467
- return disposing(h, this.finalNorm.forward(h));
468
744
  }
469
745
 
470
746
  logitsFromHidden(h: MlxArray): MlxArray {
@@ -56,7 +56,7 @@ export function isDrafterModelType(modelType: string): boolean {
56
56
 
57
57
  /** DiffusionGemma (model_type `diffusion_gemma`): block/masked-diffusion canvas
58
58
  * model. Non-autoregressive — routed through the diffusion engine, not the AR
59
- * loop. See docs/design/diffusion-gemma-port.md. */
59
+ * loop. See docs/design/generic-model-support.md. */
60
60
  export function isDiffusionGemmaConfig(config: ModelConfig): boolean {
61
61
  return config.modelType === "diffusion_gemma";
62
62
  }
@@ -0,0 +1,51 @@
1
+ // Experimental variant 8, k=3 / T=256 / L<=12 only.
2
+ // The word-per-lane kernel uses 24 of 32 SIMD lanes for a 3-bit block.
3
+ // Assign eight outputs to every lane, load adjacent packed words, and align
4
+ // them once so each unrolled decoder has constant shifts. Row accumulation,
5
+ // split boundaries and the separate partial reduction stay identical to v6.
6
+ export const BALANCED_SCATTER_SOURCE = String.raw`
7
+ const uint lane = thread_index_in_simdgroup;
8
+ const uint sg = simdgroup_index_in_threadgroup;
9
+ const uint block = thread_position_in_grid.y * (uint)SG_TG + sg;
10
+ const uint nBlocks = (uint)C / (uint)BT;
11
+ const uint zi = thread_position_in_grid.z;
12
+ const uint sample = zi / (uint)SPLITS;
13
+ const uint split = zi - sample * (uint)SPLITS;
14
+ if (block >= nBlocks || sample >= (uint)M) return;
15
+ const uint wpb = (uint)(BT * K / 32);
16
+ const ulong rowWords = (ulong)(INTERLEAVE ? 2 : nBlocks) * wpb;
17
+ const uint pBase = lane * 8u * (uint)K;
18
+ const uint wi = pBase >> 5u;
19
+ const uint off = pBase & 31u;
20
+ const uint next = (wi + 1u == wpb) ? 0u : wi + 1u;
21
+ const device uint32_t* words = codes + (INTERLEAVE
22
+ ? (ulong)(block / 2) * R * 2 * wpb + (block % 2) * wpb : (ulong)block * wpb);
23
+ const uint rowsPer = ((uint)R + (uint)SPLITS - 1u) / (uint)SPLITS;
24
+ const uint r0 = split * rowsPer;
25
+ const uint r1 = metal::min((uint)R, r0 + rowsPer);
26
+ const device T* xs = x + (ulong)sample * (ulong)R;
27
+ float acc[8];
28
+ #pragma clang loop unroll(full)
29
+ for (uint i = 0; i < 8u; ++i) acc[i] = 0.0f;
30
+ for (uint r = r0; r < r1; ++r) {
31
+ const uint word = words[(ulong)r * rowWords + wi];
32
+ const uint nxt = words[(ulong)r * rowWords + next];
33
+ const uint lo = off == 0u ? word : (word >> off) | (nxt << (32u - off));
34
+ const uint hi = off == 0u ? nxt : nxt >> off;
35
+ const float xr = float(xs[r]);
36
+ const float sr = float(scales[r]);
37
+ #pragma clang loop unroll(full)
38
+ for (uint i = 0; i < 8u; ++i) {
39
+ const uint shift = i * (uint)K;
40
+ uint win = lo >> shift;
41
+ if (shift + (uint)L > 32u) win |= hi << (32u - shift);
42
+ win &= (1u << (uint)L) - 1u;
43
+ const float value = (float)trellis_y(win) * (1.0f / 147.800537109375f);
44
+ acc[i] = metal::fma(value * sr, xr, acc[i]);
45
+ }
46
+ }
47
+ const ulong outBase = ((ulong)sample * (ulong)SPLITS + split) * (ulong)C;
48
+ #pragma clang loop unroll(full)
49
+ for (uint i = 0; i < 8u; ++i)
50
+ partial[outBase + block * (uint)BT + (uint)BT - 1u - lane * 8u - i] = acc[i];
51
+ `;