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
@@ -0,0 +1,114 @@
1
+ import type { MlxArray } from "../../mlx/array";
2
+ import * as ops from "../../mlx/ops";
3
+ import type { Cache } from "../../model/gemma4";
4
+ import type { RuntimeModel } from "../../model/factory";
5
+ import type { Sampler } from "../../sampler";
6
+ import type { DraftProvider, DraftSource } from "../../spec/source";
7
+ import { runtimeConfig, type RuntimeConfig } from "../../runtime-config";
8
+ import type { MlxModelMemory } from "./autoregressive";
9
+ import type { SpeculativeTransaction } from "../../inference/rollback";
10
+ import { bindCacheRollback } from "./rollback";
11
+ import type { GraphDescriptor } from "../../inference/graph";
12
+ import { bindLegacyDraftTarget } from "./draft-target";
13
+
14
+ /** Bound target operations and draft construction for one speculative run.
15
+ * A replacement graph supplies this entire port, including any hidden taps.
16
+ * The verifier never needs a RuntimeModel or an artifact-family check. */
17
+ export interface MlxSpeculativeBinding {
18
+ readonly runtime?: RuntimeConfig;
19
+ readonly memory?: MlxModelMemory;
20
+ readonly descriptor: GraphDescriptor;
21
+ readonly eosTokenIds: readonly number[];
22
+ readonly prefillTailSplit: boolean;
23
+ makeCache(): Cache[];
24
+ openDraft(sampler: Sampler, caches: Cache[]): DraftSource;
25
+ bindRollback(caches: Cache[]): SpeculativeTransaction;
26
+ forward(ids: MlxArray, caches: Cache[], tapLayers?: number[]):
27
+ Promise<{ hidden: MlxArray; ctxML: MlxArray | null }>;
28
+ projectLogits(hidden: MlxArray): MlxArray;
29
+ /** Establish the implementation's verify kernel context; restore on close. */
30
+ pinVerify?(): { close(): void };
31
+ }
32
+
33
+ /** Legacy mutable tap/kernel fields require the gateway's exclusive lease. */
34
+ export function bindLegacySpeculativeModel(model: RuntimeModel, provider: DraftProvider): MlxSpeculativeBinding {
35
+ const runtime = runtimeConfig();
36
+ return {
37
+ runtime,
38
+ memory: model,
39
+ descriptor: Object.freeze({ id: `legacy-spec:${model.config.modelType}`, backend: "mlx",
40
+ graphAbi: "mlx-hidden-bsh-v1", stateAbi: "legacy-cache-array-v1", artifact: "legacy-resident-model" }),
41
+ eosTokenIds: model.config.eosTokenIds,
42
+ prefillTailSplit: runtime.flag("MLX_BUN_PREFILL_TAIL_SPLIT", true),
43
+ makeCache: model.makeCache.bind(model),
44
+ openDraft: (sampler, caches) => provider.open({ sampler, target: bindLegacyDraftTarget(model, caches) }),
45
+ bindRollback: bindCacheRollback,
46
+ forward: (ids, caches, tapLayers) => legacyForwardWithTaps(model, ids, caches, tapLayers),
47
+ projectLogits: model.logitsFromHidden.bind(model),
48
+ ...("setSpecKernelPinned" in model ? {
49
+ pinVerify() {
50
+ model.setSpecKernelPinned(true);
51
+ return { close() { model.setSpecKernelPinned(false); } };
52
+ },
53
+ } : {}),
54
+ };
55
+ }
56
+
57
+ export function assertMlxSpeculativeBinding(binding: MlxSpeculativeBinding): void {
58
+ const descriptor = binding.descriptor;
59
+ if (descriptor.backend !== "mlx" || descriptor.graphAbi !== "mlx-hidden-bsh-v1" ||
60
+ descriptor.stateAbi !== "legacy-cache-array-v1")
61
+ throw new Error(`speculative binding ${descriptor.id} has an incompatible backend, graph, or state ABI`);
62
+ }
63
+
64
+ /** Run one target forward, and when `tapLayers` is set (⟹ a Gemma4 target — a
65
+ * DSpark-style source), also return the captured multi-layer context
66
+ * [1,L,m*H] (tapLayers concatenated on the feature axis; index nLayers is the
67
+ * post-finalNorm sentinel). Non-tapping sources get ctxML=null and never
68
+ * touch model.hiddenTap. Mirrors generate-dflash.ts forwardTapped. */
69
+ async function legacyForwardWithTaps(
70
+ model: RuntimeModel,
71
+ ids: MlxArray,
72
+ caches: Cache[],
73
+ tapLayers: number[] | undefined,
74
+ ): Promise<{ hidden: MlxArray; ctxML: MlxArray | null }> {
75
+ if (!tapLayers) {
76
+ const asyncModel = model as RuntimeModel & {
77
+ forwardHiddenAsync?: (
78
+ ids: MlxArray,
79
+ caches: Cache[],
80
+ ) => Promise<MlxArray>;
81
+ };
82
+ const hidden = typeof asyncModel.forwardHiddenAsync === "function"
83
+ ? await asyncModel.forwardHiddenAsync(ids, caches)
84
+ : model.forwardHidden(ids, caches);
85
+ return { hidden, ctxML: null };
86
+ }
87
+ if (!("hiddenTap" in model)) throw new Error("target graph does not support hidden taps");
88
+ const m = model;
89
+ const previousTap = m.hiddenTap;
90
+ const cap = new Map<number, MlxArray>();
91
+ m.hiddenTap = { layers: new Set(tapLayers), captured: cap };
92
+ let hidden: MlxArray | null = null;
93
+ try {
94
+ hidden = model.forwardHidden(ids, caches);
95
+ const perLayer = tapLayers.map((li) => {
96
+ const a = cap.get(li);
97
+ if (!a) throw new Error(`spec tap: layer ${li} not captured`);
98
+ return a;
99
+ });
100
+ const ctxML = ops.concatAxis(perLayer, 2); // [1,L,m*H]
101
+ for (const [, a] of cap) a.dispose();
102
+ cap.clear(); // consumed — the finally must not double-dispose
103
+ const out = { hidden, ctxML };
104
+ hidden = null; // ownership returned to the caller
105
+ return out;
106
+ } finally {
107
+ // On any throw (forward mid-capture, missing layer, concat), free the
108
+ // partially-captured tap tensors and the orphaned hidden. On success both
109
+ // are already gone (cap cleared, hidden nulled).
110
+ hidden?.dispose();
111
+ for (const [, a] of cap) a.dispose();
112
+ m.hiddenTap = previousTap;
113
+ }
114
+ }
@@ -0,0 +1,26 @@
1
+ import type { StateView } from "../../contracts/resources";
2
+ import { disposeResources, ownResource } from "../../engine/resources";
3
+ import type { MlxArray } from "../../mlx/array";
4
+ import type { Cache } from "../../model/gemma4-base";
5
+
6
+ /** Compatibility boundary for legacy state(). Numerical code consumes a lease,
7
+ * never the legacy ownership marker. No copy, evaluation or fence is added. */
8
+ export function leaseCacheState(cache: Cache): StateView<MlxArray> {
9
+ return ownResource<readonly MlxArray[]>(cache.state(), cache.stateNeedsDispose ? disposeResources : () => {});
10
+ }
11
+
12
+ /** Preserve the existing prefill evaluation order: temporary views first,
13
+ * borrowed state second. Partial acquisition releases every acquired view. */
14
+ export function leaseCacheStates(caches: readonly Cache[]): StateView<MlxArray> {
15
+ const leases: StateView<MlxArray>[] = [];
16
+ const close = () => disposeResources(leases.map((lease) => ({ dispose: () => lease.close() })));
17
+ try {
18
+ for (const owned of [true, false]) {
19
+ for (const cache of caches) if (!!cache.stateNeedsDispose === owned) leases.push(leaseCacheState(cache));
20
+ }
21
+ return ownResource(leases.flatMap((lease) => [...lease.borrow()]), close);
22
+ } catch (error) {
23
+ try { close(); } catch (cleanupError) { throw new AggregateError([error, cleanupError], "state view acquisition failed"); }
24
+ throw error;
25
+ }
26
+ }
@@ -0,0 +1,73 @@
1
+ import { createCompletionMethod } from "../../serve/session-completion-engine";
2
+ import type { ExecutionPlanner, InferenceEngine, Timer } from "../../contracts/generation";
3
+ import { createInferenceEngine, type EngineOptions } from "../../engine/engine";
4
+ import { throwIfCancelled } from "../../engine/cancellation";
5
+ import type { GenerateOptions, GenerateStats } from "../../generate";
6
+ import type { GenerationGateway, RequestShape } from "../../serve/generation-gateway";
7
+
8
+ /** Data-only API for text requests whose policy/shape was already
9
+ * resolved by the caller. Native request resources still belong to the prepared
10
+ * completion API; they are deliberately absent from this token API. */
11
+ export type TextGenerationOptions = Omit<GenerateOptions,
12
+ "signal" | "cache" | "grammar" | "fill" | "adapters" | "promptEmbeddings" |
13
+ "imageMask" | "multimodalMask" | "visionPixels" | "onPrefillDone" |
14
+ "onDecodeCheckpoint" | "initialPendingToken" | "initialGeneratedTokens" |
15
+ "originalPromptTokens" | "snapshotAt" | "checkpointEveryTokens"
16
+ >;
17
+
18
+ export interface TextGenerationRequest {
19
+ readonly promptIds: readonly number[];
20
+ readonly options: TextGenerationOptions;
21
+ readonly shape: RequestShape;
22
+ }
23
+
24
+ export const hostTimer: Timer = {
25
+ after(milliseconds, callback) {
26
+ const timer = setTimeout(callback, milliseconds);
27
+ return () => { clearTimeout(timer); };
28
+ },
29
+ };
30
+
31
+ /** Reuses the gateway's placement, exclusive lease, prompt cache, batching,
32
+ * and quant-specific execution. The portable session never imports them.
33
+ * Closing this engine cancels its sessions; it does not dispose a shared gateway. */
34
+ export function createTextInferenceEngine(
35
+ gateway: Pick<GenerationGateway, "place" | "run">,
36
+ options: Omit<EngineOptions, "timer"> & { timer?: Timer } = {},
37
+ ): InferenceEngine<TextGenerationRequest, GenerateStats> {
38
+ const planner: ExecutionPlanner<TextGenerationRequest, GenerateStats> = {
39
+ async plan(request, cancellation) {
40
+ throwIfCancelled(cancellation);
41
+ if (request.shape.hasVision || request.shape.hasAdapters || request.shape.hasGrammar)
42
+ throw new Error("native request resources require the prepared completion API");
43
+ const placement = gateway.place(request.shape, request.options);
44
+ const maxTokens = request.options.maxTokens ?? 512;
45
+ return {
46
+ id: `text:${placement.mechanism}`, outputTokenLimit: maxTokens,
47
+ method: createCompletionMethod(gateway, {
48
+ prompt: [...request.promptIds], options: request.options,
49
+ shape: request.shape, placement,
50
+ }),
51
+ };
52
+ },
53
+ };
54
+ const engine = createInferenceEngine(planner, { ...options, timer: options.timer ?? hostTimer });
55
+ return {
56
+ open(request, control) {
57
+ // Snapshot before demand-start: callers may reuse/mutate request arrays.
58
+ // TextGenerationOptions contains data only; no native handles or callbacks.
59
+ const snapshot = structuredClone(request);
60
+ if (!snapshot.promptIds.length || snapshot.promptIds.some((id) => !Number.isInteger(id) || id < 0 || id > 0xffffffff))
61
+ return Promise.reject(new Error("prompt must contain valid token ids"));
62
+ const maxTokens = snapshot.options.maxTokens ?? 512;
63
+ if (!Number.isSafeInteger(maxTokens) || maxTokens <= 0)
64
+ return Promise.reject(new Error("text generation requires a positive token limit"));
65
+ if ((snapshot.options.topLogprobs ?? 0) > (options.maxTopLogprobs ?? 20))
66
+ return Promise.reject(new Error("top logprobs exceed session delivery capacity"));
67
+ if (control.output === "collect" && (snapshot.options.logprobs || snapshot.options.topLogprobs))
68
+ return Promise.reject(new Error("token logprobs require stream consumption"));
69
+ return engine.open(snapshot, control);
70
+ },
71
+ close: () => engine.close(),
72
+ };
73
+ }
package/src/cli.ts CHANGED
@@ -35,6 +35,14 @@ import {
35
35
  } from "./runtime-config";
36
36
 
37
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
+
38
46
  // The appliance path: naked `mlx-bun` (or only options, e.g.
39
47
  // `mlx-bun --port 9000`) runs `serve` — first run downloads a model and
40
48
  // opens the chat UI. `--help`/`--version` and explicit subcommands still win.
@@ -103,13 +111,19 @@ const SERVER_FLAGS = `Server options:
103
111
  restarts (long-context agent TTFT drops from a
104
112
  full re-prefill to a zero-copy mmap restore).
105
113
  Off unless set.
106
- --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]
107
116
  --ssd-demote-idle <sec> Demote prompt-cache entries idle this long to the
108
117
  SSD tier, freeing their GPU memory (prefixes stay
109
118
  reachable via zero-copy restore). 0 disables.
110
119
  [default: 300 when --ssd-cache is on]
111
120
  --ssd-cache-verify Verify tensor hashes on every restore (reads all
112
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.
113
127
  --isolate Run the inference engine as a CHILD process and
114
128
  keep this process a pure UI/API proxy — the UI
115
129
  stays instant under GPU load and survives engine
@@ -181,7 +195,7 @@ Model & quality:
181
195
  (bf16), 4 / 8 (uniform bits), or turbo[:k<bits>v
182
196
  <bits>] (rotation-based TurboQuant — default
183
197
  k8v3; kBits in {2,4,5,8}, vBits in {2,3,4,5,8};
184
- docs/design/turboquant-kv.md)
198
+ docs/design/turboquant.md)
185
199
  [default: off — quantized KV measured 5-20% slower
186
200
  decode than bf16 at ≤16k (on mlx-lm too); it buys
187
201
  memory headroom, so opt in when context is tight.
@@ -196,7 +210,7 @@ Model & quality:
196
210
  --kv-quant, --draft-model; bypasses the prompt
197
211
  cache; runs uncompiled decode. Bit-exact with the
198
212
  plain path; expect a small decode cost at batch=1
199
- (the gather copy). docs/design/paged-kv-cache.md
213
+ (the gather copy). docs/design/kv-cache.md
200
214
  [default: off]
201
215
  --paged-kv-block-size <n> Tokens per KV block [default: 256 = the plain
202
216
  cache's growth step]
@@ -212,13 +226,14 @@ Model & quality:
212
226
  pass --temp 0 for its unset-request behavior)
213
227
  [default: the model's generation_config.json]
214
228
  --max-tokens <n> Completion cap when a request omits max_tokens
215
- (mlx_lm.server flag; its default is 512 there)
216
- [default: 128 GLM-5.2; otherwise 65536 chat /
217
- 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]
218
233
  --hlg-sampling on|off Piecewise tone-curve sampling (HLG): rolls off the
219
234
  top, boosts the mids, gentles the tail. Gain folds
220
235
  from --temperature. [default: off]
221
- See docs/design/hlg-sampling.md
236
+ See docs/archive/hlg-sampling.md
222
237
  --hlg-width <nats> HLG mid-region half-width [default: 4]
223
238
  --hlg-shoulder <nats> HLG highlight rolloff scale [default: 4]
224
239
  --hlg-toe <nats> HLG shadow rolloff scale [default: 6]
@@ -391,7 +406,7 @@ Usage: mlx-bun benchmark [query] [options]
391
406
 
392
407
  Runs mlx-bun by itself (no other systems required) and records the
393
408
  result to the eval DB. For quotable, cross-stack numbers use
394
- ./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
395
410
  and runs the mlx-lm/optiq comparison legs.
396
411
 
397
412
  [query] Model to benchmark (--query <q> is accepted for the
@@ -475,7 +490,7 @@ Options:
475
490
  --clear Archive the eval DB (timestamped backup) and start fresh
476
491
 
477
492
  Table view: when, model, bench kind, KV mode, decode tok/s, TTFT,
478
- peak memory, commit. Runs are written by ./benchmark.sh.`,
493
+ peak memory, commit. Runs are written by bun scripts/bench-serve.ts all.`,
479
494
 
480
495
  train: `mlx-bun train — fine-tune a LoRA adapter on your data
481
496
 
@@ -825,14 +840,14 @@ function openChatUi(url: string, hostPort: string): void {
825
840
  /** Resolve the decode ROUTE: a tier alias (--l1/--l2) sets the whole route,
826
841
  * and an explicit per-fork flag (--kv-quant/--fused-sdpa/…) overrides the
827
842
  * alias. Installs decode runtime options and returns the kv-quant mode. See
828
- * docs/design/parity-tier-dag.md + unified-engine-frontier-plan.md.
843
+ * docs/design/unified-engine-frontier-plan.md + unified-engine-frontier-plan.md.
829
844
  * Each tier is a GUARANTEE about which reference you reproduce bit-for-bit:
830
845
  * --l1 bit-for-bit IDENTICAL to mlx-lm — drop-in replacement for mlx-lm
831
846
  * (bf16 KV; THE naked default since 2026-07-05)
832
847
  * --l2 bit-for-bit IDENTICAL to mlx-optiq — drop-in replacement for
833
848
  * mlx-optiq: quantized KV per kv_config.json + fused N-tiled SDPA for
834
849
  * L>1 + STOCK unfused L=1 decode (the optiq-golden composition,
835
- * scripts/regen-kvq-goldens.ts + regen-mixed-kv-goldens.ts).
850
+ * scripts/regen.ts kvq + regen-mixed-kv-goldens.ts).
836
851
  * There is no --l3: output-changing experiments live in the Lab (env flags
837
852
  * with a bench + expiry) until one beats the L1 baseline in a paired A/B —
838
853
  * docs/design/unified-engine-frontier-plan.md §6-7. */
@@ -897,7 +912,7 @@ function applyDecodeRoute(): { kvQuant?: "off" | "config" | number; turboQuant?:
897
912
  if (kv === "off") return { kvQuant: "off" };
898
913
  if (kv === "config") return { kvQuant: "config" };
899
914
  if (kv) {
900
- // TurboQuant (docs/design/turboquant-kv.md): a distinct axis from the
915
+ // TurboQuant (docs/design/turboquant.md): a distinct axis from the
901
916
  // affine kvQuant above — dequantize-on-fetch, stock unfused ops.sdpa,
902
917
  // no kvQuant value at all (mutually exclusive with kvBits/kvConfig).
903
918
  let turboQuant: TurboQuantScheme | null;
@@ -940,23 +955,41 @@ function serverRuntimeFlags(): { port: number; serverOptions: import("./server")
940
955
  if (kvBudgetGB > 0) serverOptions.kvBudgetBytes = kvBudgetGB * 1e9;
941
956
  const pcRaw = opt("prompt-cache"); // null if absent; an explicit value (incl. 0) wins → `--prompt-cache 0` DISABLES the cache
942
957
  if (pcRaw !== null) serverOptions.promptCacheBytes = Math.max(0, Number(pcRaw)) * 2 ** 30;
943
- // 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
944
959
  // instead of TCP — the isolated parent proxies here. Internal flag,
945
960
  // spawned by --isolate; usable directly for socket-level integration.
946
961
  const unixRaw = opt("unix");
947
962
  if (unixRaw !== null) serverOptions.unixSocket = unixRaw;
948
- // 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.
949
964
  const ssdDir = opt("ssd-cache");
950
965
  if (ssdDir !== null) {
951
966
  serverOptions.ssdCacheDir = ssdDir;
952
967
  const maxRaw = opt("ssd-cache-max");
953
- 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
+ }
954
978
  if (flag("ssd-cache-verify")) serverOptions.ssdCacheVerify = true;
955
979
  const demoteRaw = opt("ssd-demote-idle");
956
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
+ }
957
990
  } else {
958
991
  // Refuse-loudly house rule: sub-flags of an absent tier must not vanish.
959
- 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)
960
993
  if (opt(f) !== null) console.warn(`--${f} has no effect without --ssd-cache — ignored`);
961
994
  if (flag("ssd-cache-verify"))
962
995
  console.warn("--ssd-cache-verify has no effect without --ssd-cache — ignored");
@@ -979,7 +1012,7 @@ function serverRuntimeFlags(): { port: number; serverOptions: import("./server")
979
1012
  if (route.kvQuant !== undefined) serverOptions.kvQuant = route.kvQuant;
980
1013
  if (route.turboQuant !== undefined) serverOptions.turboQuant = route.turboQuant;
981
1014
  // --paged-kv (env: MLX_BUN_PAGED_KV=1): OPTIONAL vLLM-style block-pool KV
982
- // 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:
983
1016
  // with no explicit --batch, pin --batch 1 (the default is 8); an explicit
984
1017
  // --batch N>1 is refused loudly by createServer rather than downgraded.
985
1018
  if (flag("paged-kv") || runtimeValue("MLX_BUN_PAGED_KV") === "1") {
@@ -1026,7 +1059,7 @@ function serverRuntimeFlags(): { port: number; serverOptions: import("./server")
1026
1059
  const maxTok = numFlag("max-tokens", 1, 10_000_000);
1027
1060
  if (maxTok !== null) serverOptions.defaultMaxTokens = Math.floor(maxTok);
1028
1061
  // HLG sampling — piecewise tone curve on the logprobs (default off). Knobs in
1029
- // 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.
1030
1063
  if (onOff("hlg-sampling") === true) {
1031
1064
  serverOptions.hlg = {
1032
1065
  enabled: true,
@@ -1538,7 +1571,7 @@ switch (cmd) {
1538
1571
  process.exit(1);
1539
1572
  }
1540
1573
  }
1541
- // Runtime isolation (--isolate, runtime-isolation.md): THIS process
1574
+ // Runtime isolation (--isolate, docs/reference/server-config.md): THIS process
1542
1575
  // becomes a pure reactor — it binds the TCP port and reverse-proxies to
1543
1576
  // an engine CHILD (the full server on a unix socket, spawned from the
1544
1577
  // same argv minus parent-only flags). Zero MLX calls in the parent: the
@@ -1580,7 +1613,7 @@ switch (cmd) {
1580
1613
  };
1581
1614
  const poolMaxRaw = opt("model-pool");
1582
1615
  const poolMax = Math.max(1, Number(poolMaxRaw ?? 1) || 1);
1583
- const { engine, pool } = startProxyServer({
1616
+ const { engine, close } = startProxyServer({
1584
1617
  port: rt.port,
1585
1618
  ...(rt.serverOptions.hostname ? { hostname: rt.serverOptions.hostname } : {}),
1586
1619
  engine: {
@@ -1596,7 +1629,7 @@ switch (cmd) {
1596
1629
  const shownHost0 = rt.serverOptions.hostname ?? "localhost";
1597
1630
  console.log(style.dim(` isolated mode: engine child starting (socket ${sock})`));
1598
1631
  console.log(` ${style.green("●")} ${style.bold("proxy up")} http://${shownHost0}:${rt.port} ${style.dim(`· ${m0.repoId} loading behind it · model pool ${poolMax}`)}`);
1599
- const onExit = () => { pool?.stopAll(); engine.stop(); process.exit(0); };
1632
+ const onExit = () => { void close().finally(() => process.exit(0)); };
1600
1633
  process.on("SIGINT", onExit);
1601
1634
  process.on("SIGTERM", onExit);
1602
1635
  await engine.ready;
@@ -1886,7 +1919,7 @@ switch (cmd) {
1886
1919
  const promptTokens = Number(opt("prompt-tokens", "0"));
1887
1920
  const kvMode = opt("kv-quant", "off")!;
1888
1921
  console.log(` ${style.dim("measures THIS machine, OUR stack only — no other systems needed.")}`);
1889
- 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.")}`);
1890
1923
  const sNative = step("native runtime");
1891
1924
  await ensureNative(sNative);
1892
1925
  sNative.done("native runtime ready");
@@ -1931,7 +1964,7 @@ switch (cmd) {
1931
1964
  : kvMode !== "off" ? { kvBits: Number(kvMode), quantizedKvStart: 0 } : {};
1932
1965
 
1933
1966
  // Warmup materializes weights so prefill timing measures prefill,
1934
- // 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)).
1935
1968
  const sWarm = step("warmup");
1936
1969
  {
1937
1970
  const wCache = model.makeCache();
@@ -2006,7 +2039,7 @@ switch (cmd) {
2006
2039
  }
2007
2040
  const { style } = await import("./tui");
2008
2041
  if (rows.length === 0) {
2009
- 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");
2010
2043
  break;
2011
2044
  }
2012
2045
  const ago = (ts: number) => {
@@ -2078,7 +2111,7 @@ switch (cmd) {
2078
2111
  // already run their own pi connect it with `mlx-bun harness pi`.
2079
2112
  const OURS_VAL = new Set([
2080
2113
  "--query", "-q", "--model", "--port", "--host", "--memory-budget", "--kv-budget", "--prompt-cache",
2081
- "--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",
2082
2115
  "--batch", "--decode-concurrency", "--adapter", "--adapter-path",
2083
2116
  "--draft-model", "--num-draft-tokens", "--draft-kind", "--ngram-max", "--ngram-min",
2084
2117
  "--compiled-decode", "--compiled-activations", "--fused-sdpa", "--thinking",
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
+ }
package/src/config.ts CHANGED
@@ -75,6 +75,17 @@ export interface QuantSpec {
75
75
  bits: number;
76
76
  groupSize: number;
77
77
  mode: string;
78
+ /** `mode: "trellis"` (packed trellis-coded weights, src/model/trellis-linear.ts):
79
+ * bits = k per weight, groupSize = block T, plus the codec geometry. */
80
+ trellis?: TrellisSpec;
81
+ }
82
+
83
+ export interface TrellisSpec {
84
+ L: number;
85
+ code: string;
86
+ /** 1 = coded along the input dim (stored [out, in·k/32]); 0 = along the
87
+ * output dim (stored [in, out·k/32], i.e. Wᵀ coded along its last axis). */
88
+ axis: 0 | 1;
78
89
  }
79
90
 
80
91
  export interface QuantizationConfig {
@@ -89,12 +100,12 @@ export interface KvQuantSpec {
89
100
  groupSize: number;
90
101
  }
91
102
 
92
- /** TurboQuant scheme (docs/design/turboquant-kv.md): rotation-based KV
103
+ /** TurboQuant scheme (docs/design/turboquant.md): rotation-based KV
93
104
  * quantization, a distinct axis from the uniform/per-layer affine kvQuant
94
105
  * above (composes with it in principle; v1 ships as a standalone CLI-only
95
106
  * runtime lever, same class as uniform kvBits — see
96
107
  * src/generate.ts maybeQuantizeKv). Valid kBits: {2,4,5,8}; vBits:
97
- * {2,3,4,5,8} (docs/design/turboquant-kv.md's supported Lloyd-Max tables). */
108
+ * {2,3,4,5,8} (docs/design/turboquant.md's supported Lloyd-Max tables). */
98
109
  export interface TurboQuantScheme {
99
110
  kBits: number;
100
111
  vBits: number;
@@ -162,11 +173,17 @@ export function parseQuantization(raw: Record<string, any> | undefined): Quantiz
162
173
  for (const [key, value] of Object.entries(raw)) {
163
174
  if (key === "bits" || key === "group_size" || key === "mode") continue;
164
175
  if (typeof value === "object" && value !== null) {
165
- perLayer.set(key, {
176
+ const spec: QuantSpec = {
166
177
  bits: (value as any).bits ?? def.bits,
167
178
  groupSize: (value as any).group_size ?? def.groupSize,
168
179
  mode: (value as any).mode ?? def.mode,
169
- });
180
+ };
181
+ const tr = (value as any).trellis;
182
+ if (spec.mode === "trellis") {
183
+ if (!tr || typeof tr !== "object") throw new Error(`quantization.${key}: mode "trellis" needs a trellis {L, code, axis} block`);
184
+ spec.trellis = { L: Number(tr.L), code: String(tr.code ?? "1mad"), axis: tr.axis === 0 ? 0 : 1 };
185
+ }
186
+ perLayer.set(key, spec);
170
187
  } else if (value === false) {
171
188
  // mlx convention: `"layer": false` means not quantized
172
189
  perLayer.set(key, { bits: 0, groupSize: 0, mode: "none" });
@@ -0,0 +1,16 @@
1
+ import type { Cancellation } from "./generation";
2
+
3
+ /** Application-facing calls. The implementation owns tokenization, numerical
4
+ * method and transport; callers provide request data and consume results. */
5
+ export interface CompletionClient<Request, Result> {
6
+ complete(request: Request): Promise<Result>;
7
+ }
8
+
9
+ export interface BatchCompletionClient<Request, Result> extends CompletionClient<Request, Result> {
10
+ /** Results preserve input order; the implementation chooses execution groups. */
11
+ completeBatch(requests: readonly Request[]): Promise<Result[]>;
12
+ }
13
+
14
+ export interface TaskClient<Request, Progress, Result> {
15
+ run(request: Request, report: (progress: Progress) => void, cancellation?: Cancellation): Promise<Result>;
16
+ }
@@ -0,0 +1,49 @@
1
+ /** Request facts only. Native resources remain with the preparation owner. */
2
+ export interface ExecutionRequirements {
3
+ readonly hasVision: boolean;
4
+ readonly hasAdapters: boolean;
5
+ readonly hasRepetitionPenalty: boolean;
6
+ readonly userSeed: boolean;
7
+ readonly kvQuant: boolean;
8
+ readonly turboQuant: boolean;
9
+ readonly hasLogitsExtras: boolean;
10
+ readonly hasGrammar: boolean;
11
+ readonly wantsLogprobs: boolean;
12
+ readonly hasDraft: boolean;
13
+ }
14
+
15
+ export interface ExecutionCapabilities {
16
+ readonly method: "autoregressive" | "denoising";
17
+ readonly continuous: boolean;
18
+ readonly quantizedBatch: boolean;
19
+ readonly grammarBatch: boolean;
20
+ readonly checkpoints: boolean;
21
+ /** Model-qualified speculative execution can retain this request's KV codec. */
22
+ readonly speculativeKvQuant?: boolean;
23
+ /** A graph-owned compiled step exists; cache geometry can still decline it. */
24
+ readonly compiledDecode?: boolean;
25
+ }
26
+
27
+ export interface ExecutionFeatures {
28
+ readonly pagedKv: boolean;
29
+ readonly fill: boolean;
30
+ readonly compiledDecode?: boolean;
31
+ readonly grammarJump?: boolean;
32
+ }
33
+
34
+ /** Selected once; consumers execute/report these values without reselecting. */
35
+ export interface ResolvedExecution {
36
+ /** Implementation-owned method ID. The built-in planner retains its known
37
+ * methods; another model may register a different set without editing this contract. */
38
+ readonly method: string;
39
+ readonly mechanism: "serial" | "continuous";
40
+ readonly pagedKv: boolean;
41
+ readonly promptCache: boolean;
42
+ readonly checkpoint: boolean;
43
+ readonly fill: boolean;
44
+ /** Permission to attempt the bound compiled step, never a promise that
45
+ * every dynamic row/state shape supports replay. */
46
+ readonly compiledDecode: boolean;
47
+ readonly grammarJump: boolean;
48
+ readonly reasons: readonly string[];
49
+ }