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
@@ -0,0 +1,555 @@
1
+ // Qwen3-VL vision tower (Qwen3.8's `vision_tower`, PLAN 14v) — faithful port
2
+ // of mlx-vlm qwen3_vl/vision.py at the qwen3_5 configuration: depth 27,
3
+ // hidden 1152, heads 16 (head_dim 72), patch 16, temporal 2, merge 2,
4
+ // intermediate 4304 with gelu_pytorch_tanh, learned 48×48 pos-embed grid
5
+ // bilinearly interpolated, 2D half/half rotary (dim 36) over full head_dim,
6
+ // pre-LN blocks with biased LayerNorms, merger LayerNorm→fc1→GELU(erf)→fc2
7
+ // to out_hidden 5120. deepstack is EMPTY for qwen3_5 and not ported.
8
+ //
9
+ // Weights: the artifact ships the tower as a bf16 sidecar
10
+ // (optiq/optiq_vision.safetensors, 333 tensors, MLX conv layout). The
11
+ // Conv3d patch embed has kernel == stride, so it is computed as one matmul
12
+ // against the weight flattened to the preprocessor's (c, t, py, px) row
13
+ // order — same values, GEMM accumulation (gate: tower output vs the mlx-vlm
14
+ // capture; e2e tokens are the decisive gate, the SigLIP precedent).
15
+ //
16
+ // Input rows arrive MERGE-BLOCK-MAJOR from qwen3vl-preprocess.ts; rope
17
+ // h/w positions and the pos-embed permutation are built in the same order.
18
+
19
+ import { ptr, read } from "bun:ffi";
20
+ import { existsSync, readFileSync } from "node:fs";
21
+ import { MlxArray, cpuStream } from "../mlx/array";
22
+ import { C, Dtype } from "../mlx/ffi";
23
+ import * as ops from "../mlx/ops";
24
+ import type { Qwen3VLPreprocessed } from "./qwen3vl-preprocess";
25
+ import {
26
+ QWEN3VL_MERGE_SIZE,
27
+ QWEN3VL_PATCH_SIZE,
28
+ QWEN3VL_TEMPORAL_PATCH_SIZE,
29
+ } from "./qwen3vl-preprocess";
30
+
31
+ const cstr = (s: string) => Buffer.from(s + "\0", "utf8");
32
+
33
+ const DEPTH = 27;
34
+ const HIDDEN = 1152;
35
+ const HEADS = 16;
36
+ const HEAD_DIM = HIDDEN / HEADS; // 72
37
+ const ROT_DIM = HEAD_DIM / 2; // 36 (VisionRotaryEmbedding dim)
38
+ const INTERMEDIATE = 4304;
39
+ const OUT_HIDDEN = 5120;
40
+ const POS_GRID = 48; // sqrt(num_position_embeddings 2304)
41
+ const ROPE_THETA = 10000;
42
+
43
+ function disposing(old: MlxArray, next: MlxArray): MlxArray {
44
+ old.dispose();
45
+ return next;
46
+ }
47
+
48
+ interface Block {
49
+ norm1W: MlxArray; norm1B: MlxArray;
50
+ norm2W: MlxArray; norm2B: MlxArray;
51
+ qkvW: MlxArray; qkvB: MlxArray;
52
+ projW: MlxArray; projB: MlxArray;
53
+ fc1W: MlxArray; fc1B: MlxArray;
54
+ fc2W: MlxArray; fc2B: MlxArray;
55
+ }
56
+
57
+ export class Qwen3VLVisionTower {
58
+ #patchW: MlxArray; // [1536, 1152] — flattened conv, already transposed
59
+ #patchB: MlxArray;
60
+ #posEmbed: MlxArray; // [2304, 1152]
61
+ #blocks: Block[];
62
+ #mergerNormW: MlxArray; #mergerNormB: MlxArray;
63
+ #mergerFc1W: MlxArray; #mergerFc1B: MlxArray;
64
+ #mergerFc2W: MlxArray; #mergerFc2B: MlxArray;
65
+
66
+ private constructor(w: Map<string, MlxArray>) {
67
+ const t = (n: string): MlxArray => {
68
+ const a = w.get(`vision_tower.${n}`);
69
+ if (!a) throw new Error(`vision sidecar missing tensor vision_tower.${n}`);
70
+ return a;
71
+ };
72
+ // Conv3d weight [1152, 2, 16, 16, 3] (out, kT, kH, kW, in) — used with
73
+ // the REAL conv3d op (kernel == stride): a value-equivalent GEMM rounds
74
+ // differently in bf16 and the ulp seeds diverge through 27 blocks.
75
+ this.#patchW = t("patch_embed.proj.weight");
76
+ this.#patchB = t("patch_embed.proj.bias");
77
+ this.#posEmbed = t("pos_embed.weight");
78
+ this.#blocks = [];
79
+ for (let i = 0; i < DEPTH; i++) {
80
+ const p = `blocks.${i}`;
81
+ this.#blocks.push({
82
+ norm1W: t(`${p}.norm1.weight`), norm1B: t(`${p}.norm1.bias`),
83
+ norm2W: t(`${p}.norm2.weight`), norm2B: t(`${p}.norm2.bias`),
84
+ qkvW: t(`${p}.attn.qkv.weight`), qkvB: t(`${p}.attn.qkv.bias`),
85
+ projW: t(`${p}.attn.proj.weight`), projB: t(`${p}.attn.proj.bias`),
86
+ fc1W: t(`${p}.mlp.linear_fc1.weight`), fc1B: t(`${p}.mlp.linear_fc1.bias`),
87
+ fc2W: t(`${p}.mlp.linear_fc2.weight`), fc2B: t(`${p}.mlp.linear_fc2.bias`),
88
+ });
89
+ }
90
+ this.#mergerNormW = t("merger.norm.weight");
91
+ this.#mergerNormB = t("merger.norm.bias");
92
+ this.#mergerFc1W = t("merger.linear_fc1.weight");
93
+ this.#mergerFc1B = t("merger.linear_fc1.bias");
94
+ this.#mergerFc2W = t("merger.linear_fc2.weight");
95
+ this.#mergerFc2B = t("merger.linear_fc2.bias");
96
+ }
97
+
98
+ /** The 333 tensor names the tower models, by constructed name. */
99
+ static #tensorNames(): string[] {
100
+ const names: string[] = [
101
+ "vision_tower.patch_embed.proj.weight", "vision_tower.patch_embed.proj.bias",
102
+ "vision_tower.pos_embed.weight",
103
+ "vision_tower.merger.norm.weight", "vision_tower.merger.norm.bias",
104
+ "vision_tower.merger.linear_fc1.weight", "vision_tower.merger.linear_fc1.bias",
105
+ "vision_tower.merger.linear_fc2.weight", "vision_tower.merger.linear_fc2.bias",
106
+ ];
107
+ for (let i = 0; i < DEPTH; i++) {
108
+ for (const nm of [
109
+ "norm1.weight", "norm1.bias", "norm2.weight", "norm2.bias",
110
+ "attn.qkv.weight", "attn.qkv.bias", "attn.proj.weight", "attn.proj.bias",
111
+ "mlp.linear_fc1.weight", "mlp.linear_fc1.bias",
112
+ "mlp.linear_fc2.weight", "mlp.linear_fc2.bias",
113
+ ]) names.push(`vision_tower.blocks.${i}.${nm}`);
114
+ }
115
+ return names;
116
+ }
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
+
146
+ /** Load one safetensors file's map and move `wanted` tensors into `out`
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 {
154
+ const arrMap = new BigUint64Array([C.mlx_map_string_to_array_new()]);
155
+ const metaMap = new BigUint64Array([C.mlx_map_string_to_string_new()]);
156
+ const arrMapPtr = ptr(arrMap);
157
+ const metaMapPtr = ptr(metaMap);
158
+ const status = C.mlx_load_safetensors(arrMapPtr, metaMapPtr, ptr(cstr(path)), cpuStream);
159
+ C.mlx_map_string_to_string_free(read.u64(metaMapPtr, 0));
160
+ const handle = read.u64(arrMapPtr, 0);
161
+ if (status !== 0) {
162
+ // The out-param map was allocated regardless — free it on the error
163
+ // path too (2026-08-18 review; one-shot leak, but a leak).
164
+ C.mlx_map_string_to_array_free(handle);
165
+ throw new Error(`failed to load ${path}`);
166
+ }
167
+ try {
168
+ for (const name of wanted) {
169
+ if (out.has(name)) continue;
170
+ const slot = new BigUint64Array([C.mlx_array_new()]);
171
+ const slotPtr = ptr(slot);
172
+ if (C.mlx_map_string_to_array_get(slotPtr, handle, ptr(cstr(source(name)))) !== 0) {
173
+ C.mlx_array_free(read.u64(slotPtr, 0));
174
+ continue;
175
+ }
176
+ out.set(name, new MlxArray(read.u64(slotPtr, 0)));
177
+ }
178
+ } finally {
179
+ C.mlx_map_string_to_array_free(handle);
180
+ }
181
+ }
182
+
183
+ static load(modelDir: string): Qwen3VLVisionTower {
184
+ const names = Qwen3VLVisionTower.#tensorNames();
185
+ const weights = new Map<string, MlxArray>();
186
+ try {
187
+ const sidecar = `${modelDir}/optiq/optiq_vision.safetensors`;
188
+ if (existsSync(sidecar)) {
189
+ // OptiQ-convention sidecar (their artifacts + ours pre-2026-08-18).
190
+ Qwen3VLVisionTower.#takeFromFile(sidecar, names, weights);
191
+ } else {
192
+ // In-main fallback: the bf16 tower tensors live in the main
193
+ // safetensors shards (the mlx-vlm convention — one copy serves both
194
+ // ecosystems; sidecar duplication retired 2026-08-18). The 5.8-family
195
+ // export names them `model.visual.*` instead of `vision_tower.*`.
196
+ const idxPath = `${modelDir}/model.safetensors.index.json`;
197
+ let files: string[];
198
+ let prefixes = ["vision_tower.", "model.visual."];
199
+ if (existsSync(idxPath)) {
200
+ const idx = JSON.parse(readFileSync(idxPath, "utf8")) as
201
+ { weight_map: Record<string, string> };
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)))];
207
+ if (files.length === 0)
208
+ throw new Error(`no vision tower tensors in ${modelDir} (index has none)`);
209
+ } else {
210
+ files = ["model.safetensors"];
211
+ }
212
+ for (const f of files)
213
+ for (const p of prefixes)
214
+ Qwen3VLVisionTower.#takeFromFile(
215
+ `${modelDir}/${f}`, names, weights,
216
+ (n) => Qwen3VLVisionTower.#sourceName(n, p),
217
+ );
218
+ }
219
+ for (const name of names)
220
+ if (!weights.has(name))
221
+ throw new Error(`qwen vision weights missing tensor ${name} (sidecar or in-main)`);
222
+ Qwen3VLVisionTower.#fixPatchEmbed(weights);
223
+ } catch (e) {
224
+ for (const [, a] of weights) a.dispose();
225
+ throw e;
226
+ }
227
+ return new Qwen3VLVisionTower(weights);
228
+ }
229
+
230
+ /** One vision block: pre-LN attention (+residual), pre-LN MLP (+residual).
231
+ * Consumes `h`. */
232
+ #block(
233
+ h: MlxArray, blk: Block, cos: MlxArray, sin: MlxArray,
234
+ scale: number, n: number, segLen: number = n,
235
+ ): MlxArray {
236
+ const xn = ops.layerNorm(h, blk.norm1W, blk.norm1B, 1e-6);
237
+ const qkv = this.#linear(xn, blk.qkvW, blk.qkvB); // [n, 3456]
238
+ xn.dispose();
239
+ const qkv4 = disposing(qkv, ops.reshape(qkv, [n, 3, HEADS, HEAD_DIM]));
240
+ const [qs, ks, vs] = ops.split(qkv4, [1, 2], 1) as [MlxArray, MlxArray, MlxArray];
241
+ qkv4.dispose();
242
+ let q = disposing(qs, ops.reshape(qs, [n, HEADS, HEAD_DIM]));
243
+ let k = disposing(ks, ops.reshape(ks, [n, HEADS, HEAD_DIM]));
244
+ const v = disposing(vs, ops.reshape(vs, [n, HEADS, HEAD_DIM]));
245
+ q = disposing(q, this.#applyRope(q, cos, sin));
246
+ k = disposing(k, this.#applyRope(k, cos, sin));
247
+ // [1, HEADS, n, HEAD_DIM] full (non-causal) attention — one image =
248
+ // one cu_seqlens segment, so no mask. The reference's ensure_fused_sdpa
249
+ // ZERO-PADS head_dim 72 → 80 (nearest fused-kernel width), runs fused
250
+ // sdpa there, and slices back; the padded kernel's accumulation is the
251
+ // parity-relevant arithmetic, so replicate it exactly.
252
+ const PAD_DIM = 80;
253
+ const padTo = (x: MlxArray): MlxArray => {
254
+ const z = ops.zeros([n, HEADS, PAD_DIM - HEAD_DIM], Dtype.bfloat16);
255
+ const p = ops.concatAxis([x, z], -1);
256
+ z.dispose();
257
+ return disposing(x, p);
258
+ };
259
+ const lift = (x: MlxArray): MlxArray => {
260
+ const p = padTo(x); // consumes x
261
+ const tr = ops.transposeAxes(p, [1, 0, 2]);
262
+ p.dispose();
263
+ return disposing(tr, ops.expandDims(tr, 0));
264
+ };
265
+ const qT = lift(q);
266
+ const kT = lift(k);
267
+ const vT = lift(v);
268
+ // cu_seqlens split: one sdpa per temporal segment (single segment for
269
+ // images), outputs concatenated in order — the reference's exact walk.
270
+ let attnP: MlxArray;
271
+ if (segLen === n) {
272
+ attnP = ops.sdpa(qT, kT, vT, scale, "", null);
273
+ } else {
274
+ const outs: MlxArray[] = [];
275
+ for (let s = 0; s < n; s += segLen) {
276
+ const qs = qT.slice([0, 0, s, 0], [1, HEADS, s + segLen, PAD_DIM]);
277
+ const ks = kT.slice([0, 0, s, 0], [1, HEADS, s + segLen, PAD_DIM]);
278
+ const vsS = vT.slice([0, 0, s, 0], [1, HEADS, s + segLen, PAD_DIM]);
279
+ outs.push(ops.sdpa(qs, ks, vsS, scale, "", null));
280
+ qs.dispose(); ks.dispose(); vsS.dispose();
281
+ }
282
+ attnP = ops.concatAxis(outs, 2);
283
+ for (const o of outs) o.dispose();
284
+ }
285
+ qT.dispose(); kT.dispose(); vT.dispose();
286
+ const attn = disposing(
287
+ attnP, attnP.slice([0, 0, 0, 0], [1, HEADS, n, HEAD_DIM]),
288
+ );
289
+ const attnT = disposing(attn, ops.transposeAxes(attn, [0, 2, 1, 3]));
290
+ const merged = disposing(attnT, ops.reshape(attnT, [n, HIDDEN]));
291
+ const proj = disposing(merged, this.#linear(merged, blk.projW, blk.projB));
292
+ h = disposing(h, disposing(proj, ops.add(h, proj)));
293
+ const mn = ops.layerNorm(h, blk.norm2W, blk.norm2B, 1e-6);
294
+ const f1 = disposing(mn, this.#linear(mn, blk.fc1W, blk.fc1B));
295
+ const act = disposing(f1, ops.geluApprox(f1)); // gelu_pytorch_tanh
296
+ const f2 = disposing(act, this.#linear(act, blk.fc2W, blk.fc2B));
297
+ return disposing(h, disposing(f2, ops.add(h, f2)));
298
+ }
299
+
300
+ /** nn.Linear on bf16 — mx.addmm(bias, x, W^T), the reference's exact bias
301
+ * path (a separate matmul+add rounds differently and seeded the ±1-ulp
302
+ * block divergence found in the stage bisect). */
303
+ #linear(x: MlxArray, w: MlxArray, b: MlxArray): MlxArray {
304
+ const wT = ops.transposeAxes(w, [1, 0]);
305
+ const y = ops.addmm(b, x, wT);
306
+ wT.dispose();
307
+ return y;
308
+ }
309
+
310
+ /** Bilinearly interpolated learned pos embeds, in merge-block-major row
311
+ * order — port of fast_pos_embed_interpolate + the merge permutation. */
312
+ #interpolatedPosEmbeds(gridH: number, gridW: number): MlxArray {
313
+ const n = gridH * gridW;
314
+ const idx = [new Int32Array(n), new Int32Array(n), new Int32Array(n), new Int32Array(n)];
315
+ const wts = [new Float32Array(n), new Float32Array(n), new Float32Array(n), new Float32Array(n)];
316
+ const lin = (count: number): Float32Array => {
317
+ // mx.linspace(0, POS_GRID-1, count) in f32.
318
+ const outArr = new Float32Array(count);
319
+ if (count === 1) { outArr[0] = 0; return outArr; }
320
+ const step = Math.fround((POS_GRID - 1) / (count - 1));
321
+ for (let i = 0; i < count; i++) outArr[i] = Math.fround(i * step);
322
+ return outArr;
323
+ };
324
+ const hIdx = lin(gridH);
325
+ const wIdx = lin(gridW);
326
+ for (let h = 0; h < gridH; h++) {
327
+ const hf = Math.trunc(hIdx[h]!);
328
+ const hc = Math.min(hf + 1, POS_GRID - 1);
329
+ const dh = Math.fround(hIdx[h]! - hf);
330
+ for (let x = 0; x < gridW; x++) {
331
+ const wf = Math.trunc(wIdx[x]!);
332
+ const wc = Math.min(wf + 1, POS_GRID - 1);
333
+ const dw = Math.fround(wIdx[x]! - wf);
334
+ const i = h * gridW + x;
335
+ idx[0]![i] = hf * POS_GRID + wf;
336
+ idx[1]![i] = hf * POS_GRID + wc;
337
+ idx[2]![i] = hc * POS_GRID + wf;
338
+ idx[3]![i] = hc * POS_GRID + wc;
339
+ wts[0]![i] = Math.fround((1 - dh) * (1 - dw));
340
+ wts[1]![i] = Math.fround((1 - dh) * dw);
341
+ wts[2]![i] = Math.fround(dh * (1 - dw));
342
+ wts[3]![i] = Math.fround(dh * dw);
343
+ }
344
+ }
345
+ // pos_embeds[k] = pos_embed[idx_k] * w_k (bf16, like the reference's
346
+ // weight tensor cast); summed pairwise in reference order.
347
+ let acc: MlxArray | null = null;
348
+ for (let k = 0; k < 4; k++) {
349
+ const ids = MlxArray.fromInt32(idx[k]!, [n]);
350
+ const rows = ops.takeAxis(this.#posEmbed, ids, 0); // [n, 1152] bf16
351
+ ids.dispose();
352
+ const wf = MlxArray.fromFloat32(wts[k]!, [n, 1]);
353
+ const wb = disposing(wf, wf.astype(Dtype.bfloat16));
354
+ const term = ops.mul(rows, wb);
355
+ rows.dispose();
356
+ wb.dispose();
357
+ acc = acc ? disposing(acc, disposing(term, ops.add(acc, term))) : term;
358
+ }
359
+ // Merge-block-major permutation (rows currently h-major patch order).
360
+ const ms = QWEN3VL_MERGE_SIZE;
361
+ const permIdx = new Int32Array(n);
362
+ let r = 0;
363
+ for (let h1 = 0; h1 < gridH / ms; h1++)
364
+ for (let w1 = 0; w1 < gridW / ms; w1++)
365
+ for (let mh = 0; mh < ms; mh++)
366
+ for (let mw = 0; mw < ms; mw++)
367
+ permIdx[r++] = (h1 * ms + mh) * gridW + (w1 * ms + mw);
368
+ const pIds = MlxArray.fromInt32(permIdx, [n]);
369
+ const out = ops.takeAxis(acc!, pIds, 0);
370
+ pIds.dispose();
371
+ acc!.dispose();
372
+ return out;
373
+ }
374
+
375
+ /** 2D rotary cos/sin [n, HEAD_DIM] f32 in merge-block-major order —
376
+ * port of rot_pos_emb + apply_rotary_pos_emb_vision's tile(·,2). */
377
+ #ropeCosSin(gridH: number, gridW: number): [MlxArray, MlxArray] {
378
+ const n = gridH * gridW;
379
+ const half = ROT_DIM / 2; // 18 inv freqs
380
+ // inv_freq on-device, the reference's exact graph — Metal powf is not
381
+ // correctly rounded, so a JS Math.pow emulation differs by 1 ulp on some
382
+ // entries (seen as 2.4e-7 in the freq gate before this).
383
+ const invFreq = (() => {
384
+ const ar = ops.arange(0, ROT_DIM, 2, Dtype.float32);
385
+ const ex = disposing(ar, ops.mulScalar(ar, 1 / ROT_DIM));
386
+ const theta = MlxArray.fromFloat32(Float32Array.from([ROPE_THETA]), [1]);
387
+ const p = ops.pow(theta, ex);
388
+ theta.dispose();
389
+ ex.dispose();
390
+ const one = MlxArray.fromFloat32(Float32Array.from([1]), [1]);
391
+ const inv = disposing(p, ops.div(one, p));
392
+ one.dispose();
393
+ const v = inv.toFloat32();
394
+ inv.dispose();
395
+ return v;
396
+ })();
397
+ const ms = QWEN3VL_MERGE_SIZE;
398
+ const freqs = new Float32Array(n * ROT_DIM);
399
+ let r = 0;
400
+ for (let h1 = 0; h1 < gridH / ms; h1++) {
401
+ for (let w1 = 0; w1 < gridW / ms; w1++) {
402
+ for (let mh = 0; mh < ms; mh++) {
403
+ for (let mw = 0; mw < ms; mw++) {
404
+ const hPos = h1 * ms + mh;
405
+ const wPos = w1 * ms + mw;
406
+ const base = r * ROT_DIM;
407
+ for (let i = 0; i < half; i++) {
408
+ freqs[base + i] = Math.fround(hPos * invFreq[i]!);
409
+ freqs[base + half + i] = Math.fround(wPos * invFreq[i]!);
410
+ }
411
+ r++;
412
+ }
413
+ }
414
+ }
415
+ }
416
+ const f = MlxArray.fromFloat32(freqs, [n, ROT_DIM]);
417
+ const cosH = ops.cos(f);
418
+ const sinH = ops.sin(f);
419
+ f.dispose();
420
+ // tile(·, (1, 2)) over the last dim → [n, HEAD_DIM].
421
+ const cos = disposing(cosH, ops.concatAxis([cosH, cosH], -1));
422
+ const sin = disposing(sinH, ops.concatAxis([sinH, sinH], -1));
423
+ return [cos, sin];
424
+ }
425
+
426
+ /** (t·cos) + (rotate_half(t)·sin), f32 math, cast back to bf16. */
427
+ #applyRope(x: MlxArray, cos: MlxArray, sin: MlxArray): MlxArray {
428
+ // x [n, HEADS, HEAD_DIM]; cos/sin [n, 1, HEAD_DIM] broadcast over heads.
429
+ const half = HEAD_DIM / 2;
430
+ const n = x.shape[0]!;
431
+ const x1 = x.slice([0, 0, 0], [n, HEADS, half]);
432
+ const x2 = x.slice([0, 0, half], [n, HEADS, HEAD_DIM]);
433
+ const negX2 = ops.mulScalar(x2, -1);
434
+ x2.dispose();
435
+ const rot = disposing(negX2, ops.concatAxis([negX2, x1], -1));
436
+ x1.dispose();
437
+ const a = ops.mul(x, cos);
438
+ const b = disposing(rot, ops.mul(rot, sin));
439
+ const sum = ops.add(a, b);
440
+ a.dispose();
441
+ b.dispose();
442
+ return disposing(sum, sum.astype(Dtype.bfloat16));
443
+ }
444
+
445
+ /** Stage-instrumented encode for parity bisection (tests/debug): returns
446
+ * named f32 copies of each pipeline stage alongside the final output. */
447
+ encodeStages(pp: Qwen3VLPreprocessed): Map<string, Float32Array> {
448
+ const stages = new Map<string, Float32Array>();
449
+ const out = this.encode(pp, (name, arr) => {
450
+ const f = arr.astype(Dtype.float32);
451
+ stages.set(name, f.toFloat32());
452
+ f.dispose();
453
+ });
454
+ const f = out.astype(Dtype.float32);
455
+ stages.set("merged", f.toFloat32());
456
+ f.dispose();
457
+ out.dispose();
458
+ return stages;
459
+ }
460
+
461
+ /** Parity-bisect helper: run ONLY the block chain (+ optional merger) on a
462
+ * caller-provided hidden grid — used to separate patch-embed seed ulp
463
+ * from block-math divergence in the stage gates. */
464
+ debugRunBlocks(
465
+ hF32: Float32Array, gridH: number, gridW: number, throughMerger = false,
466
+ ): Float32Array {
467
+ const n = gridH * gridW;
468
+ const hf = MlxArray.fromFloat32(hF32, [n, HIDDEN]);
469
+ let h = disposing(hf, hf.astype(Dtype.bfloat16));
470
+ const [cosF, sinF] = this.#ropeCosSin(gridH, gridW);
471
+ const cos = disposing(cosF, ops.expandDims(cosF, 1));
472
+ const sin = disposing(sinF, ops.expandDims(sinF, 1));
473
+ const scale = Math.pow(HEAD_DIM, -0.5);
474
+ for (const blk of this.#blocks) h = this.#block(h, blk, cos, sin, scale, n);
475
+ cos.dispose();
476
+ sin.dispose();
477
+ if (throughMerger) {
478
+ const mnorm = disposing(h, ops.layerNorm(h, this.#mergerNormW, this.#mergerNormB, 1e-6));
479
+ const grouped = disposing(mnorm, ops.reshape(mnorm, [n / 4, HIDDEN * 4]));
480
+ const f1 = disposing(grouped, this.#linear(grouped, this.#mergerFc1W, this.#mergerFc1B));
481
+ const act = disposing(f1, ops.geluPrecise(f1));
482
+ h = disposing(act, this.#linear(act, this.#mergerFc2W, this.#mergerFc2B));
483
+ }
484
+ const f = disposing(h, h.astype(Dtype.float32));
485
+ const out = f.toFloat32();
486
+ f.dispose();
487
+ return out;
488
+ }
489
+
490
+ /** Encode one preprocessed image → [imageTokens, 5120] bf16. */
491
+ encode(
492
+ pp: Qwen3VLPreprocessed,
493
+ tap?: (name: string, arr: MlxArray) => void,
494
+ ): MlxArray {
495
+ const [gridT, gridH, gridW] = pp.gridThw;
496
+ const n = pp.rows;
497
+ const pvF = MlxArray.fromFloat32(pp.pixelValues, [n, pp.cols]);
498
+ const pv = disposing(pvF, pvF.astype(Dtype.bfloat16));
499
+ // PatchEmbed: rows (c,t,py,px) → [N, tps, ps, ps, C] NDHWC → Conv3d with
500
+ // kernel == stride — the reference's exact reshape/moveaxis/conv graph.
501
+ const tps = QWEN3VL_TEMPORAL_PATCH_SIZE;
502
+ const ps = QWEN3VL_PATCH_SIZE;
503
+ const r5 = disposing(pv, ops.reshape(pv, [n, 3, tps, ps, ps]));
504
+ const tr = disposing(r5, ops.transposeAxes(r5, [0, 2, 3, 4, 1]));
505
+ const ndhwc = disposing(tr, ops.contiguous(tr));
506
+ const convOut = disposing(
507
+ ndhwc, ops.conv3d(ndhwc, this.#patchW, [tps, ps, ps]),
508
+ );
509
+ let h = disposing(convOut, ops.reshape(convOut, [n, HIDDEN]));
510
+ h = disposing(h, ops.add(h, this.#patchB));
511
+ tap?.("patch", h);
512
+ // Per-temporal-group pos/rope embeds are identical — tile the (h,w)
513
+ // block gridT times (the reference tiles coords/pos_embed per frame).
514
+ const tile = (a: MlxArray): MlxArray => {
515
+ if (gridT === 1) return a;
516
+ const parts = new Array(gridT).fill(a);
517
+ return disposing(a, ops.concatAxis(parts, 0));
518
+ };
519
+ const pos = tile(this.#interpolatedPosEmbeds(gridH, gridW));
520
+ tap?.("pos", pos);
521
+ h = disposing(h, disposing(pos, ops.add(h, pos)));
522
+
523
+ const [cosF0, sinF0] = this.#ropeCosSin(gridH, gridW);
524
+ const cosF = tile(cosF0);
525
+ const sinF = tile(sinF0);
526
+ const cos = disposing(cosF, ops.expandDims(cosF, 1)); // [n,1,72]
527
+ const sin = disposing(sinF, ops.expandDims(sinF, 1));
528
+
529
+ const scale = Math.pow(HEAD_DIM, -0.5);
530
+ // Attention never crosses temporal groups (cu_seqlens = h·w per frame
531
+ // pair): images run one segment, videos gridT segments.
532
+ const segLen = gridH * gridW;
533
+ let blkIdx = 0;
534
+ for (const blk of this.#blocks) {
535
+ h = this.#block(h, blk, cos, sin, scale, n, segLen);
536
+ tap?.(`blk${blkIdx}`, h);
537
+ blkIdx++;
538
+ }
539
+ tap?.("blocks", h);
540
+ cos.dispose();
541
+ sin.dispose();
542
+
543
+ // Merger: LayerNorm(1152) → reshape 4× → fc1 → GELU(erf) → fc2.
544
+ const mnorm = disposing(h, ops.layerNorm(h, this.#mergerNormW, this.#mergerNormB, 1e-6));
545
+ const grouped = disposing(mnorm, ops.reshape(mnorm, [n / 4, HIDDEN * 4]));
546
+ const f1 = disposing(grouped, this.#linear(grouped, this.#mergerFc1W, this.#mergerFc1B));
547
+ const act = disposing(f1, ops.geluPrecise(f1)); // reference nn.GELU()
548
+ return disposing(act, this.#linear(act, this.#mergerFc2W, this.#mergerFc2B));
549
+ }
550
+
551
+ dispose(): void {
552
+ // Sidecar arrays are process-pinned (house rule: mmap-backed weights are
553
+ // never handed a JS dtor); nothing to free eagerly.
554
+ }
555
+ }