mlx-bun 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (364) hide show
  1. package/README.md +71 -588
  2. package/docs/README.md +91 -0
  3. package/docs/{design → archive/investigations}/audio-input-plan.md +4 -4
  4. package/docs/{design → archive/investigations}/diffusion-gemma-port.md +3 -3
  5. package/docs/{design → archive/investigations}/faithful-l1-consolidation.md +4 -4
  6. package/docs/{design → archive/investigations}/minicpm5-decode-megakernel.md +11 -11
  7. package/docs/{design → archive/investigations}/optimization_plan.md +1 -1
  8. package/docs/{design → archive/investigations}/pre-colibri-stabilization.md +5 -5
  9. package/docs/{design → archive/investigations}/the-dreaming-handoff.md +2 -2
  10. package/docs/design/batching.md +569 -0
  11. package/docs/design/decode-speed-program.md +3178 -169
  12. package/docs/design/dreaming-nightly-pipeline.md +675 -282
  13. package/docs/design/generic-model-support.md +579 -334
  14. package/docs/design/kv-cache.md +680 -0
  15. package/docs/design/orpo-training.md +673 -1239
  16. package/docs/design/speculative-decoding.md +1627 -0
  17. package/docs/design/turboquant.md +2102 -0
  18. package/docs/design/unified-engine-frontier-plan.md +1831 -727
  19. package/docs/design/web-chat-redesign.md +666 -1083
  20. package/docs/planning/PRODUCT_ROADMAP.md +34 -1
  21. package/docs/planning/release-notes-v0.3.0.md +35 -0
  22. package/docs/planning/video-series.md +2 -2
  23. package/docs/reference/benchmarks.md +1162 -0
  24. package/docs/reference/cli.md +394 -159
  25. package/docs/reference/distribution.md +321 -182
  26. package/docs/reference/environment.md +348 -0
  27. package/docs/reference/glossary.md +39 -0
  28. package/docs/reference/library-api.md +566 -168
  29. package/docs/reference/memory.md +227 -64
  30. package/docs/reference/models.md +104 -6
  31. package/docs/reference/server-api.md +886 -425
  32. package/docs/reference/server-config.md +638 -330
  33. package/docs/reference/training.md +393 -254
  34. package/docs/reference/troubleshooting.md +166 -0
  35. package/package.json +9 -4
  36. package/src/audio/decode.ts +2 -2
  37. package/src/audio/features.ts +2 -2
  38. package/src/backends/mlx/autoregressive.ts +108 -0
  39. package/src/backends/mlx/batch-group.ts +1646 -0
  40. package/src/backends/mlx/diffusion.ts +39 -0
  41. package/src/backends/mlx/draft-target.ts +47 -0
  42. package/src/backends/mlx/evaluation.ts +105 -0
  43. package/src/backends/mlx/fill-append.ts +45 -0
  44. package/src/backends/mlx/gateway-binding.ts +71 -0
  45. package/src/backends/mlx/graph.ts +38 -0
  46. package/src/backends/mlx/memory-client.ts +189 -0
  47. package/src/backends/mlx/memory-guard.ts +40 -0
  48. package/src/backends/mlx/methods.ts +90 -0
  49. package/src/backends/mlx/model-prompt.ts +164 -0
  50. package/src/backends/mlx/model-serving.ts +79 -0
  51. package/src/backends/mlx/prefill.ts +33 -0
  52. package/src/backends/mlx/request-policy.ts +20 -0
  53. package/src/backends/mlx/rollback.ts +51 -0
  54. package/src/backends/mlx/serial-executor.ts +273 -0
  55. package/src/backends/mlx/speculative.ts +114 -0
  56. package/src/backends/mlx/state-views.ts +26 -0
  57. package/src/backends/mlx/text-engine.ts +73 -0
  58. package/src/cli.ts +58 -25
  59. package/src/client.ts +66 -0
  60. package/src/config.ts +21 -4
  61. package/src/contracts/completion.ts +16 -0
  62. package/src/contracts/execution.ts +49 -0
  63. package/src/contracts/generation.ts +103 -0
  64. package/src/contracts/host.ts +7 -0
  65. package/src/contracts/jobs.ts +66 -0
  66. package/src/contracts/pi-provider.ts +30 -0
  67. package/src/contracts/pi.ts +259 -0
  68. package/src/contracts/resources.ts +18 -0
  69. package/src/contracts/scheduling.ts +27 -0
  70. package/src/contracts/tsconfig.json +14 -0
  71. package/src/diffusion/diffusion-generate.ts +218 -153
  72. package/src/embed.ts +1 -1
  73. package/src/engine/admission.ts +59 -0
  74. package/src/engine/cancellation.ts +38 -0
  75. package/src/engine/engine.ts +56 -0
  76. package/src/engine/execution-coordinator.ts +70 -0
  77. package/src/engine/execution-plan.ts +51 -0
  78. package/src/engine/resources.ts +42 -0
  79. package/src/engine/scheduler.ts +67 -0
  80. package/src/engine/session.ts +247 -0
  81. package/src/eval/README.md +44 -1
  82. package/src/eval/kl.ts +236 -3
  83. package/src/eval/reference-logits.ts +207 -0
  84. package/src/eval/runner.ts +8 -96
  85. package/src/fill/echo-index.ts +188 -0
  86. package/src/fill/fill-session.ts +452 -0
  87. package/src/fill/proposal.ts +68 -0
  88. package/src/fill/schema-rows.ts +506 -0
  89. package/src/fill/tool-boundary.ts +103 -0
  90. package/src/fit.ts +1 -1
  91. package/src/generate.ts +656 -268
  92. package/src/grammar.ts +12 -7
  93. package/src/harness-pi.ts +14 -33
  94. package/src/hub-rest.ts +4 -4
  95. package/src/index.ts +7 -0
  96. package/src/inference/denoising.ts +17 -0
  97. package/src/inference/graph.ts +33 -0
  98. package/src/inference/prefill.ts +39 -0
  99. package/src/inference/rollback.ts +7 -0
  100. package/src/jobs/db.ts +4 -0
  101. package/src/jobs/index.ts +2 -0
  102. package/src/jobs/job-entry.ts +12 -12
  103. package/src/jobs/runner.ts +98 -13
  104. package/src/jobs/task-client.ts +19 -0
  105. package/src/jobs/types.ts +2 -58
  106. package/src/kv-store.ts +109 -49
  107. package/src/lab/README.md +18 -0
  108. package/src/{assets → lab/curve}/curve-designer.html +2 -2
  109. package/src/{curve-sampler.ts → lab/curve/curve-sampler.ts} +2 -2
  110. package/src/{expert-trace.ts → lab/expert-trace/expert-trace.ts} +5 -5
  111. package/src/{model → lab/paged-kv}/paged-kv.ts +9 -6
  112. package/src/library.ts +40 -0
  113. package/src/lora.ts +18 -0
  114. package/src/media-fetch.ts +1 -1
  115. package/src/memory/article.ts +1 -1
  116. package/src/memory/chunk.ts +1 -1
  117. package/src/memory/model.ts +30 -169
  118. package/src/memory/prompts.ts +1 -1
  119. package/src/memory/rest.ts +1 -1
  120. package/src/memory/schedule.ts +1 -1
  121. package/src/memory/stages.ts +4 -1
  122. package/src/memory/vault.ts +1 -3
  123. package/src/mlx/array.ts +9 -10
  124. package/src/mlx/compile.ts +12 -2
  125. package/src/mlx/ffi.ts +58 -11
  126. package/src/mlx/materialize.ts +23 -0
  127. package/src/mlx/metal-kernel.ts +10 -11
  128. package/src/mlx/ops.ts +33 -13
  129. package/src/mlx/quantized-rows.ts +36 -0
  130. package/src/mlx/token-bitmask.ts +52 -0
  131. package/src/mlx/turboquant-kv-decode.ts +82 -0
  132. package/src/mlx/turboquant-kv-inverse.ts +122 -0
  133. package/src/mlx/turboquant-ops.ts +33 -3
  134. package/src/mlx/turboquant-tables.ts +2 -2
  135. package/src/model/batched-mask.ts +8 -2
  136. package/src/model/batched-quant.ts +1 -1
  137. package/src/model/batched-rotating-quant.ts +2 -2
  138. package/src/model/batched-rotating.ts +1 -1
  139. package/src/model/compiled-decode.ts +1 -1
  140. package/src/model/diffusion-gemma.ts +8 -8
  141. package/src/model/factory.ts +76 -33
  142. package/src/model/fingerprint.ts +1 -1
  143. package/src/model/flash-attention.ts +3 -3
  144. package/src/model/gemma4-base.ts +26 -9
  145. package/src/model/gemma4.ts +6 -6
  146. package/src/model/generated/gemma4-12b.ts +2 -2
  147. package/src/model/generated/gemma4-26b.ts +2 -2
  148. package/src/model/generated/gemma4-e4b.ts +2 -2
  149. package/src/model/glm52-dsa.ts +13 -9
  150. package/src/model/glm52-memory.ts +1 -1
  151. package/src/model/implementation.ts +69 -0
  152. package/src/model/minicpm5.ts +6 -8
  153. package/src/model/profile.ts +12 -2
  154. package/src/model/qwen-append.ts +12 -0
  155. package/src/model/qwen-conv.ts +52 -0
  156. package/src/model/qwen3_5-checkpoint.ts +164 -0
  157. package/src/model/qwen3_5.ts +140 -56
  158. package/src/model/support.ts +1 -1
  159. package/src/model/trellis-balanced-scatter.ts +51 -0
  160. package/src/model/trellis-linear.ts +672 -0
  161. package/src/model/trellis-shared-m.ts +151 -0
  162. package/src/model/trellis-shared-scatter.ts +144 -0
  163. package/src/model/trellis-splitk-prefill.ts +158 -0
  164. package/src/model/trellis-tiled-prefill.ts +136 -0
  165. package/src/model/trellis-vector-expand.ts +62 -0
  166. package/src/model/trellis-wide-prefill.ts +94 -0
  167. package/src/model/universal/dense.ts +1 -1
  168. package/src/model/universal/modules.ts +8 -1
  169. package/src/native-pack.ts +6 -5
  170. package/src/pi-provider.ts +6 -64
  171. package/src/pi-web.ts +8 -315
  172. package/src/prompt-cache.ts +96 -53
  173. package/src/quantize/allocator.ts +1 -1
  174. package/src/quantize/config-writer.ts +10 -3
  175. package/src/quantize/rotate.ts +4 -4
  176. package/src/quantize/trellis.ts +586 -0
  177. package/src/responses.ts +28 -1
  178. package/src/runtime-config.ts +18 -4
  179. package/src/sampler.ts +4 -4
  180. package/src/serve/batch-scheduler.ts +8 -1671
  181. package/src/serve/chat-request.ts +391 -0
  182. package/src/serve/chat-stage.ts +195 -0
  183. package/src/serve/checkpoint-identity.ts +64 -0
  184. package/src/serve/completion-executor.ts +15 -7
  185. package/src/serve/discovery-routes.ts +12 -13
  186. package/src/serve/generation-gateway.ts +154 -193
  187. package/src/serve/http.ts +159 -0
  188. package/src/serve/inference-request.ts +79 -0
  189. package/src/serve/isolate.ts +297 -105
  190. package/src/serve/lab-routes.ts +3 -0
  191. package/src/serve/lane-registry.ts +2 -1
  192. package/src/serve/model-admin-routes.ts +7 -5
  193. package/src/serve/model-binding.ts +43 -0
  194. package/src/serve/model-host.ts +512 -0
  195. package/src/serve/openai-wire.ts +103 -0
  196. package/src/serve/parent-application.ts +56 -0
  197. package/src/serve/pipeline.ts +33 -0
  198. package/src/serve/preparation.ts +33 -0
  199. package/src/serve/request-plan.ts +19 -15
  200. package/src/serve/request-prep.ts +346 -0
  201. package/src/serve/response-lease.ts +24 -0
  202. package/src/serve/responses-client.ts +69 -0
  203. package/src/serve/session-completion-engine.ts +110 -0
  204. package/src/serve/session-search.ts +1 -1
  205. package/src/serve/static-routes.ts +5 -5
  206. package/src/serve/text-completion-stage.ts +104 -0
  207. package/src/serve/token-streams.ts +519 -0
  208. package/src/server.ts +317 -2649
  209. package/src/spec/assistant-source.ts +10 -44
  210. package/src/spec/deepspec-source.ts +6 -6
  211. package/src/spec/dflash-source.ts +8 -8
  212. package/src/spec/dspark/ab-stats.ts +3 -3
  213. package/src/spec/dspark/calibration.ts +2 -2
  214. package/src/spec/dspark/deepspec-module.ts +2 -2
  215. package/src/spec/dspark/generate-dflash.ts +2 -2
  216. package/src/spec/dspark/generate.ts +2 -2
  217. package/src/spec/dspark/loader.ts +1 -1
  218. package/src/spec/dspark/module-dflash.ts +4 -3
  219. package/src/spec/dspark/module.ts +3 -3
  220. package/src/spec/dspark/quantize-drafter.ts +1 -1
  221. package/src/spec/generate.ts +1 -1
  222. package/src/spec/glm52-mtp-source.ts +1 -1
  223. package/src/spec/ngram-source.ts +1 -1
  224. package/src/spec/prefix-state.ts +45 -0
  225. package/src/spec/qwen-mtp-source.ts +166 -69
  226. package/src/spec/serve-loop.ts +154 -156
  227. package/src/spec/source.ts +42 -10
  228. package/src/spec/two-model.ts +1 -1
  229. package/src/ssd-cache.ts +80 -12
  230. package/src/ssd-durability.ts +25 -13
  231. package/src/tool-call.ts +51 -14
  232. package/src/train/flash-cce.ts +2 -2
  233. package/src/train/forward.ts +6 -0
  234. package/src/train/loss.ts +1 -1
  235. package/src/train/prefix-shared.ts +5 -1
  236. package/src/train/segmented.ts +5 -3
  237. package/src/train/trainer.ts +6 -6
  238. package/src/vision/prompt.ts +1 -1
  239. package/src/vision/qwen3vl-tower.ts +52 -10
  240. package/src/web/app.html +1 -1
  241. package/src/web/app.js +5 -3
  242. package/src/web/src/adapters-panel.ts +3 -3
  243. package/src/web/src/assistant.ts +2 -36
  244. package/src/web/src/chat.ts +4 -4
  245. package/src/web/src/composer.ts +4 -4
  246. package/src/web/src/hub.ts +1 -1
  247. package/src/web/src/markdown.ts +0 -0
  248. package/src/web/src/memory-panel.ts +1 -1
  249. package/src/web/src/model-picker.ts +1 -1
  250. package/src/web/src/palette.ts +1 -1
  251. package/src/web/src/protocol.ts +3 -39
  252. package/src/web/src/sessions.ts +1 -1
  253. package/src/web/src/ui-catalog.ts +4 -18
  254. package/src/web-assets.ts +47 -0
  255. package/src/weights.ts +55 -9
  256. package/docs/archive/investigations/curve-runs/bisector-route.json +0 -98
  257. package/docs/archive/investigations/curve-runs/bisector-trace.json +0 -1393
  258. package/docs/archive/investigations/curve-runs/curve-terrain.json +0 -1179
  259. package/docs/archive/investigations/curve-runs/curve-terrain.svg +0 -42
  260. package/docs/archive/investigations/hlg-ab.html +0 -34
  261. package/docs/archive/investigations/hlg-explorer.html +0 -238
  262. package/docs/archive/investigations/hlg-figs/cliff.svg +0 -1
  263. package/docs/archive/investigations/hlg-figs/cross.svg +0 -1
  264. package/docs/archive/investigations/hlg-figs/edges.svg +0 -1
  265. package/docs/archive/investigations/hlg-figs/k20-grids.svg +0 -1
  266. package/docs/archive/investigations/hlg-figs/k20-peaks.svg +0 -1
  267. package/docs/archive/investigations/hlg-figs/lw-curve.svg +0 -1
  268. package/docs/archive/investigations/hlg-figs/terrain.svg +0 -1
  269. package/docs/archive/investigations/hlg-report.html +0 -262
  270. package/docs/archive/investigations/hlg-runs/hlg-ab.json +0 -149
  271. package/docs/archive/investigations/hlg-runs/hlg-blind-prompts.md +0 -87
  272. package/docs/archive/investigations/hlg-runs/hlg-blind.json +0 -107
  273. package/docs/archive/investigations/hlg-runs/hlg-cliff.json +0 -197
  274. package/docs/archive/investigations/hlg-runs/hlg-cross.json +0 -505
  275. package/docs/archive/investigations/hlg-runs/hlg-edge.json +0 -388
  276. package/docs/archive/investigations/hlg-runs/hlg-eval1.json +0 -25
  277. package/docs/archive/investigations/hlg-runs/hlg-frontier.json +0 -86
  278. package/docs/archive/investigations/hlg-runs/hlg-grid.json +0 -242
  279. package/docs/archive/investigations/hlg-runs/hlg-grid2.json +0 -343
  280. package/docs/archive/investigations/hlg-runs/hlg-lw-coarse.json +0 -110
  281. package/docs/archive/investigations/hlg-runs/hlg-map.json +0 -383
  282. package/docs/archive/investigations/hlg-runs/run0-original.log +0 -159
  283. package/docs/archive/investigations/hlg-runs/runA-recommended.log +0 -131
  284. package/docs/archive/investigations/hlg-runs/runAA-cliff-cpm5.log +0 -26
  285. package/docs/archive/investigations/hlg-runs/runAB-grid2-e4b.log +0 -18
  286. package/docs/archive/investigations/hlg-runs/runAC-peak-e4b-partial.log +0 -25
  287. package/docs/archive/investigations/hlg-runs/runAD-ab-samples.log +0 -8
  288. package/docs/archive/investigations/hlg-runs/runAE-edge-e4b.log +0 -51
  289. package/docs/archive/investigations/hlg-runs/runAF-logit-calibration.log +0 -10
  290. package/docs/archive/investigations/hlg-runs/runAG-eval-config2.log +0 -16
  291. package/docs/archive/investigations/hlg-runs/runB-gain-variants.log +0 -192
  292. package/docs/archive/investigations/hlg-runs/runC-pivots.log +0 -162
  293. package/docs/archive/investigations/hlg-runs/runD-oetf.log +0 -132
  294. package/docs/archive/investigations/hlg-runs/runE-eotf.log +0 -162
  295. package/docs/archive/investigations/hlg-runs/runF-shaper-working.log +0 -86
  296. package/docs/archive/investigations/hlg-runs/runG-shaper-sweep.log +0 -101
  297. package/docs/archive/investigations/hlg-runs/runH-wsweep.log +0 -176
  298. package/docs/archive/investigations/hlg-runs/runI-asweep.log +0 -176
  299. package/docs/archive/investigations/hlg-runs/runJ-knobmap.log +0 -49
  300. package/docs/archive/investigations/hlg-runs/runK-frontier.log +0 -19
  301. package/docs/archive/investigations/hlg-runs/runL-widemap.log +0 -65
  302. package/docs/archive/investigations/hlg-runs/runM-frontier-emb.log +0 -15
  303. package/docs/archive/investigations/hlg-runs/runN-verify.log +0 -116
  304. package/docs/archive/investigations/hlg-runs/runO-frontier-seed5000.log +0 -15
  305. package/docs/archive/investigations/hlg-runs/runP-grid-AxSm.log +0 -15
  306. package/docs/archive/investigations/hlg-runs/runQ-grid-tgW.log +0 -15
  307. package/docs/archive/investigations/hlg-runs/runR-blind-transcript.log +0 -79
  308. package/docs/archive/investigations/hlg-runs/runS-cross-coherence.log +0 -33
  309. package/docs/archive/investigations/hlg-runs/runT-cross-cpm5.log +0 -33
  310. package/docs/archive/investigations/hlg-runs/runU-cross-12b.log +0 -33
  311. package/docs/archive/investigations/hlg-runs/runV-lw-e4b.log +0 -22
  312. package/docs/archive/investigations/hlg-runs/runW-lw-12b.log +0 -22
  313. package/docs/archive/investigations/hlg-runs/runX-lw-cpm5.log +0 -22
  314. package/docs/archive/investigations/hlg-runs/runY-cliff-e4b.log +0 -26
  315. package/docs/archive/investigations/hlg-runs/runZ-cliff-12b.log +0 -26
  316. package/docs/archive/mlx-bun-lab-report.html +0 -275
  317. package/docs/archive/wwdc-mlx-bun.html +0 -848
  318. package/docs/design/adapters-end-to-end.md +0 -205
  319. package/docs/design/batching-perf-path.md +0 -255
  320. package/docs/design/batching-v2-plan.md +0 -529
  321. package/docs/design/bucketing-stage.md +0 -197
  322. package/docs/design/compat-cli-surface-design.md +0 -230
  323. package/docs/design/docs-reorg-plan.md +0 -165
  324. package/docs/design/dspark-serving-program.md +0 -371
  325. package/docs/design/dspark-speculative-decoding.md +0 -318
  326. package/docs/design/grammar-spec-batching-integration.md +0 -301
  327. package/docs/design/memory-inference-path.md +0 -422
  328. package/docs/design/memory-synthesis.md +0 -287
  329. package/docs/design/memory-system.md +0 -237
  330. package/docs/design/mlx-lm-serving-execution-seam.md +0 -81
  331. package/docs/design/mlx-lm-tool-parity-plan.md +0 -545
  332. package/docs/design/omlx-adoption-map.md +0 -98
  333. package/docs/design/orpo-dynamic-lambda.md +0 -220
  334. package/docs/design/orpo-future-enhancements.md +0 -340
  335. package/docs/design/paged-kv-cache.md +0 -214
  336. package/docs/design/parallel-slots.md +0 -567
  337. package/docs/design/parity-tier-dag.md +0 -161
  338. package/docs/design/repo-cleanup-plan.md +0 -231
  339. package/docs/design/runtime-isolation.md +0 -263
  340. package/docs/design/segmented-backward-training.md +0 -543
  341. package/docs/design/spec-decode-larger-targets.md +0 -229
  342. package/docs/design/ssd-kv-cold-tier.md +0 -415
  343. package/docs/design/structured-output.md +0 -484
  344. package/docs/design/tauri-desktop-app.md +0 -127
  345. package/docs/design/the-dreaming-master-plan.md +0 -445
  346. package/docs/design/turboquant-kv.md +0 -235
  347. package/docs/design/turboquant-weights.md +0 -467
  348. package/docs/design/w4a16-compute-precision-spike.md +0 -232
  349. package/docs/design/web-chat-beat-matrix.md +0 -315
  350. package/docs/design/write-pipeline-entity.md +0 -468
  351. package/docs/planning/IDEAS.md +0 -69
  352. package/docs/reference/embedding.md +0 -197
  353. package/docs/reference/features-matrix.md +0 -101
  354. package/docs/reference/orpo-quickstart.md +0 -135
  355. package/src/flags.ts +0 -11
  356. /package/docs/{design → archive/investigations}/colibri-glm52-port.md +0 -0
  357. /package/docs/{planning → archive/planning}/chunk-task-roadmap.md +0 -0
  358. /package/docs/{planning → archive/planning}/curve-sampler-research-plan.md +0 -0
  359. /package/docs/{planning → archive/planning}/memory-docs-and-dag-plan.md +0 -0
  360. /package/docs/{planning → archive/planning}/pi-integration-review.md +0 -0
  361. /package/docs/{planning → archive/planning}/release-notes-v0.2.0.md +0 -0
  362. /package/src/{memory-plan.ts → admission-plan.ts} +0 -0
  363. /package/{docs/archive/investigations → src/serve/assets}/curve-terrain.html +0 -0
  364. /package/{docs/dag → src/serve/assets}/training-inference-map.html +0 -0
package/src/mlx/ffi.ts CHANGED
@@ -10,10 +10,10 @@
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
17
  import { runtimeValue } from "../runtime-config";
18
18
 
19
19
  const { ptr: P, i32, u64, f32, f64, cstring } = FFIType;
@@ -21,7 +21,7 @@ const { ptr: P, i32, u64, f32, f64, cstring } = FFIType;
21
21
  /** libmlxc resolution, in order: explicit env override → next to the
22
22
  * executable (the `bun build --compile` sidecar layout, where
23
23
  * libmlxc.dylib + libmlx.dylib + mlx.metallib ship beside the binary —
24
- * see docs/reference/embedding.md) → the downloaded native-pack cache
24
+ * see docs/reference/distribution.md) → the downloaded native-pack cache
25
25
  * (src/native-pack.ts; populated on first run by the CLI) → homebrew
26
26
  * (arm64, then Intel prefix). Keep in sync with nativeRuntimeDir(). */
27
27
  function resolveLibmlxc(): string {
@@ -38,8 +38,12 @@ function resolveLibmlxc(): string {
38
38
  }
39
39
 
40
40
  export const LIBMLXC_PATH = resolveLibmlxc();
41
-
42
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 },
43
47
  // array lifecycle
44
48
  mlx_array_new: { args: [], returns: u64 },
45
49
  mlx_array_new_data: { args: [P, P, i32, i32], returns: u64 },
@@ -78,6 +82,7 @@ export const C = dlopen(LIBMLXC_PATH, {
78
82
  mlx_device_info_get: { args: [P, u64], returns: i32 },
79
83
  mlx_device_info_free: { args: [u64], returns: i32 },
80
84
  mlx_device_info_get_size: { args: [P, u64, cstring], returns: i32 },
85
+ mlx_device_info_get_string: { args: [P, u64, cstring], returns: i32 },
81
86
  mlx_get_default_device: { args: [P], returns: i32 },
82
87
  mlx_device_free: { args: [u64], returns: i32 },
83
88
  mlx_synchronize: { args: [u64], returns: i32 },
@@ -98,6 +103,10 @@ export const C = dlopen(LIBMLXC_PATH, {
98
103
  mlx_fast_rms_norm: { args: [P, u64, u64, f32, u64], returns: i32 },
99
104
  // (res, x, weight may-be-null, bias may-be-null, eps, stream)
100
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
+ },
101
110
  mlx_matmul: { args: [P, u64, u64, u64], returns: i32 },
102
111
  mlx_contiguous: { args: [P, u64, FFIType.bool, u64], returns: i32 },
103
112
  mlx_copy: { args: [P, u64, u64], returns: i32 },
@@ -108,15 +117,14 @@ export const C = dlopen(LIBMLXC_PATH, {
108
117
  mlx_softmax_axis: { args: [P, u64, i32, FFIType.bool, u64], returns: i32 },
109
118
  mlx_max_axis: { args: [P, u64, i32, FFIType.bool, u64], returns: i32 },
110
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 },
111
123
  mlx_maximum: { args: [P, u64, u64, u64], returns: i32 },
112
124
  mlx_expand_dims: { args: [P, u64, i32, u64], returns: i32 },
113
125
  mlx_logical_or: { args: [P, u64, u64, u64], returns: i32 },
114
126
  // (res, x, dims, traditional, opt base, scale, offset, freqs may-be-null, stream)
115
127
  mlx_fast_rope: { args: [P, u64, i32, FFIType.bool, u64, f32, i32, u64, u64], returns: i32 },
116
- // (res, q, k, v, scale, mask_mode, mask may-be-null, sinks may-be-null, stream)
117
- mlx_fast_scaled_dot_product_attention: {
118
- args: [P, u64, u64, u64, f32, cstring, u64, u64, u64], returns: i32,
119
- },
120
128
  // ops (grown as phases need them)
121
129
  mlx_add: { args: [P, u64, u64, u64], returns: i32 },
122
130
  mlx_subtract: { args: [P, u64, u64, u64], returns: i32 },
@@ -150,8 +158,6 @@ export const C = dlopen(LIBMLXC_PATH, {
150
158
  mlx_arange: { args: [P, f64, f64, f64, i32, u64], returns: i32 },
151
159
  mlx_take_along_axis: { args: [P, u64, u64, i32, u64], returns: i32 },
152
160
  mlx_put_along_axis: { args: [P, u64, u64, u64, i32, u64], returns: i32 },
153
- mlx_cumsum: { args: [P, u64, i32, FFIType.bool, FFIType.bool, u64], returns: i32 },
154
- mlx_cummax: { args: [P, u64, i32, FFIType.bool, FFIType.bool, u64], returns: i32 },
155
161
  mlx_where: { args: [P, u64, u64, u64, u64], returns: i32 },
156
162
  mlx_logsumexp_axis: { args: [P, u64, i32, FFIType.bool, u64], returns: i32 },
157
163
  mlx_greater_equal: { args: [P, u64, u64, u64], returns: i32 },
@@ -220,7 +226,7 @@ export const C = dlopen(LIBMLXC_PATH, {
220
226
  mlx_save_safetensors: { args: [cstring, u64, u64], returns: i32 },
221
227
  mlx_map_string_to_array_insert: { args: [u64, cstring, u64], returns: i32 },
222
228
  mlx_map_string_to_string_insert: { args: [u64, cstring, cstring], returns: i32 },
223
- // 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).
224
230
  // mlx_closure is the usual one-pointer struct; the func-payload variant
225
231
  // carries an id we use to route the trace callback back to JS.
226
232
  mlx_closure_new: { args: [], returns: u64 },
@@ -314,6 +320,22 @@ export function takeMlxError(): string | null {
314
320
  return e;
315
321
  }
316
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
+
317
339
  /** mlx_dtype enum values (array.h). */
318
340
  export const enum Dtype {
319
341
  bool = 0, uint8 = 1, uint16 = 2, uint32 = 3, uint64 = 4,
@@ -405,6 +427,31 @@ export function clearCache(): void {
405
427
  C.mlx_clear_cache();
406
428
  }
407
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
+
408
455
  /** Metal's recommended max working-set size for the default device. */
409
456
  export function maxRecommendedWorkingSetSize(): number {
410
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
@@ -170,7 +170,7 @@ export function ropeScaled(
170
170
  * of a baked int — required inside compiled decode graphs, where the
171
171
  * offset changes every step but the graph must not. Same kernel as
172
172
  * `rope`; bit-exactness vs the static form is asserted in
173
- * tests/compile.test.ts. */
173
+ * tests/unit/compile.test.ts. */
174
174
  export function ropeDynamic(
175
175
  x: MlxArray, dims: number, base: number | null, offset: MlxArray,
176
176
  freqs: MlxArray | null, s: S = gpuStream,
@@ -189,7 +189,7 @@ export function ropeDynamic(
189
189
  * the dynamic twin of `ropeScaled`, for the universal rope factory's
190
190
  * batched-decode path (per-row [B] offsets under left-padding). Same
191
191
  * kernel as `ropeDynamic`; static/dynamic bit-exactness is asserted in
192
- * tests/compile.test.ts. */
192
+ * tests/unit/compile.test.ts. */
193
193
  export function ropeScaledDynamic(
194
194
  x: MlxArray, dims: number, traditional: boolean, base: number | null,
195
195
  scale: number, offset: MlxArray, freqs: MlxArray | null, s: S = gpuStream,
@@ -213,7 +213,7 @@ export function sdpa(
213
213
  outArray("fast_sdpa", (o) =>
214
214
  C.mlx_fast_scaled_dot_product_attention(
215
215
  o, q.handle, k.handle, v.handle, scale, ptr(cstr(maskMode)),
216
- maskArr?.handle ?? 0n, 0n, s,
216
+ maskArr?.handle ?? 0n, 0n, false, s,
217
217
  ),
218
218
  ),
219
219
  );
@@ -314,7 +314,8 @@ export function takeAxis(a: MlxArray, indices: MlxArray, axis: number, s: S = gp
314
314
  }
315
315
 
316
316
  export function reshape(a: MlxArray, shape: number[], s: S = gpuStream): MlxArray {
317
- 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]);
318
319
  return new MlxArray(
319
320
  outArray("reshape", (o) => C.mlx_reshape(o, a.handle, ptr(buf), BigInt(shape.length), s)),
320
321
  );
@@ -454,14 +455,14 @@ export function putAlongAxis(
454
455
 
455
456
  export function cumsum(a: MlxArray, axis: number, s: S = gpuStream): MlxArray {
456
457
  return new MlxArray(
457
- 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)),
458
459
  );
459
460
  }
460
461
 
461
462
  /** Cumulative maximum along `axis` (inclusive, forward) — mlx.core.cummax. */
462
463
  export function cummax(a: MlxArray, axis: number, s: S = gpuStream): MlxArray {
463
464
  return new MlxArray(
464
- 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)),
465
466
  );
466
467
  }
467
468
 
@@ -476,10 +477,13 @@ export function where(cond: MlxArray, x: MlxArray, y: MlxArray, s: S = gpuStream
476
477
  // (lab/repro/bun-ffi-f64/) traced the root cause to a Bun bug: after DFG
477
478
  // tier-up, typed-array reads following a bun:ffi call return stale values
478
479
  // (the JIT eliminates the load across the native call). Not f64 marshaling
479
- // — args reach C intact. See PLAN.md Phase 4 findings. Large constant
480
- // ranges (topP's vocab arange) are cached for the process lifetime.
481
- 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 }>();
482
484
  const ARANGE_CACHE_MIN = 65536;
485
+ const ARANGE_CACHE_BYTES = 8 * 1024 * 1024;
486
+ let arangeCacheBytes = 0;
483
487
 
484
488
  export function arange(start: number, stop: number, step: number, dtype: Dtype, s: S = gpuStream): MlxArray {
485
489
  if (!Number.isInteger(start) || !Number.isInteger(step) || step === 0)
@@ -487,7 +491,11 @@ export function arange(start: number, stop: number, step: number, dtype: Dtype,
487
491
  const n = Math.max(0, Math.ceil((stop - start) / step));
488
492
  const key = `${start}|${step}|${n}|${dtype}`;
489
493
  const cached = arangeCache.get(key);
490
- 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
+ }
491
499
 
492
500
  let arr: MlxArray;
493
501
  if (dtype === Dtype.int32) {
@@ -503,8 +511,20 @@ export function arange(start: number, stop: number, step: number, dtype: Dtype,
503
511
  arr = new MlxArray(outArray("arange", (o) => C.mlx_arange(o, start, stop, step, dtype, s)));
504
512
  }
505
513
  if (n >= ARANGE_CACHE_MIN) {
506
- arangeCache.set(key, arr);
507
- 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
+ }
508
528
  }
509
529
  return arr;
510
530
  }
@@ -611,7 +631,7 @@ export function sliceDynamic(
611
631
  /** slice_update with the start index as an ARRAY (one int32 per entry in
612
632
  * `axes`) — the compiled-decode form of the per-step cache write. Same
613
633
  * write as `sliceUpdate`; bit-exactness vs the static form is asserted
614
- * in tests/compile.test.ts. */
634
+ * in tests/unit/compile.test.ts. */
615
635
  export function sliceUpdateDynamic(
616
636
  src: MlxArray, update: MlxArray, start: MlxArray, axes: number[], s: S = gpuStream,
617
637
  ): MlxArray {
@@ -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
+ }
@@ -0,0 +1,82 @@
1
+ // Exact byte-packed TurboQuant K/V decoding. The caller owns output arrays.
2
+ // Rotation and cache policy belong to the existing codec/cache layers.
3
+ import { type MlxArray, gpuStream } from './array';
4
+ import { Dtype, type MlxHandle } from './ffi';
5
+ import { isShapelessTracing } from './compile';
6
+ import { MetalKernel } from './metal-kernel';
7
+
8
+ export type PackedKvArrays = readonly [
9
+ keyIndices: MlxArray, keyScales: MlxArray, keyZeros: MlxArray,
10
+ valueIndices: MlxArray, valueScales: MlxArray,
11
+ ];
12
+
13
+ const source = `
14
+ uint col = thread_position_in_grid.x, pos = thread_position_in_grid.y;
15
+ uint bh = thread_position_in_grid.z;
16
+ if (col >= D || pos >= vp_shape[2] || bh >= vp_shape[0] * vp_shape[1]) return;
17
+ uint b = bh / vp_shape[1], h = bh % vp_shape[1];
18
+ uint ko = b * kp_strides[0] + h * kp_strides[1] + pos * kp_strides[2];
19
+ uint vpo = b * vp_strides[0] + h * vp_strides[1] + pos * vp_strides[2];
20
+ uint kso = b * ks_strides[0] + h * ks_strides[1] + pos * ks_strides[2] + (col / KG) * ks_strides[3];
21
+ uint kzo = b * kz_strides[0] + h * kz_strides[1] + pos * kz_strides[2] + (col / KG) * kz_strides[3];
22
+ uint vso = b * vs_strides[0] + h * vs_strides[1] + pos * vs_strides[2] + (col / VG) * vs_strides[3];
23
+ int key_code;
24
+ if (KBITS == 8) key_code = int(kp[ko + col * kp_strides[3]]);
25
+ else {
26
+ uint bit = col * KBITS, byte = bit / 8u, shift = bit % 8u;
27
+ uint word = uint(kp[ko + byte * kp_strides[3]]);
28
+ if (shift + KBITS > 8u) word |= uint(kp[ko + (byte + 1u) * kp_strides[3]]) << 8u;
29
+ key_code = int((word >> shift) & ((1u << KBITS) - 1u));
30
+ }
31
+ // Keep the incumbent f32 addition before multiplication.
32
+ float shifted = float(key_code) + float(kz[kzo]);
33
+ uint i = (bh * vp_shape[2] + pos) * D + col;
34
+ outK[i] = OutK(shifted * float(ks[kso]));
35
+ uint bit = col * VBITS, byte = bit / 8u, shift = bit % 8u;
36
+ uint word = uint(vp[vpo + byte * vp_strides[3]]);
37
+ if (shift + VBITS > 8u) word |= uint(vp[vpo + (byte + 1u) * vp_strides[3]]) << 8u;
38
+ uint code = (word >> shift) & ((1u << VBITS) - 1u);
39
+ outV[i] = OutV(centroids[code] * float(vs[vso]));
40
+ `;
41
+
42
+ let kernel: MetalKernel | undefined;
43
+ function jointKernel(): MetalKernel {
44
+ return kernel ??= new MetalKernel({
45
+ name: 'turboquant_kv_decode',
46
+ inputNames: ['kp', 'ks', 'kz', 'vp', 'vs', 'centroids'],
47
+ outputNames: ['outK', 'outV'], source, ensureRowContiguous: false,
48
+ });
49
+ }
50
+
51
+ /** Return bf16 K and rotated V (f32 for eager inverse rotation, bf16 for
52
+ * deferred rotation). Null means the ordinary codec must handle this input.
53
+ * Input arrays and the shared centroid table remain borrowed. */
54
+ export function tryJointDecodePackedKv(
55
+ inputs: PackedKvArrays, centroids: MlxArray, kBits: number, vBits: number,
56
+ headDim: number, deferV: boolean, stream: MlxHandle = gpuStream,
57
+ ): [MlxArray, MlxArray] | null {
58
+ if (stream !== gpuStream || isShapelessTracing() ||
59
+ ![64, 128, 256, 512].includes(headDim) ||
60
+ ![2, 4, 5, 8].includes(kBits) || ![2, 3, 4, 5, 8].includes(vBits)) return null;
61
+ const [kp, ks, kz, vp, vs] = inputs;
62
+ if (kp.dtype !== (kBits === 8 ? Dtype.int8 : Dtype.uint8) ||
63
+ vp.dtype !== Dtype.uint8 || centroids.dtype !== Dtype.float32 ||
64
+ centroids.ndim !== 1 || centroids.size !== (1 << vBits)) return null;
65
+ const shapes = inputs.map(a => a.shape);
66
+ if (shapes.some(shape => shape.length !== 4)) return null;
67
+ const [b, h, n] = shapes[3]!;
68
+ if (!b || !h || !n || b * h * n * headDim >= 2 ** 32 ||
69
+ shapes.some(shape => shape[0] !== b || shape[1] !== h || shape[2] !== n) ||
70
+ shapes[0]![3] !== headDim * kBits / 8 || shapes[3]![3] !== headDim * vBits / 8 ||
71
+ shapes[1]![3] !== shapes[2]![3]) return null;
72
+ const keyGroup = headDim / shapes[1]![3]!, valueGroup = headDim / shapes[4]![3]!;
73
+ if (![32, 64].includes(keyGroup) || ![32, 64].includes(valueGroup) ||
74
+ [ks, kz, vs].some(a => ![Dtype.float16, Dtype.bfloat16, Dtype.float32].includes(a.dtype))) return null;
75
+ const shape = [b, h, n, headDim], valueDtype = deferV ? Dtype.bfloat16 : Dtype.float32;
76
+ return jointKernel().apply([...inputs, centroids], {
77
+ templateDtypes: { OutK: Dtype.bfloat16, OutV: valueDtype },
78
+ templateInts: { D: headDim, KBITS: kBits, VBITS: vBits, KG: keyGroup, VG: valueGroup },
79
+ grid: [headDim, n, b * h], threadGroup: [Math.min(headDim, 256), Math.max(1, 256 / headDim), 1],
80
+ outputs: [{ shape, dtype: Dtype.bfloat16 }, { shape, dtype: valueDtype }], stream,
81
+ }) as [MlxArray, MlxArray];
82
+ }
@@ -0,0 +1,122 @@
1
+ // Joint packed K/V decode with eager inverse rotation in the measured D256 regime.
2
+ // Hadamard butterfly/layout follows MLX 0.31.2,
3
+ // mlx/backend/metal/kernels/hadamard.h, Copyright © 2024 Apple Inc., MIT.
4
+ import { type MlxArray, gpuStream } from "./array";
5
+ import { Dtype, type MlxHandle } from "./ffi";
6
+ import { isShapelessTracing } from "./compile";
7
+ import { MetalKernel } from "./metal-kernel";
8
+ import { type PackedKvArrays } from "./turboquant-kv-decode";
9
+
10
+ const header = `
11
+ template <short R> inline void tq_inverse_radix(thread float* x) {
12
+ constexpr short logR = __builtin_ctz(R);
13
+ short h = 1;
14
+ #pragma unroll
15
+ for (short s = 0; s < logR; s++) {
16
+ #pragma unroll
17
+ for (short i = 0; i < R / 2; i++) {
18
+ short k = i & (h - 1), j = ((i - k) << 1) + k;
19
+ float a = x[j], b = x[j + h];
20
+ x[j] = a + b; x[j + h] = a - b;
21
+ }
22
+ h <<= 1;
23
+ }
24
+ }
25
+ `;
26
+ const source = `
27
+ constexpr short D = 256, R = 16, NT = 16, LOG_R = 4;
28
+ uint pos = thread_position_in_grid.y, bh = thread_position_in_grid.z;
29
+ short i = thread_position_in_grid.x;
30
+ bool valid = pos < vp_shape[2];
31
+ uint b = bh / vp_shape[1], hidx = bh % vp_shape[1];
32
+ uint ko = b * kp_strides[0] + hidx * kp_strides[1] + pos * kp_strides[2];
33
+ uint vo = b * vp_strides[0] + hidx * vp_strides[1] + pos * vp_strides[2];
34
+ uint kso = b * ks_strides[0] + hidx * ks_strides[1] + pos * ks_strides[2];
35
+ uint kzo = b * kz_strides[0] + hidx * kz_strides[1] + pos * kz_strides[2];
36
+ uint vso = b * vs_strides[0] + hidx * vs_strides[1] + pos * vs_strides[2];
37
+ uint row = (bh * vp_shape[2] + pos) * D;
38
+ threadgroup float storage[D * 16];
39
+ threadgroup float* buf = storage + thread_position_in_threadgroup.y * D;
40
+ #pragma unroll
41
+ for (short j = 0; j < R / 4; j++) {
42
+ short index = j * 4 * NT + i * 4;
43
+ #pragma unroll
44
+ for (short r = 0; r < 4; r++) {
45
+ uint col = index + r;
46
+ if (!valid) { buf[col] = 0.0f; continue; }
47
+ int key_code = int(kp[ko + col * kp_strides[3]]);
48
+ float shifted = float(key_code) + float(kz[kzo + (col / 32) * kz_strides[3]]);
49
+ outK[row + col] = OutK(shifted * float(ks[kso + (col / 32) * ks_strides[3]]));
50
+ uint bit = col * 3u, byte = bit / 8u, shift = bit % 8u;
51
+ uint word = uint(vp[vo + byte * vp_strides[3]]);
52
+ if (shift + 3u > 8u) word |= uint(vp[vo + (byte + 1u) * vp_strides[3]]) << 8u;
53
+ uint code = (word >> shift) & 7u;
54
+ buf[col] = centroids[code * centroids_strides[0]] * float(vs[vso + (col / 32) * vs_strides[3]]);
55
+ }
56
+ }
57
+ threadgroup_barrier(mem_flags::mem_threadgroup);
58
+ float x[R]; short h = 1;
59
+ #pragma unroll
60
+ for (short s = 0; s < 2; s++) {
61
+ short k = i & (h - 1), j = ((i - k) << LOG_R) + k;
62
+ #pragma unroll
63
+ for (short r = 0; r < R; r++) x[r] = buf[j + h * r];
64
+ tq_inverse_radix<R>(x);
65
+ #pragma unroll
66
+ for (short r = 0; r < R; r++) buf[j + h * r] = x[r];
67
+ h <<= LOG_R;
68
+ threadgroup_barrier(mem_flags::mem_threadgroup);
69
+ }
70
+ #pragma unroll
71
+ for (short j = 0; j < R / 4; j++) {
72
+ short index = j * 4 * NT + i * 4;
73
+ #pragma unroll
74
+ for (short r = 0; r < 4; r++) {
75
+ uint col = index + r;
76
+ float normalized = buf[col] * 0.0625f;
77
+ if (valid) outV[row + col] = OutV(normalized * signs[col * signs_strides[0]]);
78
+ }
79
+ }
80
+ `;
81
+
82
+ let kernel: MetalKernel | undefined;
83
+ function inverseKernel(): MetalKernel {
84
+ return kernel ??= new MetalKernel({
85
+ name: "turboquant_kv_inverse256",
86
+ inputNames: ["kp", "ks", "kz", "vp", "vs", "centroids", "signs"],
87
+ outputNames: ["outK", "outV"], header, source, ensureRowContiguous: false,
88
+ });
89
+ }
90
+
91
+ /** Borrow packed inputs and codec tables; return owned bf16 K/V or null.
92
+ * Only the measured eager D256/k8v3/B1/H4/fp16/group32 regime is eligible.
93
+ * Other regimes continue through the existing joint decoder or original codec.
94
+ * N affects dispatch size but is never a shader template parameter. */
95
+ export function tryDecodePackedKvInverse256(
96
+ inputs: PackedKvArrays, centroids: MlxArray, signs: MlxArray,
97
+ kBits: number, vBits: number, headDim: number, s: MlxHandle = gpuStream,
98
+ ): [MlxArray, MlxArray] | null {
99
+ if (s !== gpuStream || isShapelessTracing() || headDim !== 256 || kBits !== 8 || vBits !== 3)
100
+ return null;
101
+ const [kp, ks, kz, vp, vs] = inputs;
102
+ const shape = kp.shape;
103
+ if (shape.length !== 4 || shape[0] !== 1 || shape[1] !== 4 || shape[3] !== 256)
104
+ return null;
105
+ const n = shape[2]!;
106
+ if (n < 8192 || n * 1024 >= 2 ** 32) return null;
107
+ const samePrefix = (a: MlxArray, width: number) => {
108
+ const x = a.shape;
109
+ return x.length === 4 && x[0] === 1 && x[1] === 4 && x[2] === n && x[3] === width;
110
+ };
111
+ if (kp.dtype !== Dtype.int8 || vp.dtype !== Dtype.uint8 || !samePrefix(vp, 96)) return null;
112
+ if (![ks, kz, vs].every(a => a.dtype === Dtype.float16 && samePrefix(a, 8))) return null;
113
+ if (centroids.dtype !== Dtype.float32 || centroids.shape.length !== 1 || centroids.shape[0] !== 8)
114
+ return null;
115
+ if (signs.dtype !== Dtype.float32 || signs.shape.length !== 1 || signs.shape[0] !== 256)
116
+ return null;
117
+ return inverseKernel().apply([...inputs, centroids, signs], {
118
+ templateDtypes: { OutK: Dtype.bfloat16, OutV: Dtype.bfloat16 },
119
+ grid: [16, Math.ceil(n / 16) * 16, 4], threadGroup: [16, 16, 1],
120
+ outputs: [{ shape, dtype: Dtype.bfloat16 }, { shape, dtype: Dtype.bfloat16 }], stream: s,
121
+ }) as [MlxArray, MlxArray];
122
+ }