mlx-bun 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (364) hide show
  1. package/README.md +71 -588
  2. package/docs/README.md +91 -0
  3. package/docs/{design → archive/investigations}/audio-input-plan.md +4 -4
  4. package/docs/{design → archive/investigations}/diffusion-gemma-port.md +3 -3
  5. package/docs/{design → archive/investigations}/faithful-l1-consolidation.md +4 -4
  6. package/docs/{design → archive/investigations}/minicpm5-decode-megakernel.md +11 -11
  7. package/docs/{design → archive/investigations}/optimization_plan.md +1 -1
  8. package/docs/{design → archive/investigations}/pre-colibri-stabilization.md +5 -5
  9. package/docs/{design → archive/investigations}/the-dreaming-handoff.md +2 -2
  10. package/docs/design/batching.md +569 -0
  11. package/docs/design/decode-speed-program.md +3178 -169
  12. package/docs/design/dreaming-nightly-pipeline.md +675 -282
  13. package/docs/design/generic-model-support.md +579 -334
  14. package/docs/design/kv-cache.md +680 -0
  15. package/docs/design/orpo-training.md +673 -1239
  16. package/docs/design/speculative-decoding.md +1627 -0
  17. package/docs/design/turboquant.md +2102 -0
  18. package/docs/design/unified-engine-frontier-plan.md +1831 -727
  19. package/docs/design/web-chat-redesign.md +666 -1083
  20. package/docs/planning/PRODUCT_ROADMAP.md +34 -1
  21. package/docs/planning/release-notes-v0.3.0.md +35 -0
  22. package/docs/planning/video-series.md +2 -2
  23. package/docs/reference/benchmarks.md +1162 -0
  24. package/docs/reference/cli.md +394 -159
  25. package/docs/reference/distribution.md +321 -182
  26. package/docs/reference/environment.md +348 -0
  27. package/docs/reference/glossary.md +39 -0
  28. package/docs/reference/library-api.md +566 -168
  29. package/docs/reference/memory.md +227 -64
  30. package/docs/reference/models.md +104 -6
  31. package/docs/reference/server-api.md +886 -425
  32. package/docs/reference/server-config.md +638 -330
  33. package/docs/reference/training.md +393 -254
  34. package/docs/reference/troubleshooting.md +166 -0
  35. package/package.json +9 -4
  36. package/src/audio/decode.ts +2 -2
  37. package/src/audio/features.ts +2 -2
  38. package/src/backends/mlx/autoregressive.ts +108 -0
  39. package/src/backends/mlx/batch-group.ts +1646 -0
  40. package/src/backends/mlx/diffusion.ts +39 -0
  41. package/src/backends/mlx/draft-target.ts +47 -0
  42. package/src/backends/mlx/evaluation.ts +105 -0
  43. package/src/backends/mlx/fill-append.ts +45 -0
  44. package/src/backends/mlx/gateway-binding.ts +71 -0
  45. package/src/backends/mlx/graph.ts +38 -0
  46. package/src/backends/mlx/memory-client.ts +189 -0
  47. package/src/backends/mlx/memory-guard.ts +40 -0
  48. package/src/backends/mlx/methods.ts +90 -0
  49. package/src/backends/mlx/model-prompt.ts +164 -0
  50. package/src/backends/mlx/model-serving.ts +79 -0
  51. package/src/backends/mlx/prefill.ts +33 -0
  52. package/src/backends/mlx/request-policy.ts +20 -0
  53. package/src/backends/mlx/rollback.ts +51 -0
  54. package/src/backends/mlx/serial-executor.ts +273 -0
  55. package/src/backends/mlx/speculative.ts +114 -0
  56. package/src/backends/mlx/state-views.ts +26 -0
  57. package/src/backends/mlx/text-engine.ts +73 -0
  58. package/src/cli.ts +58 -25
  59. package/src/client.ts +66 -0
  60. package/src/config.ts +21 -4
  61. package/src/contracts/completion.ts +16 -0
  62. package/src/contracts/execution.ts +49 -0
  63. package/src/contracts/generation.ts +103 -0
  64. package/src/contracts/host.ts +7 -0
  65. package/src/contracts/jobs.ts +66 -0
  66. package/src/contracts/pi-provider.ts +30 -0
  67. package/src/contracts/pi.ts +259 -0
  68. package/src/contracts/resources.ts +18 -0
  69. package/src/contracts/scheduling.ts +27 -0
  70. package/src/contracts/tsconfig.json +14 -0
  71. package/src/diffusion/diffusion-generate.ts +218 -153
  72. package/src/embed.ts +1 -1
  73. package/src/engine/admission.ts +59 -0
  74. package/src/engine/cancellation.ts +38 -0
  75. package/src/engine/engine.ts +56 -0
  76. package/src/engine/execution-coordinator.ts +70 -0
  77. package/src/engine/execution-plan.ts +51 -0
  78. package/src/engine/resources.ts +42 -0
  79. package/src/engine/scheduler.ts +67 -0
  80. package/src/engine/session.ts +247 -0
  81. package/src/eval/README.md +44 -1
  82. package/src/eval/kl.ts +236 -3
  83. package/src/eval/reference-logits.ts +207 -0
  84. package/src/eval/runner.ts +8 -96
  85. package/src/fill/echo-index.ts +188 -0
  86. package/src/fill/fill-session.ts +452 -0
  87. package/src/fill/proposal.ts +68 -0
  88. package/src/fill/schema-rows.ts +506 -0
  89. package/src/fill/tool-boundary.ts +103 -0
  90. package/src/fit.ts +1 -1
  91. package/src/generate.ts +656 -268
  92. package/src/grammar.ts +12 -7
  93. package/src/harness-pi.ts +14 -33
  94. package/src/hub-rest.ts +4 -4
  95. package/src/index.ts +7 -0
  96. package/src/inference/denoising.ts +17 -0
  97. package/src/inference/graph.ts +33 -0
  98. package/src/inference/prefill.ts +39 -0
  99. package/src/inference/rollback.ts +7 -0
  100. package/src/jobs/db.ts +4 -0
  101. package/src/jobs/index.ts +2 -0
  102. package/src/jobs/job-entry.ts +12 -12
  103. package/src/jobs/runner.ts +98 -13
  104. package/src/jobs/task-client.ts +19 -0
  105. package/src/jobs/types.ts +2 -58
  106. package/src/kv-store.ts +109 -49
  107. package/src/lab/README.md +18 -0
  108. package/src/{assets → lab/curve}/curve-designer.html +2 -2
  109. package/src/{curve-sampler.ts → lab/curve/curve-sampler.ts} +2 -2
  110. package/src/{expert-trace.ts → lab/expert-trace/expert-trace.ts} +5 -5
  111. package/src/{model → lab/paged-kv}/paged-kv.ts +9 -6
  112. package/src/library.ts +40 -0
  113. package/src/lora.ts +18 -0
  114. package/src/media-fetch.ts +1 -1
  115. package/src/memory/article.ts +1 -1
  116. package/src/memory/chunk.ts +1 -1
  117. package/src/memory/model.ts +30 -169
  118. package/src/memory/prompts.ts +1 -1
  119. package/src/memory/rest.ts +1 -1
  120. package/src/memory/schedule.ts +1 -1
  121. package/src/memory/stages.ts +4 -1
  122. package/src/memory/vault.ts +1 -3
  123. package/src/mlx/array.ts +9 -10
  124. package/src/mlx/compile.ts +12 -2
  125. package/src/mlx/ffi.ts +58 -11
  126. package/src/mlx/materialize.ts +23 -0
  127. package/src/mlx/metal-kernel.ts +10 -11
  128. package/src/mlx/ops.ts +33 -13
  129. package/src/mlx/quantized-rows.ts +36 -0
  130. package/src/mlx/token-bitmask.ts +52 -0
  131. package/src/mlx/turboquant-kv-decode.ts +82 -0
  132. package/src/mlx/turboquant-kv-inverse.ts +122 -0
  133. package/src/mlx/turboquant-ops.ts +33 -3
  134. package/src/mlx/turboquant-tables.ts +2 -2
  135. package/src/model/batched-mask.ts +8 -2
  136. package/src/model/batched-quant.ts +1 -1
  137. package/src/model/batched-rotating-quant.ts +2 -2
  138. package/src/model/batched-rotating.ts +1 -1
  139. package/src/model/compiled-decode.ts +1 -1
  140. package/src/model/diffusion-gemma.ts +8 -8
  141. package/src/model/factory.ts +76 -33
  142. package/src/model/fingerprint.ts +1 -1
  143. package/src/model/flash-attention.ts +3 -3
  144. package/src/model/gemma4-base.ts +26 -9
  145. package/src/model/gemma4.ts +6 -6
  146. package/src/model/generated/gemma4-12b.ts +2 -2
  147. package/src/model/generated/gemma4-26b.ts +2 -2
  148. package/src/model/generated/gemma4-e4b.ts +2 -2
  149. package/src/model/glm52-dsa.ts +13 -9
  150. package/src/model/glm52-memory.ts +1 -1
  151. package/src/model/implementation.ts +69 -0
  152. package/src/model/minicpm5.ts +6 -8
  153. package/src/model/profile.ts +12 -2
  154. package/src/model/qwen-append.ts +12 -0
  155. package/src/model/qwen-conv.ts +52 -0
  156. package/src/model/qwen3_5-checkpoint.ts +164 -0
  157. package/src/model/qwen3_5.ts +140 -56
  158. package/src/model/support.ts +1 -1
  159. package/src/model/trellis-balanced-scatter.ts +51 -0
  160. package/src/model/trellis-linear.ts +672 -0
  161. package/src/model/trellis-shared-m.ts +151 -0
  162. package/src/model/trellis-shared-scatter.ts +144 -0
  163. package/src/model/trellis-splitk-prefill.ts +158 -0
  164. package/src/model/trellis-tiled-prefill.ts +136 -0
  165. package/src/model/trellis-vector-expand.ts +62 -0
  166. package/src/model/trellis-wide-prefill.ts +94 -0
  167. package/src/model/universal/dense.ts +1 -1
  168. package/src/model/universal/modules.ts +8 -1
  169. package/src/native-pack.ts +6 -5
  170. package/src/pi-provider.ts +6 -64
  171. package/src/pi-web.ts +8 -315
  172. package/src/prompt-cache.ts +96 -53
  173. package/src/quantize/allocator.ts +1 -1
  174. package/src/quantize/config-writer.ts +10 -3
  175. package/src/quantize/rotate.ts +4 -4
  176. package/src/quantize/trellis.ts +586 -0
  177. package/src/responses.ts +28 -1
  178. package/src/runtime-config.ts +18 -4
  179. package/src/sampler.ts +4 -4
  180. package/src/serve/batch-scheduler.ts +8 -1671
  181. package/src/serve/chat-request.ts +391 -0
  182. package/src/serve/chat-stage.ts +195 -0
  183. package/src/serve/checkpoint-identity.ts +64 -0
  184. package/src/serve/completion-executor.ts +15 -7
  185. package/src/serve/discovery-routes.ts +12 -13
  186. package/src/serve/generation-gateway.ts +154 -193
  187. package/src/serve/http.ts +159 -0
  188. package/src/serve/inference-request.ts +79 -0
  189. package/src/serve/isolate.ts +297 -105
  190. package/src/serve/lab-routes.ts +3 -0
  191. package/src/serve/lane-registry.ts +2 -1
  192. package/src/serve/model-admin-routes.ts +7 -5
  193. package/src/serve/model-binding.ts +43 -0
  194. package/src/serve/model-host.ts +512 -0
  195. package/src/serve/openai-wire.ts +103 -0
  196. package/src/serve/parent-application.ts +56 -0
  197. package/src/serve/pipeline.ts +33 -0
  198. package/src/serve/preparation.ts +33 -0
  199. package/src/serve/request-plan.ts +19 -15
  200. package/src/serve/request-prep.ts +346 -0
  201. package/src/serve/response-lease.ts +24 -0
  202. package/src/serve/responses-client.ts +69 -0
  203. package/src/serve/session-completion-engine.ts +110 -0
  204. package/src/serve/session-search.ts +1 -1
  205. package/src/serve/static-routes.ts +5 -5
  206. package/src/serve/text-completion-stage.ts +104 -0
  207. package/src/serve/token-streams.ts +519 -0
  208. package/src/server.ts +317 -2649
  209. package/src/spec/assistant-source.ts +10 -44
  210. package/src/spec/deepspec-source.ts +6 -6
  211. package/src/spec/dflash-source.ts +8 -8
  212. package/src/spec/dspark/ab-stats.ts +3 -3
  213. package/src/spec/dspark/calibration.ts +2 -2
  214. package/src/spec/dspark/deepspec-module.ts +2 -2
  215. package/src/spec/dspark/generate-dflash.ts +2 -2
  216. package/src/spec/dspark/generate.ts +2 -2
  217. package/src/spec/dspark/loader.ts +1 -1
  218. package/src/spec/dspark/module-dflash.ts +4 -3
  219. package/src/spec/dspark/module.ts +3 -3
  220. package/src/spec/dspark/quantize-drafter.ts +1 -1
  221. package/src/spec/generate.ts +1 -1
  222. package/src/spec/glm52-mtp-source.ts +1 -1
  223. package/src/spec/ngram-source.ts +1 -1
  224. package/src/spec/prefix-state.ts +45 -0
  225. package/src/spec/qwen-mtp-source.ts +166 -69
  226. package/src/spec/serve-loop.ts +154 -156
  227. package/src/spec/source.ts +42 -10
  228. package/src/spec/two-model.ts +1 -1
  229. package/src/ssd-cache.ts +80 -12
  230. package/src/ssd-durability.ts +25 -13
  231. package/src/tool-call.ts +51 -14
  232. package/src/train/flash-cce.ts +2 -2
  233. package/src/train/forward.ts +6 -0
  234. package/src/train/loss.ts +1 -1
  235. package/src/train/prefix-shared.ts +5 -1
  236. package/src/train/segmented.ts +5 -3
  237. package/src/train/trainer.ts +6 -6
  238. package/src/vision/prompt.ts +1 -1
  239. package/src/vision/qwen3vl-tower.ts +52 -10
  240. package/src/web/app.html +1 -1
  241. package/src/web/app.js +5 -3
  242. package/src/web/src/adapters-panel.ts +3 -3
  243. package/src/web/src/assistant.ts +2 -36
  244. package/src/web/src/chat.ts +4 -4
  245. package/src/web/src/composer.ts +4 -4
  246. package/src/web/src/hub.ts +1 -1
  247. package/src/web/src/markdown.ts +0 -0
  248. package/src/web/src/memory-panel.ts +1 -1
  249. package/src/web/src/model-picker.ts +1 -1
  250. package/src/web/src/palette.ts +1 -1
  251. package/src/web/src/protocol.ts +3 -39
  252. package/src/web/src/sessions.ts +1 -1
  253. package/src/web/src/ui-catalog.ts +4 -18
  254. package/src/web-assets.ts +47 -0
  255. package/src/weights.ts +55 -9
  256. package/docs/archive/investigations/curve-runs/bisector-route.json +0 -98
  257. package/docs/archive/investigations/curve-runs/bisector-trace.json +0 -1393
  258. package/docs/archive/investigations/curve-runs/curve-terrain.json +0 -1179
  259. package/docs/archive/investigations/curve-runs/curve-terrain.svg +0 -42
  260. package/docs/archive/investigations/hlg-ab.html +0 -34
  261. package/docs/archive/investigations/hlg-explorer.html +0 -238
  262. package/docs/archive/investigations/hlg-figs/cliff.svg +0 -1
  263. package/docs/archive/investigations/hlg-figs/cross.svg +0 -1
  264. package/docs/archive/investigations/hlg-figs/edges.svg +0 -1
  265. package/docs/archive/investigations/hlg-figs/k20-grids.svg +0 -1
  266. package/docs/archive/investigations/hlg-figs/k20-peaks.svg +0 -1
  267. package/docs/archive/investigations/hlg-figs/lw-curve.svg +0 -1
  268. package/docs/archive/investigations/hlg-figs/terrain.svg +0 -1
  269. package/docs/archive/investigations/hlg-report.html +0 -262
  270. package/docs/archive/investigations/hlg-runs/hlg-ab.json +0 -149
  271. package/docs/archive/investigations/hlg-runs/hlg-blind-prompts.md +0 -87
  272. package/docs/archive/investigations/hlg-runs/hlg-blind.json +0 -107
  273. package/docs/archive/investigations/hlg-runs/hlg-cliff.json +0 -197
  274. package/docs/archive/investigations/hlg-runs/hlg-cross.json +0 -505
  275. package/docs/archive/investigations/hlg-runs/hlg-edge.json +0 -388
  276. package/docs/archive/investigations/hlg-runs/hlg-eval1.json +0 -25
  277. package/docs/archive/investigations/hlg-runs/hlg-frontier.json +0 -86
  278. package/docs/archive/investigations/hlg-runs/hlg-grid.json +0 -242
  279. package/docs/archive/investigations/hlg-runs/hlg-grid2.json +0 -343
  280. package/docs/archive/investigations/hlg-runs/hlg-lw-coarse.json +0 -110
  281. package/docs/archive/investigations/hlg-runs/hlg-map.json +0 -383
  282. package/docs/archive/investigations/hlg-runs/run0-original.log +0 -159
  283. package/docs/archive/investigations/hlg-runs/runA-recommended.log +0 -131
  284. package/docs/archive/investigations/hlg-runs/runAA-cliff-cpm5.log +0 -26
  285. package/docs/archive/investigations/hlg-runs/runAB-grid2-e4b.log +0 -18
  286. package/docs/archive/investigations/hlg-runs/runAC-peak-e4b-partial.log +0 -25
  287. package/docs/archive/investigations/hlg-runs/runAD-ab-samples.log +0 -8
  288. package/docs/archive/investigations/hlg-runs/runAE-edge-e4b.log +0 -51
  289. package/docs/archive/investigations/hlg-runs/runAF-logit-calibration.log +0 -10
  290. package/docs/archive/investigations/hlg-runs/runAG-eval-config2.log +0 -16
  291. package/docs/archive/investigations/hlg-runs/runB-gain-variants.log +0 -192
  292. package/docs/archive/investigations/hlg-runs/runC-pivots.log +0 -162
  293. package/docs/archive/investigations/hlg-runs/runD-oetf.log +0 -132
  294. package/docs/archive/investigations/hlg-runs/runE-eotf.log +0 -162
  295. package/docs/archive/investigations/hlg-runs/runF-shaper-working.log +0 -86
  296. package/docs/archive/investigations/hlg-runs/runG-shaper-sweep.log +0 -101
  297. package/docs/archive/investigations/hlg-runs/runH-wsweep.log +0 -176
  298. package/docs/archive/investigations/hlg-runs/runI-asweep.log +0 -176
  299. package/docs/archive/investigations/hlg-runs/runJ-knobmap.log +0 -49
  300. package/docs/archive/investigations/hlg-runs/runK-frontier.log +0 -19
  301. package/docs/archive/investigations/hlg-runs/runL-widemap.log +0 -65
  302. package/docs/archive/investigations/hlg-runs/runM-frontier-emb.log +0 -15
  303. package/docs/archive/investigations/hlg-runs/runN-verify.log +0 -116
  304. package/docs/archive/investigations/hlg-runs/runO-frontier-seed5000.log +0 -15
  305. package/docs/archive/investigations/hlg-runs/runP-grid-AxSm.log +0 -15
  306. package/docs/archive/investigations/hlg-runs/runQ-grid-tgW.log +0 -15
  307. package/docs/archive/investigations/hlg-runs/runR-blind-transcript.log +0 -79
  308. package/docs/archive/investigations/hlg-runs/runS-cross-coherence.log +0 -33
  309. package/docs/archive/investigations/hlg-runs/runT-cross-cpm5.log +0 -33
  310. package/docs/archive/investigations/hlg-runs/runU-cross-12b.log +0 -33
  311. package/docs/archive/investigations/hlg-runs/runV-lw-e4b.log +0 -22
  312. package/docs/archive/investigations/hlg-runs/runW-lw-12b.log +0 -22
  313. package/docs/archive/investigations/hlg-runs/runX-lw-cpm5.log +0 -22
  314. package/docs/archive/investigations/hlg-runs/runY-cliff-e4b.log +0 -26
  315. package/docs/archive/investigations/hlg-runs/runZ-cliff-12b.log +0 -26
  316. package/docs/archive/mlx-bun-lab-report.html +0 -275
  317. package/docs/archive/wwdc-mlx-bun.html +0 -848
  318. package/docs/design/adapters-end-to-end.md +0 -205
  319. package/docs/design/batching-perf-path.md +0 -255
  320. package/docs/design/batching-v2-plan.md +0 -529
  321. package/docs/design/bucketing-stage.md +0 -197
  322. package/docs/design/compat-cli-surface-design.md +0 -230
  323. package/docs/design/docs-reorg-plan.md +0 -165
  324. package/docs/design/dspark-serving-program.md +0 -371
  325. package/docs/design/dspark-speculative-decoding.md +0 -318
  326. package/docs/design/grammar-spec-batching-integration.md +0 -301
  327. package/docs/design/memory-inference-path.md +0 -422
  328. package/docs/design/memory-synthesis.md +0 -287
  329. package/docs/design/memory-system.md +0 -237
  330. package/docs/design/mlx-lm-serving-execution-seam.md +0 -81
  331. package/docs/design/mlx-lm-tool-parity-plan.md +0 -545
  332. package/docs/design/omlx-adoption-map.md +0 -98
  333. package/docs/design/orpo-dynamic-lambda.md +0 -220
  334. package/docs/design/orpo-future-enhancements.md +0 -340
  335. package/docs/design/paged-kv-cache.md +0 -214
  336. package/docs/design/parallel-slots.md +0 -567
  337. package/docs/design/parity-tier-dag.md +0 -161
  338. package/docs/design/repo-cleanup-plan.md +0 -231
  339. package/docs/design/runtime-isolation.md +0 -263
  340. package/docs/design/segmented-backward-training.md +0 -543
  341. package/docs/design/spec-decode-larger-targets.md +0 -229
  342. package/docs/design/ssd-kv-cold-tier.md +0 -415
  343. package/docs/design/structured-output.md +0 -484
  344. package/docs/design/tauri-desktop-app.md +0 -127
  345. package/docs/design/the-dreaming-master-plan.md +0 -445
  346. package/docs/design/turboquant-kv.md +0 -235
  347. package/docs/design/turboquant-weights.md +0 -467
  348. package/docs/design/w4a16-compute-precision-spike.md +0 -232
  349. package/docs/design/web-chat-beat-matrix.md +0 -315
  350. package/docs/design/write-pipeline-entity.md +0 -468
  351. package/docs/planning/IDEAS.md +0 -69
  352. package/docs/reference/embedding.md +0 -197
  353. package/docs/reference/features-matrix.md +0 -101
  354. package/docs/reference/orpo-quickstart.md +0 -135
  355. package/src/flags.ts +0 -11
  356. /package/docs/{design → archive/investigations}/colibri-glm52-port.md +0 -0
  357. /package/docs/{planning → archive/planning}/chunk-task-roadmap.md +0 -0
  358. /package/docs/{planning → archive/planning}/curve-sampler-research-plan.md +0 -0
  359. /package/docs/{planning → archive/planning}/memory-docs-and-dag-plan.md +0 -0
  360. /package/docs/{planning → archive/planning}/pi-integration-review.md +0 -0
  361. /package/docs/{planning → archive/planning}/release-notes-v0.2.0.md +0 -0
  362. /package/src/{memory-plan.ts → admission-plan.ts} +0 -0
  363. /package/{docs/archive/investigations → src/serve/assets}/curve-terrain.html +0 -0
  364. /package/{docs/dag → src/serve/assets}/training-inference-map.html +0 -0
@@ -115,9 +115,42 @@ export class Qwen3VLVisionTower {
115
115
  return names;
116
116
  }
117
117
 
118
+ /** Checkpoint-generation naming for the tower. Pre-5.8 artifacts store
119
+ * `vision_tower.*`; the transformers-5.8 export stores `model.visual.*`
120
+ * (mlx-lm's text-only qwen3_5 drops BOTH prefixes in `Model.sanitize`, so
121
+ * the mapping between them is ours). Verified 2026-09-01 across
122
+ * Qwen3.8-27B-tqalloc-norot vs the staged pre-5.8 artifact: 331/333
123
+ * tensors byte-identical under the rename, patch_embed.proj.weight
124
+ * identical under the layout permutation below, and merger.linear_fc2
125
+ * differs only because the staged artifact is rotation-folded. */
126
+ static #sourceName(canonical: string, prefix: string): string {
127
+ return prefix + canonical.slice("vision_tower.".length);
128
+ }
129
+
130
+ /** HF stores the Conv3d patch embed as (out, in, kT, kH, kW); MLX (and this
131
+ * tower) want (out, kT, kH, kW, in). Proven a pure permutation on the
132
+ * artifacts above — transpose(0,2,3,4,1) of the 5.8 tensor is bit-equal to
133
+ * the pre-5.8 one. */
134
+ static #fixPatchEmbed(w: Map<string, MlxArray>): void {
135
+ const key = "vision_tower.patch_embed.proj.weight";
136
+ const a = w.get(key);
137
+ if (!a || a.shape.length !== 5) return;
138
+ if (a.shape[4] === 3) return; // already MLX layout
139
+ const t = ops.transposeAxes(a, [0, 2, 3, 4, 1]);
140
+ const c = ops.contiguous(t);
141
+ t.dispose();
142
+ a.dispose();
143
+ w.set(key, c);
144
+ }
145
+
118
146
  /** Load one safetensors file's map and move `wanted` tensors into `out`
119
- * (missing names are skipped; the map is always freed). */
120
- static #takeFromFile(path: string, wanted: string[], out: Map<string, MlxArray>): void {
147
+ * under their CANONICAL (`vision_tower.*`) names; `source` maps each
148
+ * canonical name to the stored one. Missing names are skipped; the map is
149
+ * always freed. */
150
+ static #takeFromFile(
151
+ path: string, wanted: string[], out: Map<string, MlxArray>,
152
+ source: (canonical: string) => string = (n) => n,
153
+ ): void {
121
154
  const arrMap = new BigUint64Array([C.mlx_map_string_to_array_new()]);
122
155
  const metaMap = new BigUint64Array([C.mlx_map_string_to_string_new()]);
123
156
  const arrMapPtr = ptr(arrMap);
@@ -136,7 +169,7 @@ export class Qwen3VLVisionTower {
136
169
  if (out.has(name)) continue;
137
170
  const slot = new BigUint64Array([C.mlx_array_new()]);
138
171
  const slotPtr = ptr(slot);
139
- if (C.mlx_map_string_to_array_get(slotPtr, handle, ptr(cstr(name))) !== 0) {
172
+ if (C.mlx_map_string_to_array_get(slotPtr, handle, ptr(cstr(source(name)))) !== 0) {
140
173
  C.mlx_array_free(read.u64(slotPtr, 0));
141
174
  continue;
142
175
  }
@@ -156,28 +189,37 @@ export class Qwen3VLVisionTower {
156
189
  // OptiQ-convention sidecar (their artifacts + ours pre-2026-08-18).
157
190
  Qwen3VLVisionTower.#takeFromFile(sidecar, names, weights);
158
191
  } else {
159
- // In-main fallback: the bf16 vision_tower.* tensors live in the main
192
+ // In-main fallback: the bf16 tower tensors live in the main
160
193
  // safetensors shards (the mlx-vlm convention — one copy serves both
161
- // ecosystems; sidecar duplication retired 2026-08-18).
194
+ // ecosystems; sidecar duplication retired 2026-08-18). The 5.8-family
195
+ // export names them `model.visual.*` instead of `vision_tower.*`.
162
196
  const idxPath = `${modelDir}/model.safetensors.index.json`;
163
197
  let files: string[];
198
+ let prefixes = ["vision_tower.", "model.visual."];
164
199
  if (existsSync(idxPath)) {
165
200
  const idx = JSON.parse(readFileSync(idxPath, "utf8")) as
166
201
  { weight_map: Record<string, string> };
167
- files = [...new Set(
168
- names.map((n) => idx.weight_map[n]).filter((f): f is string => !!f),
169
- )];
202
+ prefixes = prefixes.filter((p) =>
203
+ Qwen3VLVisionTower.#sourceName(names[0]!, p) in idx.weight_map);
204
+ files = [...new Set(prefixes.flatMap((p) => names
205
+ .map((n) => idx.weight_map[Qwen3VLVisionTower.#sourceName(n, p)])
206
+ .filter((f): f is string => !!f)))];
170
207
  if (files.length === 0)
171
- throw new Error(`no vision_tower tensors in ${modelDir} (index has none)`);
208
+ throw new Error(`no vision tower tensors in ${modelDir} (index has none)`);
172
209
  } else {
173
210
  files = ["model.safetensors"];
174
211
  }
175
212
  for (const f of files)
176
- Qwen3VLVisionTower.#takeFromFile(`${modelDir}/${f}`, names, weights);
213
+ for (const p of prefixes)
214
+ Qwen3VLVisionTower.#takeFromFile(
215
+ `${modelDir}/${f}`, names, weights,
216
+ (n) => Qwen3VLVisionTower.#sourceName(n, p),
217
+ );
177
218
  }
178
219
  for (const name of names)
179
220
  if (!weights.has(name))
180
221
  throw new Error(`qwen vision weights missing tensor ${name} (sidecar or in-main)`);
222
+ Qwen3VLVisionTower.#fixPatchEmbed(weights);
181
223
  } catch (e) {
182
224
  for (const [, a] of weights) a.dispose();
183
225
  throw e;
package/src/web/app.html CHANGED
@@ -1216,7 +1216,7 @@
1216
1216
  <!-- PWA installability (plan §9 Phase 3, beat-matrix Axis 10): manifest +
1217
1217
  inline SVG icon (src/web/manifest.webmanifest, src/web/icon.svg — no
1218
1218
  binary PNGs, per the hygiene gate; some browsers won't render an SVG
1219
- app icon and that's an accepted tradeoff, see features-matrix.md).
1219
+ app icon and that's an accepted tradeoff, see docs/reference/server-config.md).
1220
1220
  theme-color matches the app's --bg token so the OS chrome doesn't
1221
1221
  flash a mismatched color on launch. -->
1222
1222
  <link rel="manifest" href="/manifest.webmanifest">
package/src/web/app.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // GENERATED — do not edit by hand.
2
2
  // Source: src/web/src/*.ts (entrypoint main.ts). To regenerate:
3
3
  // bun scripts/build-web.ts
4
- // tests/web-build.test.ts enforces that this file matches the source.
4
+ // tests/using/web-build.test.ts enforces that this file matches the source.
5
5
  // src/web/src/api.ts
6
6
  async function api(path, opts) {
7
7
  const { body, ...rest } = opts || {};
@@ -2892,11 +2892,13 @@ function initMemoryPanel() {
2892
2892
  maybeShowConsentCard();
2893
2893
  }
2894
2894
 
2895
+ // src/contracts/pi.ts
2896
+ var APP_ROUTE_IDS = ["chat", "quantize", "finetune", "dataset", "status"];
2897
+
2895
2898
  // src/web/src/ui-catalog.ts
2896
- var ROUTE_IDS = ["chat", "quantize", "finetune", "dataset", "status"];
2897
2899
  var VIEW_IDS = ["memory-panel", "hub-panel", "settings", "adapters-panel"];
2898
2900
  function isRouteId(v) {
2899
- return ROUTE_IDS.includes(v);
2901
+ return APP_ROUTE_IDS.includes(v);
2900
2902
  }
2901
2903
  function isViewId(v) {
2902
2904
  return VIEW_IDS.includes(v);
@@ -9,9 +9,9 @@
9
9
  // docs/reference/server-api.md), with compatible-graying and a why-not
10
10
  // tooltip. Actions: mount, select, unselect, and stack two compatible
11
11
  // adapters as "a+b" — resolveSpec/injectAdapter already support composite
12
- // ids end to end (proved in tests/pi-web.test.ts's stacking test; the
12
+ // ids end to end (proved in tests/research/pi-web.test.ts's stacking test; the
13
13
  // shape-validated mount is proved against real weights in the
14
- // MLX_BUN_TEST_LORA=1-gated tests/lora.test.ts).
14
+ // MLX_BUN_TEST_LORA=1-gated tests/parity/lora.test.ts).
15
15
  //
16
16
  // Single source of SELECTION state, not a single fetch: composer.ts's quick
17
17
  // <select> and this table each hit /v1/adapters(+/available) independently
@@ -158,7 +158,7 @@ export function renderAdapterRow(
158
158
  }
159
159
 
160
160
  /** Full table body: a stack-mode toggle + composed-spec bar (when picks
161
- * exist), then one section of rows. Exported so tests/web-app.test.ts can
161
+ * exist), then one section of rows. Exported so tests/using/web-app.test.ts can
162
162
  * exercise esc() discipline without a live DOM/network. */
163
163
  export function renderAdaptersBodyHtml(state: AdaptersPanelState): string {
164
164
  if (!state.available.length) {
@@ -1,3 +1,5 @@
1
+ import type { UiSnapshotElement, UiSnapshot, WizardStep } from "../../contracts/pi";
2
+ export type { UiSnapshotElement, UiSnapshot, WizardStep } from "../../contracts/pi";
1
3
  // GENERATED-ADJACENT source module — part of the src/web/src/* split (plan
2
4
  // §6.6 "the app-aware assistant", §9 Phase 3, beat matrix Axis 12). Built
3
5
  // into src/web/app.js by scripts/build-web.ts.
@@ -26,42 +28,6 @@
26
28
 
27
29
  import { getSpotlightTarget, isViewId, ROUTE_LABELS, type RouteId, type ViewId } from "./ui-catalog";
28
30
 
29
- /* ────────────────────────────────────────────────────────────────────
30
- 1. SEE — the structured DOM snapshot
31
- ──────────────────────────────────────────────────────────────────── */
32
-
33
- export interface UiSnapshotElement {
34
- ref: string;
35
- tag: string;
36
- label: string;
37
- kind: "interactive" | "region";
38
- role?: string;
39
- selector: string;
40
- spotlightId?: string;
41
- }
42
-
43
- export interface UiSnapshot {
44
- route: string;
45
- capturedAt: string;
46
- elements: UiSnapshotElement[];
47
- }
48
-
49
- /** Process-state fields layered onto the snapshot (§6.6 "the snapshot
50
- * carries process state, not just elements") — wizard step for the
51
- * quantize/finetune/dataset wizards, when the current route has one.
52
- * Derived by reading the DOM the step indicator already renders
53
- * (markdown.ts's renderSteps into #q-steps/#f-steps/#d-steps + the
54
- * [data-qstep]/[data-fstep]/[data-dstep] visibility toggle quantize.ts/
55
- * finetune.ts/dataset.ts already do) rather than adding a callback hook to
56
- * those three controllers — zero behavior change to files outside this
57
- * wave's scope, and the DOM IS the source of truth those controllers
58
- * already write to. */
59
- export interface WizardStep {
60
- index: number;
61
- count: number;
62
- label: string;
63
- }
64
-
65
31
  const STEP_CONTAINER_BY_ROUTE: Partial<Record<RouteId, string>> = {
66
32
  quantize: "q-steps",
67
33
  finetune: "f-steps",
@@ -10,15 +10,15 @@
10
10
  // typed, checkable seams between these concerns).
11
11
  //
12
12
  // Typed WS contract (plan §9 Phase 2 item 2): ClientMessage/ServerMessage
13
- // and the history/session shapes are imported TYPE-ONLY from ../../pi-web
13
+ // and the history/session shapes are imported TYPE-ONLY from ../../contracts/pi
14
14
  // so this module (and the whole bundle) contains zero server code — the
15
15
  // bundler errors on bun:ffi if a value import ever sneaks in here, which is
16
16
  // the intended guard (see scripts/build-web.ts).
17
17
 
18
18
  import type {
19
19
  ClientMessage, HistoryItem, ReadyGenDefaults, ServerMessage, SessionListItem,
20
- } from "../../pi-web";
21
- import type { Lane } from "../../serve/lane-registry";
20
+ } from "../../contracts/pi";
21
+ import type { Lane } from "../../contracts/pi";
22
22
  import {
23
23
  $, el, toast, currentRoute, closeDrawer,
24
24
  storedCodingToolsPreference, renderCodingToolsState, renderToolApprovals,
@@ -44,7 +44,7 @@ import { isRouteId } from "./ui-catalog";
44
44
 
45
45
  /* ────────────────────────────────────────────────────────────────────
46
46
  Chat-with-files RAG v1 — Sources panel (plan §9 Phase 3, beat matrix
47
- Axis 5). Pure HTML builder so tests/web-app.test.ts can exercise the
47
+ Axis 5). Pure HTML builder so tests/using/web-app.test.ts can exercise the
48
48
  esc() discipline directly, same reasoning as renderAdapterOptionsHtml
49
49
  etc. in composer.ts.
50
50
  ──────────────────────────────────────────────────────────────────── */
@@ -10,8 +10,8 @@
10
10
  // the original single IIFE), but every WS send goes through a callback
11
11
  // passed in from chat.ts, which owns the actual WebSocket.
12
12
 
13
- import type { ReadyGenDefaults, SamplingOverrides } from "../../pi-web";
14
- import type { Lane } from "../../serve/lane-registry";
13
+ import type { ReadyGenDefaults, SamplingOverrides } from "../../contracts/pi";
14
+ import type { Lane } from "../../contracts/pi";
15
15
  import { api } from "./api";
16
16
  import type { ApiEnvelope } from "./protocol";
17
17
  import {
@@ -200,7 +200,7 @@ function escHtml(s: unknown): string {
200
200
 
201
201
  /** Pure: builds the <option> markup for the adapter <select> from the
202
202
  * /v1/adapters/available list. Split out from refreshAdapters() (which
203
- * does the fetch + DOM write) so tests/web-app.test.ts can exercise the
203
+ * does the fetch + DOM write) so tests/using/web-app.test.ts can exercise the
204
204
  * esc() discipline on this template directly without a DOM or network —
205
205
  * every interpolated field (id, rank, path, base_model) must be escaped,
206
206
  * since adapter ids/paths are user-controlled (on-disk directory names). */
@@ -719,7 +719,7 @@ export function renderLane(lane: Lane | undefined): void {
719
719
  already resolves, so the agent's memory tools pick it up naturally, same
720
720
  as in the vault's own prose). No vault -> files-only picker. Pure
721
721
  query-detection/insertion helpers below are unit-tested directly
722
- (tests/web-app.test.ts); DOM wiring (initMentionPicker) owns the
722
+ (tests/using/web-app.test.ts); DOM wiring (initMentionPicker) owns the
723
723
  picker's open/close/keyboard-nav state.
724
724
  ──────────────────────────────────────────────────────────────────── */
725
725
 
@@ -119,7 +119,7 @@ function localRowHtml(m: HubLocalRow): string {
119
119
  }
120
120
 
121
121
  /** Pure render, exported for esc()-discipline + empty-state tests
122
- * (tests/web-app.test.ts, mirroring renderModelPopBodyHtml's pattern). */
122
+ * (tests/using/web-app.test.ts, mirroring renderModelPopBodyHtml's pattern). */
123
123
  export function renderHubLocalHtml(models: HubLocalRow[]): string {
124
124
  if (!models.length) {
125
125
  return '<div class="hub-empty">No models downloaded yet — search Hugging Face below to get one.</div>';
Binary file
@@ -394,7 +394,7 @@ export async function refreshSidebarEntry(): Promise<void> {
394
394
  // by a literal list rather than an import: this module must stay
395
395
  // server-code-free (bundler-enforced, see chat.ts's header comment), so it
396
396
  // can't import a value from src/memory/tools.ts even though the identifiers
397
- // are the same. tests/web-app.test.ts cross-checks this list against the
397
+ // are the same. tests/using/web-app.test.ts cross-checks this list against the
398
398
  // real export so drift is caught at test time instead of silently.
399
399
  export const MEMORY_CHIP_TOOL_NAMES = [
400
400
  "memory_resolve", "memory_category", "memory_read", "memory_section",
@@ -85,7 +85,7 @@ function renderRow(m: LibraryRow): string {
85
85
  }
86
86
 
87
87
  /** Pure render of the popover body from a /library response. Exported for
88
- * tests/web-app.test.ts's esc() discipline check (repo ids are
88
+ * tests/using/web-app.test.ts's esc() discipline check (repo ids are
89
89
  * user-controlled HF strings — the same class of interpolation hazard
90
90
  * finding #15 flagged for the adapter dropdown). */
91
91
  export function renderModelPopBodyHtml(models: LibraryRow[]): string {
@@ -39,7 +39,7 @@ interface PaletteAction {
39
39
  }
40
40
 
41
41
  /** Pure: the static action list, independent of any live session data —
42
- * independently testable (tests/web-app.test.ts asserts labels/ids without
42
+ * independently testable (tests/using/web-app.test.ts asserts labels/ids without
43
43
  * needing a DOM). Session rows and message-search hits are appended
44
44
  * dynamically in refreshResults() below, not part of this list. */
45
45
  export function staticActions(): PaletteAction[] {
@@ -1,47 +1,11 @@
1
- // Shared frontend-only protocol shapes that don't already live on the
2
- // server side as exported types. The WS contract itself (ClientMessage /
3
- // ServerMessage / HistoryItem / SessionListItem / ReadyGenDefaults) is
4
- // imported type-only from ../../pi-web — see chat.ts. This file only holds
5
- // shapes with no server-side export to anchor to (e.g. the SSE job-stream
6
- // events, which live as a doc-comment in app.html today, not a type).
7
- //
8
- // Pointer comment for the server side: src/web/src/protocol.ts mirrors the
9
- // SSE line protocol documented at the jobStream() call site (api.ts) —
10
- // keep the two adjacent if the job event shape changes server-side
11
- // (scripts/ that emit `data:` lines for /api/jobs/:id/stream).
12
-
13
- /** One line of the /api/jobs/:id/stream SSE protocol (JSON per `data:` line). */
14
- export type JobEvent =
15
- | { type: "log"; line: string }
16
- | {
17
- type: "stage";
18
- stage: string;
19
- progress?: number;
20
- message?: string;
21
- output_dir?: string;
22
- adapter_path?: string;
23
- // Dataset-generation-specific row counts (present on the dataset
24
- // builder's job stream only; absent/ignored for quantize/finetune).
25
- n_train?: number;
26
- n_valid?: number;
27
- }
28
- | {
29
- type: "metric";
30
- kind: "train" | "val";
31
- step: number;
32
- loss: number;
33
- learning_rate?: number;
34
- tokens_per_sec?: number;
35
- progress?: number;
36
- message?: string;
37
- }
38
- | { type: "done"; n_train?: number; n_valid?: number }
39
- | { type: "failed"; error?: string };
1
+ import type { JobEvent } from "../../contracts/jobs";
2
+ export type { JobEvent } from "../../contracts/jobs";
40
3
 
41
4
  /** Handlers passed to jobStream(); each key is an optional callback for the
42
5
  * matching JobEvent variant, plus an `error` callback for the EventSource's
43
6
  * own onerror (transport failure, not a `failed` protocol event). */
44
7
  export interface JobStreamHandlers {
8
+ started?: (e: Extract<JobEvent, { type: "started" }>) => void;
45
9
  log?: (e: Extract<JobEvent, { type: "log" }>) => void;
46
10
  stage?: (e: Extract<JobEvent, { type: "stage" }>) => void;
47
11
  metric?: (e: Extract<JobEvent, { type: "metric" }>) => void;
@@ -20,7 +20,7 @@
20
20
  // wires each rendered `.sess` row's click to `onOpen` below — this just
21
21
  // finds and clicks the right row instead of duplicating that send()).
22
22
 
23
- import type { SessionListItem } from "../../pi-web";
23
+ import type { SessionListItem } from "../../contracts/pi";
24
24
  import { $, el, injectStyles } from "./shell";
25
25
  import { api } from "./api";
26
26
 
@@ -2,28 +2,14 @@
2
2
  // §7/§9 Phase 3, §6.6 "app-aware assistant"). Built into src/web/app.js by
3
3
  // scripts/build-web.ts.
4
4
  //
5
- // The frontend half of the app-aware assistant's route/spotlight catalog.
6
- // Mirrors PortfolioManager's ui-catalog.json pattern (client/src/lib/
7
- // ui-catalog.ts + server/src/agent/ui-catalog.ts, read together with
8
- // server/src/agent/portfolio-tools.ts as the reference implementation) but
9
- // inlined as a TS module (not a shared JSON file) since mlx-bun's server and
10
- // browser bundles are separate build outputs (scripts/build-web.ts vs bun's
11
- // own TS execution) with no shared-JSON-import convention today — pi-web.ts
12
- // mirrors ROUTE_IDS/SPOTLIGHT_TARGETS as plain string arrays for its own
13
- // server-side validation (navigate_app/spotlight_ui reject anything not in
14
- // that list BEFORE touching the browser) so route validity is checked at
15
- // both ends without a build-time shared asset.
16
- //
17
- // Keep this list's ROUTE_IDS in sync with shell.ts's ROUTES tuple (chat,
18
- // quantize, finetune, dataset, status) plus the Developer-only overlays that
19
- // aren't hash-routes (memory panel, hub panel, settings) — those are
20
- // `view`s, not `route`s (see UiContext.view below), reached by opening an
21
- // overlay rather than changing the URL hash.
5
+ // Shared route IDs keep browser navigation and server validation in agreement.
6
+ // Spotlight selectors and overlay IDs belong to the browser.
22
7
 
23
8
  /** A hash-routed page (shell.ts's Route union, minus "routes" — the DAG
24
9
  * diagram tab isn't a useful assistant destination and is itself
25
10
  * feature-detected/hidden when unavailable). */
26
- export const ROUTE_IDS = ["chat", "quantize", "finetune", "dataset", "status"] as const;
11
+ import { APP_ROUTE_IDS as ROUTE_IDS } from "../../contracts/pi";
12
+ export { ROUTE_IDS };
27
13
  export type RouteId = (typeof ROUTE_IDS)[number];
28
14
 
29
15
  /** A non-route overlay/panel the assistant can navigate to or spotlight
@@ -0,0 +1,47 @@
1
+ // Embedded web app — the unified SPA (chat / quantize / finetune /
2
+ // dataset / status). `with { type: "text" }` inlines the file in both
3
+ // `bun run` and the compiled single binary. bun-types types *.html
4
+ // imports as HTMLBundle (the html loader), but the text attribute makes
5
+ // the runtime value a string — hence the double cast.
6
+ import appHtml from "./web/app.html" with { type: "text" };
7
+ import curveDesignerHtml from "./lab/curve/curve-designer.html" with { type: "text" };
8
+ // Vendored, no-CDN static assets referenced by app.html (convention: any
9
+ // self-contained JS/CSS too big to inline gets `with { type: "text" }`
10
+ // imported here and served under /assets/<name>; see src/web/vendor/README
11
+ // for how hljs.js was built). Add new vendored assets the same way.
12
+ import hljsJs from "./web/vendor/hljs.js" with { type: "text" };
13
+ import hljsCss from "./web/vendor/hljs-theme.css" with { type: "text" };
14
+ // The frontend bundle (plan §7/§9 Phase 2 module split): GENERATED from
15
+ // src/web/src/*.ts by `bun scripts/build-web.ts` — see that file's header
16
+ // and tests/using/web-build.test.ts (the freshness gate). Same
17
+ // with { type: "text" } + /assets/<name> pattern as the vendored assets
18
+ // above; app.html's <script defer src="/assets/app.js"> loads it.
19
+ import appJs from "./web/app.js" with { type: "text" };
20
+ // PWA installability (plan §9 Phase 3, beat-matrix Axis 10): a manifest +
21
+ // a single inline SVG icon (no binary PNGs — the hygiene gate forbids
22
+ // tracked binary files; some browsers won't show an SVG app icon, which is
23
+ // an accepted tradeoff, see docs/reference/server-config.md) + a shell-
24
+ // only service worker. Same with { type: "text" } + /assets/<name>-shaped
25
+ // pattern as the vendored assets above; see src/web/sw.js's header for why
26
+ // it deliberately does NOT cache API/WS traffic.
27
+ import manifestWebmanifest from "./web/manifest.webmanifest" with { type: "text" };
28
+ import iconSvg from "./web/icon.svg" with { type: "text" };
29
+ import swJs from "./web/sw.js" with { type: "text" };
30
+ const APP_PAGE = appHtml as unknown as string;
31
+ const HLJS_JS = hljsJs as unknown as string;
32
+ const HLJS_CSS = hljsCss as unknown as string;
33
+ const APP_JS = appJs as unknown as string;
34
+ const MANIFEST_WEBMANIFEST = manifestWebmanifest as unknown as string;
35
+ const ICON_SVG = iconSvg as unknown as string;
36
+ const SW_JS = swJs as unknown as string;
37
+ const CURVE_PAGE = curveDesignerHtml as unknown as string;
38
+ export const STATIC_ROUTE_ASSETS = {
39
+ appPage: APP_PAGE,
40
+ appJs: APP_JS,
41
+ hljsJs: HLJS_JS,
42
+ hljsCss: HLJS_CSS,
43
+ manifest: MANIFEST_WEBMANIFEST,
44
+ iconSvg: ICON_SVG,
45
+ serviceWorker: SW_JS,
46
+ curvePage: CURVE_PAGE,
47
+ };
package/src/weights.ts CHANGED
@@ -59,12 +59,30 @@ const NATIVE_WEIGHTS: WeightsNativeBindings = {
59
59
  ),
60
60
  };
61
61
 
62
+ /**
63
+ * Checkpoint-generation normalization, installed once at load.
64
+ *
65
+ * `names` maps the GRAPH's canonical tensor name onto the name the artifact
66
+ * actually stores; a canonical name absent from the map does not exist (the
67
+ * same way a reference `sanitize()` drops a tensor). `fixup` is an optional
68
+ * lazy value repair applied to the loaded array — return null to keep it.
69
+ *
70
+ * Weights only APPLIES a view; the rules belong to the architecture that owns
71
+ * the naming (see `qwen35WeightsView` in model/qwen3_5-checkpoint.ts).
72
+ */
73
+ export interface WeightsView {
74
+ readonly names: ReadonlyMap<string, string>;
75
+ fixup?(canonical: string, arr: MlxArray): MlxArray | null;
76
+ }
77
+
62
78
  export class Weights {
63
79
  readonly shards: ShardedSafetensors;
64
80
  /** shard filename → native mlx map handle (string → lazy array). */
65
81
  readonly #maps = new Map<string, bigint>();
82
+ /** Cached arrays, keyed by CANONICAL name (post-view). */
66
83
  readonly #arrays = new Map<string, MlxArray>();
67
84
  readonly #native: WeightsNativeBindings;
85
+ #view: WeightsView | null = null;
68
86
 
69
87
  private constructor(
70
88
  shards: ShardedSafetensors,
@@ -120,6 +138,19 @@ export class Weights {
120
138
  }
121
139
  }
122
140
 
141
+ /** Install the checkpoint normalization. Must precede the first tensor()
142
+ * call: the array cache and every shard lookup below are keyed through it. */
143
+ setView(view: WeightsView): void {
144
+ if (this.#arrays.size > 0)
145
+ throw new Error("Weights.setView after tensors were materialized");
146
+ this.#view = view;
147
+ }
148
+
149
+ /** Canonical name → the stored name (identity when no view is installed). */
150
+ #source(name: string): string {
151
+ return this.#view ? this.#view.names.get(name) ?? name : name;
152
+ }
153
+
123
154
  /** Free one shard's native map AND every cached tensor from it, releasing
124
155
  * all bytes materialized while consuming that shard. The shard transparently
125
156
  * re-opens on the next tensor() touching it (header parse only — lazy).
@@ -130,7 +161,7 @@ export class Weights {
130
161
  const sf = this.shards.files.get(file);
131
162
  if (!sf) return;
132
163
  for (const [name, arr] of this.#arrays) {
133
- if (this.shards.tensorToFile.get(name) === sf) {
164
+ if (this.shards.tensorToFile.get(this.#source(name)) === sf) {
134
165
  arr.dispose();
135
166
  this.#arrays.delete(name);
136
167
  }
@@ -144,29 +175,36 @@ export class Weights {
144
175
 
145
176
  /** The shard filename holding `name` (for releaseShard scheduling). */
146
177
  fileOf(name: string): string | undefined {
147
- const sf = this.shards.tensorToFile.get(name);
178
+ const sf = this.shards.tensorToFile.get(this.#source(name));
148
179
  if (!sf) return undefined;
149
180
  for (const [file, cand] of this.shards.files) if (cand === sf) return file;
150
181
  return undefined;
151
182
  }
152
183
 
184
+ /** Canonical names — with a view installed, exactly the tensors the graph
185
+ * can ask for (renamed, drops excluded). */
153
186
  get tensorNames(): string[] {
154
- return this.shards.tensorNames;
187
+ return this.#view ? [...this.#view.names.keys()] : this.shards.tensorNames;
155
188
  }
156
189
 
157
190
  info(name: string): TensorInfo {
158
- return this.shards.info(name);
191
+ return this.shards.info(this.#source(name));
159
192
  }
160
193
 
161
194
  has(name: string): boolean {
162
- return this.shards.tensorToFile.has(name);
195
+ return this.#view
196
+ ? this.#view.names.has(name)
197
+ : this.shards.tensorToFile.has(name);
163
198
  }
164
199
 
165
- /** Lazy mlx array for a tensor; cached per name. */
200
+ /** Lazy mlx array for a tensor; cached per canonical name. */
166
201
  tensor(name: string): MlxArray {
167
202
  let arr = this.#arrays.get(name);
168
203
  if (!arr) {
169
- const sf = this.shards.tensorToFile.get(name);
204
+ if (this.#view && !this.#view.names.has(name))
205
+ throw new Error(`no tensor named ${name}`);
206
+ const source = this.#source(name);
207
+ const sf = this.shards.tensorToFile.get(source);
170
208
  if (!sf) throw new Error(`no tensor named ${name}`);
171
209
  let entry = [...this.#maps.entries()]
172
210
  .find(([file]) => this.shards.files.get(file) === sf);
@@ -179,9 +217,17 @@ export class Weights {
179
217
  const mapHandle = entry[1];
180
218
  const slot = new BigUint64Array([C.mlx_array_new()]);
181
219
  const slotPtr = ptr(slot);
182
- if (C.mlx_map_string_to_array_get(slotPtr, mapHandle, ptr(cstr(name))) !== 0)
183
- throw new Error(`tensor ${name} missing from native map`);
220
+ if (C.mlx_map_string_to_array_get(slotPtr, mapHandle, ptr(cstr(source))) !== 0)
221
+ throw new Error(`tensor ${source} missing from native map`);
184
222
  arr = new MlxArray(read.u64(slotPtr, 0));
223
+ // Value repair (γ−1 → γ, conv layout): the fixed array is a lazy graph
224
+ // node over the source, so the raw handle is released immediately — the
225
+ // node keeps its own reference to the underlying data.
226
+ const fixed = this.#view?.fixup?.(name, arr) ?? null;
227
+ if (fixed) {
228
+ arr.dispose();
229
+ arr = fixed;
230
+ }
185
231
  this.#arrays.set(name, arr);
186
232
  }
187
233
  return arr;