mlx-bun 0.2.0 → 0.4.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 (433) hide show
  1. package/README.md +71 -588
  2. package/docs/README.md +94 -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/archive/investigations/openjarvis-fit.md +198 -0
  8. package/docs/{design → archive/investigations}/optimization_plan.md +1 -1
  9. package/docs/{design → archive/investigations}/pre-colibri-stabilization.md +5 -5
  10. package/docs/archive/investigations/qwen38-27b-campaign-log.md +1092 -0
  11. package/docs/{design → archive/investigations}/the-dreaming-handoff.md +2 -2
  12. package/docs/archive/planning/release-notes-v0.3.0.md +35 -0
  13. package/docs/design/batching.md +1841 -0
  14. package/docs/design/decode-speed-program.md +2346 -169
  15. package/docs/design/dreaming-nightly-pipeline.md +675 -282
  16. package/docs/design/generic-model-support.md +655 -334
  17. package/docs/design/kv-cache.md +824 -0
  18. package/docs/design/orpo-training.md +673 -1239
  19. package/docs/design/speculative-decoding.md +1990 -0
  20. package/docs/design/turboquant.md +2212 -0
  21. package/docs/design/unified-engine-frontier-plan.md +1833 -727
  22. package/docs/design/web-chat-redesign.md +666 -1083
  23. package/docs/planning/PRODUCT_ROADMAP.md +50 -1
  24. package/docs/planning/release-notes-v0.4.0.md +45 -0
  25. package/docs/planning/video-series.md +2 -2
  26. package/docs/reference/benchmarks.md +3613 -0
  27. package/docs/reference/cli.md +434 -160
  28. package/docs/reference/distribution.md +328 -182
  29. package/docs/reference/environment.md +398 -0
  30. package/docs/reference/glossary.md +39 -0
  31. package/docs/reference/library-api.md +566 -168
  32. package/docs/reference/memory.md +227 -64
  33. package/docs/reference/models.md +113 -6
  34. package/docs/reference/server-api.md +944 -429
  35. package/docs/reference/server-config.md +740 -338
  36. package/docs/reference/training.md +393 -254
  37. package/docs/reference/troubleshooting.md +158 -0
  38. package/package.json +9 -4
  39. package/src/audio/decode.ts +2 -2
  40. package/src/audio/features.ts +2 -2
  41. package/src/backends/mlx/assistant-target.ts +46 -0
  42. package/src/backends/mlx/autoregressive-draft-rows.ts +120 -0
  43. package/src/backends/mlx/autoregressive.ts +108 -0
  44. package/src/backends/mlx/batch-group.ts +1790 -0
  45. package/src/backends/mlx/cache-attachment.ts +67 -0
  46. package/src/backends/mlx/cache-layout.ts +31 -0
  47. package/src/backends/mlx/checkpoint-state.ts +53 -0
  48. package/src/backends/mlx/continuation-persistence.ts +106 -0
  49. package/src/backends/mlx/continuation-request.ts +43 -0
  50. package/src/backends/mlx/continuation.ts +77 -0
  51. package/src/backends/mlx/diffusion.ts +39 -0
  52. package/src/backends/mlx/draft-target.ts +32 -0
  53. package/src/backends/mlx/evaluation.ts +105 -0
  54. package/src/backends/mlx/fill-append.ts +45 -0
  55. package/src/backends/mlx/full-kv-row-append.ts +32 -0
  56. package/src/backends/mlx/full-kv-row-donor.ts +54 -0
  57. package/src/backends/mlx/full-transitioning-kv-rows.ts +65 -0
  58. package/src/backends/mlx/gateway-binding.ts +130 -0
  59. package/src/backends/mlx/graph.ts +38 -0
  60. package/src/backends/mlx/kv-codec.ts +9 -0
  61. package/src/backends/mlx/kv-maintenance.ts +133 -0
  62. package/src/backends/mlx/kv-tensor-rows.ts +212 -0
  63. package/src/backends/mlx/memory-client.ts +189 -0
  64. package/src/backends/mlx/methods.ts +90 -0
  65. package/src/backends/mlx/model-prompt.ts +164 -0
  66. package/src/backends/mlx/model-serving.ts +79 -0
  67. package/src/backends/mlx/prefill-cohort.ts +89 -0
  68. package/src/backends/mlx/prefill-rows.ts +200 -0
  69. package/src/backends/mlx/prefill.ts +33 -0
  70. package/src/backends/mlx/projected-context-rows.ts +72 -0
  71. package/src/backends/mlx/request-policy.ts +20 -0
  72. package/src/backends/mlx/request-state-policy.ts +41 -0
  73. package/src/backends/mlx/rollback.ts +89 -0
  74. package/src/backends/mlx/row-sampling.ts +49 -0
  75. package/src/backends/mlx/serial-executor.ts +242 -0
  76. package/src/backends/mlx/speculative-group.ts +359 -0
  77. package/src/backends/mlx/speculative-round.ts +161 -0
  78. package/src/backends/mlx/speculative.ts +114 -0
  79. package/src/backends/mlx/state-rows.ts +104 -0
  80. package/src/backends/mlx/state-views.ts +46 -0
  81. package/src/backends/mlx/target-layout-capability.ts +35 -0
  82. package/src/backends/mlx/text-engine.ts +73 -0
  83. package/src/backends/mlx/transitioning-kv-rows.ts +165 -0
  84. package/src/cli.ts +66 -58
  85. package/src/client.ts +66 -0
  86. package/src/config.ts +21 -4
  87. package/src/contracts/completion.ts +16 -0
  88. package/src/contracts/execution.ts +60 -0
  89. package/src/contracts/generation.ts +103 -0
  90. package/src/contracts/host.ts +7 -0
  91. package/src/contracts/jobs.ts +66 -0
  92. package/src/contracts/pi-provider.ts +30 -0
  93. package/src/contracts/pi.ts +259 -0
  94. package/src/contracts/prefix-cache.ts +23 -0
  95. package/src/contracts/resources.ts +25 -0
  96. package/src/contracts/sampling.ts +23 -0
  97. package/src/contracts/scheduling.ts +45 -0
  98. package/src/contracts/tsconfig.json +14 -0
  99. package/src/diffusion/diffusion-generate.ts +218 -153
  100. package/src/embed.ts +1 -1
  101. package/src/engine/admission.ts +59 -0
  102. package/src/engine/cancellation.ts +38 -0
  103. package/src/engine/engine.ts +56 -0
  104. package/src/engine/execution-coordinator.ts +70 -0
  105. package/src/engine/execution-plan.ts +53 -0
  106. package/src/engine/resources.ts +53 -0
  107. package/src/engine/scheduler.ts +72 -0
  108. package/src/engine/session.ts +247 -0
  109. package/src/eval/README.md +44 -1
  110. package/src/eval/kl.ts +236 -3
  111. package/src/eval/reference-logits.ts +207 -0
  112. package/src/eval/runner.ts +8 -96
  113. package/src/fill/echo-index.ts +188 -0
  114. package/src/fill/fill-session.ts +452 -0
  115. package/src/fill/proposal.ts +68 -0
  116. package/src/fill/schema-rows.ts +506 -0
  117. package/src/fill/tool-boundary.ts +103 -0
  118. package/src/fit.ts +30 -16
  119. package/src/generate.ts +709 -417
  120. package/src/grammar.ts +12 -7
  121. package/src/harness-pi.ts +14 -33
  122. package/src/hub-rest.ts +4 -4
  123. package/src/index.ts +7 -0
  124. package/src/inference/denoising.ts +17 -0
  125. package/src/inference/draft-acceptance.ts +41 -0
  126. package/src/inference/draft-output.ts +49 -0
  127. package/src/inference/graph.ts +33 -0
  128. package/src/inference/prefill.ts +39 -0
  129. package/src/inference/rollback.ts +7 -0
  130. package/src/jobs/db.ts +4 -0
  131. package/src/jobs/index.ts +2 -0
  132. package/src/jobs/job-entry.ts +12 -12
  133. package/src/jobs/runner.ts +98 -13
  134. package/src/jobs/task-client.ts +19 -0
  135. package/src/jobs/types.ts +2 -58
  136. package/src/kv-scheme.ts +70 -12
  137. package/src/kv-store.ts +177 -63
  138. package/src/lab/README.md +18 -0
  139. package/src/{assets → lab/curve}/curve-designer.html +2 -2
  140. package/src/{curve-sampler.ts → lab/curve/curve-sampler.ts} +2 -2
  141. package/src/{expert-trace.ts → lab/expert-trace/expert-trace.ts} +5 -5
  142. package/src/lab/paged-kv/paged-kv-rows.ts +83 -0
  143. package/src/{model → lab/paged-kv}/paged-kv.ts +32 -15
  144. package/src/library.ts +40 -0
  145. package/src/lora.ts +18 -0
  146. package/src/media-fetch.ts +1 -1
  147. package/src/memory/article.ts +1 -1
  148. package/src/memory/chunk.ts +1 -1
  149. package/src/memory/model.ts +30 -169
  150. package/src/memory/prompts.ts +1 -1
  151. package/src/memory/rest.ts +1 -1
  152. package/src/memory/schedule.ts +1 -1
  153. package/src/memory/stages.ts +4 -1
  154. package/src/memory/vault.ts +1 -3
  155. package/src/mlx/array.ts +11 -11
  156. package/src/mlx/compile.ts +12 -2
  157. package/src/mlx/ffi.ts +58 -11
  158. package/src/mlx/materialize.ts +23 -0
  159. package/src/mlx/metal-kernel.ts +10 -11
  160. package/src/mlx/ops.ts +33 -13
  161. package/src/mlx/quantized-rows.ts +36 -0
  162. package/src/mlx/token-bitmask.ts +52 -0
  163. package/src/mlx/turboquant-kv-decode.ts +82 -0
  164. package/src/mlx/turboquant-kv-inverse.ts +122 -0
  165. package/src/mlx/turboquant-ops.ts +33 -3
  166. package/src/mlx/turboquant-tables.ts +2 -2
  167. package/src/model/artifact-identity.ts +73 -0
  168. package/src/model/batched-kv.ts +235 -0
  169. package/src/model/batched-mask.ts +20 -6
  170. package/src/model/batched-quant.ts +9 -3
  171. package/src/model/batched-quantized-kv.ts +144 -0
  172. package/src/model/batched-rotating-quant.ts +78 -17
  173. package/src/model/batched-rotating-state.ts +76 -0
  174. package/src/model/batched-rotating.ts +65 -23
  175. package/src/model/batched-row-storage.ts +52 -0
  176. package/src/model/batched-ssm.ts +52 -0
  177. package/src/model/batched-turboquant-kv.ts +93 -0
  178. package/src/model/compiled-decode.ts +8 -2
  179. package/src/model/decoded-kv-donor.ts +12 -0
  180. package/src/model/delayed-quantized-kv.ts +72 -0
  181. package/src/model/delayed-rotating-quantized-kv.ts +93 -0
  182. package/src/model/delayed-turboquant-kv.ts +55 -0
  183. package/src/model/diffusion-gemma.ts +8 -8
  184. package/src/model/factory.ts +76 -33
  185. package/src/model/fingerprint.ts +1 -1
  186. package/src/model/flash-attention.ts +3 -3
  187. package/src/model/full-prefill-padding.ts +56 -0
  188. package/src/model/full-prefill-row.ts +105 -0
  189. package/src/model/gemma4-base.ts +179 -80
  190. package/src/model/gemma4.ts +40 -32
  191. package/src/model/generated/gemma4-12b.ts +6 -3
  192. package/src/model/generated/gemma4-26b.ts +6 -3
  193. package/src/model/generated/gemma4-e4b.ts +6 -3
  194. package/src/model/glm52-cache.ts +80 -4
  195. package/src/model/glm52-dsa.ts +13 -9
  196. package/src/model/glm52-memory.ts +1 -1
  197. package/src/model/glm52-mla.ts +12 -6
  198. package/src/model/glm52.ts +19 -40
  199. package/src/model/implementation.ts +69 -0
  200. package/src/model/kv-attention-view.ts +67 -0
  201. package/src/model/minicpm5.ts +17 -15
  202. package/src/model/profile.ts +12 -2
  203. package/src/model/qwen-append.ts +12 -0
  204. package/src/model/qwen-conv.ts +52 -0
  205. package/src/model/qwen3-delta-state.ts +74 -0
  206. package/src/model/qwen3-delta.ts +59 -36
  207. package/src/model/qwen3_5-checkpoint.ts +164 -0
  208. package/src/model/qwen3_5.ts +202 -78
  209. package/src/model/rotating-kv-donor.ts +35 -0
  210. package/src/model/rotating-kv-layout.ts +262 -0
  211. package/src/model/rotating-row-transaction.ts +35 -0
  212. package/src/model/speculative-rotating-kv.ts +101 -0
  213. package/src/model/ssm-prefill-padding.ts +39 -0
  214. package/src/model/support.ts +1 -1
  215. package/src/model/trellis-balanced-scatter.ts +51 -0
  216. package/src/model/trellis-linear.ts +672 -0
  217. package/src/model/trellis-shared-m.ts +151 -0
  218. package/src/model/trellis-shared-scatter.ts +144 -0
  219. package/src/model/trellis-splitk-prefill.ts +158 -0
  220. package/src/model/trellis-tiled-prefill.ts +136 -0
  221. package/src/model/trellis-vector-expand.ts +62 -0
  222. package/src/model/trellis-wide-prefill.ts +94 -0
  223. package/src/model/turboquant-codec.ts +83 -0
  224. package/src/model/universal/dense.ts +15 -14
  225. package/src/model/universal/modules.ts +8 -1
  226. package/src/native-pack.ts +6 -5
  227. package/src/pi-provider.ts +6 -64
  228. package/src/pi-web.ts +8 -315
  229. package/src/preflight.ts +31 -15
  230. package/src/prompt-cache.ts +110 -69
  231. package/src/quantize/allocator.ts +1 -1
  232. package/src/quantize/config-writer.ts +10 -3
  233. package/src/quantize/rotate.ts +4 -4
  234. package/src/quantize/trellis.ts +586 -0
  235. package/src/responses.ts +28 -1
  236. package/src/runtime-config.ts +18 -4
  237. package/src/sampler.ts +40 -29
  238. package/src/serve/batch-scheduler.ts +8 -1671
  239. package/src/serve/chat-request.ts +391 -0
  240. package/src/serve/chat-stage.ts +195 -0
  241. package/src/serve/checkpoint-identity.ts +64 -0
  242. package/src/serve/completion-executor.ts +15 -7
  243. package/src/serve/discovery-routes.ts +12 -13
  244. package/src/serve/generation-gateway.ts +183 -213
  245. package/src/serve/http.ts +159 -0
  246. package/src/serve/inference-request.ts +79 -0
  247. package/src/serve/isolate.ts +297 -105
  248. package/src/serve/lab-routes.ts +3 -0
  249. package/src/serve/lane-registry.ts +2 -1
  250. package/src/serve/model-admin-routes.ts +7 -5
  251. package/src/serve/model-binding.ts +43 -0
  252. package/src/serve/model-host.ts +511 -0
  253. package/src/serve/openai-wire.ts +103 -0
  254. package/src/serve/parent-application.ts +56 -0
  255. package/src/serve/pipeline.ts +33 -0
  256. package/src/serve/preparation.ts +33 -0
  257. package/src/serve/request-plan.ts +23 -18
  258. package/src/serve/request-prep.ts +344 -0
  259. package/src/serve/response-lease.ts +24 -0
  260. package/src/serve/responses-client.ts +69 -0
  261. package/src/serve/session-completion-engine.ts +110 -0
  262. package/src/serve/session-search.ts +1 -1
  263. package/src/serve/static-routes.ts +5 -5
  264. package/src/serve/text-completion-stage.ts +104 -0
  265. package/src/serve/token-streams.ts +519 -0
  266. package/src/server.ts +335 -2711
  267. package/src/spec/assistant-rows.ts +92 -0
  268. package/src/spec/assistant-source.ts +31 -55
  269. package/src/spec/cache-identity.ts +22 -0
  270. package/src/spec/deepspec-rows.ts +29 -0
  271. package/src/spec/deepspec-source.ts +16 -14
  272. package/src/spec/dflash-source.ts +36 -12
  273. package/src/spec/drafter.ts +153 -142
  274. package/src/spec/dspark/ab-stats.ts +3 -3
  275. package/src/spec/dspark/calibration.ts +2 -2
  276. package/src/spec/dspark/deepspec-module.ts +97 -122
  277. package/src/spec/dspark/generate-dflash.ts +2 -2
  278. package/src/spec/dspark/generate.ts +2 -2
  279. package/src/spec/dspark/loader.ts +1 -1
  280. package/src/spec/dspark/module-dflash.ts +129 -172
  281. package/src/spec/dspark/module.ts +3 -3
  282. package/src/spec/dspark/quantize-drafter.ts +1 -1
  283. package/src/spec/generate.ts +1 -1
  284. package/src/spec/glm52-mtp-graph.ts +29 -0
  285. package/src/spec/glm52-mtp-rows.ts +132 -0
  286. package/src/spec/glm52-mtp-source.ts +54 -137
  287. package/src/spec/glm52-mtp-state.ts +33 -0
  288. package/src/spec/ngram-source.ts +93 -21
  289. package/src/spec/projected-draft-rows.ts +73 -0
  290. package/src/spec/qwen-mtp-module.ts +171 -0
  291. package/src/spec/qwen-mtp-rows.ts +219 -0
  292. package/src/spec/qwen-mtp-source.ts +147 -195
  293. package/src/spec/qwen-mtp-state.ts +30 -0
  294. package/src/spec/serve-loop.ts +201 -201
  295. package/src/spec/source.ts +110 -13
  296. package/src/spec/two-model-rows.ts +61 -0
  297. package/src/spec/two-model.ts +44 -102
  298. package/src/ssd-cache.ts +99 -19
  299. package/src/ssd-durability.ts +36 -20
  300. package/src/tool-call.ts +51 -14
  301. package/src/train/flash-cce.ts +2 -2
  302. package/src/train/forward.ts +6 -0
  303. package/src/train/loss.ts +1 -1
  304. package/src/train/prefix-shared.ts +5 -1
  305. package/src/train/segmented.ts +5 -3
  306. package/src/train/trainer.ts +6 -6
  307. package/src/vision/prompt.ts +1 -1
  308. package/src/vision/qwen3vl-tower.ts +52 -10
  309. package/src/web/app.html +1 -1
  310. package/src/web/app.js +62 -60
  311. package/src/web/src/adapters-panel.ts +3 -3
  312. package/src/web/src/assistant.ts +2 -36
  313. package/src/web/src/chat.ts +4 -4
  314. package/src/web/src/composer.ts +4 -4
  315. package/src/web/src/hub.ts +1 -1
  316. package/src/web/src/markdown.ts +0 -0
  317. package/src/web/src/memory-panel.ts +1 -1
  318. package/src/web/src/model-picker.ts +1 -1
  319. package/src/web/src/palette.ts +1 -1
  320. package/src/web/src/protocol.ts +3 -39
  321. package/src/web/src/sessions.ts +1 -1
  322. package/src/web/src/ui-catalog.ts +4 -18
  323. package/src/web-assets.ts +47 -0
  324. package/src/weights.ts +55 -9
  325. package/docs/archive/investigations/curve-runs/bisector-route.json +0 -98
  326. package/docs/archive/investigations/curve-runs/bisector-trace.json +0 -1393
  327. package/docs/archive/investigations/curve-runs/curve-terrain.json +0 -1179
  328. package/docs/archive/investigations/curve-runs/curve-terrain.svg +0 -42
  329. package/docs/archive/investigations/hlg-ab.html +0 -34
  330. package/docs/archive/investigations/hlg-explorer.html +0 -238
  331. package/docs/archive/investigations/hlg-figs/cliff.svg +0 -1
  332. package/docs/archive/investigations/hlg-figs/cross.svg +0 -1
  333. package/docs/archive/investigations/hlg-figs/edges.svg +0 -1
  334. package/docs/archive/investigations/hlg-figs/k20-grids.svg +0 -1
  335. package/docs/archive/investigations/hlg-figs/k20-peaks.svg +0 -1
  336. package/docs/archive/investigations/hlg-figs/lw-curve.svg +0 -1
  337. package/docs/archive/investigations/hlg-figs/terrain.svg +0 -1
  338. package/docs/archive/investigations/hlg-report.html +0 -262
  339. package/docs/archive/investigations/hlg-runs/hlg-ab.json +0 -149
  340. package/docs/archive/investigations/hlg-runs/hlg-blind-prompts.md +0 -87
  341. package/docs/archive/investigations/hlg-runs/hlg-blind.json +0 -107
  342. package/docs/archive/investigations/hlg-runs/hlg-cliff.json +0 -197
  343. package/docs/archive/investigations/hlg-runs/hlg-cross.json +0 -505
  344. package/docs/archive/investigations/hlg-runs/hlg-edge.json +0 -388
  345. package/docs/archive/investigations/hlg-runs/hlg-eval1.json +0 -25
  346. package/docs/archive/investigations/hlg-runs/hlg-frontier.json +0 -86
  347. package/docs/archive/investigations/hlg-runs/hlg-grid.json +0 -242
  348. package/docs/archive/investigations/hlg-runs/hlg-grid2.json +0 -343
  349. package/docs/archive/investigations/hlg-runs/hlg-lw-coarse.json +0 -110
  350. package/docs/archive/investigations/hlg-runs/hlg-map.json +0 -383
  351. package/docs/archive/investigations/hlg-runs/run0-original.log +0 -159
  352. package/docs/archive/investigations/hlg-runs/runA-recommended.log +0 -131
  353. package/docs/archive/investigations/hlg-runs/runAA-cliff-cpm5.log +0 -26
  354. package/docs/archive/investigations/hlg-runs/runAB-grid2-e4b.log +0 -18
  355. package/docs/archive/investigations/hlg-runs/runAC-peak-e4b-partial.log +0 -25
  356. package/docs/archive/investigations/hlg-runs/runAD-ab-samples.log +0 -8
  357. package/docs/archive/investigations/hlg-runs/runAE-edge-e4b.log +0 -51
  358. package/docs/archive/investigations/hlg-runs/runAF-logit-calibration.log +0 -10
  359. package/docs/archive/investigations/hlg-runs/runAG-eval-config2.log +0 -16
  360. package/docs/archive/investigations/hlg-runs/runB-gain-variants.log +0 -192
  361. package/docs/archive/investigations/hlg-runs/runC-pivots.log +0 -162
  362. package/docs/archive/investigations/hlg-runs/runD-oetf.log +0 -132
  363. package/docs/archive/investigations/hlg-runs/runE-eotf.log +0 -162
  364. package/docs/archive/investigations/hlg-runs/runF-shaper-working.log +0 -86
  365. package/docs/archive/investigations/hlg-runs/runG-shaper-sweep.log +0 -101
  366. package/docs/archive/investigations/hlg-runs/runH-wsweep.log +0 -176
  367. package/docs/archive/investigations/hlg-runs/runI-asweep.log +0 -176
  368. package/docs/archive/investigations/hlg-runs/runJ-knobmap.log +0 -49
  369. package/docs/archive/investigations/hlg-runs/runK-frontier.log +0 -19
  370. package/docs/archive/investigations/hlg-runs/runL-widemap.log +0 -65
  371. package/docs/archive/investigations/hlg-runs/runM-frontier-emb.log +0 -15
  372. package/docs/archive/investigations/hlg-runs/runN-verify.log +0 -116
  373. package/docs/archive/investigations/hlg-runs/runO-frontier-seed5000.log +0 -15
  374. package/docs/archive/investigations/hlg-runs/runP-grid-AxSm.log +0 -15
  375. package/docs/archive/investigations/hlg-runs/runQ-grid-tgW.log +0 -15
  376. package/docs/archive/investigations/hlg-runs/runR-blind-transcript.log +0 -79
  377. package/docs/archive/investigations/hlg-runs/runS-cross-coherence.log +0 -33
  378. package/docs/archive/investigations/hlg-runs/runT-cross-cpm5.log +0 -33
  379. package/docs/archive/investigations/hlg-runs/runU-cross-12b.log +0 -33
  380. package/docs/archive/investigations/hlg-runs/runV-lw-e4b.log +0 -22
  381. package/docs/archive/investigations/hlg-runs/runW-lw-12b.log +0 -22
  382. package/docs/archive/investigations/hlg-runs/runX-lw-cpm5.log +0 -22
  383. package/docs/archive/investigations/hlg-runs/runY-cliff-e4b.log +0 -26
  384. package/docs/archive/investigations/hlg-runs/runZ-cliff-12b.log +0 -26
  385. package/docs/archive/mlx-bun-lab-report.html +0 -275
  386. package/docs/archive/wwdc-mlx-bun.html +0 -848
  387. package/docs/design/adapters-end-to-end.md +0 -205
  388. package/docs/design/batching-perf-path.md +0 -255
  389. package/docs/design/batching-v2-plan.md +0 -529
  390. package/docs/design/bucketing-stage.md +0 -197
  391. package/docs/design/compat-cli-surface-design.md +0 -230
  392. package/docs/design/docs-reorg-plan.md +0 -165
  393. package/docs/design/dspark-serving-program.md +0 -371
  394. package/docs/design/dspark-speculative-decoding.md +0 -318
  395. package/docs/design/grammar-spec-batching-integration.md +0 -301
  396. package/docs/design/memory-inference-path.md +0 -422
  397. package/docs/design/memory-synthesis.md +0 -287
  398. package/docs/design/memory-system.md +0 -237
  399. package/docs/design/mlx-lm-serving-execution-seam.md +0 -81
  400. package/docs/design/mlx-lm-tool-parity-plan.md +0 -545
  401. package/docs/design/omlx-adoption-map.md +0 -98
  402. package/docs/design/orpo-dynamic-lambda.md +0 -220
  403. package/docs/design/orpo-future-enhancements.md +0 -340
  404. package/docs/design/paged-kv-cache.md +0 -214
  405. package/docs/design/parallel-slots.md +0 -567
  406. package/docs/design/parity-tier-dag.md +0 -161
  407. package/docs/design/repo-cleanup-plan.md +0 -231
  408. package/docs/design/runtime-isolation.md +0 -263
  409. package/docs/design/segmented-backward-training.md +0 -543
  410. package/docs/design/spec-decode-larger-targets.md +0 -229
  411. package/docs/design/ssd-kv-cold-tier.md +0 -415
  412. package/docs/design/structured-output.md +0 -484
  413. package/docs/design/tauri-desktop-app.md +0 -127
  414. package/docs/design/the-dreaming-master-plan.md +0 -445
  415. package/docs/design/turboquant-kv.md +0 -235
  416. package/docs/design/turboquant-weights.md +0 -467
  417. package/docs/design/w4a16-compute-precision-spike.md +0 -232
  418. package/docs/design/web-chat-beat-matrix.md +0 -315
  419. package/docs/design/write-pipeline-entity.md +0 -468
  420. package/docs/planning/IDEAS.md +0 -69
  421. package/docs/reference/embedding.md +0 -197
  422. package/docs/reference/features-matrix.md +0 -101
  423. package/docs/reference/orpo-quickstart.md +0 -135
  424. package/src/flags.ts +0 -11
  425. /package/docs/{design → archive/investigations}/colibri-glm52-port.md +0 -0
  426. /package/docs/{planning → archive/planning}/chunk-task-roadmap.md +0 -0
  427. /package/docs/{planning → archive/planning}/curve-sampler-research-plan.md +0 -0
  428. /package/docs/{planning → archive/planning}/memory-docs-and-dag-plan.md +0 -0
  429. /package/docs/{planning → archive/planning}/pi-integration-review.md +0 -0
  430. /package/docs/{planning → archive/planning}/release-notes-v0.2.0.md +0 -0
  431. /package/src/{memory-plan.ts → admission-plan.ts} +0 -0
  432. /package/{docs/archive/investigations → src/serve/assets}/curve-terrain.html +0 -0
  433. /package/{docs/dag → src/serve/assets}/training-inference-map.html +0 -0
@@ -1,3 +1,6 @@
1
+ import { AdmissionPool } from "../engine/admission";
2
+ import { acquireReservation } from "./preparation";
3
+ import { cleanupFailure, disposeResources } from "../engine/resources";
1
4
  // GenerationGateway is the scheduling seam between CompletionExecutor and
2
5
  // the inference runtime. It declares one mechanism per request: the preserved
3
6
  // strict/dedicated serial executor, or the continuous scheduler (`--batch N`).
@@ -26,31 +29,25 @@
26
29
  // server.py's all-caches-have-merge check). GLM's capability owns compressed
27
30
  // MLA/DSA merge/extract/filter and byte projection; Qwen3.5's SSM cache owns
28
31
  // its row state. The remaining request-shape exclusions include vision, LoRA,
29
- // serial-only KV modes, and user-fixed seed
32
+ // serial-only KV modes
30
33
  // (reproducibility ⇒ solo, matching mlx-lm's _is_batchable). Temperature /
31
34
  // top-p / top-k DO batch (each row samples with its own seed). Full-attention
32
35
  // AND sliding-window (Gemma) models both batch — the scheduler assembles each
33
36
  // layer's cache by type. Prompt-cache prefix reuse works on the batch lane
34
37
  // too (Phase 3.2): joiners take() at admission; never-merged lone rows put()
35
- // back on finish (merged rows' KV is not extracted — their entries age out).
38
+ // back zero-copy on finish; merged rows with ≥256 prompt tokens are extracted
39
+ // and put back by BatchScheduler.#extractAndPut (see docs/design/batching.md).
36
40
 
37
41
  import { MlxArray } from "../mlx/array";
38
42
  import type { RuntimeModel } from "../model/factory";
39
- import { DiffusionGemmaModel } from "../model/diffusion-gemma";
40
- import {
41
- KVCache,
42
- RotatingKVCache,
43
- isBatchableCache,
44
- isPlainKvCache,
45
- isRotatingPlainCache,
46
- } from "../model/gemma4-base";
47
- import { SSMCache } from "../model/qwen3-delta";
48
- import { UniversalDenseModel } from "../model/universal/dense";
49
43
  import type { GenerateOptions, GenerateStats, TokenLogprobs } from "../generate";
50
44
  import type { KvScheme } from "../kv-scheme";
45
+ import { createRowSampling } from "../backends/mlx/row-sampling";
51
46
  import { makeStepSampler } from "../sampler";
52
- import { BatchScheduler, type RowPromptCache } from "./batch-scheduler";
53
- import { runtimeValue } from "../runtime-config";
47
+ import type { RowPromptCache } from "../backends/mlx/batch-group";
48
+ import { bindMlxGateway, type MlxGatewayBinding, type MlxBatchGroup } from "../backends/mlx/gateway-binding";
49
+ import type { RuntimeConfig } from "../runtime-config";
50
+ import type { ExecutionRequirements, ResolvedExecution } from "../contracts/execution";
54
51
  import type { PromptResponseTrace } from "./prompt-response-trace";
55
52
 
56
53
  /** Async mutex: acquire() resolves to a release fn; releases run FIFO. */
@@ -61,18 +58,38 @@ class AsyncMutex {
61
58
  get locked(): boolean {
62
59
  return this.#pending > 0;
63
60
  }
64
- acquire(): Promise<() => void> {
61
+ acquire(signal?: AbortSignal): Promise<() => void> {
62
+ if (signal?.aborted) return Promise.reject(signal.reason);
65
63
  this.#pending++;
66
64
  let release!: () => void;
67
65
  const gate = new Promise<void>((r) => { release = r; });
68
66
  const wait = this.#tail;
69
67
  this.#tail = this.#tail.then(() => gate);
70
68
  let released = false;
71
- return wait.then(() => () => {
69
+ const unlock = () => {
72
70
  if (released) return; // idempotent: a double release must not skew #pending
73
71
  released = true;
74
72
  this.#pending--;
75
73
  release();
74
+ };
75
+ return new Promise((resolve, reject) => {
76
+ let settled = false;
77
+ const abort = () => {
78
+ if (settled) return;
79
+ settled = true;
80
+ signal?.removeEventListener("abort", abort);
81
+ // Release this waiter's gate, not its predecessor's. Later acquirers
82
+ // still wait for the active holder, preserving mutual exclusion.
83
+ unlock();
84
+ reject(signal!.reason);
85
+ };
86
+ signal?.addEventListener("abort", abort, { once: true });
87
+ wait.then(() => {
88
+ if (settled) return;
89
+ settled = true;
90
+ signal?.removeEventListener("abort", abort);
91
+ resolve(unlock);
92
+ });
76
93
  });
77
94
  }
78
95
  }
@@ -85,7 +102,7 @@ export type Vision = {
85
102
  embeddings: MlxArray;
86
103
  /** bool [L] image-token mask for the bidirectional attention overlay.
87
104
  * Absent when the prompt carries ANY audio — audio(-containing) prompts
88
- * run fully causal (audio-input-plan.md §3.3 Q1). */
105
+ * run fully causal (docs/design/generic-model-support.md §3.3 Q1). */
89
106
  imageMask?: MlxArray;
90
107
  /** bool [L] union multimodal soft-token mask (image | audio) for
91
108
  * per-layer-input id zeroing. Absent on the legacy vision-only shape,
@@ -97,7 +114,7 @@ export type Vision = {
97
114
  };
98
115
 
99
116
  /** Per-token sink: returning `false` halts this generation (stop sequence).
100
- * `logprobs` is only populated on the serial lane when the request asked for
117
+ * `logprobs` is populated on either lane when the request asked for
101
118
  * logprobs capture (GenerateOptions.logprobs / topLogprobs). */
102
119
  export type OnToken = (
103
120
  token: number,
@@ -112,53 +129,11 @@ export type SerialRun = (
112
129
  onToken: OnToken,
113
130
  vision?: Vision,
114
131
  trace?: PromptResponseTrace,
132
+ execution?: ResolvedExecution,
115
133
  ) => Promise<GenerateStats>;
116
134
 
117
135
  /** What the batchable decision needs from a request (cheap to compute). */
118
- export interface RequestShape {
119
- readonly hasVision: boolean;
120
- readonly hasAdapters: boolean;
121
- readonly hasRepetitionPenalty: boolean;
122
- /** The user explicitly set `seed` (reproducibility) — not the random default. */
123
- readonly userSeed: boolean;
124
- /** KV quantization is active. Per-layer configs may use the continuous
125
- * scheduler; uniform schemes use the serial mechanism. */
126
- readonly kvQuant: boolean;
127
- /** TurboQuant is active (docs/design/turboquant-kv.md). Solo-only in v1:
128
- * TurboQuantKVCache is a novel Cache implementation (not a KVCache/
129
- * RotatingKVCache subclass), so #modelCachesBatchable() already excludes
130
- * it automatically once a request's cache is converted — this flag is the
131
- * BELT on top of that automatic BRACES, an explicit refusal at the
132
- * request-shape level so a turbo request never reaches the scheduler even
133
- * before any cache conversion has happened. Both layers exist on purpose. */
134
- readonly turboQuant: boolean;
135
- /** Any of the mlx-lm sampler/processor extensions is active: min_p, XTC,
136
- * logit_bias, presence/frequency penalty. INFORMATIONAL ONLY: since the
137
- * continuous scheduler grew per-row samplers/logits processors, these all
138
- * compose. Placement deliberately does not gate on this field (see the
139
- * repetition-penalty note in #supportsContinuous). Kept so /stats and
140
- * scheduling traces can show what
141
- * a request carries. */
142
- readonly hasLogitsExtras: boolean;
143
- /** A grammar controller compiled for this request (response_format /
144
- * guided_*). Degrade-path requests (compile failed → prompt injection)
145
- * have NO controller and stay batchable — the injection already happened
146
- * at the prompt level. B0 routes grammar to serial; B1 makes it batchable
147
- * (per-row matchers), with MLX_BUN_GRAMMAR_BATCH=0 forcing the B0 serial
148
- * fallback as the A/B + kill switch. */
149
- readonly hasGrammar: boolean;
150
- /** The request asked for logprobs/top_logprobs capture. Batch-lane logprobs
151
- * are deferred (the scheduler's per-row sampler doesn't capture or read
152
- * back logprob arrays yet), so these route to the serial lane like the
153
- * other mlx-lm request extensions above. */
154
- readonly wantsLogprobs: boolean;
155
- /** A draft model is configured (serve --draft-model). Server-level and
156
- * upstream-parity: mlx_lm.server sets is_batchable = (draft is None), so
157
- * every request routes serial while a draft is mounted — speculation is a
158
- * B=1 latency optimization, batching a throughput one; they are different
159
- * mechanisms by design (grammar-spec-batching-integration.md). */
160
- readonly hasDraft: boolean;
161
- }
136
+ export interface RequestShape extends ExecutionRequirements {}
162
137
 
163
138
  export type GenerationMechanism = "serial" | "continuous";
164
139
 
@@ -167,12 +142,22 @@ export type GenerationMechanism = "serial" | "continuous";
167
142
  export interface GenerationPlacement {
168
143
  readonly shape: RequestShape;
169
144
  readonly mechanism: GenerationMechanism;
145
+ readonly execution?: ResolvedExecution;
146
+ }
147
+
148
+ export function disposeUnstartedRequest(options: GenerateOptions, vision?: Vision): void {
149
+ disposeResources([options.grammar, vision?.embeddings, vision?.imageMask,
150
+ vision?.multimodalMask, options.visionPixels].filter((resource) => resource != null));
170
151
  }
171
152
 
172
153
  export class GenerationGateway {
154
+ #closed = false;
155
+ readonly #runtime: RuntimeConfig;
156
+ readonly #binding: MlxGatewayBinding;
157
+ readonly #requests: AdmissionPool;
173
158
  readonly #mutex = new AsyncMutex();
174
159
  readonly #batch: number;
175
- #scheduler: BatchScheduler | null = null;
160
+ #scheduler: MlxBatchGroup | null = null;
176
161
  #rowsSubmitted = 0;
177
162
  /** Serial-lane requests waiting for / holding the mutex. While > 0 the
178
163
  * scheduler pauses admission (drain) so they can't be starved. */
@@ -182,11 +167,13 @@ export class GenerationGateway {
182
167
  /** Lazy, memoized: can the configured KV scheme convert every named cache? */
183
168
  #kvSchemeBatchable: boolean | null = null;
184
169
  constructor(
185
- private readonly model: RuntimeModel,
170
+ model: RuntimeModel | MlxGatewayBinding,
186
171
  batch: number,
187
172
  private readonly serialRun: SerialRun,
188
173
  private readonly opts: {
189
174
  kvBudgetBytes?: number;
175
+ checkpoints?: boolean;
176
+ stateCodecs?: import("../kv-store").CacheCodecProvider;
190
177
  /** The server-wide KV scheme (server.ts kvScheme) — threaded to the
191
178
  * scheduler at construction when batchable (Phase 3.1). turboQuant is
192
179
  * never threaded to the scheduler (always solo-only — see placement);
@@ -197,9 +184,14 @@ export class GenerationGateway {
197
184
  * back on finish. Safe to share with the serial lane — both use it
198
185
  * only inside this gateway's mutual-exclusion domain. */
199
186
  promptCache?: RowPromptCache;
187
+ adapterNamespace?: (adapters: string[]) => string;
200
188
  } = {},
201
189
  ) {
190
+ this.#binding = "createBatchGroup" in model ? model : bindMlxGateway(model);
191
+ this.#runtime = this.#binding.runtime;
192
+ this.opts = Object.freeze({ ...opts });
202
193
  this.#batch = Math.max(1, Math.floor(batch));
194
+ this.#requests = new AdmissionPool(this.#batch);
203
195
  }
204
196
 
205
197
  /** True if `--batch N` (N>1) is on (batchability is then per-request). */
@@ -229,19 +221,31 @@ export class GenerationGateway {
229
221
  * running/pending. activeRows/pendingRows alone MISS the serial lane —
230
222
  * a serial generation holds the mutex but shows zero rows. */
231
223
  get busy(): boolean {
232
- return this.#mutex.locked || this.activeRows > 0 || this.pendingRows > 0;
224
+ return this.#requests.active > 0 || this.#requests.queued > 0 ||
225
+ this.#mutex.locked || this.activeRows > 0 || this.pendingRows > 0;
233
226
  }
234
227
 
235
- /** Resolves once the engine is idle (poll-based, ~`pollMs` granularity —
236
- * cheap vs the multi-hundred-ms flushes it paces; resolves without a
237
- * timer when already idle). The SSD write-behind gates each per-tensor
238
- * flush step on this so durability work never steals blocking
239
- * GPU-sync + writeSync slices from an active decode (the 2026-07-07
240
- * decode@ctx contamination — see server.ts's write-behind block). */
228
+ async close(): Promise<void> {
229
+ this.#closed = true;
230
+ this.#requests.close();
231
+ await this.#scheduler?.close();
232
+ await this.onIdle();
233
+ }
234
+
235
+ /** Observe idleness. Call runWhenIdle for background work that must also
236
+ * acquire ownership atomically with the idle check. */
241
237
  async onIdle(pollMs = 20): Promise<void> {
242
238
  while (this.busy) await new Promise<void>((r) => setTimeout(r, pollMs));
243
239
  }
244
240
 
241
+ /** Background work waits without requesting a batch drain. The final busy
242
+ * check and mutex acquisition have no await between them, so a foreground
243
+ * request cannot enter between the check and ownership registration. */
244
+ async runWhenIdle<T>(fn: () => Promise<T>, pollMs = 20): Promise<T> {
245
+ while (this.busy) await new Promise<void>(resolve => setTimeout(resolve, pollMs));
246
+ return this.runExclusive(fn);
247
+ }
248
+
245
249
  /** Cumulative rows routed to the batch lane since server start. The serial
246
250
  * lane never advances this — /stats' race-free lane-routing observable
247
251
  * (active/pending are instantaneous and read 0 once a request finishes). */
@@ -257,113 +261,22 @@ export class GenerationGateway {
257
261
  };
258
262
  }
259
263
 
260
- /** Cache-capability gate (mirrors mlx-lm server.py's all-caches-have-merge
261
- * check): the scheduler's dynamic-B ops (temporalView/merge/filter) exist
262
- * on KVCache, RotatingKVCache, and SSMCache (hybrid gated-DeltaNet models
263
- * — Qwen3.5 — batch via SSMCache.mergeRows/filter; MLX_BUN_BATCH_SSM=0 is
264
- * the kill switch back to serial routing). */
265
264
  #modelCachesBatchable(): boolean {
266
- if (this.#cacheBatchable === null) {
267
- // Tier-0 universal models: batchable for PLAIN full-attention archs
268
- // only. The 2026-07-03 uneven-row bug (scalar-offset RoPE decoded
269
- // padded rows at wrong positions) is fixed via
270
- // UniversalRope.applyDynamic + ropeOffsetArr and GATED token-exact vs
271
- // mlx-lm B=2 on Llama-3.2-3B (static + dynamic join/leave,
272
- // tests/batched-decode-parity.test.ts "Llama 3B Tier-0"). The
273
- // maskArray archs (gemma2-family: forwardLayers builds a pad-blind
274
- // causal mask) and sliding-window universal archs remain UNVALIDATED
275
- // cells → serial, per the per-model-cell discipline.
276
- if (this.model instanceof UniversalDenseModel) {
277
- const a = this.model.args;
278
- this.#cacheBatchable =
279
- !a.maskArray && !a.layerTypes?.includes("sliding_attention");
280
- return this.#cacheBatchable;
281
- }
282
- const ssmOk = runtimeValue("MLX_BUN_BATCH_SSM") !== "0";
283
- const proto = this.model.makeCache(); // fresh caches hold no buffers
284
- this.#cacheBatchable = proto.every(
285
- (c) =>
286
- c instanceof KVCache ||
287
- c instanceof RotatingKVCache ||
288
- isBatchableCache(c) ||
289
- (ssmOk && c instanceof SSMCache),
290
- );
291
- for (const c of proto) c.dispose();
292
- }
293
- return this.#cacheBatchable;
265
+ return this.#cacheBatchable ??= this.#binding.cachesBatchable();
294
266
  }
295
267
 
296
- /** Phase 3.1 + milestone 2: the server's kv scheme batches iff it is a
297
- * per-layer kvConfig (the L2 mixed scheme) whose every configured layer
298
- * is a full-attention KVCache (BatchedQuantDecodeMaskCache + quantized
299
- * merge/extend/filter) or a rotating RotatingKVCache
300
- * (BatchedRotatingQuantCache — milestone 2, gemma's kv_config). Uniform
301
- * kvBits (quantizedKvStart threshold semantics via the serial no-byLayer
302
- * path) and configs naming SSM layers stay serial. */
303
268
  #kvBatchable(): boolean {
304
- if (this.#kvSchemeBatchable === null) {
305
- const scheme = this.opts.kvScheme;
306
- if (scheme?.kind !== "affine-config") {
307
- this.#kvSchemeBatchable = false;
308
- } else {
309
- const proto = this.model.makeCache();
310
- try {
311
- this.#kvSchemeBatchable = scheme.batchable(
312
- this.model.config,
313
- (layerIdx) =>
314
- isPlainKvCache(proto[layerIdx]) || isRotatingPlainCache(proto[layerIdx]),
315
- );
316
- } finally {
317
- for (const cache of proto) cache.dispose();
318
- }
319
- }
320
- }
321
- return this.#kvSchemeBatchable;
269
+ return this.#kvSchemeBatchable ??= !!this.opts.kvScheme && this.#binding.kvBatchable(this.opts.kvScheme);
322
270
  }
323
271
 
324
- /** Whether the declared execution composition is implemented by the
325
- * continuous scheduler. This is a support check only: it never rewrites
326
- * MTP/drafting, KV, TurboQuant, grammar, adapters, or sampling. */
327
- #supportsContinuous(shape: RequestShape): boolean {
328
- // DiffusionGemma is non-autoregressive — the batch scheduler assumes the AR
329
- // KV-cache decode path, so it always runs serially through generate().
330
- if (this.model instanceof DiffusionGemmaModel) return false;
331
- if (!this.batchingEnabled) return false;
332
- if (!this.#modelCachesBatchable()) return false;
333
- // repetitionPenalty / logits extras (min_p, XTC, logit_bias,
334
- // presence/frequency penalties) batch through the same StepSampler and
335
- // per-row device history as the serial lane. (Load-bearing beyond opt-in
336
- // knobs: some models — Qwen3.5 — ship a default repetition penalty in
337
- // generation_config.json, which used to route EVERY request serial.)
338
- return (
339
- !shape.hasVision &&
340
- !shape.hasAdapters &&
341
- !shape.wantsLogprobs &&
342
- !shape.userSeed &&
343
- // Phase 3.1: a kv-quant request batches when the server's scheme is
344
- // the batchable kvConfig composition (the scheduler applies it);
345
- // otherwise it routes serial exactly as before.
346
- !(shape.kvQuant && !this.#kvBatchable()) &&
347
- // TurboQuant: solo-only in v1, unconditionally — never partially
348
- // batchable like kvConfig. The automatic instanceof exclusion in
349
- // #modelCachesBatchable() already covers this once a cache converts;
350
- // this is the explicit belt-and-braces refusal at request-shape time
351
- // (mirrors the kvQuant pattern above — both layers exist on purpose).
352
- !shape.turboQuant &&
353
- !shape.hasDraft &&
354
- // Grammar: B1 makes it batchable (per-row matchers) unless the kill
355
- // switch forces serial. MLX_BUN_GRAMMAR_BATCH=0 = B0 behavior (serial),
356
- // the A/B + kill lever for the new code, house style.
357
- !(shape.hasGrammar && runtimeValue("MLX_BUN_GRAMMAR_BATCH") === "0")
358
- );
359
- }
360
-
361
- place(shape: RequestShape): GenerationPlacement {
272
+ place(shape: RequestShape, options: GenerateOptions = {}): GenerationPlacement {
362
273
  const frozenShape = Object.freeze(shape);
363
- return Object.freeze({
364
- shape: frozenShape,
365
- mechanism: this.#supportsContinuous(frozenShape) ? "continuous" : "serial",
274
+ const execution = this.#binding.plan(frozenShape, options, {
275
+ continuous: this.batchingEnabled,
276
+ quantizedBatch: (shape.kvQuant || shape.turboQuant) && this.#kvBatchable(),
277
+ checkpoints: this.opts.checkpoints === true,
366
278
  });
279
+ return Object.freeze({ shape: frozenShape, mechanism: execution.mechanism, execution });
367
280
  }
368
281
 
369
282
  /** Run `fn` with exclusive ownership of the GPU + shared model state (the
@@ -372,26 +285,37 @@ export class GenerationGateway {
372
285
  * mount/unmount all come through here, so nothing runs concurrently with
373
286
  * batched decode steps (or with each other). Registers as a serial waiter
374
287
  * so the scheduler drains (stops admitting) until `fn` has run. */
288
+ /** Shared by inference, preparation and managed GPU subprocesses. */
289
+ async acquireExecutionLease(
290
+ signal?: AbortSignal,
291
+ trace?: PromptResponseTrace,
292
+ ): Promise<import("../contracts/resources").DisposableResource> {
293
+ if (this.#closed) throw new Error("generation gateway is closed");
294
+ this.#serialWaiters++;
295
+ const closeAdmission = trace?.begin("engine.admission_wait", { mechanism: "serial" });
296
+ const resume = () => {
297
+ this.#serialWaiters--;
298
+ if (this.#serialWaiters === 0) this.#scheduler?.kick();
299
+ };
300
+ try {
301
+ const release = await this.#mutex.acquire(signal);
302
+ let released = false;
303
+ return { dispose: () => {
304
+ if (released) return;
305
+ released = true;
306
+ release(); resume();
307
+ } };
308
+ } catch (error) { resume(); throw error; }
309
+ finally { closeAdmission?.(); }
310
+ }
311
+
375
312
  async runExclusive<T>(
376
313
  fn: () => Promise<T>,
377
314
  trace?: PromptResponseTrace,
315
+ signal?: AbortSignal,
378
316
  ): Promise<T> {
379
- this.#serialWaiters++;
380
- const closeAdmission = trace?.begin("engine.admission_wait", {
381
- mechanism: "serial",
382
- });
383
- try {
384
- const release = await this.#mutex.acquire();
385
- closeAdmission?.();
386
- try {
387
- return await fn();
388
- } finally {
389
- release();
390
- }
391
- } finally {
392
- this.#serialWaiters--;
393
- if (this.#serialWaiters === 0) this.#scheduler?.kick(); // resume admission
394
- }
317
+ const lease = await this.acquireExecutionLease(signal, trace);
318
+ try { return await fn(); } finally { lease.dispose(); }
395
319
  }
396
320
 
397
321
  /** Run one generation on the appropriate lane. onToken is invoked per emitted
@@ -406,8 +330,26 @@ export class GenerationGateway {
406
330
  signal?: AbortSignal,
407
331
  trace?: PromptResponseTrace,
408
332
  ): Promise<GenerateStats> {
333
+ let reservation;
334
+ try { reservation = await acquireReservation(this.#requests, signal); }
335
+ catch (error) { cleanupFailure(error, () => disposeUnstartedRequest(options, vision)); }
336
+ try { return await this.#run(promptIds, options, onToken, vision, shape, placement, signal, trace); }
337
+ finally { reservation.dispose(); }
338
+ }
339
+
340
+ async #run(
341
+ promptIds: number[],
342
+ options: GenerateOptions & { stopSequences?: string[] },
343
+ onToken: OnToken,
344
+ vision: Vision | undefined,
345
+ shape: RequestShape,
346
+ placement: GenerationPlacement,
347
+ signal?: AbortSignal,
348
+ trace?: PromptResponseTrace,
349
+ ): Promise<GenerateStats> {
350
+ const disposeUnstarted = () => disposeUnstartedRequest(options, vision);
409
351
  if (placement.shape !== shape) {
410
- options.grammar?.dispose();
352
+ disposeUnstarted();
411
353
  throw new Error("generation placement does not belong to this request shape");
412
354
  }
413
355
  try {
@@ -415,7 +357,7 @@ export class GenerationGateway {
415
357
  } catch (e) {
416
358
  // The gateway is the first component that accepts ownership of a
417
359
  // compiled controller. An already-aborted request reaches neither lane.
418
- options.grammar?.dispose();
360
+ disposeUnstarted();
419
361
  throw e;
420
362
  }
421
363
  if (placement.mechanism === "serial") {
@@ -430,40 +372,59 @@ export class GenerationGateway {
430
372
  let lastHop = performance.now();
431
373
  const hop = (): Promise<void> => new Promise<void>((r) => setImmediate(r));
432
374
  const hoppingOnToken: OnToken = (token, lp) => {
433
- if (signal?.aborted) return false;
375
+ signal?.throwIfAborted();
434
376
  const r = onToken(token, lp);
435
377
  if (r === false) return false;
436
378
  if (performance.now() - lastHop < 25) return r;
437
379
  lastHop = performance.now();
438
380
  if (r instanceof Promise)
439
- return r.then((v) =>
440
- v === false || signal?.aborted ? false : hop().then(() => signal?.aborted ? false : v),
441
- );
442
- return hop().then(() => signal?.aborted ? false : r);
381
+ return r.then(async (v) => {
382
+ if (v === false) return false;
383
+ signal?.throwIfAborted();
384
+ await hop();
385
+ signal?.throwIfAborted();
386
+ return v;
387
+ });
388
+ return hop().then(() => {
389
+ signal?.throwIfAborted();
390
+ return r;
391
+ });
443
392
  };
393
+ let started = false;
444
394
  return this.runExclusive(async () => {
445
395
  signal?.throwIfAborted();
396
+ started = true;
446
397
  const stats = await this.serialRun(
447
- promptIds, options, hoppingOnToken, vision, trace,
398
+ promptIds, signal ? { ...options, signal } : options, hoppingOnToken, vision, trace, placement.execution,
448
399
  );
449
400
  signal?.throwIfAborted();
450
401
  return stats;
451
- }, trace)
402
+ }, trace, signal)
452
403
  // Covers a serial waiter aborted before serialRun takes ownership.
453
404
  // generate() also disposes defensively; the operation is idempotent.
454
- .finally(() => options.grammar?.dispose());
405
+ .finally(() => {
406
+ if (started) options.grammar?.dispose();
407
+ else disposeUnstarted();
408
+ });
455
409
  }
456
410
 
411
+ const adapters = options.adapters?.length ? [...options.adapters] : undefined;
412
+ const adapterKey = adapters ? JSON.stringify(adapters) : "";
413
+ const cacheNamespace = adapters
414
+ ? () => this.opts.adapterNamespace?.(adapters) ?? adapterKey : "";
415
+ const context = adapters
416
+ ? this.#binding.bindAdapterContext?.(adapters, `adapters:${adapterKey}`) : undefined;
417
+
457
418
  // The scheduler owns grammar ready/accept sequencing. StepSampler owns the
458
419
  // shared processors -> mask -> logprobs -> sample -> history contract.
459
- const stepSampler = makeStepSampler(options, {
460
- tokenRepresentation: "device",
461
- grammarWait: "external",
462
- historyUpdate: "after-sample",
463
- initialHistory: promptIds,
464
- });
465
- const sample = (logits1V: MlxArray, step: number): MlxArray =>
466
- stepSampler.sample(logits1V, step).token;
420
+ const method = this.#binding.methodRequest?.(placement.execution, options);
421
+ const continuation = method ? undefined : this.#binding.continuationRequest?.(placement.execution, options, promptIds, onToken);
422
+ const sampling = method ? undefined : continuation ?? createRowSampling(makeStepSampler(options, {
423
+ tokenRepresentation: "device", grammarWait: "external",
424
+ historyUpdate: "after-sample", initialHistory: promptIds,
425
+ captureSelectedLogprob: options.logprobs === true,
426
+ captureTopLogprobs: options.topLogprobs,
427
+ }), onToken);
467
428
 
468
429
  let st;
469
430
  this.#rowsSubmitted++;
@@ -472,12 +433,14 @@ export class GenerationGateway {
472
433
  });
473
434
  try {
474
435
  st = await this.#ensureScheduler().submit({
475
- promptIds,
436
+ promptIds, context, cacheNamespace, prefillChunkSize: options.prefillChunkSize,
437
+ continuation: continuation?.continuation,
438
+ statePolicy: this.#binding.statePolicy?.(placement.execution, options, promptIds.length + (options.maxTokens ?? 512)),
439
+ compiledDecode: placement.execution?.compiledDecode,
476
440
  maxTokens: options.maxTokens ?? 512,
477
- eosTokenIds: options.eosTokenIds ?? this.model.config.eosTokenIds,
478
- sample,
479
- plainGreedy: stepSampler.isPlainGreedy,
480
- onToken,
441
+ eosTokenIds: options.eosTokenIds ?? this.#binding.config.eosTokenIds,
442
+ ...(method ? { method, onToken } : { sample: sampling!.sample,
443
+ plainGreedy: sampling!.plainGreedy, onToken: sampling!.onToken }),
481
444
  onAdmitted: closeAdmission,
482
445
  trace,
483
446
  ...(signal ? { signal } : {}),
@@ -491,7 +454,7 @@ export class GenerationGateway {
491
454
  });
492
455
  } finally {
493
456
  closeAdmission?.();
494
- stepSampler.dispose();
457
+ sampling?.dispose();
495
458
  // The scheduler never owns per-row grammar state.
496
459
  options.grammar?.dispose();
497
460
  }
@@ -500,15 +463,22 @@ export class GenerationGateway {
500
463
  promptTokens: st.promptTokens,
501
464
  cachedTokens: st.cachedTokens,
502
465
  generatedTokens: st.generatedTokens,
503
- prefillMs: 0, decodeMs: 0, prefillTps: 0, decodeTps: 0,
466
+ finishReason: st.finishReason,
467
+ prefillMs: st.prefillMs,
468
+ decodeMs: st.decodeMs,
469
+ prefillTps: st.prefillMs > 0 ? ((st.promptTokens - st.cachedTokens) / st.prefillMs) * 1000 : 0,
470
+ decodeTps: st.decodeMs > 0 && st.generatedTokens > 1 ? ((st.generatedTokens - 1) / st.decodeMs) * 1000 : 0,
504
471
  cacheTokens: [],
472
+ ...(st.spec ? { spec: st.spec } : {}),
505
473
  };
506
474
  }
507
475
 
508
- #ensureScheduler(): BatchScheduler {
476
+ #ensureScheduler(): MlxBatchGroup {
509
477
  if (!this.#scheduler)
510
- this.#scheduler = new BatchScheduler(this.model, {
478
+ this.#scheduler = this.#binding.createBatchGroup({
479
+ runtime: this.#runtime,
511
480
  maxBatch: this.#batch,
481
+ stateCodecs: this.opts.stateCodecs,
512
482
  kvBudgetBytes: this.opts.kvBudgetBytes,
513
483
  // Phase 3.1: the batchable kvConfig composition is applied by the
514
484
  // scheduler (solo rows convert at serial chunk boundaries, then