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
@@ -2,13 +2,13 @@
2
2
  //
3
3
  // Records every MoE router decision as JSONL — one record per router call:
4
4
  // { c: callSeq, l: layerIdx, s: [B, L, k], i: [flattened uint32 expert ids] }
5
- // Analyse with scripts/analyze-expert-trace.ts (coverage curve, working-set
5
+ // Analyse with analyze-expert-trace.ts (deleted 2026-08-23; git history) (coverage curve, working-set
6
6
  // size, within-task stability, cross-task specialisation).
7
7
  //
8
8
  // Two ways to drive it:
9
9
  // - env: `MLX_BUN_EXPERT_TRACE=<path> mlx-bun serve ...` (auto-starts here)
10
10
  // - programmatic: beginExpertTrace(path) / endExpertTrace() per domain
11
- // (scripts/run-expert-trace.ts uses this to trace several domains from
11
+ // (run-expert-trace.ts (deleted 2026-08-23; git history) uses this to trace several domains from
12
12
  // one model load).
13
13
  //
14
14
  // Recording adds a per-call GPU->host sync (it reads the routed indices back),
@@ -17,9 +17,9 @@
17
17
  // Router.forward is a single branch (zero overhead) — inert by default, same
18
18
  // discipline as the slots flag.
19
19
 
20
- import { Dtype } from "./mlx/ffi";
21
- import type { MlxArray } from "./mlx/array";
22
- import { runtimeValue } from "./runtime-config";
20
+ import { Dtype } from "../../mlx/ffi";
21
+ import type { MlxArray } from "../../mlx/array";
22
+ import { runtimeValue } from "../../runtime-config";
23
23
 
24
24
  let sink: Bun.FileSink | null = null;
25
25
  let seq = 0;
@@ -1,5 +1,5 @@
1
1
  // Paged KV cache — vLLM-style block-pool storage behind the standard Cache
2
- // interface (docs/design/paged-kv-cache.md). OPTIONAL and default-off
2
+ // interface (docs/design/kv-cache.md). OPTIONAL and default-off
3
3
  // (`--paged-kv` / GenerateOptions.pagedKv); v1 scope is serial batch=1
4
4
  // decode on Gemma4-family plain full-attention layers, bf16 only.
5
5
  //
@@ -11,7 +11,7 @@
11
11
  // block axis + transpose + reshape) and hand that to the unchanged
12
12
  // ops.sdpa call site — identical bytes to what a plain KVCache would have
13
13
  // fetched, so the paged path is gated bit-exact, not KL-tolerated
14
- // (tests/paged-kv-parity.test.ts). The gather IS the cost: a full K/V copy
14
+ // (tests/parity/paged-kv-parity.test.ts). The gather IS the cost: a full K/V copy
15
15
  // per step, pure bandwidth tax at batch=1 — v1 ships the abstraction for
16
16
  // the batched/CoW follow-ups, not a speed win (see the design doc's
17
17
  // Motivation, which says this honestly).
@@ -21,10 +21,10 @@
21
21
  // gateway's #modelCachesBatchable, generated forwards' #matches() — must
22
22
  // EXCLUDE paged caches and fall back to the monolith/uncompiled path.
23
23
 
24
- import { MlxArray } from "../mlx/array";
25
- import { Dtype } from "../mlx/ffi";
26
- import * as ops from "../mlx/ops";
27
- import { createCausalMask, type Cache, type Mask } from "./gemma4-base";
24
+ import { MlxArray } from "../../mlx/array";
25
+ import { Dtype } from "../../mlx/ffi";
26
+ import * as ops from "../../mlx/ops";
27
+ import { createCausalMask, type Cache, type Mask } from "../../model/gemma4-base";
28
28
 
29
29
  /** Typed pool-exhaustion error: a generation outgrew its pool. Sizing from
30
30
  * prompt+maxTokens at construction makes this unreachable in practice;
@@ -134,6 +134,9 @@ export class BlockPool {
134
134
  * shape): head count / head dims / dtype come from the first k/v pair,
135
135
  * so the wiring (maybePageKv) needs no per-model shape plumbing. */
136
136
  export class PagedKVCache implements Cache {
137
+ /** Distinct kind: paged layout is serial-only and never merges into a
138
+ * batch, so no capability guard should ever match it as plain. */
139
+ signature(): string { return "kv:paged"; }
137
140
  /** Matches KVCache.STEP: v1's growth granularity is a permutation of
138
141
  * today's 256-token step into fixed reusable slots, not a new tuning
139
142
  * axis (--paged-kv-block-size overrides for experiments). */
package/src/library.ts ADDED
@@ -0,0 +1,40 @@
1
+ /** CPU-safe high-level entry. Native modules load only after bootstrap. */
2
+ export { createInferenceEngine } from "./engine/engine";
3
+ export { CancellationSource } from "./engine/cancellation";
4
+ export { createCompletionClient, createDirectHost } from "./client";
5
+ export type * from "./contracts/generation";
6
+ export type * from "./contracts/completion";
7
+ export type * from "./contracts/host";
8
+
9
+ /** Install/locate the native pack before importing the compatibility API. */
10
+ export async function initializeMlx(): Promise<typeof import("./index")> {
11
+ const { ensureNativeRuntime } = await import("./native-pack");
12
+ await ensureNativeRuntime();
13
+ return import("./index");
14
+ }
15
+
16
+ /** Own one local worker. Model resolution and native initialization remain in
17
+ * the existing CLI; importing this module does not start it or download weights. */
18
+ export async function openIsolatedHost(model: string, options: {
19
+ arguments?: readonly string[];
20
+ command?: readonly string[];
21
+ readyTimeoutMs?: number;
22
+ } = {}): Promise<import("./contracts/host").EngineHost<Request, Response>> {
23
+ const { EngineChild } = await import("./serve/isolate");
24
+ const { tmpdir } = await import("node:os");
25
+ const { join } = await import("node:path");
26
+ const { fileURLToPath } = await import("node:url");
27
+ const source = fileURLToPath(new URL("./cli.ts", import.meta.url));
28
+ if (!options.command && source.includes("$bunfs"))
29
+ throw new Error("a compiled library consumer must supply the mlx-bun executable as command");
30
+ const command = options.command ?? [process.execPath, source];
31
+ if (!command.length) throw new Error("engine command must not be empty");
32
+ const socketPath = join(tmpdir(), `mlx-bun-library-${crypto.randomUUID()}.sock`);
33
+ const host = new EngineChild({
34
+ argv: [...command,
35
+ "serve", "--model", model, ...options.arguments ?? [], "--unix", socketPath, "--no-open"],
36
+ socketPath, readyTimeoutMs: options.readyTimeoutMs,
37
+ });
38
+ try { await host.ready; return host; }
39
+ catch (error) { await host.close(); throw error; }
40
+ }
package/src/lora.ts CHANGED
@@ -12,6 +12,7 @@
12
12
 
13
13
  import { existsSync, readdirSync, statSync } from "node:fs";
14
14
  import { resolve } from "node:path";
15
+ import { createHash } from "node:crypto";
15
16
  import { ptr, read } from "bun:ffi";
16
17
  import { MlxArray, cpuStream } from "./mlx/array";
17
18
  import { C } from "./mlx/ffi";
@@ -198,6 +199,7 @@ export class AdapterManager {
198
199
  readonly #mounted = new Map<string, AdapterInfo>();
199
200
  /** Owned adapter arrays per id (disposed on unmount). */
200
201
  readonly #arrays = new Map<string, MlxArray[]>();
202
+ readonly #revisions = new Map<string, string>();
201
203
 
202
204
  constructor(model: RuntimeModel) {
203
205
  this.#model = model;
@@ -216,6 +218,11 @@ export class AdapterManager {
216
218
 
217
219
  const weightsFile = adapterWeightsFile(path);
218
220
  const { scale, rank: configRank, rsLora } = await readAdapterScale(path);
221
+ // Hash once at mount, before native allocation. Reusing an adapter name
222
+ // for different weights or scale must never reuse its prefix/checkpoint.
223
+ const digest = createHash("sha256").update(JSON.stringify({ scale, rank: configRank, rsLora }));
224
+ for await (const chunk of Bun.file(weightsFile).stream()) digest.update(chunk);
225
+ const revision = digest.digest("hex");
219
226
  const tensors = loadAdapterTensors(weightsFile);
220
227
  const targets = this.#model.loraTargets();
221
228
 
@@ -347,6 +354,7 @@ export class AdapterManager {
347
354
  ramBytes,
348
355
  };
349
356
  this.#mounted.set(id, info);
357
+ this.#revisions.set(id, revision);
350
358
  this.#arrays.set(id, validated.flatMap(({ lw }) => [lw.a, lw.b]));
351
359
  return info;
352
360
  }
@@ -355,6 +363,7 @@ export class AdapterManager {
355
363
  * stay in place for other adapters, like the reference. */
356
364
  unmount(id: string): number {
357
365
  if (!this.#mounted.delete(id)) return 0;
366
+ this.#revisions.delete(id);
358
367
  let removed = 0;
359
368
  for (const linear of this.#model.loraTargets().values()) {
360
369
  if (linear.adapters?.delete(id)) removed++;
@@ -366,6 +375,15 @@ export class AdapterManager {
366
375
  return removed;
367
376
  }
368
377
 
378
+ /** Ordered, content-addressed namespace for the actual mounted composition. */
379
+ cacheNamespace(ids: readonly string[]): string {
380
+ return ids.map((id) => {
381
+ const revision = this.#revisions.get(id);
382
+ if (!revision) throw new Error(`adapter ${id} is no longer mounted`);
383
+ return `${id}@${revision}`;
384
+ }).join("+");
385
+ }
386
+
369
387
  list(): AdapterInfo[] {
370
388
  return [...this.#mounted.values()];
371
389
  }
@@ -17,7 +17,7 @@
17
17
  // preserving the logical Host header and HTTPS SNI, so the transport cannot
18
18
  // re-resolve to a private address between validation and connect.
19
19
  //
20
- // This module must stay free of mlx imports so tests/media-fetch.test.ts
20
+ // This module must stay free of mlx imports so tests/unit/media-fetch.test.ts
21
21
  // runs model-free.
22
22
 
23
23
  import { lookup } from "node:dns/promises";
@@ -3,7 +3,7 @@
3
3
  // The Dreaming write pipeline emits Wikipedia-shaped articles: each entity gets
4
4
  // one Markdown file with a fixed top-to-bottom skeleton —
5
5
  // H1 → series banner → infobox → lead → sections → See also → Notes → References
6
- // (see docs/design/the-dreaming-master-plan.md and write-pipeline-entity.md).
6
+ // (see docs/design/dreaming-nightly-pipeline.md and docs/design/dreaming-nightly-pipeline.md).
7
7
  // This module reads that grammar deterministically: the structured outputs the
8
8
  // read side traverses (infobox key-value facts, the [[wikilink]] graph, footnote
9
9
  // bijection) are parsed here, never by the model.
@@ -16,7 +16,7 @@
16
16
  //
17
17
  // Re-chunk only conversations whose updated_at > chunked_at (NULL chunked_at = a
18
18
  // never-segmented conversation). Setting chunked_at after a successful run makes
19
- // re-running a no-op. See docs/design/the-dreaming-master-plan.md → P5-T2.
19
+ // re-running a no-op. See docs/design/dreaming-nightly-pipeline.md → P5-T2.
20
20
 
21
21
  import { MemoryStore, chunkId } from "./db";
22
22
  import { callLocal, type LocalInput } from "./model";
@@ -24,12 +24,8 @@
24
24
  // server and to the trainer's prompt-region render (dataset.ts:98-101).
25
25
 
26
26
  import { existsSync, readdirSync } from "node:fs";
27
- import { loadTaskModel, greedyDecodeBitExact, type TaskModel } from "../eval/runner";
28
27
  import type { ChatMessage, ChatTemplate } from "../chat-template";
29
28
  import type { LoadedTokenizer } from "../tokenizer";
30
- import { BatchScheduler, type RowSampler } from "../serve/batch-scheduler";
31
- import * as ops from "../mlx/ops";
32
- import type { MlxArray } from "../mlx/array";
33
29
  import { runtimeValue } from "../runtime-config";
34
30
 
35
31
  const HF_HUB = `${process.env.HOME}/.cache/huggingface/hub`;
@@ -38,7 +34,7 @@ const E4B_REPO = "models--mlx-community--gemma-4-e4b-it-OptiQ-4bit";
38
34
  /** Resolve the e4b snapshot dir by globbing snapshots/ for the one carrying
39
35
  * config.json — so a freshly-downloaded model needs no hardcoded commit hash.
40
36
  * Returns an _unresolved sentinel (precheck → STOP) until the download lands.
41
- * Mirrors tests/paths.ts hfSnapshot / SNAPSHOT_E4B, inlined to keep src self-
37
+ * Mirrors tests/support/paths.ts hfSnapshot / SNAPSHOT_E4B, inlined to keep src self-
42
38
  * contained (no src→tests dependency). */
43
39
  function resolveE4bSnapshot(): string {
44
40
  const base = `${HF_HUB}/${E4B_REPO}/snapshots`;
@@ -140,30 +136,18 @@ export function memoryPromptIds(
140
136
  /** Async mutex: acquire() resolves to a release fn; releases run FIFO. Serializes
141
137
  * memory generation ops so the global `loraState.active` is owned exclusively for
142
138
  * one op's lifetime (set for the chunk batch, reset after). */
143
- class AsyncMutex {
144
- #tail: Promise<void> = Promise.resolve();
145
- acquire(): Promise<() => void> {
146
- let release!: () => void;
147
- const gate = new Promise<void>((r) => { release = r; });
148
- const wait = this.#tail;
149
- this.#tail = this.#tail.then(() => gate);
150
- return wait.then(() => release);
151
- }
152
- }
153
-
154
- interface MemoryRuntime {
155
- tm: TaskModel;
156
- template: ChatTemplate;
157
- scheduler: BatchScheduler;
158
- /** True once the `memory-chunk` adapter is mounted on the shared model. */
159
- hasChunkAdapter: boolean;
139
+ export interface MemoryCompletionRequest {
140
+ readonly stage: string;
141
+ readonly input: LocalInput;
142
+ readonly maxTokens: number;
160
143
  }
144
+ export type MemoryCompletionClient = import("../contracts/completion").BatchCompletionClient<MemoryCompletionRequest, string>;
161
145
 
162
146
  /** Max rows in a memory batch (continuous-batching width). 1 disables batching
163
147
  * (callLocalBatch then loops on the bit-exact greedy fallback).
164
148
  * Default 1 (serial): batching measured 1.7-1.9x SLOWER for the real
165
149
  * extract/chunk workload (heterogeneous prefills pad) and can diverge on
166
- * near-ties — see docs/design/memory-inference-path.md "Verification results".
150
+ * near-ties — see docs/design/dreaming-nightly-pipeline.md "Verification results".
167
151
  * Opt back in with MLX_BUN_MEMORY_BATCH=8 if a length-bucketed scheduler
168
152
  * lands. (Decision: Josh, 2026-07-01.) */
169
153
  export function memoryBatchSize(): number {
@@ -171,152 +155,29 @@ export function memoryBatchSize(): number {
171
155
  return Number.isFinite(n) && n >= 1 ? Math.floor(n) : 1;
172
156
  }
173
157
 
174
- function requireModel(): void {
175
- if (!existsSync(`${MODEL_ID}/config.json`)) {
176
- throw new Error(
177
- `memory: Gemma-4-e4b is not downloaded (looked under ${HF_HUB}/${E4B_REPO}/snapshots). ` +
178
- `Fetch it first:\n HF_HUB_DISABLE_XET=1 hf download mlx-community/gemma-4-e4b-it-OptiQ-4bit`,
179
- );
180
- }
181
- }
182
-
183
- let runtimePromise: Promise<MemoryRuntime> | null = null;
184
- const opLock = new AsyncMutex(); // serializes whole memory ops (loraState ownership)
185
- const schedLock = new AsyncMutex(); // the scheduler's exclusive-GPU lock
186
-
187
- /** Lazily load the ONE shared model + mount memory-chunk + build the scheduler.
188
- * Never auto-downloads; throws (with the fetch hint) until the snapshot lands. */
189
- async function getRuntime(): Promise<MemoryRuntime> {
190
- if (!runtimePromise) {
191
- runtimePromise = (async () => {
192
- requireModel();
193
- const tm = await loadTaskModel(MODEL_ID); // base e4b, no adapter
194
- if (!tm.template) throw new Error(`memory: ${MODEL_ID} has no chat template`);
195
- let hasChunkAdapter = false;
196
- const chunkDir = adapterDirFor("chunk");
197
- if (chunkDir) {
198
- // Mount ONCE; activation is per-call via loraState.active (see decodeGreedy
199
- // / batchDecode). Mounting alone runs base — it must be activated.
200
- const { AdapterManager } = await import("../lora");
201
- await new AdapterManager(tm.model).mount("memory-chunk", chunkDir);
202
- hasChunkAdapter = true;
203
- }
204
- const scheduler = new BatchScheduler(tm.model, {
205
- maxBatch: memoryBatchSize(),
206
- lock: { acquire: () => schedLock.acquire() },
207
- });
208
- return { tm, template: tm.template, scheduler, hasChunkAdapter };
209
- })();
210
- }
211
- return runtimePromise;
212
- }
213
-
214
- /** The adapters to activate for a stage. Only `chunk` is adapter-bound (and only
215
- * when `memory-chunk` is mounted); every base stage runs with `active = []`. */
216
- function activeFor(stage: string, rt: MemoryRuntime): string[] {
217
- return stage === "chunk" && rt.hasChunkAdapter ? ["memory-chunk"] : [];
218
- }
219
-
220
- /** One bit-exact greedy decode for a stage (raw-forward argmax, runner parity).
221
- * Sets the stage's adapter for the call and resets `active` to [] after. */
222
- function decodeGreedy(rt: MemoryRuntime, stage: string, input: LocalInput, maxTokens: number): string {
223
- const active = activeFor(stage, rt);
224
- rt.tm.model.loraState.active = active;
225
- const ids = memoryPromptIds(stage, input, rt.tm.tokenizer, rt.template);
226
- try {
227
- return greedyDecodeBitExact(
228
- { ...rt.tm, activeAdapters: active.length ? active : undefined },
229
- ids,
230
- maxTokens,
231
- );
232
- } finally {
233
- rt.tm.model.loraState.active = [];
234
- }
235
- }
236
-
237
- /** Batched decode for N independent inputs of ONE stage. Sets the stage adapter
238
- * globally for the whole batch (the scheduler reads `loraState.active` per
239
- * forward, so all rows get it uniformly — a batch is necessarily single-adapter),
240
- * submits every row to the shared scheduler with a greedy per-row sampler, and
241
- * resets `active` after all rows finish. Order-preserving: out[i] ↔ inputs[i].
242
- *
243
- * Decode-path caveat: this samples via forwardHidden/logitsFromHidden (the
244
- * scheduler), which can diverge from the runner's raw-forward greedy on near-ties
245
- * past ~32 tokens. For the JSON/binary memory stages (validated by
246
- * well-formedness) this is acceptable; the single-call fallback stays bit-exact. */
247
- async function batchDecode(
248
- rt: MemoryRuntime,
249
- stage: string,
250
- inputs: LocalInput[],
251
- maxTokens: number,
252
- ): Promise<string[]> {
253
- const active = activeFor(stage, rt);
254
- rt.tm.model.loraState.active = active;
255
- const eos = rt.tm.config.eosTokenIds;
256
- const greedySample: RowSampler = (logits1V: MlxArray) => ops.argmaxAxis(logits1V, -1);
257
- try {
258
- return await Promise.all(
259
- inputs.map((inp) => {
260
- const ids = memoryPromptIds(stage, inp, rt.tm.tokenizer, rt.template);
261
- const toks: number[] = [];
262
- return rt.scheduler
263
- .submit({
264
- promptIds: ids,
265
- maxTokens,
266
- eosTokenIds: eos,
267
- sample: greedySample,
268
- onToken: (t) => { toks.push(t); },
269
- })
270
- .then(() => rt.tm.tokenizer.decode(toks, true));
271
- }),
272
- );
273
- } finally {
274
- rt.tm.model.loraState.active = [];
275
- }
276
- }
277
-
278
- // ---------------------------------------------------------------------------
279
- // Public seams
280
- // ---------------------------------------------------------------------------
281
158
 
282
- /** Call the local model for a pipeline `stage` with a {system?, user} input.
283
- * Renders through the shared chat-template path, runs the MODEL precheck, and
284
- * decodes ONE completion on the bit-exact greedy path (the safe fallback used by
285
- * every single-call site). NEVER auto-downloads. */
286
- export async function callLocal(
287
- stage: string,
288
- input: LocalInput,
289
- opts?: { maxTokens?: number },
290
- ): Promise<string> {
291
- const rt = await getRuntime();
292
- const release = await opLock.acquire();
293
- try {
294
- return decodeGreedy(rt, stage, input, opts?.maxTokens ?? 256);
295
- } finally {
296
- release();
297
- }
159
+ /** One configured client per pipeline. Importing this module does not load MLX. */
160
+ export function createMemoryCalls(client: MemoryCompletionClient) {
161
+ return {
162
+ callLocal(stage: string, input: LocalInput, opts?: { maxTokens?: number }): Promise<string> {
163
+ return client.complete({ stage, input, maxTokens: opts?.maxTokens ?? 256 });
164
+ },
165
+ callLocalBatch(stage: string, inputs: LocalInput[], opts?: { maxTokens?: number }): Promise<string[]> {
166
+ if (!inputs.length) return Promise.resolve([]);
167
+ return client.completeBatch(inputs.map((input) => ({ stage, input, maxTokens: opts?.maxTokens ?? 256 })));
168
+ },
169
+ };
298
170
  }
299
171
 
300
- /** N independent calls for ONE stage (homogeneous adapter by construction).
301
- * Routed through the shared in-process BatchScheduler when batch>1 AND N>1, else
302
- * looped on the bit-exact greedy fallback. Order-preserving: out[i] ↔ inputs[i]. */
303
- export async function callLocalBatch(
304
- stage: string,
305
- inputs: LocalInput[],
306
- opts?: { maxTokens?: number },
307
- ): Promise<string[]> {
308
- if (inputs.length === 0) return [];
309
- const rt = await getRuntime();
310
- const maxTokens = opts?.maxTokens ?? 256;
311
- const release = await opLock.acquire();
312
- try {
313
- if (inputs.length === 1 || memoryBatchSize() <= 1) {
314
- const out: string[] = [];
315
- for (const inp of inputs) out.push(decodeGreedy(rt, stage, inp, maxTokens));
316
- return out;
317
- }
318
- return await batchDecode(rt, stage, inputs, maxTokens);
319
- } finally {
320
- release();
321
- }
322
- }
172
+ const defaultCalls = createMemoryCalls({
173
+ async complete(request) {
174
+ const { memoryCompletionClient } = await import("../backends/mlx/memory-client");
175
+ return memoryCompletionClient.complete(request);
176
+ },
177
+ async completeBatch(requests) {
178
+ const { memoryCompletionClient } = await import("../backends/mlx/memory-client");
179
+ return memoryCompletionClient.completeBatch(requests);
180
+ },
181
+ });
182
+ export const callLocal = defaultCalls.callLocal;
183
+ export const callLocalBatch = defaultCalls.callLocalBatch;
@@ -7,7 +7,7 @@
7
7
  // Topics_to_Ignore.md) and are read in at synthesis time so the system governs
8
8
  // itself in its own substrate.
9
9
  //
10
- // See docs/design/memory-system.md → "Synthesis runs on the local model".
10
+ // See docs/design/dreaming-nightly-pipeline.md → "Synthesis runs on the local model".
11
11
 
12
12
  import { readFileSync } from "node:fs";
13
13
  import { join } from "node:path";
@@ -11,7 +11,7 @@
11
11
  // rest of server.ts's /api/* conventions. Route dispatch (matching
12
12
  // url.pathname + method) lives in server.ts; this file is the pure handler
13
13
  // bodies so they're unit-testable without booting a model context (see
14
- // tests/memory-rest.test.ts).
14
+ // tests/using/memory-rest.test.ts).
15
15
 
16
16
  import { realpath } from "node:fs/promises";
17
17
  import { tmpdir } from "node:os";
@@ -11,7 +11,7 @@
11
11
  // it. That job runs the full synthesis DAG and produces/updates articles on the
12
12
  // schedule with no further configuration.
13
13
  //
14
- // See docs/design/memory-system.md → "Scheduling (launchd)".
14
+ // See docs/design/dreaming-nightly-pipeline.md → "Scheduling (launchd)".
15
15
 
16
16
  import { spawn } from "node:child_process";
17
17
  import { mkdir, rm, writeFile } from "node:fs/promises";
@@ -356,7 +356,10 @@ export async function runExtractStage(
356
356
  // SERIAL fallback (injected `call`, single chunk, or batching disabled).
357
357
  let extracted = 0;
358
358
  for (const id of targetIds) {
359
- await extractEntities(store, id, { resolver, policy: policy || undefined, call: opts.call });
359
+ // Pass the (possibly empty) policy through: `|| undefined` made extractEntities
360
+ // re-load Meta/Entities.md and THROW on a vault without it, while the batched
361
+ // path above proceeds with "" — the serial path must match.
362
+ await extractEntities(store, id, { resolver, policy, call: opts.call });
360
363
  extracted++;
361
364
  if (extracted % 10 === 0) {
362
365
  opts.onEvent?.({ type: "log", message: ` extracted ${extracted}/${target} chunks` });
@@ -9,7 +9,7 @@
9
9
  // an already-working system at real scale — they're pure filesystem functions
10
10
  // with no MCP coupling, so they drop straight in. We keep lucien's good
11
11
  // descriptive concepts (articles, [[wikilinks]], Meta governance pages,
12
- // conv: citations) and drop the mythology. See docs/design/memory-system.md.
12
+ // conv: citations) and drop the mythology. See docs/design/dreaming-nightly-pipeline.md.
13
13
  //
14
14
  // M0 is the read path: setup the vault, then list / read / search / index it.
15
15
  // Synthesis (writing articles from conversations) is M1 and lives elsewhere.
@@ -987,10 +987,8 @@ const REFERENCE_LINKS: { name: string; source: string }[] = [
987
987
  { name: "mlx-bun_Server_API", source: join(REPO_ROOT, "docs", "reference", "server-api.md") },
988
988
  { name: "mlx-bun_Server_Config", source: join(REPO_ROOT, "docs", "reference", "server-config.md") },
989
989
  { name: "mlx-bun_Library_API", source: join(REPO_ROOT, "docs", "reference", "library-api.md") },
990
- { name: "mlx-bun_Embedding", source: join(REPO_ROOT, "docs", "reference", "embedding.md") },
991
990
  { name: "mlx-bun_Distribution", source: join(REPO_ROOT, "docs", "reference", "distribution.md") },
992
991
  { name: "mlx-bun_Training", source: join(REPO_ROOT, "docs", "reference", "training.md") },
993
- { name: "mlx-bun_ORPO_Quickstart", source: join(REPO_ROOT, "docs", "reference", "orpo-quickstart.md") },
994
992
  { name: "mlx-bun_Product_Roadmap", source: join(REPO_ROOT, "docs", "planning", "PRODUCT_ROADMAP.md") },
995
993
  ];
996
994
 
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);