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
package/src/mlx/array.ts CHANGED
@@ -46,17 +46,16 @@ let nextPinId = 1;
46
46
  // do nothing — a dtor mlx can safely call from ANY thread. The pointed-to
47
47
  // memory's lifetime is the CALLER's contract (weight mmaps live for the
48
48
  // process; restored KV is COPIED at load since 2026-07-07 — see kv-store.ts
49
- // loadKvCache — so no restore mapping needs pinning anymore). The address
50
- // comes via dlsym because
51
- // bun:ffi's symbol .ptr returns the pointer bit-cast to float64
52
- // (lab/repro/bun-ffi-f64) — passing that back truncates to NULL.
53
- const libcDlsym = dlopen("/usr/lib/libSystem.B.dylib", {
54
- dlsym: { args: ["u64", "ptr"], returns: "u64" },
49
+ // loadKvCache — so no restore mapping needs pinning anymore). Bun >= 1.4
50
+ // returns a numeric function address from .ptr; verified against dlsym on
51
+ // 1.4.0 and 1.4.2. Keep the library open for every native destructor's lifetime.
52
+ const libcFree = dlopen("/usr/lib/libSystem.B.dylib", {
53
+ free: { args: ["ptr"], returns: "void" },
55
54
  });
56
- const FREE_NAME = Buffer.from("free\0");
57
- const RTLD_DEFAULT = 0xfffffffffffffffen;
58
- const freeFnAddr = Number(libcDlsym.symbols.dlsym(RTLD_DEFAULT, ptr(FREE_NAME)));
59
- if (!freeFnAddr) throw new Error("dlsym(free) failed — zero-copy arrays need a native no-op dtor");
55
+ // The pinned bun-types predates the runtime's numeric .ptr property.
56
+ const freeFnAddr = Number(Reflect.get(libcFree.symbols.free, "ptr"));
57
+ if (!Number.isSafeInteger(freeFnAddr) || freeFnAddr <= 0)
58
+ throw new Error("free.ptr is invalid — zero-copy arrays need a native no-op dtor");
60
59
 
61
60
  export function pinnedBufferCount(): number {
62
61
  return pinned.size;
@@ -23,8 +23,14 @@ import { MlxArray } from "./array";
23
23
 
24
24
  export type TraceFn = (inputs: MlxArray[]) => MlxArray[];
25
25
 
26
- const traceRegistry = new Map<number, { fn: TraceFn; count: number }>();
26
+ const traceRegistry = new Map<number, { fn: TraceFn; count: number; shapeless: boolean }>();
27
27
  let nextTraceId = 1;
28
+ let shapelessTracing = false;
29
+
30
+ /** Fixed-shape custom kernels must fall back while a shapeless graph traces. */
31
+ export function isShapelessTracing(): boolean {
32
+ return shapelessTracing;
33
+ }
28
34
  /** Error thrown by a trace fn, carried across the C boundary (the
29
35
  * trampoline must not throw into mlx; it returns non-zero instead). */
30
36
  let traceError: unknown = null;
@@ -40,9 +46,11 @@ export let traceCalls = 0;
40
46
  // error handler in ffi.ts).
41
47
  const trampoline = new JSCallback(
42
48
  (resPtr: number, argsVec: bigint, payload: number): number => {
49
+ const previousShapeless = shapelessTracing;
43
50
  try {
44
51
  const entry = traceRegistry.get(payload);
45
52
  if (!entry) throw new Error(`compile trampoline: unknown trace id ${payload}`);
53
+ shapelessTracing ||= entry.shapeless;
46
54
  const fn = entry.fn;
47
55
  entry.count++;
48
56
  traceCalls++;
@@ -66,6 +74,8 @@ const trampoline = new JSCallback(
66
74
  } catch (e) {
67
75
  traceError = e;
68
76
  return 1;
77
+ } finally {
78
+ shapelessTracing = previousShapeless;
69
79
  }
70
80
  },
71
81
  { args: ["ptr", "u64", "ptr"], returns: "i32" },
@@ -95,7 +105,7 @@ export class CompiledFunction {
95
105
 
96
106
  constructor(fn: TraceFn, shapeless = true) {
97
107
  this.#traceId = nextTraceId++;
98
- traceRegistry.set(this.#traceId, { fn, count: 0 });
108
+ traceRegistry.set(this.#traceId, { fn, count: 0, shapeless });
99
109
  // payload is the trace id smuggled through the void* (never deref'd);
100
110
  // dtor is null — nothing to free on the C side.
101
111
  const raw = C.mlx_closure_new_func_payload(trampoline.ptr, this.#traceId as never, null);
package/src/mlx/ffi.ts CHANGED
@@ -10,21 +10,22 @@
10
10
  // - mlx_optional_int is `{ int32 value; bool has_value }` = 8 bytes, by
11
11
  // value in one register: value in bits 0..31, has_value at bit 32.
12
12
 
13
- import { dlopen, FFIType, ptr, read } from "bun:ffi";
13
+ import { dlopen, FFIType, ptr, read, type Pointer } from "bun:ffi";
14
14
  import { existsSync } from "node:fs";
15
15
  import { dirname, join } from "node:path";
16
- import { nativePackDir } from "../native-pack";
16
+ import { MLX_CORE_VERSION, nativePackDir } from "../native-pack";
17
+ import { runtimeValue } from "../runtime-config";
17
18
 
18
19
  const { ptr: P, i32, u64, f32, f64, cstring } = FFIType;
19
20
 
20
21
  /** libmlxc resolution, in order: explicit env override → next to the
21
22
  * executable (the `bun build --compile` sidecar layout, where
22
23
  * libmlxc.dylib + libmlx.dylib + mlx.metallib ship beside the binary —
23
- * see docs/reference/embedding.md) → the downloaded native-pack cache
24
+ * see docs/reference/distribution.md) → the downloaded native-pack cache
24
25
  * (src/native-pack.ts; populated on first run by the CLI) → homebrew
25
26
  * (arm64, then Intel prefix). Keep in sync with nativeRuntimeDir(). */
26
27
  function resolveLibmlxc(): string {
27
- const env = process.env.MLX_BUN_LIBMLXC;
28
+ const env = runtimeValue("MLX_BUN_LIBMLXC");
28
29
  if (env) return env;
29
30
  const beside = join(dirname(process.execPath), "libmlxc.dylib");
30
31
  if (existsSync(beside)) return beside;
@@ -37,8 +38,12 @@ function resolveLibmlxc(): string {
37
38
  }
38
39
 
39
40
  export const LIBMLXC_PATH = resolveLibmlxc();
40
-
41
41
  export const C = dlopen(LIBMLXC_PATH, {
42
+ // linked MLX core version, independent of the C API family
43
+ mlx_version: { args: [P], returns: i32 },
44
+ mlx_string_new: { args: [], returns: u64 },
45
+ mlx_string_data: { args: [u64], returns: cstring },
46
+ mlx_string_free: { args: [u64], returns: i32 },
42
47
  // array lifecycle
43
48
  mlx_array_new: { args: [], returns: u64 },
44
49
  mlx_array_new_data: { args: [P, P, i32, i32], returns: u64 },
@@ -77,6 +82,7 @@ export const C = dlopen(LIBMLXC_PATH, {
77
82
  mlx_device_info_get: { args: [P, u64], returns: i32 },
78
83
  mlx_device_info_free: { args: [u64], returns: i32 },
79
84
  mlx_device_info_get_size: { args: [P, u64, cstring], returns: i32 },
85
+ mlx_device_info_get_string: { args: [P, u64, cstring], returns: i32 },
80
86
  mlx_get_default_device: { args: [P], returns: i32 },
81
87
  mlx_device_free: { args: [u64], returns: i32 },
82
88
  mlx_synchronize: { args: [u64], returns: i32 },
@@ -97,8 +103,13 @@ export const C = dlopen(LIBMLXC_PATH, {
97
103
  mlx_fast_rms_norm: { args: [P, u64, u64, f32, u64], returns: i32 },
98
104
  // (res, x, weight may-be-null, bias may-be-null, eps, stream)
99
105
  mlx_fast_layer_norm: { args: [P, u64, u64, u64, f32, u64], returns: i32 },
106
+ // MLX 0.32.2: mask, sinks, force_fused, stream.
107
+ mlx_fast_scaled_dot_product_attention: {
108
+ args: [P, u64, u64, u64, f32, cstring, u64, u64, FFIType.bool, u64], returns: i32,
109
+ },
100
110
  mlx_matmul: { args: [P, u64, u64, u64], returns: i32 },
101
111
  mlx_contiguous: { args: [P, u64, FFIType.bool, u64], returns: i32 },
112
+ mlx_copy: { args: [P, u64, u64], returns: i32 },
102
113
  // (res: mlx_vector_array*, w, opt group_size, opt bits, mode, global_scale, stream)
103
114
  mlx_quantize: { args: [P, u64, u64, u64, cstring, u64, u64], returns: i32 },
104
115
  mlx_vector_array_get: { args: [P, u64, u64], returns: i32 },
@@ -106,15 +117,14 @@ export const C = dlopen(LIBMLXC_PATH, {
106
117
  mlx_softmax_axis: { args: [P, u64, i32, FFIType.bool, u64], returns: i32 },
107
118
  mlx_max_axis: { args: [P, u64, i32, FFIType.bool, u64], returns: i32 },
108
119
  mlx_sum_axis: { args: [P, u64, i32, FFIType.bool, u64], returns: i32 },
120
+ // Optional dtype uses the eight-byte value/has_value layout of optional int.
121
+ mlx_cumsum_axis: { args: [P, u64, i32, FFIType.bool, FFIType.bool, u64, u64], returns: i32 },
122
+ mlx_cummax_axis: { args: [P, u64, i32, FFIType.bool, FFIType.bool, u64], returns: i32 },
109
123
  mlx_maximum: { args: [P, u64, u64, u64], returns: i32 },
110
124
  mlx_expand_dims: { args: [P, u64, i32, u64], returns: i32 },
111
125
  mlx_logical_or: { args: [P, u64, u64, u64], returns: i32 },
112
126
  // (res, x, dims, traditional, opt base, scale, offset, freqs may-be-null, stream)
113
127
  mlx_fast_rope: { args: [P, u64, i32, FFIType.bool, u64, f32, i32, u64, u64], returns: i32 },
114
- // (res, q, k, v, scale, mask_mode, mask may-be-null, sinks may-be-null, stream)
115
- mlx_fast_scaled_dot_product_attention: {
116
- args: [P, u64, u64, u64, f32, cstring, u64, u64, u64], returns: i32,
117
- },
118
128
  // ops (grown as phases need them)
119
129
  mlx_add: { args: [P, u64, u64, u64], returns: i32 },
120
130
  mlx_subtract: { args: [P, u64, u64, u64], returns: i32 },
@@ -148,8 +158,6 @@ export const C = dlopen(LIBMLXC_PATH, {
148
158
  mlx_arange: { args: [P, f64, f64, f64, i32, u64], returns: i32 },
149
159
  mlx_take_along_axis: { args: [P, u64, u64, i32, u64], returns: i32 },
150
160
  mlx_put_along_axis: { args: [P, u64, u64, u64, i32, u64], returns: i32 },
151
- mlx_cumsum: { args: [P, u64, i32, FFIType.bool, FFIType.bool, u64], returns: i32 },
152
- mlx_cummax: { args: [P, u64, i32, FFIType.bool, FFIType.bool, u64], returns: i32 },
153
161
  mlx_where: { args: [P, u64, u64, u64, u64], returns: i32 },
154
162
  mlx_logsumexp_axis: { args: [P, u64, i32, FFIType.bool, u64], returns: i32 },
155
163
  mlx_greater_equal: { args: [P, u64, u64, u64], returns: i32 },
@@ -173,16 +181,11 @@ export const C = dlopen(LIBMLXC_PATH, {
173
181
  // (res, input, weight, stride, padding, dilation, groups, stream) — depthwise
174
182
  // causal conv1d for Qwen3.5 gated-DeltaNet (groups == channels, padding 0).
175
183
  mlx_conv1d: { args: [P, u64, u64, i32, i32, i32, i32, u64], returns: i32 },
176
- // C signature: (res, input, weight, stride_0, stride_1, padding_0,
177
- // padding_1, dilation_0, dilation_1, groups, stream) — NHWC conv2d for the
178
- // gemma-4 audio SSCP subsampler.
179
- // HAZARD (lab/repro/bun-ffi-stack-args): Apple arm64 packs sub-8-byte STACK
180
- // args at natural size (dilation_1@sp+0, groups@sp+4, stream@sp+8) but
181
- // bun:ffi 1.3.14 writes one 8-byte slot per arg — the callee reads shifted
182
- // garbage and mlx segfaults on a bogus stream. Workaround: declare the two
183
- // stack ints as ONE u64 (dilation_1 | groups<<32) so Bun's slot layout
184
- // byte-matches the Apple ABI. ops.conv2d does the packing.
185
- mlx_conv2d: { args: [P, u64, u64, i32, i32, i32, i32, i32, u64, u64], returns: i32 },
184
+ // Natural mlx-c signatures. Bun 1.4 fixed the macOS arm64 stack packing
185
+ // bug recorded in lab/repro/bun-ffi-stack-args, so these must match the
186
+ // header exactly. mlx-bun now requires Bun 1.4 or newer.
187
+ mlx_conv2d: { args: [P, u64, u64, i32, i32, i32, i32, i32, i32, i32, u64], returns: i32 },
188
+ mlx_conv3d: { args: [P, u64, u64, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, u64], returns: i32 },
186
189
  // --- training: autograd (value_and_grad) — proven in lab/spikes/phase-train-vag.ts.
187
190
  // mlx_value_and_grad(res: mlx_closure_value_and_grad*, fun: mlx_closure,
188
191
  // argnums: int*, n) → builds a value+grad closure differentiating the
@@ -223,7 +226,7 @@ export const C = dlopen(LIBMLXC_PATH, {
223
226
  mlx_save_safetensors: { args: [cstring, u64, u64], returns: i32 },
224
227
  mlx_map_string_to_array_insert: { args: [u64, cstring, u64], returns: i32 },
225
228
  mlx_map_string_to_string_insert: { args: [u64, cstring, cstring], returns: i32 },
226
- // compile via closures (Phase A compiled decode — docs/design/optimization_plan.md).
229
+ // compile via closures (Phase A compiled decode — docs/archive/investigations/optimization_plan.md).
227
230
  // mlx_closure is the usual one-pointer struct; the func-payload variant
228
231
  // carries an id we use to route the trace callback back to JS.
229
232
  mlx_closure_new: { args: [], returns: u64 },
@@ -317,6 +320,22 @@ export function takeMlxError(): string | null {
317
320
  return e;
318
321
  }
319
322
 
323
+ function readRuntimeVersion(): string {
324
+ const slot = new BigUint64Array([C.mlx_string_new()]);
325
+ const p = ptr(slot);
326
+ try {
327
+ if (C.mlx_version(p) !== 0)
328
+ throw new Error(`mlx_version failed: ${takeMlxError() ?? "(no mlx message)"}`);
329
+ return C.mlx_string_data(read.u64(p, 0)).toString();
330
+ } finally { C.mlx_string_free(read.u64(p, 0)); }
331
+ }
332
+
333
+ /** Read once at startup; kernel eligibility must follow the linked core. */
334
+ export const MLX_VERSION = readRuntimeVersion();
335
+ if (MLX_VERSION !== MLX_CORE_VERSION) {
336
+ throw new Error(`mlx-bun requires its bundled MLX ${MLX_CORE_VERSION}; ${LIBMLXC_PATH} loaded MLX ${MLX_VERSION}`);
337
+ }
338
+
320
339
  /** mlx_dtype enum values (array.h). */
321
340
  export const enum Dtype {
322
341
  bool = 0, uint8 = 1, uint16 = 2, uint32 = 3, uint64 = 4,
@@ -408,6 +427,31 @@ export function clearCache(): void {
408
427
  C.mlx_clear_cache();
409
428
  }
410
429
 
430
+ let cachedDeviceArchitecture: string | undefined;
431
+
432
+ /** Native architecture identifier, used to match MLX's hardware dispatch. */
433
+ export function deviceArchitecture(): string {
434
+ if (cachedDeviceArchitecture !== undefined) return cachedDeviceArchitecture;
435
+ const devSlot = new BigUint64Array(1), devPtr = ptr(devSlot);
436
+ if (C.mlx_get_default_device(devPtr) !== 0)
437
+ throw new Error(`mlx_get_default_device failed: ${takeMlxError() ?? ""}`);
438
+ const dev = read.u64(devPtr, 0);
439
+ const infoSlot = new BigUint64Array([C.mlx_device_info_new()]), infoPtr = ptr(infoSlot);
440
+ try {
441
+ if (C.mlx_device_info_get(infoPtr, dev) !== 0)
442
+ throw new Error(`mlx_device_info_get failed: ${takeMlxError() ?? ""}`);
443
+ const out = new BigUint64Array(1), outPtr = ptr(out);
444
+ const key = Buffer.from("architecture\0", "utf8");
445
+ if (C.mlx_device_info_get_string(outPtr, read.u64(infoPtr, 0), ptr(key)) !== 0)
446
+ throw new Error("architecture not in device info");
447
+ // Copy before the device-info object releases the native string.
448
+ return cachedDeviceArchitecture = new CString(Number(read.u64(outPtr, 0)) as Pointer).toString();
449
+ } finally {
450
+ C.mlx_device_info_free(read.u64(infoPtr, 0));
451
+ C.mlx_device_free(dev);
452
+ }
453
+ }
454
+
411
455
  /** Metal's recommended max working-set size for the default device. */
412
456
  export function maxRecommendedWorkingSetSize(): number {
413
457
  const devSlot = new BigUint64Array(1);
@@ -0,0 +1,23 @@
1
+ // Explicit compact storage for a value that must release a larger backing
2
+ // buffer. MLX 0.31.2 Copy::eval shares storage; contiguous() may also alias.
3
+ // MLX's dynamic slice allocates the logical output size and has a native VJP.
4
+ import { MlxArray } from "./array";
5
+ import * as ops from "./ops";
6
+
7
+ let zero: MlxArray | undefined;
8
+
9
+ /** Borrow an array; return an owned, row-contiguous copy on the GPU.
10
+ * Values and dtype are unchanged. Evaluation stays lazy and never reads data
11
+ * back to the host. Use only when independent compact storage is required;
12
+ * immutable array aliases normally avoid this allocation and dispatch.
13
+ * Slice sizes are concrete, so use shape-specialized compiled graphs. */
14
+ export function materializeCopy(input: MlxArray): MlxArray {
15
+ zero ??= MlxArray.fromInt32(new Int32Array([0]), []);
16
+ if (input.ndim > 0) return ops.sliceDynamic(input, zero, [0], input.shape);
17
+ const vector = ops.reshape(input, [1]);
18
+ try {
19
+ const copy = ops.sliceDynamic(vector, zero, [0], [1]);
20
+ try { return ops.reshape(copy, []); }
21
+ finally { copy.dispose(); }
22
+ } finally { vector.dispose(); }
23
+ }
@@ -1,4 +1,4 @@
1
- // mx.fast.metal_kernel from Bun (docs/design/optimization_plan.md Phase E): the
1
+ // mx.fast.metal_kernel from Bun (docs/archive/investigations/optimization_plan.md Phase E): the
2
2
  // intended MLX extension point for custom Metal kernels — no fork. The
3
3
  // kernel body is Metal Shading Language; mlx generates the signature
4
4
  // from input/output names (inputs by name, outputs by name, plus
@@ -29,15 +29,15 @@ export interface MetalKernelSpec {
29
29
  }
30
30
 
31
31
  export interface MetalKernelCall {
32
- /** Concrete output shapes/dtypes. Provide this OR `outputShapeFn` (the latter
33
- * makes the kernel derive its outputs from the input shapes, like a real MLX
34
- * primitive — required to compose inside an mx.compile'd / shapeless closure,
35
- * where the wrapper must answer "given these input shapes, what comes out?"). */
32
+ /** Concrete output shapes/dtypes. Provide this OR `outputShapeFn`.
33
+ * Both are captured when apply() builds the custom primitive. */
36
34
  outputs?: { shape: number[]; dtype: Dtype }[];
37
- /** Derive output shapes/dtypes from the (current/traced) input shapes. */
35
+ /** Derive outputs during eager construction or a shape-specific compile trace.
36
+ * This JS callback is not native shape inference: MLX's CustomKernel lacks
37
+ * output_shapes(), so it cannot participate in shapeless replay. */
38
38
  outputShapeFn?: (inputs: MlxArray[]) => { shape: number[]; dtype: Dtype }[];
39
- /** Concrete launch grid, OR derive it from inputs (so it tracks shape under
40
- * compile replay the same way `outputShapeFn` tracks output shapes). */
39
+ /** Concrete launch grid, OR derive it during eager construction or tracing.
40
+ * Native replay reuses the captured grid without invoking this callback. */
41
41
  grid: [number, number, number] | ((inputs: MlxArray[]) => [number, number, number]);
42
42
  threadGroup: [number, number, number];
43
43
  templateInts?: Record<string, number>;
@@ -73,9 +73,8 @@ export class MetalKernel {
73
73
  // flash-CCE pin bug). Pure-additive: holds references, changes no logic.
74
74
  const keepAlive: unknown[] = [];
75
75
  try {
76
- // Derive outputs + grid from the (current) input shapes when a function is
77
- // given — so the SAME kernel object answers correctly for any input shape
78
- // during a compile/replay trace, instead of baking in one call's shapes.
76
+ // Resolve callbacks once for this eager call or shape-specific trace.
77
+ // A compiled graph captures these values; replay does not call JS again.
79
78
  const outputs = call.outputShapeFn ? call.outputShapeFn(inputs) : call.outputs;
80
79
  if (!outputs) throw new Error("metal_kernel: provide outputs or outputShapeFn");
81
80
  const grid = typeof call.grid === "function" ? call.grid(inputs) : call.grid;
package/src/mlx/ops.ts CHANGED
@@ -113,6 +113,15 @@ export function matmul(a: MlxArray, b: MlxArray, s: S = gpuStream): MlxArray {
113
113
  return new MlxArray(outArray("matmul", (o) => C.mlx_matmul(o, a.handle, b.handle, s)));
114
114
  }
115
115
 
116
+ /** Bit-identical copy through a FRESH primitive node (mx.copy). Unlike
117
+ * `contiguous`, this always creates a new graph node even for an already-
118
+ * contiguous input — the tool for DETACHING an array from a multi-output
119
+ * primitive's sibling set (a retained sibling output pins every co-output's
120
+ * buffer natively; see gatedDeltaUpdate's state detach, 2026-08-20). */
121
+ export function copyOf(a: MlxArray, s: S = gpuStream): MlxArray {
122
+ return new MlxArray(outArray("copy", (o) => C.mlx_copy(o, a.handle, s)));
123
+ }
124
+
116
125
  /** Materialize a row-major contiguous copy (needed before rawBytes on views). */
117
126
  export function contiguous(a: MlxArray, s: S = gpuStream): MlxArray {
118
127
  return new MlxArray(
@@ -161,7 +170,7 @@ export function ropeScaled(
161
170
  * of a baked int — required inside compiled decode graphs, where the
162
171
  * offset changes every step but the graph must not. Same kernel as
163
172
  * `rope`; bit-exactness vs the static form is asserted in
164
- * tests/compile.test.ts. */
173
+ * tests/unit/compile.test.ts. */
165
174
  export function ropeDynamic(
166
175
  x: MlxArray, dims: number, base: number | null, offset: MlxArray,
167
176
  freqs: MlxArray | null, s: S = gpuStream,
@@ -180,7 +189,7 @@ export function ropeDynamic(
180
189
  * the dynamic twin of `ropeScaled`, for the universal rope factory's
181
190
  * batched-decode path (per-row [B] offsets under left-padding). Same
182
191
  * kernel as `ropeDynamic`; static/dynamic bit-exactness is asserted in
183
- * tests/compile.test.ts. */
192
+ * tests/unit/compile.test.ts. */
184
193
  export function ropeScaledDynamic(
185
194
  x: MlxArray, dims: number, traditional: boolean, base: number | null,
186
195
  scale: number, offset: MlxArray, freqs: MlxArray | null, s: S = gpuStream,
@@ -204,7 +213,7 @@ export function sdpa(
204
213
  outArray("fast_sdpa", (o) =>
205
214
  C.mlx_fast_scaled_dot_product_attention(
206
215
  o, q.handle, k.handle, v.handle, scale, ptr(cstr(maskMode)),
207
- maskArr?.handle ?? 0n, 0n, s,
216
+ maskArr?.handle ?? 0n, 0n, false, s,
208
217
  ),
209
218
  ),
210
219
  );
@@ -305,7 +314,8 @@ export function takeAxis(a: MlxArray, indices: MlxArray, axis: number, s: S = gp
305
314
  }
306
315
 
307
316
  export function reshape(a: MlxArray, shape: number[], s: S = gpuStream): MlxArray {
308
- const buf = new Int32Array(shape);
317
+ // bun:ffi cannot take a pointer to an empty view; ndim=0 ignores the dummy.
318
+ const buf = new Int32Array(shape.length ? shape : [0]);
309
319
  return new MlxArray(
310
320
  outArray("reshape", (o) => C.mlx_reshape(o, a.handle, ptr(buf), BigInt(shape.length), s)),
311
321
  );
@@ -445,14 +455,14 @@ export function putAlongAxis(
445
455
 
446
456
  export function cumsum(a: MlxArray, axis: number, s: S = gpuStream): MlxArray {
447
457
  return new MlxArray(
448
- outArray("cumsum", (o) => C.mlx_cumsum(o, a.handle, axis, false, true, s)),
458
+ outArray("cumsum", (o) => C.mlx_cumsum_axis(o, a.handle, axis, false, true, 0n, s)),
449
459
  );
450
460
  }
451
461
 
452
462
  /** Cumulative maximum along `axis` (inclusive, forward) — mlx.core.cummax. */
453
463
  export function cummax(a: MlxArray, axis: number, s: S = gpuStream): MlxArray {
454
464
  return new MlxArray(
455
- outArray("cummax", (o) => C.mlx_cummax(o, a.handle, axis, false, true, s)),
465
+ outArray("cummax", (o) => C.mlx_cummax_axis(o, a.handle, axis, false, true, s)),
456
466
  );
457
467
  }
458
468
 
@@ -467,10 +477,13 @@ export function where(cond: MlxArray, x: MlxArray, y: MlxArray, s: S = gpuStream
467
477
  // (lab/repro/bun-ffi-f64/) traced the root cause to a Bun bug: after DFG
468
478
  // tier-up, typed-array reads following a bun:ffi call return stale values
469
479
  // (the JIT eliminates the load across the native call). Not f64 marshaling
470
- // — args reach C intact. See PLAN.md Phase 4 findings. Large constant
471
- // ranges (topP's vocab arange) are cached for the process lifetime.
472
- const arangeCache = new Map<string, MlxArray>();
480
+ // — args reach C intact. See PLAN.md Phase 4 findings. Reuse large ranges
481
+ // across attention layers and sampling, but bound their retained storage:
482
+ // causal masks request a new range on every round once context exceeds 64K.
483
+ const arangeCache = new Map<string, { array: MlxArray; bytes: number }>();
473
484
  const ARANGE_CACHE_MIN = 65536;
485
+ const ARANGE_CACHE_BYTES = 8 * 1024 * 1024;
486
+ let arangeCacheBytes = 0;
474
487
 
475
488
  export function arange(start: number, stop: number, step: number, dtype: Dtype, s: S = gpuStream): MlxArray {
476
489
  if (!Number.isInteger(start) || !Number.isInteger(step) || step === 0)
@@ -478,7 +491,11 @@ export function arange(start: number, stop: number, step: number, dtype: Dtype,
478
491
  const n = Math.max(0, Math.ceil((stop - start) / step));
479
492
  const key = `${start}|${step}|${n}|${dtype}`;
480
493
  const cached = arangeCache.get(key);
481
- if (cached) return cached.slice([0], [n], s);
494
+ if (cached) {
495
+ arangeCache.delete(key);
496
+ arangeCache.set(key, cached);
497
+ return cached.array.slice([0], [n], s);
498
+ }
482
499
 
483
500
  let arr: MlxArray;
484
501
  if (dtype === Dtype.int32) {
@@ -494,8 +511,20 @@ export function arange(start: number, stop: number, step: number, dtype: Dtype,
494
511
  arr = new MlxArray(outArray("arange", (o) => C.mlx_arange(o, start, stop, step, dtype, s)));
495
512
  }
496
513
  if (n >= ARANGE_CACHE_MIN) {
497
- arangeCache.set(key, arr);
498
- return arr.slice([0], [n], s);
514
+ const bytes = arr.nbytes;
515
+ if (bytes <= ARANGE_CACHE_BYTES) {
516
+ while (arangeCacheBytes + bytes > ARANGE_CACHE_BYTES) {
517
+ const [oldKey, old] = arangeCache.entries().next().value!;
518
+ arangeCache.delete(oldKey);
519
+ arangeCacheBytes -= old.bytes;
520
+ // Returned slices retain their own native reference, including lazy
521
+ // views that have not been evaluated when this owner is evicted.
522
+ old.array.dispose();
523
+ }
524
+ arangeCache.set(key, { array: arr, bytes });
525
+ arangeCacheBytes += bytes;
526
+ return arr.slice([0], [n], s);
527
+ }
499
528
  }
500
529
  return arr;
501
530
  }
@@ -602,7 +631,7 @@ export function sliceDynamic(
602
631
  /** slice_update with the start index as an ARRAY (one int32 per entry in
603
632
  * `axes`) — the compiled-decode form of the per-step cache write. Same
604
633
  * write as `sliceUpdate`; bit-exactness vs the static form is asserted
605
- * in tests/compile.test.ts. */
634
+ * in tests/unit/compile.test.ts. */
606
635
  export function sliceUpdateDynamic(
607
636
  src: MlxArray, update: MlxArray, start: MlxArray, axes: number[], s: S = gpuStream,
608
637
  ): MlxArray {
@@ -880,20 +909,36 @@ export function conv1d(
880
909
 
881
910
  /** nn.Conv2d: input [B, H, W, C_in] (NHWC), weight [C_out, kH, kW, C_in/groups]
882
911
  * → [B, H', W', C_out]. mlx weight layout — the oracle transposes PyTorch's
883
- * [C_out, C_in, kH, kW] at load. For the gemma-4 audio SSCP subsampler.
884
- * dilation_1 and groups ride in one packed u64 — see the mlx_conv2d hazard
885
- * note in ffi.ts (bun:ffi stack-arg ABI bug). */
912
+ * [C_out, C_in, kH, kW] at load. For the gemma-4 audio SSCP subsampler. */
886
913
  export function conv2d(
887
914
  input: MlxArray, weight: MlxArray, stride: [number, number] = [1, 1],
888
915
  padding: [number, number] = [0, 0], dilation: [number, number] = [1, 1],
889
916
  groups = 1, s: S = gpuStream,
890
917
  ): MlxArray {
891
- const dilation1AndGroups =
892
- (BigInt(dilation[1] >>> 0) & 0xffffffffn) | (BigInt(groups >>> 0) << 32n);
893
918
  return new MlxArray(
894
919
  outArray("conv2d", (o) =>
895
920
  C.mlx_conv2d(o, input.handle, weight.handle, stride[0], stride[1],
896
- padding[0], padding[1], dilation[0], dilation1AndGroups, s),
921
+ padding[0], padding[1], dilation[0], dilation[1], groups, s),
922
+ ),
923
+ );
924
+ }
925
+
926
+ /** nn.Conv3d: input [B, D, H, W, C_in] (NDHWC), weight [C_out, kD, kH, kW,
927
+ * C_in/groups] → [B, D', H', W', C_out]. The Qwen3-VL vision patch embed
928
+ * (kernel == stride) — the conv KERNEL's accumulation order is the parity-
929
+ * relevant arithmetic; a value-equivalent GEMM differs by bf16 ulp. */
930
+ export function conv3d(
931
+ input: MlxArray, weight: MlxArray,
932
+ stride: [number, number, number] = [1, 1, 1],
933
+ padding: [number, number, number] = [0, 0, 0],
934
+ dilation: [number, number, number] = [1, 1, 1],
935
+ groups = 1, s: S = gpuStream,
936
+ ): MlxArray {
937
+ return new MlxArray(
938
+ outArray("conv3d", (o) =>
939
+ C.mlx_conv3d(o, input.handle, weight.handle, stride[0], stride[1],
940
+ stride[2], padding[0], padding[1], padding[2], dilation[0],
941
+ dilation[1], dilation[2], groups, s),
897
942
  ),
898
943
  );
899
944
  }
@@ -0,0 +1,36 @@
1
+ import type { MlxArray } from "./array";
2
+ import * as ops from "./ops";
3
+
4
+ /** Run independent affine M=1 matrix-vector products in one native batched call.
5
+ * Weight/scales/bias views share their original buffers. MLX 0.32.2's
6
+ * flattened M>=2 path uses different arithmetic, so appending known tokens
7
+ * must explicitly select this layout when preserving decode state.
8
+ */
9
+ export function quantizedMatmulRows(
10
+ x: MlxArray, w: MlxArray, scales: MlxArray, biases: MlxArray | null,
11
+ spec: ops.QuantSpec,
12
+ ): MlxArray {
13
+ const shape = x.shape, width = shape[shape.length - 1]!;
14
+ const rows = shape.reduce((a, b) => a * b, 1) / width;
15
+ if (rows === 1) return ops.quantizedMatmul(x, w, scales, biases, spec);
16
+ if (spec.mode !== "affine")
17
+ throw new Error("Independent-row batched matmul requires affine quantization");
18
+ if (w.shape.length !== 2 || scales.shape.length !== 2 || (biases && biases.shape.length !== 2))
19
+ throw new Error("Independent-row matmul requires shared two-dimensional weights");
20
+ const views: MlxArray[] = [];
21
+ const view = (value: MlxArray, newShape: number[]) => {
22
+ const result = ops.reshape(value, newShape); views.push(result); return result;
23
+ };
24
+ let output: MlxArray | undefined;
25
+ try {
26
+ const input = view(x, [rows, 1, width]);
27
+ const weight = view(w, [1, ...w.shape]);
28
+ const scale = view(scales, [1, ...scales.shape]);
29
+ const bias = biases ? view(biases, [1, ...biases.shape]) : null;
30
+ output = ops.quantizedMatmul(input, weight, scale, bias, spec);
31
+ return ops.reshape(output, [...shape.slice(0, -1), output.shape.at(-1)!]);
32
+ } finally {
33
+ output?.dispose();
34
+ for (const value of views) value.dispose();
35
+ }
36
+ }
@@ -0,0 +1,52 @@
1
+ // XGrammar's additive 0/-Infinity mask contract, decoded from packed bits.
2
+ // Reference: mlc-ai/xgrammar b5048784, apply_token_bitmask_mlx.py (Apache-2.0).
3
+ import { MlxArray } from "./array";
4
+ import { CompiledFunction } from "./compile";
5
+ import { Dtype } from "./ffi";
6
+ import { MetalKernel } from "./metal-kernel";
7
+
8
+ let compiled: CompiledFunction | undefined;
9
+ let kernel: MetalKernel | undefined;
10
+
11
+ function maskGraph(logits: MlxArray, bits: MlxArray): MlxArray {
12
+ kernel ??= new MetalKernel({
13
+ name: "mlx_bun_token_bitmask",
14
+ inputNames: ["logits", "bits"],
15
+ outputNames: ["out"],
16
+ source: String.raw`
17
+ const uint i = thread_position_in_grid.x;
18
+ if (i >= SIZE) return;
19
+ const uint id = i % V;
20
+ const uint word = uint(bits[id >> 5]);
21
+ const bool valid = ((word >> (id & 31)) & 1u) != 0;
22
+ out[i] = logits[i] + T(valid ? 0.0f : -INFINITY);
23
+ `,
24
+ });
25
+ return kernel.apply([logits, bits], {
26
+ outputs: [{ shape: logits.shape, dtype: logits.dtype }],
27
+ grid: [logits.size, 1, 1],
28
+ threadGroup: [128, 1, 1],
29
+ templateDtypes: { T: logits.dtype },
30
+ templateInts: { V: logits.shape.at(-1)!, SIZE: logits.size },
31
+ })[0]!;
32
+ }
33
+
34
+ /** Apply one little-endian token bitmask to every row along the final axis.
35
+ * Borrow logits; return an owned lazy result. Copy the mutable host bits now,
36
+ * so a matcher can advance before GPU evaluation. Missing words reject tokens.
37
+ * Preserve the additive-mask expression, including NaNs and signed zero.
38
+ * Shape-specialized compilation handles bf16/f16/f32 and strided inputs. */
39
+ export function applyTokenBitmask(logits: MlxArray, bits: Int32Array): MlxArray {
40
+ const vocab = logits.shape.at(-1);
41
+ if (!vocab || logits.size === 0 || logits.size > 0x7fffffff)
42
+ throw new Error("token bitmask requires nonempty logits with at most 2^31-1 elements");
43
+ if (![Dtype.bfloat16, Dtype.float16, Dtype.float32].includes(logits.dtype))
44
+ throw new Error("token bitmask requires bf16, f16 or f32 logits");
45
+ const copy = new Int32Array(Math.ceil(vocab / 32));
46
+ copy.set(bits.subarray(0, copy.length));
47
+ const deviceBits = MlxArray.fromInt32(copy, [copy.length]);
48
+ try {
49
+ compiled ??= new CompiledFunction(([x, mask]) => [maskGraph(x!, mask!)], false);
50
+ return compiled.apply([logits, deviceBits])[0]!;
51
+ } finally { deviceBits.dispose(); }
52
+ }