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,4 +1,11 @@
1
- // Runtime isolation — the parent half (docs/design/runtime-isolation.md).
1
+ import { ExecutionCoordinator } from "../engine/execution-coordinator";
2
+ import { AdmissionPool, AdmissionRejected } from "../engine/admission";
3
+ import { acquireReservation } from "./preparation";
4
+ import { createParentApplication } from "./parent-application";
5
+ import type { DisposableResource } from "../contracts/resources";
6
+ import { retainResponseLease } from "./response-lease";
7
+ import type { EngineHost } from "../contracts/host";
8
+ // Runtime isolation — the parent half (docs/reference/server-config.md).
2
9
  //
3
10
  // ARCHITECTURE (decision 2026-07-05, deviating from the doc's original
4
11
  // structured-IPC sketch — recorded there): the inference ENGINE is the
@@ -33,11 +40,17 @@ import { join } from "node:path";
33
40
 
34
41
  /** How to (re)start the engine child. `argv[0]` is the executable. */
35
42
  export interface EngineSpec {
43
+ /** Parent-owned activity lease; covers startup and complete response bodies. */
44
+ acquire?: (signal: AbortSignal) => Promise<DisposableResource>;
36
45
  argv: string[];
37
46
  socketPath: string;
38
47
  /** Max ms to wait for the child's /health after spawn (weights load —
39
48
  * large models take a while). Default 15 min. */
40
49
  readyTimeoutMs?: number;
50
+ /** Bound automatic restarts in a rolling window, including startup failure. */
51
+ maxRestarts?: number;
52
+ restartWindowMs?: number;
53
+ restartDelayMs?: number;
41
54
  }
42
55
 
43
56
  const HOP_BY_HOP = new Set([
@@ -50,17 +63,23 @@ export function defaultSocketPath(): string {
50
63
  }
51
64
 
52
65
  /** The persistent engine child: spawn, health-gate, respawn-on-crash. */
53
- export class EngineChild {
66
+ export class EngineChild implements EngineHost<Request, Response> {
54
67
  readonly spec: EngineSpec;
55
68
  #proc: ReturnType<typeof Bun.spawn> | null = null;
56
69
  #ready: Promise<void>;
57
70
  #stopping = false;
71
+ #lifetime = new AbortController();
58
72
  restarts = 0;
59
73
  #lastSpawnAt = 0;
74
+ #restartTimes: number[] = [];
75
+ #backoff: ReturnType<typeof setTimeout> | undefined;
76
+ #wakeBackoff: (() => void) | undefined;
77
+ #startups = new Set<Promise<void>>();
60
78
 
61
79
  constructor(spec: EngineSpec) {
62
- this.spec = spec;
80
+ this.spec = Object.freeze({ ...spec, argv: [...spec.argv] });
63
81
  this.#ready = this.#spawn();
82
+ void this.#ready.catch(() => {}); // failures remain observable through ready/forward
64
83
  }
65
84
 
66
85
  get ready(): Promise<void> {
@@ -71,38 +90,78 @@ export class EngineChild {
71
90
  return this.#proc?.pid ?? null;
72
91
  }
73
92
 
74
- async #spawn(): Promise<void> {
75
- this.#lastSpawnAt = Date.now();
76
- try { unlinkSync(this.spec.socketPath); } catch {}
77
- const proc = Bun.spawn(this.spec.argv, {
78
- stdio: ["ignore", "inherit", "inherit"], // load progress → user's terminal
79
- env: process.env,
80
- });
81
- this.#proc = proc;
82
- void proc.exited.then((code) => {
83
- if (this.#stopping || this.#proc !== proc) return;
84
- console.error(`[isolate] engine exited (code ${code}) — respawning`);
85
- this.restarts++;
86
- // Crash-loop backoff: an engine that dies within 10 s of spawning
87
- // (bad flags, OOM on load) waits 5 s before the retry.
88
- const delay = Date.now() - this.#lastSpawnAt < 10_000 ? 5_000 : 0;
89
- this.#ready = new Promise((r) => setTimeout(r, delay)).then(() => this.#spawn());
90
- });
91
- // Health-gate: poll the child's /health over the socket until it
92
- // answers — model load happens behind this.
93
- const deadline = Date.now() + (this.spec.readyTimeoutMs ?? 15 * 60_000);
94
- while (Date.now() < deadline) {
95
- if (proc.killed || this.#proc !== proc) throw new Error("engine died during startup");
96
- try {
97
- const r = await fetch("http://engine/health", {
98
- unix: this.spec.socketPath,
99
- signal: AbortSignal.timeout(2_000),
100
- } as RequestInit & { unix: string });
101
- if (r.ok) return;
102
- } catch { /* not up yet */ }
103
- await new Promise((r) => setTimeout(r, 250));
93
+ #spawn(): Promise<void> {
94
+ const work = this.#start();
95
+ this.#startups.add(work);
96
+ void work.finally(() => this.#startups.delete(work)).catch(() => {});
97
+ return work;
98
+ }
99
+
100
+ async #start(): Promise<void> {
101
+ if (this.#stopping) throw new Error("engine host is closed");
102
+ const lease = await this.spec.acquire?.(this.#lifetime.signal);
103
+ let starting: ReturnType<typeof Bun.spawn> | undefined;
104
+ let healthy = false;
105
+ try {
106
+ if (this.#stopping) throw new Error("engine host is closed");
107
+ this.#lastSpawnAt = Date.now();
108
+ try { unlinkSync(this.spec.socketPath); } catch {}
109
+ const proc = Bun.spawn(this.spec.argv, {
110
+ stdio: ["ignore", "inherit", "inherit"], // load progress → user's terminal
111
+ env: process.env,
112
+ });
113
+ this.#proc = proc;
114
+ starting = proc;
115
+ void proc.exited.then((code) => {
116
+ if (this.#stopping || this.#proc !== proc) return;
117
+ const now = Date.now();
118
+ const window = this.spec.restartWindowMs ?? 60_000;
119
+ this.#restartTimes = this.#restartTimes.filter((time) => now - time < window);
120
+ if (this.#restartTimes.length >= (this.spec.maxRestarts ?? 3)) {
121
+ this.#ready = Promise.reject(new Error(`engine restart limit reached after exit ${code}`));
122
+ void this.#ready.catch(() => {});
123
+ return;
124
+ }
125
+ this.#restartTimes.push(now);
126
+ console.error(`[isolate] engine exited (code ${code}) — respawning`);
127
+ this.restarts++;
128
+ // Crash-loop backoff: an engine that dies within 10 s of spawning
129
+ // (bad flags, OOM on load) waits 5 s before the retry.
130
+ const delay = this.spec.restartDelayMs ?? (Date.now() - this.#lastSpawnAt < 10_000 ? 5_000 : 0);
131
+ this.#ready = new Promise<void>((resolve) => {
132
+ this.#wakeBackoff = resolve;
133
+ this.#backoff = setTimeout(resolve, delay);
134
+ }).then(() => {
135
+ this.#backoff = undefined; this.#wakeBackoff = undefined;
136
+ return this.#spawn();
137
+ });
138
+ void this.#ready.catch(() => {});
139
+ });
140
+ // Health-gate: poll the child's /health over the socket until it
141
+ // answers — model load happens behind this.
142
+ const deadline = Date.now() + (this.spec.readyTimeoutMs ?? 15 * 60_000);
143
+ while (Date.now() < deadline) {
144
+ if (this.#stopping || proc.killed || proc.exitCode !== null || this.#proc !== proc)
145
+ throw new Error("engine died during startup");
146
+ try {
147
+ const r = await fetch("http://engine/health", {
148
+ unix: this.spec.socketPath,
149
+ signal: AbortSignal.timeout(2_000),
150
+ } as RequestInit & { unix: string });
151
+ if (r.ok) { healthy = true; return; }
152
+ } catch { /* not up yet */ }
153
+ await new Promise((r) => setTimeout(r, 250));
154
+ }
155
+ proc.kill();
156
+ throw new Error("engine did not become healthy in time");
157
+ } finally {
158
+ if (!healthy && starting && starting.exitCode === null) {
159
+ starting.kill();
160
+ const force = setTimeout(() => { if (starting!.exitCode === null) starting!.kill("SIGKILL"); }, 3000);
161
+ try { await starting.exited; } finally { clearTimeout(force); }
162
+ }
163
+ lease?.dispose();
104
164
  }
105
- throw new Error("engine did not become healthy in time");
106
165
  }
107
166
 
108
167
  /** Forward one request to the child; the response streams through.
@@ -124,33 +183,81 @@ export class EngineChild {
124
183
  }
125
184
 
126
185
  async #forwardOnce(request: Request): Promise<Response> {
127
- await this.#ready;
128
- const headers = new Headers();
129
- request.headers.forEach((v, k) => {
130
- if (!HOP_BY_HOP.has(k.toLowerCase())) headers.set(k, v);
186
+ if (this.#stopping) throw new Error("engine host is closed");
187
+ request.signal.throwIfAborted();
188
+ await new Promise<void>((resolve, reject) => {
189
+ const abort = () => { request.signal.removeEventListener("abort", abort); reject(request.signal.reason); };
190
+ request.signal.addEventListener("abort", abort, { once: true });
191
+ this.#ready.then(() => { request.signal.removeEventListener("abort", abort); resolve(); },
192
+ (error) => { request.signal.removeEventListener("abort", abort); reject(error); });
131
193
  });
132
- const proxied = await fetch(request.url, {
133
- method: request.method,
134
- headers,
135
- body: request.body,
136
- signal: request.signal, // client abort → child sees the disconnect
137
- redirect: "manual",
138
- unix: this.spec.socketPath,
139
- // Streaming request bodies need half-duplex; buffered bodies ignore it.
140
- duplex: "half",
141
- } as RequestInit & { unix: string; duplex: string });
142
- const outHeaders = new Headers();
143
- proxied.headers.forEach((v, k) => {
144
- if (!HOP_BY_HOP.has(k.toLowerCase())) outHeaders.set(k, v);
145
- });
146
- return new Response(proxied.body, { status: proxied.status, headers: outHeaders });
194
+ if (this.#stopping) throw new Error("engine host is closed");
195
+ request.signal.throwIfAborted();
196
+ const signal = AbortSignal.any([request.signal, this.#lifetime.signal]);
197
+ const lease = await this.spec.acquire?.(signal);
198
+ try {
199
+ signal.throwIfAborted();
200
+ const headers = new Headers();
201
+ request.headers.forEach((v, k) => {
202
+ if (!HOP_BY_HOP.has(k.toLowerCase())) headers.set(k, v);
203
+ });
204
+ const proxied = await fetch(request.url, {
205
+ method: request.method,
206
+ headers,
207
+ body: request.body,
208
+ signal, // client abort → child sees the disconnect
209
+ redirect: "manual",
210
+ unix: this.spec.socketPath,
211
+ // Streaming request bodies need half-duplex; buffered bodies ignore it.
212
+ duplex: "half",
213
+ } as RequestInit & { unix: string; duplex: string });
214
+ const outHeaders = new Headers();
215
+ proxied.headers.forEach((v, k) => {
216
+ if (!HOP_BY_HOP.has(k.toLowerCase())) outHeaders.set(k, v);
217
+ });
218
+ const response = new Response(proxied.body, { status: proxied.status, headers: outHeaders });
219
+ return lease ? retainResponseLease(response, lease) : response;
220
+ } catch (error) { lease?.dispose(); throw error; }
221
+ }
222
+
223
+ /** A live UDS response owns the worker's native lease. Disconnect releases it
224
+ * even if the parent crashes. External cancellation applies to acquisition;
225
+ * the returned owner alone releases an admitted lease after job death. */
226
+ async reserveExecution(signal: AbortSignal): Promise<DisposableResource> {
227
+ if (!this.#proc || this.#proc.exitCode !== null) return { dispose() {} };
228
+ const abort = new AbortController();
229
+ const cancel = () => abort.abort(signal.reason);
230
+ signal.throwIfAborted();
231
+ signal.addEventListener("abort", cancel, { once: true });
232
+ try {
233
+ const response = await fetch("http://engine/admin/lease", {
234
+ method: "POST", unix: this.spec.socketPath, signal: abort.signal,
235
+ } as RequestInit & { unix: string });
236
+ if (!response.ok) { await response.body?.cancel(); throw new Error(`worker lease failed (${response.status})`); }
237
+ return { dispose() { abort.abort(); void response.body?.cancel().catch(() => {}); } };
238
+ } catch (error) { abort.abort(); throw error; }
239
+ finally { signal.removeEventListener("abort", cancel); }
147
240
  }
148
241
 
149
242
  stop(): void {
150
243
  this.#stopping = true;
244
+ this.#lifetime.abort(new Error("engine host is closed"));
245
+ clearTimeout(this.#backoff);
246
+ this.#wakeBackoff?.();
151
247
  this.#proc?.kill();
152
248
  try { unlinkSync(this.spec.socketPath); } catch {}
153
249
  }
250
+
251
+ async close(): Promise<void> {
252
+ this.stop();
253
+ const process = this.#proc;
254
+ const force = process ? setTimeout(() => { if (process.exitCode === null) process.kill("SIGKILL"); }, 3000) : undefined;
255
+ try {
256
+ // Startup owns an admission lease even before a child exists. Earlier
257
+ // crashed attempts can still be unwinding after #ready switches to a retry.
258
+ await Promise.allSettled([this.#ready, ...this.#startups, ...(process ? [process.exited] : [])]);
259
+ } finally { if (force) clearTimeout(force); }
260
+ }
154
261
  }
155
262
 
156
263
  /** Model resolution the parent injects (Registry-backed in production;
@@ -158,7 +265,7 @@ export class EngineChild {
158
265
  * drop-in ignore semantics (served by the default model, like mlx-lm). */
159
266
  export type ModelResolver = (query: string) => { repoId: string; path: string } | null;
160
267
 
161
- /** Child-per-model pool (P2, runtime-isolation.md): LRU residency over
268
+ /** Child-per-model pool (P2, docs/reference/server-config.md): LRU residency over
162
269
  * engine children. Eviction is graceful and lossless — POST /admin/drain
163
270
  * (gateway quiesce + demote the prompt cache to the SSD tier, keyed by
164
271
  * that model's own fingerprint dir) and only then stop the child; the
@@ -170,6 +277,9 @@ export class ModelPool {
170
277
  #children = new Map<string, EngineChild>(); // repoId → child
171
278
  #lru: string[] = []; // most-recent last
172
279
  #spawning = new Map<string, Promise<EngineChild>>();
280
+ #coldStarts = new AdmissionPool(1);
281
+ #starting = new Set<EngineChild>();
282
+ #stopping = false;
173
283
  readonly defaultKey: string;
174
284
 
175
285
  constructor(
@@ -210,40 +320,55 @@ export class ModelPool {
210
320
  /** Route a request's `model` field to a child, spawning on first use.
211
321
  * undefined/empty/unknown → the default child (drop-in semantics). */
212
322
  async childFor(modelField: string | undefined | null): Promise<EngineChild> {
213
- if (!modelField) return this.#use(this.defaultKey);
323
+ if (this.#stopping) throw new Error("model pool is closed");
324
+ modelField ||= this.defaultKey;
214
325
  // Already resident under this exact id?
215
326
  if (this.#children.has(modelField)) return this.#use(modelField);
216
327
  const rec = (() => {
217
328
  try { return this.opts.resolve(modelField); } catch { return null; }
218
329
  })();
219
- if (!rec) return this.#use(this.defaultKey); // unknown → ignore, like mlx-lm
220
- if (this.#children.has(rec.repoId)) return this.#use(rec.repoId);
221
- const inflight = this.#spawning.get(rec.repoId);
222
- if (inflight) return inflight.then(() => this.#use(rec.repoId));
330
+ if (!rec && modelField !== this.defaultKey) return this.childFor(this.defaultKey);
331
+ const resolved = rec ?? { repoId: this.defaultKey, path: "" };
332
+ if (this.#children.has(resolved.repoId)) return this.#use(resolved.repoId);
333
+ const inflight = this.#spawning.get(resolved.repoId);
334
+ if (inflight) return inflight.then(() => this.#use(resolved.repoId));
223
335
  const spawnP = (async () => {
224
- const sock = this.#socketFor(rec.repoId);
225
- const child = new EngineChild({
226
- argv: [...this.opts.selfArgv, ...engineArgvForModel(this.opts.rawArgs, sock, rec.path)],
336
+ const admission = await this.#coldStarts.acquire();
337
+ try {
338
+ if (this.#stopping) throw new Error("model pool is closed");
339
+ const sock = this.#socketFor(resolved.repoId);
340
+ const child = new EngineChild(resolved.repoId === this.defaultKey ? this.opts.defaultChild.spec : {
341
+ argv: [...this.opts.selfArgv, ...engineArgvForModel(this.opts.rawArgs, sock, resolved.path)],
227
342
  socketPath: sock,
343
+ acquire: this.opts.defaultChild.spec.acquire,
228
344
  });
229
- // SPAWN-OVERLAP: the old model keeps serving while this loads.
230
- await child.ready;
231
- this.#children.set(rec.repoId, child);
232
- this.#lru.push(rec.repoId);
233
- await this.#evictOverCap();
234
- return child;
345
+ this.#starting.add(child);
346
+ try {
347
+ await child.ready;
348
+ if (this.#stopping) throw new Error("model pool is closed");
349
+ this.#children.set(resolved.repoId, child);
350
+ this.#lru.push(resolved.repoId);
351
+ await this.#evictOverCap();
352
+ return child;
353
+ } catch (error) {
354
+ await child.close();
355
+ throw error;
356
+ } finally { this.#starting.delete(child); }
357
+ } finally { admission.dispose(); }
235
358
  })();
236
- this.#spawning.set(rec.repoId, spawnP);
359
+ this.#spawning.set(resolved.repoId, spawnP);
237
360
  try {
238
361
  return await spawnP;
239
362
  } finally {
240
- this.#spawning.delete(rec.repoId);
363
+ this.#spawning.delete(resolved.repoId);
241
364
  }
242
365
  }
243
366
 
244
367
  #use(key: string): EngineChild {
245
368
  this.#bump(key);
246
- return this.#children.get(key)!;
369
+ const child = this.#children.get(key);
370
+ if (!child) throw new Error(`model ${key} is no longer resident`);
371
+ return child;
247
372
  }
248
373
 
249
374
  /** Evict least-recently-used children over the cap: drain + demote (the
@@ -253,23 +378,38 @@ export class ModelPool {
253
378
  const victim = this.#lru.find((k) => this.#children.has(k));
254
379
  if (!victim || this.#children.size <= 1) return;
255
380
  const child = this.#children.get(victim)!;
256
- this.#children.delete(victim);
257
- this.#lru = this.#lru.filter((k) => k !== victim);
381
+ const lease = await child.spec.acquire?.(AbortSignal.timeout(120_000));
258
382
  try {
259
- await fetch("http://engine/admin/drain", {
260
- method: "POST",
261
- unix: child.spec.socketPath,
262
- signal: AbortSignal.timeout(120_000),
263
- } as RequestInit & { unix: string });
264
- } catch { /* best-effort — state also persists via evict-spill */ }
265
- child.stop();
266
- console.log(`[isolate] evicted engine ${victim} (pool cap ${this.opts.poolMax})`);
383
+ // Another overlapping eviction may have completed while we waited.
384
+ if (this.#children.get(victim) !== child) continue;
385
+ this.#children.delete(victim);
386
+ this.#lru = this.#lru.filter((k) => k !== victim);
387
+ try {
388
+ const response = await fetch("http://engine/admin/drain", {
389
+ method: "POST", unix: child.spec.socketPath,
390
+ signal: AbortSignal.timeout(120_000),
391
+ } as RequestInit & { unix: string });
392
+ await response.arrayBuffer();
393
+ } catch { /* best-effort — state also persists via evict-spill */ }
394
+ await child.close();
395
+ console.log(`[isolate] evicted engine ${victim} (pool cap ${this.opts.poolMax})`);
396
+ } finally { lease?.dispose(); }
267
397
  }
268
398
  }
269
399
 
270
400
  stopAll(): void {
271
- for (const c of this.#children.values()) c.stop();
401
+ this.#stopping = true;
402
+ this.#coldStarts.close();
403
+ for (const child of new Set([...this.#children.values(), ...this.#starting])) child.stop();
272
404
  this.#children.clear();
405
+ this.#lru = [];
406
+ }
407
+
408
+ async close(): Promise<void> {
409
+ const children = new Set([...this.#children.values(), ...this.#starting]);
410
+ this.stopAll();
411
+ await Promise.all([...children].map((child) => child.close()));
412
+ await Promise.allSettled(this.#spawning.values());
273
413
  }
274
414
  }
275
415
 
@@ -280,6 +420,8 @@ const MODEL_ROUTED = new Set([
280
420
  ]);
281
421
 
282
422
  export interface ProxyServerOptions {
423
+ /** Application-store injection for embedded hosts and model-free tests. */
424
+ createJobStore?: () => Promise<import("../jobs/db").JobStore>;
283
425
  port: number;
284
426
  hostname?: string;
285
427
  engine: EngineSpec;
@@ -301,11 +443,61 @@ export function startProxyServer(opts: ProxyServerOptions): {
301
443
  server: ReturnType<typeof Bun.serve>;
302
444
  engine: EngineChild;
303
445
  pool: ModelPool | null;
446
+ close(): Promise<void>;
304
447
  } {
305
- const engine = new EngineChild(opts.engine);
448
+ const coordinator = new ExecutionCoordinator();
449
+ const acquire = (mode: "shared" | "exclusive", signal: AbortSignal) =>
450
+ acquireReservation({ acquire: (cancellation) => coordinator.acquire(mode, cancellation) }, signal);
451
+ const engine = new EngineChild({ ...opts.engine, async acquire(signal) {
452
+ const parent = await acquire("shared", signal);
453
+ try {
454
+ const supplied = await opts.engine.acquire?.(signal);
455
+ return { dispose() { try { supplied?.dispose(); } finally { parent.dispose(); } } };
456
+ } catch (error) { parent.dispose(); throw error; }
457
+ } });
306
458
  const pool = opts.pool
307
459
  ? new ModelPool({ ...opts.pool, defaultChild: engine })
308
460
  : null;
461
+ const children = () => pool ? pool.residentKeys.map((key) => pool.child(key)!).filter(Boolean) : [engine];
462
+ const forwardRequest = async (request: Request): Promise<Response> => {
463
+ const url = new URL(request.url);
464
+ // P2 routing: generation endpoints carry `model` in the JSON body —
465
+ // buffer it (small), pick/spawn that model's child, forward the
466
+ // buffered body. Everything else rides the default engine.
467
+ if (pool && request.method === "POST" && MODEL_ROUTED.has(url.pathname)) {
468
+ const raw = await request.text();
469
+ let modelField: string | undefined;
470
+ try { modelField = (JSON.parse(raw) as { model?: string }).model; } catch { /* malformed → default child answers with its own 400 */ }
471
+ const target = await pool.childFor(modelField);
472
+ return await target.forward(new Request(request.url, {
473
+ method: request.method,
474
+ headers: request.headers,
475
+ body: raw,
476
+ signal: request.signal,
477
+ }));
478
+ }
479
+ return await (pool ? await pool.childFor(undefined) : engine).forward(request);
480
+ };
481
+ const application = createParentApplication({
482
+ createJobStore: opts.createJobStore,
483
+ serverPort: () => server.port,
484
+ async acquireGpu(signal) {
485
+ const lease = await acquire("exclusive", signal);
486
+ try {
487
+ // Network cancellation is not a native completion fence. Explicitly
488
+ // drain every resident worker before the subprocess may touch the GPU.
489
+ const results = await Promise.allSettled(children().map((child) => child.reserveExecution(signal)));
490
+ const held = results.filter((result) => result.status === "fulfilled").map((result) => result.value);
491
+ const failed = results.find((result) => result.status === "rejected");
492
+ if (failed) { for (const worker of held) worker.dispose(); throw failed.reason; }
493
+ return { dispose() { for (const worker of held) worker.dispose(); lease.dispose(); } };
494
+ } catch (error) { lease.dispose(); throw error; }
495
+ },
496
+ invalidateLibrary() {
497
+ for (const child of children()) void child.forward(new Request("http://engine/library?refresh=1"))
498
+ .then((response) => response.arrayBuffer()).catch(() => {});
499
+ },
500
+ });
309
501
  const server = Bun.serve({
310
502
  port: opts.port,
311
503
  ...(opts.hostname ? { hostname: opts.hostname } : {}),
@@ -317,32 +509,26 @@ export function startProxyServer(opts: ProxyServerOptions): {
317
509
  { error: { message: "WebSocket chat is not proxied under --isolate yet; run without --isolate for the web chat UI" } },
318
510
  { status: 501 },
319
511
  );
320
- if (url.pathname === "/engine" && request.method === "GET")
512
+ if (url.pathname === "/engine" && request.method === "GET") {
513
+ // A pool may have evicted and recreated the default worker. Inspect
514
+ // current residency without starting a worker merely for diagnostics.
515
+ const current = pool ? pool.child(pool.defaultKey) : engine;
321
516
  return Response.json({
322
517
  isolated: true,
323
- pid: engine.pid,
324
- restarts: engine.restarts,
325
- socket: engine.spec.socketPath,
518
+ response_store: application.responseStats,
519
+ pid: current?.pid ?? null,
520
+ restarts: current?.restarts ?? null,
521
+ socket: current?.spec.socketPath ?? null,
326
522
  ...(pool ? { pool: { resident: pool.residentKeys, default: pool.defaultKey } } : {}),
327
523
  });
524
+ }
328
525
  try {
329
- // P2 routing: generation endpoints carry `model` in the JSON body —
330
- // buffer it (small), pick/spawn that model's child, forward the
331
- // buffered body. Everything else rides the default engine.
332
- if (pool && request.method === "POST" && MODEL_ROUTED.has(url.pathname)) {
333
- const raw = await request.text();
334
- let modelField: string | undefined;
335
- try { modelField = (JSON.parse(raw) as { model?: string }).model; } catch { /* malformed → default child answers with its own 400 */ }
336
- const target = await pool.childFor(modelField);
337
- return await target.forward(new Request(request.url, {
338
- method: request.method,
339
- headers: request.headers,
340
- body: raw,
341
- signal: request.signal,
342
- }));
343
- }
344
- return await engine.forward(request);
526
+ const local = await application.handle(request, forwardRequest);
527
+ if (local) return local;
528
+ return await forwardRequest(request);
345
529
  } catch (e) {
530
+ if (e instanceof AdmissionRejected) return Response.json(
531
+ { error: { message: e.message, type: "resource_admission", code: "queue_full" } }, { status: 429 });
346
532
  if (request.signal.aborted) return new Response(null, { status: 499 });
347
533
  return Response.json(
348
534
  {
@@ -356,7 +542,13 @@ export function startProxyServer(opts: ProxyServerOptions): {
356
542
  }
357
543
  },
358
544
  });
359
- return { server, engine, pool };
545
+ let closing: Promise<void> | undefined;
546
+ return { server, engine, pool, close: () => closing ??= (async () => {
547
+ await server.stop(true);
548
+ coordinator.close();
549
+ await application.close();
550
+ if (pool) await pool.close(); else await engine.close();
551
+ })() };
360
552
  }
361
553
 
362
554
  /** serve's value-taking flags — needed to tell a flag VALUE from the bare
@@ -368,7 +560,7 @@ const SERVE_VALUE_FLAGS = new Set([
368
560
  "--batch", "--decode-concurrency", "--kv-quant",
369
561
  "--kv-group-size", "--quantized-kv-start", "--kv-budget",
370
562
  "--memory-budget", "--prompt-cache", "--ssd-cache", "--ssd-cache-max",
371
- "--ssd-demote-idle", "--adapter", "--max-tokens", "--unix",
563
+ "--ssd-demote-idle", "--generation-checkpoint", "--adapter", "--max-tokens", "--unix",
372
564
  "--model-pool", "--compiled-decode", "--compiled-activations",
373
565
  "--fused-sdpa", "--thinking",
374
566
  ]);
@@ -2,6 +2,7 @@ import type { JobStore } from "../jobs";
2
2
 
3
3
  export interface LabRouteDeps {
4
4
  ensureJobs(): Promise<JobStore>;
5
+ acquireGpu?: (signal: AbortSignal) => Promise<import("../contracts/resources").DisposableResource>;
5
6
  serverPort(): number | undefined;
6
7
  invalidateLibrary(): void;
7
8
  }
@@ -103,6 +104,7 @@ export async function handleLabRoute(
103
104
  "finetune",
104
105
  { ...body, adapter_path: adapterPath },
105
106
  adapterPath,
107
+ { acquire: deps.acquireGpu },
106
108
  );
107
109
  return Response.json({ ok: true, job_id: jobId, adapter_path: adapterPath });
108
110
  }
@@ -332,6 +334,7 @@ async function submitQuantize(request: Request, deps: LabRouteDeps): Promise<Res
332
334
  rotate_weights: body.rotate_weights,
333
335
  rotation_seed: body.rotation_seed,
334
336
  }, outDir, {
337
+ acquire: deps.acquireGpu,
335
338
  onComplete: () => deps.invalidateLibrary(),
336
339
  });
337
340
  return Response.json({ ok: true, job_id: jobId, output_dir: outDir });
@@ -16,7 +16,8 @@
16
16
  // a same-process debugging/telemetry aid, not a durable store — entries for
17
17
  // long-dead requests must not leak memory over a long-running server.
18
18
 
19
- export type Lane = "serial" | "serial+spec" | "batched";
19
+ import type { Lane } from "../contracts/pi";
20
+ export type { Lane } from "../contracts/pi";
20
21
 
21
22
  const MAX_ENTRIES = 512;
22
23
  const lanes = new Map<string, Lane>();
@@ -1,6 +1,6 @@
1
- import type { ServerContext } from "../server";
1
+ import { modelServingBinding } from "../backends/mlx/model-serving";
2
+ import type { ServingContext } from "./model-host";
2
3
  import type { GenerationGateway } from "./generation-gateway";
3
- import { embedMany, isEmbeddingModel } from "../embed";
4
4
  import { listAvailableAdapters } from "../lora";
5
5
 
6
6
  export type ModelAdminRoute =
@@ -29,7 +29,7 @@ export function matchModelAdminRoute(method: string, pathname: string): ModelAdm
29
29
  export async function handleModelAdminRoute(
30
30
  url: URL,
31
31
  request: Request,
32
- ctx: ServerContext,
32
+ ctx: ServingContext,
33
33
  gateway: Pick<GenerationGateway, "runExclusive">,
34
34
  ): Promise<Response | null> {
35
35
  const route = matchModelAdminRoute(request.method, url.pathname);
@@ -37,7 +37,9 @@ export async function handleModelAdminRoute(
37
37
 
38
38
  switch (route.kind) {
39
39
  case "embeddings": {
40
- if (!isEmbeddingModel(ctx.model)) {
40
+ const binding = modelServingBinding(ctx);
41
+ const embed = binding.embed?.bind(binding);
42
+ if (!embed) {
41
43
  return Response.json({
42
44
  error: {
43
45
  message: `served model "${ctx.modelId}" is not an embedding model; ` +
@@ -66,7 +68,7 @@ export async function handleModelAdminRoute(
66
68
  }, { status: 400 });
67
69
  }
68
70
  const instruction = typeof body.instruction === "string" ? body.instruction : undefined;
69
- const results = embedMany(ctx.model, ctx.tokenizer, inputs, instruction);
71
+ const results = await gateway.runExclusive(async () => embed(inputs, instruction), undefined, request.signal);
70
72
  let totalTokens = 0;
71
73
  const data = results.map((result, index) => {
72
74
  totalTokens += result.tokens;