mlx-bun 0.0.13 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (427) hide show
  1. package/README.md +71 -578
  2. package/bin/mlx-bun.mjs +1 -1
  3. package/docs/README.md +91 -0
  4. package/docs/{design → archive}/hlg-sampling.md +3 -3
  5. package/docs/{design → archive/investigations}/audio-input-plan.md +4 -4
  6. package/docs/{design → archive/investigations}/colibri-glm52-port.md +1 -1
  7. package/docs/{design → archive/investigations}/diffusion-gemma-port.md +3 -3
  8. package/docs/{design → archive/investigations}/faithful-l1-consolidation.md +4 -4
  9. package/docs/{investigations → archive/investigations}/hlg-sampling-investigation.md +1 -1
  10. package/docs/{design → archive/investigations}/minicpm5-decode-megakernel.md +11 -11
  11. package/docs/{design → archive/investigations}/optimization_plan.md +1 -1
  12. package/docs/archive/investigations/pareto-specialized-runtime-findings.md +92 -0
  13. package/docs/{design → archive/investigations}/pre-colibri-stabilization.md +5 -5
  14. package/docs/{design → archive/investigations}/the-dreaming-handoff.md +2 -2
  15. package/docs/{planning → archive/planning}/chat-ui-vision.md +1 -1
  16. package/docs/{planning → archive/planning}/chunk-task-roadmap.md +1 -1
  17. package/docs/{planning → archive/planning}/curve-sampler-research-plan.md +4 -4
  18. package/docs/{planning → archive/planning}/memory-docs-and-dag-plan.md +1 -1
  19. package/docs/{planning → archive/planning}/release-notes-v0.0.9.md +1 -1
  20. package/docs/archive/planning/release-notes-v0.2.0.md +49 -0
  21. package/docs/{planning → archive/planning}/website-readme-pass-plan.md +1 -1
  22. package/docs/design/batching.md +569 -0
  23. package/docs/design/decode-speed-program.md +3178 -169
  24. package/docs/design/dreaming-nightly-pipeline.md +675 -282
  25. package/docs/design/generic-model-support.md +579 -334
  26. package/docs/design/kv-cache.md +680 -0
  27. package/docs/design/orpo-training.md +673 -1239
  28. package/docs/design/speculative-decoding.md +1627 -0
  29. package/docs/design/turboquant.md +2102 -0
  30. package/docs/design/unified-engine-frontier-plan.md +1831 -727
  31. package/docs/design/web-chat-redesign.md +666 -1083
  32. package/docs/planning/PRODUCT_ROADMAP.md +34 -1
  33. package/docs/planning/release-notes-v0.3.0.md +35 -0
  34. package/docs/planning/video-series.md +421 -0
  35. package/docs/reference/benchmarks.md +1162 -0
  36. package/docs/reference/cli.md +395 -149
  37. package/docs/reference/distribution.md +321 -181
  38. package/docs/reference/environment.md +348 -0
  39. package/docs/reference/glossary.md +39 -0
  40. package/docs/reference/library-api.md +580 -142
  41. package/docs/reference/memory.md +227 -64
  42. package/docs/reference/models.md +137 -6
  43. package/docs/reference/server-api.md +938 -422
  44. package/docs/reference/server-config.md +652 -320
  45. package/docs/reference/training.md +393 -254
  46. package/docs/reference/troubleshooting.md +166 -0
  47. package/package.json +10 -5
  48. package/src/admission-plan.ts +18 -0
  49. package/src/anthropic.ts +92 -3
  50. package/src/audio/decode.ts +2 -2
  51. package/src/audio/features.ts +2 -2
  52. package/src/backends/mlx/autoregressive.ts +108 -0
  53. package/src/backends/mlx/batch-group.ts +1646 -0
  54. package/src/backends/mlx/diffusion.ts +39 -0
  55. package/src/backends/mlx/draft-target.ts +47 -0
  56. package/src/backends/mlx/evaluation.ts +105 -0
  57. package/src/backends/mlx/fill-append.ts +45 -0
  58. package/src/backends/mlx/gateway-binding.ts +71 -0
  59. package/src/backends/mlx/graph.ts +38 -0
  60. package/src/backends/mlx/memory-client.ts +189 -0
  61. package/src/backends/mlx/memory-guard.ts +40 -0
  62. package/src/backends/mlx/methods.ts +90 -0
  63. package/src/backends/mlx/model-prompt.ts +164 -0
  64. package/src/backends/mlx/model-serving.ts +79 -0
  65. package/src/backends/mlx/prefill.ts +33 -0
  66. package/src/backends/mlx/request-policy.ts +20 -0
  67. package/src/backends/mlx/rollback.ts +51 -0
  68. package/src/backends/mlx/serial-executor.ts +273 -0
  69. package/src/backends/mlx/speculative.ts +114 -0
  70. package/src/backends/mlx/state-views.ts +26 -0
  71. package/src/backends/mlx/text-engine.ts +73 -0
  72. package/src/cli.ts +189 -61
  73. package/src/client.ts +66 -0
  74. package/src/config.ts +21 -4
  75. package/src/contracts/completion.ts +16 -0
  76. package/src/contracts/execution.ts +49 -0
  77. package/src/contracts/generation.ts +103 -0
  78. package/src/contracts/host.ts +7 -0
  79. package/src/contracts/jobs.ts +66 -0
  80. package/src/contracts/pi-provider.ts +30 -0
  81. package/src/contracts/pi.ts +259 -0
  82. package/src/contracts/resources.ts +18 -0
  83. package/src/contracts/scheduling.ts +27 -0
  84. package/src/contracts/tsconfig.json +14 -0
  85. package/src/diffusion/diffusion-generate.ts +218 -153
  86. package/src/embed.ts +1 -1
  87. package/src/engine/admission.ts +59 -0
  88. package/src/engine/cancellation.ts +38 -0
  89. package/src/engine/engine.ts +56 -0
  90. package/src/engine/execution-coordinator.ts +70 -0
  91. package/src/engine/execution-plan.ts +51 -0
  92. package/src/engine/resources.ts +42 -0
  93. package/src/engine/scheduler.ts +67 -0
  94. package/src/engine/session.ts +247 -0
  95. package/src/eval/README.md +44 -1
  96. package/src/eval/kl.ts +263 -23
  97. package/src/eval/reference-logits.ts +207 -0
  98. package/src/eval/runner.ts +10 -79
  99. package/src/eval/tasks/bfcl.ts +2 -1
  100. package/src/eval/tasks/gsm8k.ts +2 -1
  101. package/src/eval/tasks/hashhop.ts +2 -1
  102. package/src/eval/tasks/humaneval.ts +5 -4
  103. package/src/eval/tasks/ifeval.ts +2 -1
  104. package/src/eval/tasks/mmlu.ts +2 -1
  105. package/src/expert-io.ts +3 -1
  106. package/src/expert-offload.ts +3 -1
  107. package/src/fill/echo-index.ts +188 -0
  108. package/src/fill/fill-session.ts +452 -0
  109. package/src/fill/proposal.ts +68 -0
  110. package/src/fill/schema-rows.ts +506 -0
  111. package/src/fill/tool-boundary.ts +103 -0
  112. package/src/fit.ts +24 -43
  113. package/src/generate.ts +740 -337
  114. package/src/grammar.ts +14 -8
  115. package/src/harness-pi.ts +15 -34
  116. package/src/hub-rest.ts +4 -4
  117. package/src/index.ts +27 -0
  118. package/src/inference/denoising.ts +17 -0
  119. package/src/inference/graph.ts +33 -0
  120. package/src/inference/prefill.ts +39 -0
  121. package/src/inference/rollback.ts +7 -0
  122. package/src/jobs/db.ts +4 -0
  123. package/src/jobs/index.ts +2 -0
  124. package/src/jobs/job-entry.ts +15 -14
  125. package/src/jobs/runner.ts +98 -13
  126. package/src/jobs/task-client.ts +19 -0
  127. package/src/jobs/types.ts +2 -58
  128. package/src/kv-scheme.ts +199 -0
  129. package/src/kv-store.ts +459 -234
  130. package/src/lab/README.md +18 -0
  131. package/src/{assets → lab/curve}/curve-designer.html +2 -2
  132. package/src/{curve-sampler.ts → lab/curve/curve-sampler.ts} +2 -2
  133. package/src/{expert-trace.ts → lab/expert-trace/expert-trace.ts} +7 -5
  134. package/src/{model → lab/paged-kv}/paged-kv.ts +9 -6
  135. package/src/library.ts +40 -0
  136. package/src/lora.ts +18 -0
  137. package/src/media-fetch.ts +20 -4
  138. package/src/memory/article.ts +1 -1
  139. package/src/memory/chunk.ts +1 -1
  140. package/src/memory/model.ts +32 -170
  141. package/src/memory/prompts.ts +1 -1
  142. package/src/memory/rest.ts +1 -1
  143. package/src/memory/schedule.ts +1 -1
  144. package/src/memory/stages.ts +4 -1
  145. package/src/memory/vault.ts +3 -4
  146. package/src/mlx/array.ts +9 -10
  147. package/src/mlx/compile.ts +12 -2
  148. package/src/mlx/ffi.ts +66 -22
  149. package/src/mlx/materialize.ts +23 -0
  150. package/src/mlx/metal-kernel.ts +10 -11
  151. package/src/mlx/ops.ts +64 -19
  152. package/src/mlx/quantized-rows.ts +36 -0
  153. package/src/mlx/token-bitmask.ts +52 -0
  154. package/src/mlx/turboquant-kv-decode.ts +82 -0
  155. package/src/mlx/turboquant-kv-inverse.ts +122 -0
  156. package/src/mlx/turboquant-ops.ts +33 -3
  157. package/src/mlx/turboquant-tables.ts +2 -2
  158. package/src/model/batched-mask.ts +9 -3
  159. package/src/model/batched-quant.ts +2 -2
  160. package/src/model/batched-rotating-quant.ts +45 -99
  161. package/src/model/batched-rotating-state.ts +73 -0
  162. package/src/model/batched-rotating.ts +57 -106
  163. package/src/model/batched-row-storage.ts +127 -0
  164. package/src/model/compiled-decode.ts +4 -3
  165. package/src/model/diffusion-gemma.ts +8 -8
  166. package/src/model/factory.ts +87 -49
  167. package/src/model/fingerprint.ts +1 -1
  168. package/src/model/flash-attention.ts +3 -3
  169. package/src/model/gemma4-base.ts +187 -17
  170. package/src/model/gemma4.ts +6 -6
  171. package/src/model/generated/gemma4-12b.ts +52 -148
  172. package/src/model/generated/gemma4-26b.ts +34 -94
  173. package/src/model/generated/gemma4-e4b.ts +28 -76
  174. package/src/model/glm52-cache.ts +19 -9
  175. package/src/model/glm52-dsa.ts +13 -9
  176. package/src/model/glm52-memory.ts +28 -1
  177. package/src/model/implementation.ts +69 -0
  178. package/src/model/minicpm5.ts +7 -8
  179. package/src/model/profile.ts +443 -0
  180. package/src/model/qwen-append.ts +12 -0
  181. package/src/model/qwen-conv.ts +52 -0
  182. package/src/model/qwen3-delta.ts +98 -2
  183. package/src/model/qwen3-mrope.ts +252 -0
  184. package/src/model/qwen3_5-checkpoint.ts +164 -0
  185. package/src/model/qwen3_5.ts +336 -60
  186. package/src/model/support.ts +1 -1
  187. package/src/model/trellis-balanced-scatter.ts +51 -0
  188. package/src/model/trellis-linear.ts +672 -0
  189. package/src/model/trellis-shared-m.ts +151 -0
  190. package/src/model/trellis-shared-scatter.ts +144 -0
  191. package/src/model/trellis-splitk-prefill.ts +158 -0
  192. package/src/model/trellis-tiled-prefill.ts +136 -0
  193. package/src/model/trellis-vector-expand.ts +62 -0
  194. package/src/model/trellis-wide-prefill.ts +94 -0
  195. package/src/model/universal/dense.ts +1 -1
  196. package/src/model/universal/modules.ts +8 -1
  197. package/src/native/frame_extract.swift +140 -0
  198. package/src/native-pack.ts +15 -7
  199. package/src/pi-provider.ts +6 -64
  200. package/src/pi-web.ts +14 -320
  201. package/src/prompt-cache.ts +97 -54
  202. package/src/quantize/allocator.ts +1 -1
  203. package/src/quantize/atomic-output.ts +38 -0
  204. package/src/quantize/calibration.ts +2 -1
  205. package/src/quantize/config-writer.ts +16 -3
  206. package/src/quantize/index.ts +28 -1
  207. package/src/quantize/job.ts +11 -0
  208. package/src/quantize/quantizer.ts +265 -100
  209. package/src/quantize/rotate.ts +603 -0
  210. package/src/quantize/safetensors-writer.ts +79 -1
  211. package/src/quantize/sensitivity.ts +41 -59
  212. package/src/quantize/trellis.ts +586 -0
  213. package/src/quantize/weight-transform.ts +292 -0
  214. package/src/responses.ts +130 -3
  215. package/src/runtime-config.ts +81 -0
  216. package/src/sampler.ts +276 -4
  217. package/src/serve/admin-routes.ts +184 -0
  218. package/src/serve/aux-routes.ts +132 -0
  219. package/src/serve/batch-scheduler.ts +8 -1558
  220. package/src/serve/chat-request.ts +391 -0
  221. package/src/serve/chat-stage.ts +195 -0
  222. package/src/serve/checkpoint-identity.ts +64 -0
  223. package/src/serve/completion-executor.ts +354 -0
  224. package/src/serve/completion-sink.ts +157 -0
  225. package/src/serve/discovery-routes.ts +204 -0
  226. package/src/serve/generation-gateway.ts +211 -243
  227. package/src/serve/http.ts +159 -0
  228. package/src/serve/inference-request.ts +79 -0
  229. package/src/serve/isolate.ts +297 -105
  230. package/src/serve/kv-budget.ts +14 -0
  231. package/src/serve/lab-routes.ts +341 -0
  232. package/src/serve/lane-registry.ts +2 -1
  233. package/src/serve/model-admin-routes.ts +155 -0
  234. package/src/serve/model-binding.ts +43 -0
  235. package/src/serve/model-host.ts +512 -0
  236. package/src/serve/openai-wire.ts +103 -0
  237. package/src/serve/parent-application.ts +56 -0
  238. package/src/serve/pipeline.ts +33 -0
  239. package/src/serve/preparation.ts +33 -0
  240. package/src/serve/prompt-response-trace.ts +147 -0
  241. package/src/serve/request-plan.ts +162 -0
  242. package/src/serve/request-prep.ts +346 -0
  243. package/src/serve/response-lease.ts +24 -0
  244. package/src/serve/responses-client.ts +69 -0
  245. package/src/serve/session-completion-engine.ts +110 -0
  246. package/src/serve/session-search.ts +1 -1
  247. package/src/serve/static-routes.ts +101 -0
  248. package/src/serve/text-completion-stage.ts +104 -0
  249. package/src/serve/token-streams.ts +519 -0
  250. package/src/server.ts +523 -3551
  251. package/src/spec/assistant-source.ts +10 -44
  252. package/src/spec/deepspec-source.ts +6 -6
  253. package/src/spec/dflash-source.ts +9 -8
  254. package/src/spec/dspark/ab-stats.ts +3 -3
  255. package/src/spec/dspark/calibration.ts +2 -2
  256. package/src/spec/dspark/deepspec-module.ts +2 -2
  257. package/src/spec/dspark/generate-dflash.ts +2 -2
  258. package/src/spec/dspark/generate.ts +2 -2
  259. package/src/spec/dspark/loader.ts +1 -1
  260. package/src/spec/dspark/module-dflash.ts +5 -4
  261. package/src/spec/dspark/module.ts +3 -3
  262. package/src/spec/dspark/quantize-drafter.ts +1 -1
  263. package/src/spec/generate.ts +1 -1
  264. package/src/spec/glm52-mtp-source.ts +9 -1
  265. package/src/spec/ngram-source.ts +1 -1
  266. package/src/spec/prefix-state.ts +45 -0
  267. package/src/spec/qwen-mtp-source.ts +528 -0
  268. package/src/spec/serve-loop.ts +179 -213
  269. package/src/spec/source.ts +42 -10
  270. package/src/spec/two-model.ts +1 -1
  271. package/src/ssd-cache.ts +95 -24
  272. package/src/ssd-durability.ts +207 -0
  273. package/src/tool-call.ts +51 -14
  274. package/src/train/flash-cce.ts +14 -13
  275. package/src/train/forward.ts +38 -1
  276. package/src/train/loss.ts +3 -2
  277. package/src/train/prefix-shared.ts +5 -1
  278. package/src/train/segmented.ts +9 -6
  279. package/src/train/trainer.ts +9 -8
  280. package/src/vision/preprocess.ts +1 -1
  281. package/src/vision/prompt.ts +48 -2
  282. package/src/vision/qwen3vl-preprocess.ts +409 -0
  283. package/src/vision/qwen3vl-prompt.ts +196 -0
  284. package/src/vision/qwen3vl-tower.ts +555 -0
  285. package/src/vision/video-frames.ts +148 -0
  286. package/src/web/app.html +1 -1
  287. package/src/web/app.js +15 -6
  288. package/src/web/src/adapters-panel.ts +3 -3
  289. package/src/web/src/assistant.ts +2 -36
  290. package/src/web/src/chat.ts +16 -9
  291. package/src/web/src/composer.ts +4 -4
  292. package/src/web/src/hub.ts +1 -1
  293. package/src/web/src/markdown.ts +0 -0
  294. package/src/web/src/memory-panel.ts +1 -1
  295. package/src/web/src/model-picker.ts +1 -1
  296. package/src/web/src/palette.ts +1 -1
  297. package/src/web/src/protocol.ts +3 -39
  298. package/src/web/src/sessions.ts +1 -1
  299. package/src/web/src/ui-catalog.ts +4 -18
  300. package/src/web-assets.ts +47 -0
  301. package/src/weights.ts +134 -33
  302. package/docs/archive/mlx-bun-lab-report.html +0 -275
  303. package/docs/archive/wwdc-mlx-bun.html +0 -848
  304. package/docs/design/adapters-end-to-end.md +0 -205
  305. package/docs/design/batching-perf-path.md +0 -255
  306. package/docs/design/batching-v2-plan.md +0 -529
  307. package/docs/design/bucketing-stage.md +0 -197
  308. package/docs/design/compat-cli-surface-design.md +0 -230
  309. package/docs/design/docs-reorg-plan.md +0 -165
  310. package/docs/design/dspark-serving-program.md +0 -371
  311. package/docs/design/dspark-speculative-decoding.md +0 -268
  312. package/docs/design/grammar-spec-batching-integration.md +0 -301
  313. package/docs/design/memory-inference-path.md +0 -422
  314. package/docs/design/memory-synthesis.md +0 -287
  315. package/docs/design/memory-system.md +0 -237
  316. package/docs/design/mlx-lm-tool-parity-plan.md +0 -545
  317. package/docs/design/omlx-adoption-map.md +0 -98
  318. package/docs/design/orpo-dynamic-lambda.md +0 -220
  319. package/docs/design/orpo-future-enhancements.md +0 -340
  320. package/docs/design/paged-kv-cache.md +0 -214
  321. package/docs/design/parallel-slots.md +0 -567
  322. package/docs/design/parity-tier-dag.md +0 -161
  323. package/docs/design/repo-cleanup-plan.md +0 -189
  324. package/docs/design/runtime-isolation.md +0 -263
  325. package/docs/design/segmented-backward-training.md +0 -543
  326. package/docs/design/spec-decode-larger-targets.md +0 -229
  327. package/docs/design/ssd-kv-cold-tier.md +0 -288
  328. package/docs/design/structured-output.md +0 -484
  329. package/docs/design/tauri-desktop-app.md +0 -127
  330. package/docs/design/the-dreaming-master-plan.md +0 -445
  331. package/docs/design/turboquant-kv.md +0 -235
  332. package/docs/design/w4a16-compute-precision-spike.md +0 -232
  333. package/docs/design/web-chat-beat-matrix.md +0 -315
  334. package/docs/design/write-pipeline-entity.md +0 -468
  335. package/docs/investigations/curve-runs/bisector-route.json +0 -98
  336. package/docs/investigations/curve-runs/bisector-trace.json +0 -1393
  337. package/docs/investigations/curve-runs/curve-terrain.json +0 -1179
  338. package/docs/investigations/curve-runs/curve-terrain.svg +0 -42
  339. package/docs/investigations/hlg-ab.html +0 -34
  340. package/docs/investigations/hlg-explorer.html +0 -238
  341. package/docs/investigations/hlg-figs/cliff.svg +0 -1
  342. package/docs/investigations/hlg-figs/cross.svg +0 -1
  343. package/docs/investigations/hlg-figs/edges.svg +0 -1
  344. package/docs/investigations/hlg-figs/k20-grids.svg +0 -1
  345. package/docs/investigations/hlg-figs/k20-peaks.svg +0 -1
  346. package/docs/investigations/hlg-figs/lw-curve.svg +0 -1
  347. package/docs/investigations/hlg-figs/terrain.svg +0 -1
  348. package/docs/investigations/hlg-report.html +0 -262
  349. package/docs/investigations/hlg-runs/hlg-ab.json +0 -149
  350. package/docs/investigations/hlg-runs/hlg-blind-prompts.md +0 -87
  351. package/docs/investigations/hlg-runs/hlg-blind.json +0 -107
  352. package/docs/investigations/hlg-runs/hlg-cliff.json +0 -197
  353. package/docs/investigations/hlg-runs/hlg-cross.json +0 -505
  354. package/docs/investigations/hlg-runs/hlg-edge.json +0 -388
  355. package/docs/investigations/hlg-runs/hlg-eval1.json +0 -25
  356. package/docs/investigations/hlg-runs/hlg-frontier.json +0 -86
  357. package/docs/investigations/hlg-runs/hlg-grid.json +0 -242
  358. package/docs/investigations/hlg-runs/hlg-grid2.json +0 -343
  359. package/docs/investigations/hlg-runs/hlg-lw-coarse.json +0 -110
  360. package/docs/investigations/hlg-runs/hlg-map.json +0 -383
  361. package/docs/investigations/hlg-runs/run0-original.log +0 -159
  362. package/docs/investigations/hlg-runs/runA-recommended.log +0 -131
  363. package/docs/investigations/hlg-runs/runAA-cliff-cpm5.log +0 -26
  364. package/docs/investigations/hlg-runs/runAB-grid2-e4b.log +0 -18
  365. package/docs/investigations/hlg-runs/runAC-peak-e4b-partial.log +0 -25
  366. package/docs/investigations/hlg-runs/runAD-ab-samples.log +0 -8
  367. package/docs/investigations/hlg-runs/runAE-edge-e4b.log +0 -51
  368. package/docs/investigations/hlg-runs/runAF-logit-calibration.log +0 -10
  369. package/docs/investigations/hlg-runs/runAG-eval-config2.log +0 -16
  370. package/docs/investigations/hlg-runs/runB-gain-variants.log +0 -192
  371. package/docs/investigations/hlg-runs/runC-pivots.log +0 -162
  372. package/docs/investigations/hlg-runs/runD-oetf.log +0 -132
  373. package/docs/investigations/hlg-runs/runE-eotf.log +0 -162
  374. package/docs/investigations/hlg-runs/runF-shaper-working.log +0 -86
  375. package/docs/investigations/hlg-runs/runG-shaper-sweep.log +0 -101
  376. package/docs/investigations/hlg-runs/runH-wsweep.log +0 -176
  377. package/docs/investigations/hlg-runs/runI-asweep.log +0 -176
  378. package/docs/investigations/hlg-runs/runJ-knobmap.log +0 -49
  379. package/docs/investigations/hlg-runs/runK-frontier.log +0 -19
  380. package/docs/investigations/hlg-runs/runL-widemap.log +0 -65
  381. package/docs/investigations/hlg-runs/runM-frontier-emb.log +0 -15
  382. package/docs/investigations/hlg-runs/runN-verify.log +0 -116
  383. package/docs/investigations/hlg-runs/runO-frontier-seed5000.log +0 -15
  384. package/docs/investigations/hlg-runs/runP-grid-AxSm.log +0 -15
  385. package/docs/investigations/hlg-runs/runQ-grid-tgW.log +0 -15
  386. package/docs/investigations/hlg-runs/runR-blind-transcript.log +0 -79
  387. package/docs/investigations/hlg-runs/runS-cross-coherence.log +0 -33
  388. package/docs/investigations/hlg-runs/runT-cross-cpm5.log +0 -33
  389. package/docs/investigations/hlg-runs/runU-cross-12b.log +0 -33
  390. package/docs/investigations/hlg-runs/runV-lw-e4b.log +0 -22
  391. package/docs/investigations/hlg-runs/runW-lw-12b.log +0 -22
  392. package/docs/investigations/hlg-runs/runX-lw-cpm5.log +0 -22
  393. package/docs/investigations/hlg-runs/runY-cliff-e4b.log +0 -26
  394. package/docs/investigations/hlg-runs/runZ-cliff-12b.log +0 -26
  395. package/docs/planning/IDEAS.md +0 -69
  396. package/docs/reference/embedding.md +0 -197
  397. package/docs/reference/features-matrix.md +0 -100
  398. package/docs/reference/orpo-quickstart.md +0 -135
  399. package/src/flags.ts +0 -13
  400. /package/docs/{investigations → archive/investigations}/chunk-finetune-experiment.md +0 -0
  401. /package/docs/{investigations → archive/investigations}/colibri-oracle-pin.md +0 -0
  402. /package/docs/{investigations → archive/investigations}/curve-bisector-routing.md +0 -0
  403. /package/docs/{investigations → archive/investigations}/decode-roofline-lookagain.md +0 -0
  404. /package/docs/{investigations → archive/investigations}/dspark-handoff.md +0 -0
  405. /package/docs/{investigations → archive/investigations}/expert-offload-single-user-moe.md +0 -0
  406. /package/docs/{investigations → archive/investigations}/indexshare-performance-spike.md +0 -0
  407. /package/docs/{investigations → archive/investigations}/kernel-perf-review-2026-07.md +0 -0
  408. /package/docs/{investigations → archive/investigations}/lab-build-journal.md +0 -0
  409. /package/docs/{investigations → archive/investigations}/openwiki-evaluation.md +0 -0
  410. /package/docs/{investigations → archive/investigations}/orpo-base-uf-experiment-and-directions.md +0 -0
  411. /package/docs/{investigations → archive/investigations}/orpo-flash-cce-pin-leak.md +0 -0
  412. /package/docs/{investigations → archive/investigations}/orpo-uf-testing-handoff.md +0 -0
  413. /package/docs/{investigations → archive/investigations}/pi-builtin-investigation.md +0 -0
  414. /package/docs/{investigations → archive/investigations}/segmented-backward-handoff.md +0 -0
  415. /package/docs/{investigations → archive/investigations}/starter-model-port-handoff.md +0 -0
  416. /package/docs/{investigations → archive/investigations}/steel-flash-cce-handoff.md +0 -0
  417. /package/docs/{investigations → archive/investigations}/trainer-validation-experiment.md +0 -0
  418. /package/docs/{planning → archive/planning}/chat-ui-vision-memory-first.md +0 -0
  419. /package/docs/{planning → archive/planning}/journal.md +0 -0
  420. /package/docs/{planning → archive/planning}/pi-integration-review.md +0 -0
  421. /package/docs/{planning → archive/planning}/release-notes-v0.0.10.md +0 -0
  422. /package/docs/{planning → archive/planning}/release-notes-v0.0.11.md +0 -0
  423. /package/docs/{planning → archive/planning}/release-notes-v0.0.12.md +0 -0
  424. /package/docs/{planning → archive/planning}/release-notes-v0.0.13.md +0 -0
  425. /package/docs/{planning → archive/planning}/web-ui-pass-plan.md +0 -0
  426. /package/{docs/investigations → src/serve/assets}/curve-terrain.html +0 -0
  427. /package/{docs/dag → src/serve/assets}/training-inference-map.html +0 -0
package/src/cli.ts CHANGED
@@ -13,7 +13,8 @@ import { existsSync } from "node:fs";
13
13
  import { basename, join, resolve } from "node:path";
14
14
  import { Registry } from "./registry";
15
15
  import { loadModelConfig, parseTurboQuantScheme, type TurboQuantScheme } from "./config";
16
- import { fit, skuMatrix, thisMachine } from "./fit";
16
+ import { fit, skuMatrix, thisMachine, type FitKvScheme } from "./fit";
17
+ import { resolveKvScheme } from "./kv-scheme";
17
18
  import { EvalDB } from "./evaldb";
18
19
  import pkg from "../package.json" with { type: "json" };
19
20
  import { renderHelp } from "./tui";
@@ -27,8 +28,21 @@ import {
27
28
  planGlm52MemoryForArtifact,
28
29
  } from "./model/glm52-memory";
29
30
  import type { JobEvent } from "./jobs/types";
31
+ import {
32
+ configureRuntime,
33
+ runtimeValue,
34
+ type RuntimeKey,
35
+ } from "./runtime-config";
30
36
 
31
37
  const argv = process.argv.slice(2);
38
+ // Private self-exec protocol for managed jobs in the single-file binary.
39
+ // Literal imports in job-entry keep the numerical runners inside the bundle.
40
+ if (argv[0] === "__job") {
41
+ const { runJobEntry } = await import("./jobs/job-entry");
42
+ await runJobEntry(argv[1]);
43
+ process.exit(0);
44
+ }
45
+
32
46
  // The appliance path: naked `mlx-bun` (or only options, e.g.
33
47
  // `mlx-bun --port 9000`) runs `serve` — first run downloads a model and
34
48
  // opens the chat UI. `--help`/`--version` and explicit subcommands still win.
@@ -97,13 +111,19 @@ const SERVER_FLAGS = `Server options:
97
111
  restarts (long-context agent TTFT drops from a
98
112
  full re-prefill to a zero-copy mmap restore).
99
113
  Off unless set.
100
- --ssd-cache-max <GB> SSD tier byte cap [default: 32 GB]
114
+ --ssd-cache-max <GB> Optional SSD tier byte cap; 0 = unlimited
115
+ [default: unlimited]
101
116
  --ssd-demote-idle <sec> Demote prompt-cache entries idle this long to the
102
117
  SSD tier, freeing their GPU memory (prefixes stay
103
118
  reachable via zero-copy restore). 0 disables.
104
119
  [default: 300 when --ssd-cache is on]
105
120
  --ssd-cache-verify Verify tensor hashes on every restore (reads all
106
121
  bytes eagerly — integrity paranoia only)
122
+ --generation-checkpoint <tokens>
123
+ Snapshot an in-flight serial generation to the SSD
124
+ cache every N emitted tokens. Repeating the same
125
+ request after restart replays and resumes it.
126
+ Requires --ssd-cache and --batch 1.
107
127
  --isolate Run the inference engine as a CHILD process and
108
128
  keep this process a pure UI/API proxy — the UI
109
129
  stays instant under GPU load and survives engine
@@ -118,10 +138,10 @@ const SERVER_FLAGS = `Server options:
118
138
  --unix <path> (internal) listen on a unix socket instead of a
119
139
  TCP port — the engine half of --isolate.
120
140
  --batch <n> Max concurrent requests decoded together
121
- [default: 8]. A LONE request runs the exact
122
- serial engine (same bits, same speed); the cap
123
- engages only when concurrent requests arrive
124
- (agent fan-out). 1 pins strict serial.
141
+ [default: 8]. A lone scheduler request uses the
142
+ parity- and latency-gated B=1 fast path; concurrent
143
+ requests can form B=N (agent fan-out). 1 pins the
144
+ preserved strict serial executor.
125
145
  --decode-concurrency is accepted as the
126
146
  mlx_lm.server alias (semantics differ; see
127
147
  docs/reference/server-config.md)
@@ -152,9 +172,13 @@ Model & quality:
152
172
  Serial lane only — with --batch N a mounted draft
153
173
  routes every request serial, like mlx_lm.server.
154
174
  --draft-kind <kind> Override draft-artifact detection:
155
- two-model | assistant | dspark | deepspec | ngram
175
+ two-model | assistant | dspark | deepspec | mtp | ngram
156
176
  (deepspec = DeepSeek's released Gemma4DSparkModel
157
- drafters; ngram = MODEL-FREE prompt lookup — no
177
+ drafters; mtp = a NATIVE multi-token-prediction
178
+ head split from the target's own release, e.g.
179
+ mlx-community/Qwen3.8-27B-MTP-bf16 — auto-detected
180
+ from model_type *_mtp;
181
+ ngram = MODEL-FREE prompt lookup — no
158
182
  --draft-model, drafts copied from the request's
159
183
  own context, lossless) [default: auto]
160
184
  --num-draft-tokens <n> Drafts per verify round [default: 3; ngram: 10;
@@ -171,7 +195,7 @@ Model & quality:
171
195
  (bf16), 4 / 8 (uniform bits), or turbo[:k<bits>v
172
196
  <bits>] (rotation-based TurboQuant — default
173
197
  k8v3; kBits in {2,4,5,8}, vBits in {2,3,4,5,8};
174
- docs/design/turboquant-kv.md)
198
+ docs/design/turboquant.md)
175
199
  [default: off — quantized KV measured 5-20% slower
176
200
  decode than bf16 at ≤16k (on mlx-lm too); it buys
177
201
  memory headroom, so opt in when context is tight.
@@ -186,7 +210,7 @@ Model & quality:
186
210
  --kv-quant, --draft-model; bypasses the prompt
187
211
  cache; runs uncompiled decode. Bit-exact with the
188
212
  plain path; expect a small decode cost at batch=1
189
- (the gather copy). docs/design/paged-kv-cache.md
213
+ (the gather copy). docs/design/kv-cache.md
190
214
  [default: off]
191
215
  --paged-kv-block-size <n> Tokens per KV block [default: 256 = the plain
192
216
  cache's growth step]
@@ -202,13 +226,14 @@ Model & quality:
202
226
  pass --temp 0 for its unset-request behavior)
203
227
  [default: the model's generation_config.json]
204
228
  --max-tokens <n> Completion cap when a request omits max_tokens
205
- (mlx_lm.server flag; its default is 512 there)
206
- [default: 128 GLM-5.2; otherwise 65536 chat /
207
- 512 raw completion]
229
+ (mlx_lm.server flag; --max-tokens 512 reproduces
230
+ its default) [default: 128 GLM-5.2 (memory-plan
231
+ reservation); otherwise none — a defaulted
232
+ request runs to EOS or the admitted context]
208
233
  --hlg-sampling on|off Piecewise tone-curve sampling (HLG): rolls off the
209
234
  top, boosts the mids, gentles the tail. Gain folds
210
235
  from --temperature. [default: off]
211
- See docs/design/hlg-sampling.md
236
+ See docs/archive/hlg-sampling.md
212
237
  --hlg-width <nats> HLG mid-region half-width [default: 4]
213
238
  --hlg-shoulder <nats> HLG highlight rolloff scale [default: 4]
214
239
  --hlg-toe <nats> HLG shadow rolloff scale [default: 6]
@@ -343,6 +368,8 @@ Usage: mlx-bun fit <query> [options]
343
368
 
344
369
  Options:
345
370
  --ctx <tokens> Context size to assess [default: 8192]
371
+ --kv-quant <mode> Bill the KV cache quantized: 4 | 8 | config | off
372
+ (mirrors serve --kv-quant; default bf16)
346
373
  --skus Also print the Apple silicon SKU matrix
347
374
 
348
375
  Solves weights + KV growth + prefill transient against wired memory and
@@ -379,7 +406,7 @@ Usage: mlx-bun benchmark [query] [options]
379
406
 
380
407
  Runs mlx-bun by itself (no other systems required) and records the
381
408
  result to the eval DB. For quotable, cross-stack numbers use
382
- ./benchmark.sh from the repo — it preflight-gates on an idle machine
409
+ bun scripts/bench-serve.ts all from the repo — it preflight-gates on an idle machine
383
410
  and runs the mlx-lm/optiq comparison legs.
384
411
 
385
412
  [query] Model to benchmark (--query <q> is accepted for the
@@ -463,7 +490,7 @@ Options:
463
490
  --clear Archive the eval DB (timestamped backup) and start fresh
464
491
 
465
492
  Table view: when, model, bench kind, KV mode, decode tok/s, TTFT,
466
- peak memory, commit. Runs are written by ./benchmark.sh.`,
493
+ peak memory, commit. Runs are written by bun scripts/bench-serve.ts all.`,
467
494
 
468
495
  train: `mlx-bun train — fine-tune a LoRA adapter on your data
469
496
 
@@ -576,6 +603,9 @@ knapsack per-layer bit allocation; implies quantization, no -q needed):
576
603
  --candidate-bits <l> Comma list the knapsack may pick from [default: 4,8]
577
604
  --calibration-mix <m> "optiq" or a JSONL path [default: optiq]
578
605
  --n-calibration <n> Calibration samples [default: 2]
606
+ --rotate-weights Fold the model's offline TurboQuant rotation before
607
+ quantization (auto-detects Llama/Qwen3.5/Qwen MTP)
608
+ --rotation-seed <n> Deterministic rotation seed [default: 42]
579
609
 
580
610
  Not supported (mlx_lm.convert flags we don't implement — the command
581
611
  exits with an error rather than guessing): --dtype, -d/--dequantize,
@@ -805,19 +835,19 @@ function openChatUi(url: string, hostPort: string): void {
805
835
  }
806
836
 
807
837
  /** Server/runtime flags shared by every mode that loads a model
808
- * (serve, pi). Env levers are set here so they're in place before the
838
+ * (serve, pi). Runtime overrides are installed here before the
809
839
  * generate/compiled-decode modules read them. */
810
840
  /** Resolve the decode ROUTE: a tier alias (--l1/--l2) sets the whole route,
811
841
  * and an explicit per-fork flag (--kv-quant/--fused-sdpa/…) overrides the
812
- * alias. Sets the decode env levers and returns the kv-quant mode. See
813
- * docs/design/parity-tier-dag.md + unified-engine-frontier-plan.md.
842
+ * alias. Installs decode runtime options and returns the kv-quant mode. See
843
+ * docs/design/unified-engine-frontier-plan.md + unified-engine-frontier-plan.md.
814
844
  * Each tier is a GUARANTEE about which reference you reproduce bit-for-bit:
815
845
  * --l1 bit-for-bit IDENTICAL to mlx-lm — drop-in replacement for mlx-lm
816
846
  * (bf16 KV; THE naked default since 2026-07-05)
817
847
  * --l2 bit-for-bit IDENTICAL to mlx-optiq — drop-in replacement for
818
848
  * mlx-optiq: quantized KV per kv_config.json + fused N-tiled SDPA for
819
849
  * L>1 + STOCK unfused L=1 decode (the optiq-golden composition,
820
- * scripts/regen-kvq-goldens.ts + regen-mixed-kv-goldens.ts).
850
+ * scripts/regen.ts kvq + regen-mixed-kv-goldens.ts).
821
851
  * There is no --l3: output-changing experiments live in the Lab (env flags
822
852
  * with a bench + expiry) until one beats the L1 baseline in a paired A/B —
823
853
  * docs/design/unified-engine-frontier-plan.md §6-7. */
@@ -863,8 +893,9 @@ function applyDecodeRoute(): { kvQuant?: "off" | "config" | number; turboQuant?:
863
893
  const pick = (name: string, base: boolean | undefined): boolean | null => {
864
894
  const ex = onOff(name); return ex !== null ? ex : (base ?? null); // explicit flag wins, else tier preset
865
895
  };
866
- const set = (env: string, val: boolean | null, invert = false) => {
867
- if (val !== null) process.env[env] = (invert ? !val : val) ? "1" : "0";
896
+ const set = (name: RuntimeKey, val: boolean | null, invert = false) => {
897
+ if (val !== null)
898
+ configureRuntime({ [name]: (invert ? !val : val) ? "1" : "0" });
868
899
  };
869
900
  // Explicit --kv-quant picks the composition its ORACLE uses: config (the
870
901
  // optiq mixed scheme) runs optiq's fused SDPA (the L2 golden composition);
@@ -874,14 +905,14 @@ function applyDecodeRoute(): { kvQuant?: "off" | "config" | number; turboQuant?:
874
905
  set("MLX_BUN_COMPILED_DECODE", pick("compiled-decode", p.compiled));
875
906
  set("MLX_BUN_NO_FUSED_SDPA", pick("fused-sdpa", fusedSdpaBase), true); // inverted env
876
907
  // Compiled activations (the faithful geglu/swiglu — mlx-lm's @mx.compile kernel).
877
- // One fork drives both env vars; qwen3/qwen3.5/universal compile unconditionally,
908
+ // One fork drives both runtime options; qwen3/qwen3.5/universal compile unconditionally,
878
909
  // so this toggles gemma geglu + minicpm5 swiglu (the only unfused-capable sites).
879
910
  set("MLX_BUN_COMPILED_GEGLU", pick("compiled-activations", p.compiledAct));
880
911
  set("MLX_BUN_COMPILED_SWIGLU", pick("compiled-activations", p.compiledAct));
881
912
  if (kv === "off") return { kvQuant: "off" };
882
913
  if (kv === "config") return { kvQuant: "config" };
883
914
  if (kv) {
884
- // TurboQuant (docs/design/turboquant-kv.md): a distinct axis from the
915
+ // TurboQuant (docs/design/turboquant.md): a distinct axis from the
885
916
  // affine kvQuant above — dequantize-on-fetch, stock unfused ops.sdpa,
886
917
  // no kvQuant value at all (mutually exclusive with kvBits/kvConfig).
887
918
  let turboQuant: TurboQuantScheme | null;
@@ -909,11 +940,12 @@ function serverRuntimeFlags(): { port: number; serverOptions: import("./server")
909
940
  process.exit(1);
910
941
  };
911
942
  const route = applyDecodeRoute(); // --l1/--l2 tier alias, with per-fork flags overriding
912
- if (flag("force-wire")) process.env.MLX_BUN_FORCE_WIRE = "1";
943
+ if (flag("force-wire")) configureRuntime({ MLX_BUN_FORCE_WIRE: "1" });
913
944
  // Remote image_url/audio_url media fetches block private/loopback/link-
914
945
  // local destinations by default (SSRF — src/media-fetch.ts); this is the
915
946
  // LAN-hosts escape hatch.
916
- if (flag("allow-private-media")) process.env.MLX_BUN_ALLOW_PRIVATE_MEDIA = "1";
947
+ if (flag("allow-private-media"))
948
+ configureRuntime({ MLX_BUN_ALLOW_PRIVATE_MEDIA: "1" });
917
949
 
918
950
  const serverOptions: import("./server").ServerOptions = {};
919
951
  const budgetGB = Number(opt("memory-budget", "0"));
@@ -923,29 +955,47 @@ function serverRuntimeFlags(): { port: number; serverOptions: import("./server")
923
955
  if (kvBudgetGB > 0) serverOptions.kvBudgetBytes = kvBudgetGB * 1e9;
924
956
  const pcRaw = opt("prompt-cache"); // null if absent; an explicit value (incl. 0) wins → `--prompt-cache 0` DISABLES the cache
925
957
  if (pcRaw !== null) serverOptions.promptCacheBytes = Math.max(0, Number(pcRaw)) * 2 ** 30;
926
- // Engine-child mode (runtime-isolation.md): listen on a unix socket
958
+ // Engine-child mode (docs/reference/server-config.md): listen on a unix socket
927
959
  // instead of TCP — the isolated parent proxies here. Internal flag,
928
960
  // spawned by --isolate; usable directly for socket-level integration.
929
961
  const unixRaw = opt("unix");
930
962
  if (unixRaw !== null) serverOptions.unixSocket = unixRaw;
931
- // SSD cold tier (docs/design/ssd-kv-cold-tier.md) — off unless a dir is given.
963
+ // SSD cold tier (docs/design/kv-cache.md) — off unless a dir is given.
932
964
  const ssdDir = opt("ssd-cache");
933
965
  if (ssdDir !== null) {
934
966
  serverOptions.ssdCacheDir = ssdDir;
935
967
  const maxRaw = opt("ssd-cache-max");
936
- if (maxRaw !== null) serverOptions.ssdCacheMaxBytes = Math.max(1, Number(maxRaw)) * 2 ** 30;
968
+ if (maxRaw !== null) {
969
+ const maxGb = Number(maxRaw);
970
+ if (!Number.isFinite(maxGb) || maxGb < 0) {
971
+ console.error(`--ssd-cache-max expects a number >= 0 (got "${maxRaw}")`);
972
+ process.exit(1);
973
+ }
974
+ serverOptions.ssdCacheMaxBytes = maxGb === 0
975
+ ? Number.POSITIVE_INFINITY
976
+ : maxGb * 2 ** 30;
977
+ }
937
978
  if (flag("ssd-cache-verify")) serverOptions.ssdCacheVerify = true;
938
979
  const demoteRaw = opt("ssd-demote-idle");
939
980
  if (demoteRaw !== null) serverOptions.ssdDemoteIdleSec = Math.max(0, Number(demoteRaw));
981
+ const checkpointRaw = opt("generation-checkpoint");
982
+ if (checkpointRaw !== null) {
983
+ const n = Number(checkpointRaw);
984
+ if (!Number.isInteger(n) || n < 1) {
985
+ console.error(`--generation-checkpoint expects an integer >= 1 (got "${checkpointRaw}")`);
986
+ process.exit(1);
987
+ }
988
+ serverOptions.generationCheckpointTokens = n;
989
+ }
940
990
  } else {
941
991
  // Refuse-loudly house rule: sub-flags of an absent tier must not vanish.
942
- for (const f of ["ssd-cache-max", "ssd-demote-idle"] as const)
992
+ for (const f of ["ssd-cache-max", "ssd-demote-idle", "generation-checkpoint"] as const)
943
993
  if (opt(f) !== null) console.warn(`--${f} has no effect without --ssd-cache — ignored`);
944
994
  if (flag("ssd-cache-verify"))
945
995
  console.warn("--ssd-cache-verify has no effect without --ssd-cache — ignored");
946
996
  }
947
997
  // --batch N: max concurrent requests batched through the mlx-lm-parity
948
- // engine (N=1 = today's serial path). --decode-concurrency is accepted for
998
+ // engine (N=1 pins the strict serial executor). --decode-concurrency is accepted for
949
999
  // drop-in compatibility with mlx_lm.server, but the semantics differ: there
950
1000
  // it caps per-BatchGenerator decode parallelism (default 32); in mlx-bun it
951
1001
  // caps continuous batching (default 8; `--batch 1` pins the serial path —
@@ -962,10 +1012,10 @@ function serverRuntimeFlags(): { port: number; serverOptions: import("./server")
962
1012
  if (route.kvQuant !== undefined) serverOptions.kvQuant = route.kvQuant;
963
1013
  if (route.turboQuant !== undefined) serverOptions.turboQuant = route.turboQuant;
964
1014
  // --paged-kv (env: MLX_BUN_PAGED_KV=1): OPTIONAL vLLM-style block-pool KV
965
- // storage, default off (docs/design/paged-kv-cache.md). Serial-only in v1:
1015
+ // storage, default off (docs/design/kv-cache.md). Serial-only in v1:
966
1016
  // with no explicit --batch, pin --batch 1 (the default is 8); an explicit
967
1017
  // --batch N>1 is refused loudly by createServer rather than downgraded.
968
- if (flag("paged-kv") || process.env.MLX_BUN_PAGED_KV === "1") {
1018
+ if (flag("paged-kv") || runtimeValue("MLX_BUN_PAGED_KV") === "1") {
969
1019
  const bsRaw = opt("paged-kv-block-size");
970
1020
  serverOptions.pagedKv = bsRaw !== null ? { blockSize: Number(bsRaw) } : {};
971
1021
  if (batchRaw === null) {
@@ -1009,7 +1059,7 @@ function serverRuntimeFlags(): { port: number; serverOptions: import("./server")
1009
1059
  const maxTok = numFlag("max-tokens", 1, 10_000_000);
1010
1060
  if (maxTok !== null) serverOptions.defaultMaxTokens = Math.floor(maxTok);
1011
1061
  // HLG sampling — piecewise tone curve on the logprobs (default off). Knobs in
1012
- // nats; the mid gain folds from --temperature. docs/design/hlg-sampling.md.
1062
+ // nats; the mid gain folds from --temperature. docs/archive/hlg-sampling.md.
1013
1063
  if (onOff("hlg-sampling") === true) {
1014
1064
  serverOptions.hlg = {
1015
1065
  enabled: true,
@@ -1086,7 +1136,7 @@ async function mountStartupAdapter(
1086
1136
  function runtimeSummary(o: import("./server").ServerOptions): string {
1087
1137
  const kv = o.turboQuant ? `turbo-k${o.turboQuant.kBits}v${o.turboQuant.vBits}`
1088
1138
  : o.kvQuant === "off" ? "off" : typeof o.kvQuant === "number" ? `kv${o.kvQuant}` : "config";
1089
- const lever = (env: string, dflt: string) => process.env[env] ?? dflt;
1139
+ const lever = (name: RuntimeKey, dflt: string) => runtimeValue(name) ?? dflt;
1090
1140
  return `kv-quant ${kv} · compiled-decode ${lever("MLX_BUN_COMPILED_DECODE", "1") === "1" ? "on" : "off"}` +
1091
1141
  (lever("MLX_BUN_COMPILED_GEGLU", "1") === "0" ? " · compiled-activations off" : "") +
1092
1142
  (o.batch && o.batch > 1 ? ` · batch ${o.batch}` : "") +
@@ -1376,7 +1426,7 @@ switch (cmd) {
1376
1426
 
1377
1427
  case "fit": {
1378
1428
  const query = positional(0);
1379
- if (!query) { console.error("usage: mlx-bun fit <query> [--ctx N] [--skus]"); process.exit(1); }
1429
+ if (!query) { console.error("usage: mlx-bun fit <query> [--ctx N] [--kv-quant 4|8|config] [--skus]"); process.exit(1); }
1380
1430
  const reg = new Registry();
1381
1431
  if (reg.list().length === 0) await reg.scan();
1382
1432
  const m = reg.resolve(query);
@@ -1443,9 +1493,32 @@ switch (cmd) {
1443
1493
  console.log();
1444
1494
  break;
1445
1495
  }
1446
- const r = fit(config, m.sizeBytes, ctx, thisMachine(), undefined, m.expertsBytes);
1496
+ // --kv-quant mirrors serve: bill the quantized cache's true bytes so
1497
+ // the reported window matches what serving with the same flag admits.
1498
+ const kvQuantOpt = opt("kv-quant");
1499
+ let fitKvScheme: FitKvScheme | undefined;
1500
+ if (kvQuantOpt === "4" || kvQuantOpt === "8") {
1501
+ fitKvScheme = resolveKvScheme({ override: Number(kvQuantOpt) }).fitOptions;
1502
+ } else if (kvQuantOpt === "config") {
1503
+ if (!config.kvQuant?.length) {
1504
+ console.error(`${m.repoId} ships no per-layer kv-quant config — use --kv-quant 4|8`);
1505
+ process.exit(1);
1506
+ }
1507
+ fitKvScheme = resolveKvScheme({
1508
+ override: "config",
1509
+ config: config.kvQuant,
1510
+ missingConfig: "error",
1511
+ }).fitOptions;
1512
+ } else if (kvQuantOpt != null && kvQuantOpt !== "off") {
1513
+ console.error(`--kv-quant ${kvQuantOpt}: expected 4, 8, config, or off`);
1514
+ process.exit(1);
1515
+ }
1516
+ const r = fit(config, m.sizeBytes, ctx, thisMachine(), undefined, m.expertsBytes, undefined, fitKvScheme);
1447
1517
  h1("will it fit?");
1448
- console.log(` ${style.bold(m.repoId)} ${style.dim(`@ ${ctx.toLocaleString()} context · this machine`)}`);
1518
+ const kvNote = fitKvScheme
1519
+ ? style.dim(` · kv-quant ${fitKvScheme.kvBits ? `${fitKvScheme.kvBits}-bit` : "config"}`)
1520
+ : "";
1521
+ console.log(` ${style.bold(m.repoId)} ${style.dim(`@ ${ctx.toLocaleString()} context · this machine`)}${kvNote}`);
1449
1522
  console.log();
1450
1523
  const expertsNote = config.text.enableMoeBlock && m.expertsBytes > 0
1451
1524
  ? style.dim(` (experts ${gb(m.expertsBytes)}; top ${config.text.topKExperts}/${config.text.numExperts} read per token)`)
@@ -1470,7 +1543,7 @@ switch (cmd) {
1470
1543
  { header: "fits", paint: (c) => (c.includes("fits") ? style.green(c) : style.dim(c)) },
1471
1544
  { header: "max context", align: "right" }, { header: "decode", align: "right" },
1472
1545
  ],
1473
- skuMatrix(config, m.sizeBytes, ctx, m.expertsBytes).map((row) => [
1546
+ skuMatrix(config, m.sizeBytes, ctx, m.expertsBytes, fitKvScheme).map((row) => [
1474
1547
  row.sku, `${row.ramGB} GB`, row.fits ? "fits" : "—",
1475
1548
  row.fits ? row.maxContext.toLocaleString() : "—",
1476
1549
  row.fits ? `~${row.decodeTps.toFixed(0)} tok/s` : "—",
@@ -1498,7 +1571,7 @@ switch (cmd) {
1498
1571
  process.exit(1);
1499
1572
  }
1500
1573
  }
1501
- // Runtime isolation (--isolate, runtime-isolation.md): THIS process
1574
+ // Runtime isolation (--isolate, docs/reference/server-config.md): THIS process
1502
1575
  // becomes a pure reactor — it binds the TCP port and reverse-proxies to
1503
1576
  // an engine CHILD (the full server on a unix socket, spawned from the
1504
1577
  // same argv minus parent-only flags). Zero MLX calls in the parent: the
@@ -1540,7 +1613,7 @@ switch (cmd) {
1540
1613
  };
1541
1614
  const poolMaxRaw = opt("model-pool");
1542
1615
  const poolMax = Math.max(1, Number(poolMaxRaw ?? 1) || 1);
1543
- const { engine, pool } = startProxyServer({
1616
+ const { engine, close } = startProxyServer({
1544
1617
  port: rt.port,
1545
1618
  ...(rt.serverOptions.hostname ? { hostname: rt.serverOptions.hostname } : {}),
1546
1619
  engine: {
@@ -1556,7 +1629,7 @@ switch (cmd) {
1556
1629
  const shownHost0 = rt.serverOptions.hostname ?? "localhost";
1557
1630
  console.log(style.dim(` isolated mode: engine child starting (socket ${sock})`));
1558
1631
  console.log(` ${style.green("●")} ${style.bold("proxy up")} http://${shownHost0}:${rt.port} ${style.dim(`· ${m0.repoId} loading behind it · model pool ${poolMax}`)}`);
1559
- const onExit = () => { pool?.stopAll(); engine.stop(); process.exit(0); };
1632
+ const onExit = () => { void close().finally(() => process.exit(0)); };
1560
1633
  process.on("SIGINT", onExit);
1561
1634
  process.on("SIGTERM", onExit);
1562
1635
  await engine.ready;
@@ -1573,10 +1646,29 @@ switch (cmd) {
1573
1646
  const config = await loadModelConfig(m.path);
1574
1647
  const report = fit(config, m.sizeBytes, 8192, undefined, undefined, m.expertsBytes);
1575
1648
  sFit.done(`${style.bold(m.repoId)} ${style.dim(`· ${gb(m.sizeBytes)} · ~${report.predictedDecodeTps.toFixed(0)} tok/s predicted`)}${picked ? style.dim(" · auto-picked (override: mlx-bun serve <query>)") : ""}`);
1649
+ // Near-ceiling advisory (admission doctrine: advise, never refuse). A
1650
+ // model near the DEFAULT Metal wired ceiling (~75% of RAM) dies with an
1651
+ // UNCATCHABLE async-GPU-OOM on long prefills (M4 24 GB × 17 GB model,
1652
+ // 2026-08-20) — surface the standard remedy up front instead.
1653
+ {
1654
+ const { maxRecommendedWorkingSetSize } = await import("./mlx/ffi");
1655
+ const ceiling = maxRecommendedWorkingSetSize();
1656
+ const sysctlRaw = Bun.spawnSync(["sysctl", "-n", "iogpu.wired_limit_mb"]).stdout.toString().trim();
1657
+ const limitIsDefault = sysctlRaw === "0" || sysctlRaw === "";
1658
+ if (limitIsDefault && ceiling > 0 && m.sizeBytes > 0.8 * ceiling) {
1659
+ const ramGb = Number(Bun.spawnSync(["sysctl", "-n", "hw.memsize"]).stdout.toString().trim()) / 2 ** 30;
1660
+ const suggestMb = Math.floor((ramGb - 2.5) * 1024);
1661
+ console.error(style.dim(
1662
+ ` ! ${gb(m.sizeBytes)} model vs ~${gb(ceiling)} default GPU ceiling — long prompts can\n` +
1663
+ ` hit an uncatchable Metal OOM. If that happens, raise the limit first:\n` +
1664
+ ` sudo sysctl iogpu.wired_limit_mb=${suggestMb} (resets on reboot)`,
1665
+ ));
1666
+ }
1667
+ }
1576
1668
  const sNative = step("native runtime");
1577
1669
  await ensureNative(sNative);
1578
1670
  sNative.done("native runtime ready");
1579
- const { createServer, loadContext } = await import("./server");
1671
+ const { createServer, loadContext, shutdownServer } = await import("./server");
1580
1672
  if (flag("expert-offload")) {
1581
1673
  if (m.expertsBytes === 0) {
1582
1674
  console.error(style.dim("--expert-offload ignored: this model has no experts (dense)"));
@@ -1609,9 +1701,9 @@ switch (cmd) {
1609
1701
  }
1610
1702
  const draftKindRaw = opt("draft-kind");
1611
1703
  const draftKind = (draftKindRaw ?? undefined) as
1612
- | "dspark" | "deepspec" | "assistant" | "two-model" | "ngram" | undefined;
1613
- if (draftKind !== undefined && !["dspark", "deepspec", "assistant", "two-model", "ngram"].includes(draftKind)) {
1614
- console.error(`--draft-kind expects two-model|assistant|dspark|deepspec|ngram (got "${draftKindRaw}")`);
1704
+ | "dspark" | "deepspec" | "assistant" | "two-model" | "ngram" | "mtp" | undefined;
1705
+ if (draftKind !== undefined && !["dspark", "deepspec", "assistant", "two-model", "ngram", "mtp"].includes(draftKind)) {
1706
+ console.error(`--draft-kind expects two-model|assistant|dspark|deepspec|mtp|ngram (got "${draftKindRaw}")`);
1615
1707
  process.exit(1);
1616
1708
  }
1617
1709
  // Prompt-lookup window bounds (ngram kind only; validated here so a typo
@@ -1639,7 +1731,10 @@ switch (cmd) {
1639
1731
  const ctx = await loadContext(m.path, m.repoId, {
1640
1732
  memoryBudgetBytes: rt.serverOptions.memoryBudgetBytes,
1641
1733
  glm: glm52RuntimeFlags(rt.serverOptions),
1642
- ...(draftModelDir || draftKind === "ngram"
1734
+ // ngram is model-free; mtp resolves the bundled <model>/mtp/ companion
1735
+ // in loadContext when no --draft-model is given (cli.md contract —
1736
+ // this gate silently dropped that case until 2026-08-20).
1737
+ ...(draftModelDir || draftKind === "ngram" || draftKind === "mtp"
1643
1738
  ? {
1644
1739
  ...(draftModelDir ? { draftModelDir } : {}),
1645
1740
  numDraftTokens,
@@ -1652,6 +1747,30 @@ switch (cmd) {
1652
1747
  sLoad.done(`weights loaded ${style.dim(`in ${(performance.now() - t0).toFixed(0)} ms`)}${draftModelDir ? style.dim(` · draft: ${draftModelDir.split("/").filter(Boolean).at(-1)}`) : draftKind === "ngram" ? style.dim(" · draft: ngram (prompt lookup)") : ""}`);
1653
1748
  await mountStartupAdapter(ctx, rt.serverOptions);
1654
1749
  const server = createServer(ctx, rt.port, { ...rt.serverOptions, owner: "serve" });
1750
+ let shutdownStarted = false;
1751
+ const onServeSignal = (signal: "SIGINT" | "SIGTERM"): void => {
1752
+ if (shutdownStarted) return;
1753
+ shutdownStarted = true;
1754
+ const rawTimeout = Number(runtimeValue("MLX_BUN_SHUTDOWN_TIMEOUT_MS"));
1755
+ const timeoutMs = Number.isFinite(rawTimeout) && rawTimeout > 0 ? rawTimeout : 120_000;
1756
+ console.log(`\n[server] ${signal}: flushing prompt-cache durability`);
1757
+ void shutdownServer(server, timeoutMs).then((result) => {
1758
+ if (!result.durability.durable) {
1759
+ const d = result.durability;
1760
+ console.warn(
1761
+ `[server] cache flush incomplete after ${Math.round(d.elapsedMs)} ms: ` +
1762
+ `${d.pendingSnapshots} snapshots, ${d.pendingSpills} spills, ` +
1763
+ `${d.droppedSpills} dropped, ${d.failedSpills} failed`,
1764
+ );
1765
+ }
1766
+ process.exit(0);
1767
+ }).catch((error) => {
1768
+ console.warn(`[server] shutdown flush failed: ${(error as Error).message}`);
1769
+ process.exit(0);
1770
+ });
1771
+ };
1772
+ process.once("SIGINT", () => onServeSignal("SIGINT"));
1773
+ process.once("SIGTERM", () => onServeSignal("SIGTERM"));
1655
1774
  const shownHost = rt.serverOptions.hostname ?? "localhost";
1656
1775
  console.log();
1657
1776
  box([
@@ -1682,7 +1801,7 @@ switch (cmd) {
1682
1801
  // three entry points (raw / OpenAI API / chat UI) differ only in how params
1683
1802
  // are POPULATED; here every param is explicit. Decode-path levers mirror
1684
1803
  // serve so you can pin the route (mlx-lm compat = --l1).
1685
- // --l1/--l2 + per-fork overrides set the decode env levers; the
1804
+ // --l1/--l2 + per-fork overrides set the decode runtime options; the
1686
1805
  // returned KV scheme is applied below so `generate --l2` runs the same
1687
1806
  // quantized-KV route (and produces the same tokens) as `serve --l2`.
1688
1807
  const route = applyDecodeRoute();
@@ -1706,13 +1825,12 @@ switch (cmd) {
1706
1825
  // bf16, same silent fallback as serve), N = uniform bits from decode
1707
1826
  // start. Unset (no tier, no --kv-quant) keeps generate's historical
1708
1827
  // default: bf16 bit-exact greedy (generateText's parity path).
1709
- const kvScheme =
1710
- route.turboQuant ? { turboQuant: route.turboQuant, quantizedKvStart: 0 }
1711
- : route.kvQuant === "off" ? {}
1712
- : route.kvQuant === "config"
1713
- ? (tm.config.kvQuant?.length ? { kvConfig: tm.config.kvQuant } : {})
1714
- : typeof route.kvQuant === "number"
1715
- ? { kvBits: route.kvQuant, quantizedKvStart: 0 }
1828
+ const kvScheme = route.turboQuant || route.kvQuant !== undefined
1829
+ ? resolveKvScheme({
1830
+ override: route.kvQuant,
1831
+ turboQuant: route.turboQuant,
1832
+ config: tm.config.kvQuant,
1833
+ }).generationOptions
1716
1834
  : undefined;
1717
1835
  const num = (n: string): number | undefined => { const v = opt(n); return v == null ? undefined : Number(v); };
1718
1836
  const text = await generateText(tm, prompt, {
@@ -1794,14 +1912,14 @@ switch (cmd) {
1794
1912
  case "benchmark": {
1795
1913
  const { banner, step, box, style } = await import("./tui");
1796
1914
  banner(pkg.version);
1797
- serverRuntimeFlags(); // env levers (--compiled-decode, --fused-sdpa, ...) apply to the run
1915
+ serverRuntimeFlags(); // decode options (--compiled-decode, --fused-sdpa, ...) apply to the run
1798
1916
  const { m, picked } = await resolveModelAuto(opt("model") ?? positional(0) ?? opt("query"));
1799
1917
  const tokens = Number(opt("tokens", "256"));
1800
1918
  const runs = Number(opt("runs", "3"));
1801
1919
  const promptTokens = Number(opt("prompt-tokens", "0"));
1802
1920
  const kvMode = opt("kv-quant", "off")!;
1803
1921
  console.log(` ${style.dim("measures THIS machine, OUR stack only — no other systems needed.")}`);
1804
- console.log(` ${style.dim("numbers on a loaded machine are not quotable; ./benchmark.sh is the preflight-gated harness.")}`);
1922
+ console.log(` ${style.dim("numbers on a loaded machine are not quotable; bun scripts/bench-serve.ts all is the preflight-gated harness.")}`);
1805
1923
  const sNative = step("native runtime");
1806
1924
  await ensureNative(sNative);
1807
1925
  sNative.done("native runtime ready");
@@ -1846,7 +1964,7 @@ switch (cmd) {
1846
1964
  : kvMode !== "off" ? { kvBits: Number(kvMode), quantizedKvStart: 0 } : {};
1847
1965
 
1848
1966
  // Warmup materializes weights so prefill timing measures prefill,
1849
- // not lazy page-in (same protocol as scripts/bench.ts).
1967
+ // not lazy page-in (same protocol as bench.ts (deleted 2026-08-23; git history)).
1850
1968
  const sWarm = step("warmup");
1851
1969
  {
1852
1970
  const wCache = model.makeCache();
@@ -1921,7 +2039,7 @@ switch (cmd) {
1921
2039
  }
1922
2040
  const { style } = await import("./tui");
1923
2041
  if (rows.length === 0) {
1924
- console.log("no eval runs recorded yet — ./benchmark.sh writes them");
2042
+ console.log("no eval runs recorded yet — bun scripts/bench-serve.ts all writes them");
1925
2043
  break;
1926
2044
  }
1927
2045
  const ago = (ts: number) => {
@@ -1993,7 +2111,7 @@ switch (cmd) {
1993
2111
  // already run their own pi connect it with `mlx-bun harness pi`.
1994
2112
  const OURS_VAL = new Set([
1995
2113
  "--query", "-q", "--model", "--port", "--host", "--memory-budget", "--kv-budget", "--prompt-cache",
1996
- "--ssd-cache", "--ssd-cache-max", "--ssd-demote-idle", "--kv-quant", "--unix",
2114
+ "--ssd-cache", "--ssd-cache-max", "--ssd-demote-idle", "--generation-checkpoint", "--kv-quant", "--unix",
1997
2115
  "--batch", "--decode-concurrency", "--adapter", "--adapter-path",
1998
2116
  "--draft-model", "--num-draft-tokens", "--draft-kind", "--ngram-max", "--ngram-min",
1999
2117
  "--compiled-decode", "--compiled-activations", "--fused-sdpa", "--thinking",
@@ -2446,6 +2564,12 @@ switch (cmd) {
2446
2564
  console.error(`--candidate-bits expects a comma list of integers in [2, 8] (got "${opt("candidate-bits")}")`);
2447
2565
  process.exit(1);
2448
2566
  }
2567
+ const rotateWeights = flag("rotate-weights");
2568
+ const rotationSeed = Number(opt("rotation-seed", "42"));
2569
+ if (!Number.isInteger(rotationSeed)) {
2570
+ console.error(`--rotation-seed expects an integer (got "${opt("rotation-seed")}")`);
2571
+ process.exit(1);
2572
+ }
2449
2573
  const mlxPath = opt("mlx-path", "mlx_model")!;
2450
2574
  const { existsSync } = await import("node:fs");
2451
2575
  if (existsSync(mlxPath)) {
@@ -2487,7 +2611,7 @@ switch (cmd) {
2487
2611
  ? `quantizing (mixed, target ${targetBpw} bpw — sensitivity sweep, ~minutes)`
2488
2612
  : `quantizing (${qBits}-bit, group ${qGroup})`,
2489
2613
  );
2490
- const { quantizeModelDir } = await import("./quantize");
2614
+ const { quantizeModelDir, automaticRotationWeightTransform } = await import("./quantize");
2491
2615
  try {
2492
2616
  const r = await quantizeModelDir(
2493
2617
  srcDir,
@@ -2500,6 +2624,9 @@ switch (cmd) {
2500
2624
  ...(candidateBits ? { candidateBits } : {}),
2501
2625
  ...(opt("calibration-mix") ? { calibrationMix: opt("calibration-mix")! } : {}),
2502
2626
  ...(opt("n-calibration") ? { nCalibration: Number(opt("n-calibration")) } : {}),
2627
+ ...(rotateWeights
2628
+ ? { weightTransform: automaticRotationWeightTransform({ seed: rotationSeed }) }
2629
+ : {}),
2503
2630
  },
2504
2631
  (e) => sQ.update(e.message),
2505
2632
  );
@@ -2511,6 +2638,7 @@ switch (cmd) {
2511
2638
  `source ${style.dim(srcDir)}`,
2512
2639
  `model ${style.bold(r.outDir)} ${style.dim(`· ${gb(r.write.totalSize)}`)}`,
2513
2640
  `quant ${style.dim(targetBpw !== undefined ? `mixed ${r.achievedBpw.toFixed(2)} bpw (target ${targetBpw})` : `${qBits}-bit g${qGroup} affine`)}`,
2641
+ ...(rotateWeights ? [`transform ${style.dim(`TurboQuant rotation seed ${rotationSeed}`)}`] : []),
2514
2642
  "",
2515
2643
  `serve it ${style.accent(`mlx-bun serve ${r.outDir}`)}`,
2516
2644
  ]);
package/src/client.ts ADDED
@@ -0,0 +1,66 @@
1
+ import type { CompletionClient } from "./contracts/completion";
2
+ import type { EngineHost } from "./contracts/host";
3
+
4
+ export interface CompletionCall {
5
+ readonly body: Readonly<Record<string, unknown>>;
6
+ readonly route?: "chat/completions" | "completions";
7
+ readonly signal?: AbortSignal;
8
+ }
9
+
10
+ export interface CompletionResponse {
11
+ choices: Array<{ text?: string; message?: { content?: string | null; [key: string]: unknown }; [key: string]: unknown }>;
12
+ usage?: Record<string, unknown>;
13
+ [key: string]: unknown;
14
+ }
15
+
16
+ /** A borrowed transport. Direct services, HTTP and isolated workers receive
17
+ * the same request and return the same result; POSTs are never retried here. */
18
+ export function createCompletionClient(options: {
19
+ baseUrl: string;
20
+ headers?: Readonly<Record<string, string>>;
21
+ host?: Pick<EngineHost<Request, Response>, "forward">;
22
+ }): CompletionClient<CompletionCall, CompletionResponse> {
23
+ const baseUrl = options.baseUrl.replace(/\/$/, "");
24
+ const headers = { ...options.headers, "content-type": "application/json" };
25
+ const send = options.host ? options.host.forward.bind(options.host) : fetch;
26
+ return {
27
+ async complete({ body, route = "chat/completions", signal }) {
28
+ signal?.throwIfAborted();
29
+ const response = await send(new Request(`${baseUrl}/${route}`, {
30
+ method: "POST", headers, body: JSON.stringify({ ...body, stream: false }), signal,
31
+ }));
32
+ if (!response.ok) {
33
+ const detail = await response.text();
34
+ throw new Error(`completion failed (${response.status}): ${detail}`);
35
+ }
36
+ const result = await response.json() as CompletionResponse;
37
+ if (!Array.isArray(result.choices)) throw new Error("completion response has no choices");
38
+ return result;
39
+ },
40
+ };
41
+ }
42
+
43
+ /** Adapts an in-process request handler. Closing waits for active handlers;
44
+ * a handler owns any response stream it returns. */
45
+ export function createDirectHost(
46
+ handler: (request: Request) => Promise<Response>, shutdown: () => Promise<void> = async () => {},
47
+ ): EngineHost<Request, Response> {
48
+ let closed = false;
49
+ let closing: Promise<void> | undefined;
50
+ const active = new Set<Promise<Response>>();
51
+ return {
52
+ ready: Promise.resolve(),
53
+ forward(request) {
54
+ if (closed) return Promise.reject(new Error("engine host is closed"));
55
+ if (request.signal.aborted) return Promise.reject(request.signal.reason);
56
+ const work = Promise.resolve().then(() => handler(request));
57
+ active.add(work);
58
+ void work.then(() => active.delete(work), () => active.delete(work));
59
+ return work;
60
+ },
61
+ close() {
62
+ closed = true;
63
+ return closing ??= (async () => { await Promise.allSettled(active); await shutdown(); })();
64
+ },
65
+ };
66
+ }