mlx-bun 0.0.13 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (427) hide show
  1. package/README.md +71 -578
  2. package/bin/mlx-bun.mjs +1 -1
  3. package/docs/README.md +91 -0
  4. package/docs/{design → archive}/hlg-sampling.md +3 -3
  5. package/docs/{design → archive/investigations}/audio-input-plan.md +4 -4
  6. package/docs/{design → archive/investigations}/colibri-glm52-port.md +1 -1
  7. package/docs/{design → archive/investigations}/diffusion-gemma-port.md +3 -3
  8. package/docs/{design → archive/investigations}/faithful-l1-consolidation.md +4 -4
  9. package/docs/{investigations → archive/investigations}/hlg-sampling-investigation.md +1 -1
  10. package/docs/{design → archive/investigations}/minicpm5-decode-megakernel.md +11 -11
  11. package/docs/{design → archive/investigations}/optimization_plan.md +1 -1
  12. package/docs/archive/investigations/pareto-specialized-runtime-findings.md +92 -0
  13. package/docs/{design → archive/investigations}/pre-colibri-stabilization.md +5 -5
  14. package/docs/{design → archive/investigations}/the-dreaming-handoff.md +2 -2
  15. package/docs/{planning → archive/planning}/chat-ui-vision.md +1 -1
  16. package/docs/{planning → archive/planning}/chunk-task-roadmap.md +1 -1
  17. package/docs/{planning → archive/planning}/curve-sampler-research-plan.md +4 -4
  18. package/docs/{planning → archive/planning}/memory-docs-and-dag-plan.md +1 -1
  19. package/docs/{planning → archive/planning}/release-notes-v0.0.9.md +1 -1
  20. package/docs/archive/planning/release-notes-v0.2.0.md +49 -0
  21. package/docs/{planning → archive/planning}/website-readme-pass-plan.md +1 -1
  22. package/docs/design/batching.md +569 -0
  23. package/docs/design/decode-speed-program.md +3178 -169
  24. package/docs/design/dreaming-nightly-pipeline.md +675 -282
  25. package/docs/design/generic-model-support.md +579 -334
  26. package/docs/design/kv-cache.md +680 -0
  27. package/docs/design/orpo-training.md +673 -1239
  28. package/docs/design/speculative-decoding.md +1627 -0
  29. package/docs/design/turboquant.md +2102 -0
  30. package/docs/design/unified-engine-frontier-plan.md +1831 -727
  31. package/docs/design/web-chat-redesign.md +666 -1083
  32. package/docs/planning/PRODUCT_ROADMAP.md +34 -1
  33. package/docs/planning/release-notes-v0.3.0.md +35 -0
  34. package/docs/planning/video-series.md +421 -0
  35. package/docs/reference/benchmarks.md +1162 -0
  36. package/docs/reference/cli.md +395 -149
  37. package/docs/reference/distribution.md +321 -181
  38. package/docs/reference/environment.md +348 -0
  39. package/docs/reference/glossary.md +39 -0
  40. package/docs/reference/library-api.md +580 -142
  41. package/docs/reference/memory.md +227 -64
  42. package/docs/reference/models.md +137 -6
  43. package/docs/reference/server-api.md +938 -422
  44. package/docs/reference/server-config.md +652 -320
  45. package/docs/reference/training.md +393 -254
  46. package/docs/reference/troubleshooting.md +166 -0
  47. package/package.json +10 -5
  48. package/src/admission-plan.ts +18 -0
  49. package/src/anthropic.ts +92 -3
  50. package/src/audio/decode.ts +2 -2
  51. package/src/audio/features.ts +2 -2
  52. package/src/backends/mlx/autoregressive.ts +108 -0
  53. package/src/backends/mlx/batch-group.ts +1646 -0
  54. package/src/backends/mlx/diffusion.ts +39 -0
  55. package/src/backends/mlx/draft-target.ts +47 -0
  56. package/src/backends/mlx/evaluation.ts +105 -0
  57. package/src/backends/mlx/fill-append.ts +45 -0
  58. package/src/backends/mlx/gateway-binding.ts +71 -0
  59. package/src/backends/mlx/graph.ts +38 -0
  60. package/src/backends/mlx/memory-client.ts +189 -0
  61. package/src/backends/mlx/memory-guard.ts +40 -0
  62. package/src/backends/mlx/methods.ts +90 -0
  63. package/src/backends/mlx/model-prompt.ts +164 -0
  64. package/src/backends/mlx/model-serving.ts +79 -0
  65. package/src/backends/mlx/prefill.ts +33 -0
  66. package/src/backends/mlx/request-policy.ts +20 -0
  67. package/src/backends/mlx/rollback.ts +51 -0
  68. package/src/backends/mlx/serial-executor.ts +273 -0
  69. package/src/backends/mlx/speculative.ts +114 -0
  70. package/src/backends/mlx/state-views.ts +26 -0
  71. package/src/backends/mlx/text-engine.ts +73 -0
  72. package/src/cli.ts +189 -61
  73. package/src/client.ts +66 -0
  74. package/src/config.ts +21 -4
  75. package/src/contracts/completion.ts +16 -0
  76. package/src/contracts/execution.ts +49 -0
  77. package/src/contracts/generation.ts +103 -0
  78. package/src/contracts/host.ts +7 -0
  79. package/src/contracts/jobs.ts +66 -0
  80. package/src/contracts/pi-provider.ts +30 -0
  81. package/src/contracts/pi.ts +259 -0
  82. package/src/contracts/resources.ts +18 -0
  83. package/src/contracts/scheduling.ts +27 -0
  84. package/src/contracts/tsconfig.json +14 -0
  85. package/src/diffusion/diffusion-generate.ts +218 -153
  86. package/src/embed.ts +1 -1
  87. package/src/engine/admission.ts +59 -0
  88. package/src/engine/cancellation.ts +38 -0
  89. package/src/engine/engine.ts +56 -0
  90. package/src/engine/execution-coordinator.ts +70 -0
  91. package/src/engine/execution-plan.ts +51 -0
  92. package/src/engine/resources.ts +42 -0
  93. package/src/engine/scheduler.ts +67 -0
  94. package/src/engine/session.ts +247 -0
  95. package/src/eval/README.md +44 -1
  96. package/src/eval/kl.ts +263 -23
  97. package/src/eval/reference-logits.ts +207 -0
  98. package/src/eval/runner.ts +10 -79
  99. package/src/eval/tasks/bfcl.ts +2 -1
  100. package/src/eval/tasks/gsm8k.ts +2 -1
  101. package/src/eval/tasks/hashhop.ts +2 -1
  102. package/src/eval/tasks/humaneval.ts +5 -4
  103. package/src/eval/tasks/ifeval.ts +2 -1
  104. package/src/eval/tasks/mmlu.ts +2 -1
  105. package/src/expert-io.ts +3 -1
  106. package/src/expert-offload.ts +3 -1
  107. package/src/fill/echo-index.ts +188 -0
  108. package/src/fill/fill-session.ts +452 -0
  109. package/src/fill/proposal.ts +68 -0
  110. package/src/fill/schema-rows.ts +506 -0
  111. package/src/fill/tool-boundary.ts +103 -0
  112. package/src/fit.ts +24 -43
  113. package/src/generate.ts +740 -337
  114. package/src/grammar.ts +14 -8
  115. package/src/harness-pi.ts +15 -34
  116. package/src/hub-rest.ts +4 -4
  117. package/src/index.ts +27 -0
  118. package/src/inference/denoising.ts +17 -0
  119. package/src/inference/graph.ts +33 -0
  120. package/src/inference/prefill.ts +39 -0
  121. package/src/inference/rollback.ts +7 -0
  122. package/src/jobs/db.ts +4 -0
  123. package/src/jobs/index.ts +2 -0
  124. package/src/jobs/job-entry.ts +15 -14
  125. package/src/jobs/runner.ts +98 -13
  126. package/src/jobs/task-client.ts +19 -0
  127. package/src/jobs/types.ts +2 -58
  128. package/src/kv-scheme.ts +199 -0
  129. package/src/kv-store.ts +459 -234
  130. package/src/lab/README.md +18 -0
  131. package/src/{assets → lab/curve}/curve-designer.html +2 -2
  132. package/src/{curve-sampler.ts → lab/curve/curve-sampler.ts} +2 -2
  133. package/src/{expert-trace.ts → lab/expert-trace/expert-trace.ts} +7 -5
  134. package/src/{model → lab/paged-kv}/paged-kv.ts +9 -6
  135. package/src/library.ts +40 -0
  136. package/src/lora.ts +18 -0
  137. package/src/media-fetch.ts +20 -4
  138. package/src/memory/article.ts +1 -1
  139. package/src/memory/chunk.ts +1 -1
  140. package/src/memory/model.ts +32 -170
  141. package/src/memory/prompts.ts +1 -1
  142. package/src/memory/rest.ts +1 -1
  143. package/src/memory/schedule.ts +1 -1
  144. package/src/memory/stages.ts +4 -1
  145. package/src/memory/vault.ts +3 -4
  146. package/src/mlx/array.ts +9 -10
  147. package/src/mlx/compile.ts +12 -2
  148. package/src/mlx/ffi.ts +66 -22
  149. package/src/mlx/materialize.ts +23 -0
  150. package/src/mlx/metal-kernel.ts +10 -11
  151. package/src/mlx/ops.ts +64 -19
  152. package/src/mlx/quantized-rows.ts +36 -0
  153. package/src/mlx/token-bitmask.ts +52 -0
  154. package/src/mlx/turboquant-kv-decode.ts +82 -0
  155. package/src/mlx/turboquant-kv-inverse.ts +122 -0
  156. package/src/mlx/turboquant-ops.ts +33 -3
  157. package/src/mlx/turboquant-tables.ts +2 -2
  158. package/src/model/batched-mask.ts +9 -3
  159. package/src/model/batched-quant.ts +2 -2
  160. package/src/model/batched-rotating-quant.ts +45 -99
  161. package/src/model/batched-rotating-state.ts +73 -0
  162. package/src/model/batched-rotating.ts +57 -106
  163. package/src/model/batched-row-storage.ts +127 -0
  164. package/src/model/compiled-decode.ts +4 -3
  165. package/src/model/diffusion-gemma.ts +8 -8
  166. package/src/model/factory.ts +87 -49
  167. package/src/model/fingerprint.ts +1 -1
  168. package/src/model/flash-attention.ts +3 -3
  169. package/src/model/gemma4-base.ts +187 -17
  170. package/src/model/gemma4.ts +6 -6
  171. package/src/model/generated/gemma4-12b.ts +52 -148
  172. package/src/model/generated/gemma4-26b.ts +34 -94
  173. package/src/model/generated/gemma4-e4b.ts +28 -76
  174. package/src/model/glm52-cache.ts +19 -9
  175. package/src/model/glm52-dsa.ts +13 -9
  176. package/src/model/glm52-memory.ts +28 -1
  177. package/src/model/implementation.ts +69 -0
  178. package/src/model/minicpm5.ts +7 -8
  179. package/src/model/profile.ts +443 -0
  180. package/src/model/qwen-append.ts +12 -0
  181. package/src/model/qwen-conv.ts +52 -0
  182. package/src/model/qwen3-delta.ts +98 -2
  183. package/src/model/qwen3-mrope.ts +252 -0
  184. package/src/model/qwen3_5-checkpoint.ts +164 -0
  185. package/src/model/qwen3_5.ts +336 -60
  186. package/src/model/support.ts +1 -1
  187. package/src/model/trellis-balanced-scatter.ts +51 -0
  188. package/src/model/trellis-linear.ts +672 -0
  189. package/src/model/trellis-shared-m.ts +151 -0
  190. package/src/model/trellis-shared-scatter.ts +144 -0
  191. package/src/model/trellis-splitk-prefill.ts +158 -0
  192. package/src/model/trellis-tiled-prefill.ts +136 -0
  193. package/src/model/trellis-vector-expand.ts +62 -0
  194. package/src/model/trellis-wide-prefill.ts +94 -0
  195. package/src/model/universal/dense.ts +1 -1
  196. package/src/model/universal/modules.ts +8 -1
  197. package/src/native/frame_extract.swift +140 -0
  198. package/src/native-pack.ts +15 -7
  199. package/src/pi-provider.ts +6 -64
  200. package/src/pi-web.ts +14 -320
  201. package/src/prompt-cache.ts +97 -54
  202. package/src/quantize/allocator.ts +1 -1
  203. package/src/quantize/atomic-output.ts +38 -0
  204. package/src/quantize/calibration.ts +2 -1
  205. package/src/quantize/config-writer.ts +16 -3
  206. package/src/quantize/index.ts +28 -1
  207. package/src/quantize/job.ts +11 -0
  208. package/src/quantize/quantizer.ts +265 -100
  209. package/src/quantize/rotate.ts +603 -0
  210. package/src/quantize/safetensors-writer.ts +79 -1
  211. package/src/quantize/sensitivity.ts +41 -59
  212. package/src/quantize/trellis.ts +586 -0
  213. package/src/quantize/weight-transform.ts +292 -0
  214. package/src/responses.ts +130 -3
  215. package/src/runtime-config.ts +81 -0
  216. package/src/sampler.ts +276 -4
  217. package/src/serve/admin-routes.ts +184 -0
  218. package/src/serve/aux-routes.ts +132 -0
  219. package/src/serve/batch-scheduler.ts +8 -1558
  220. package/src/serve/chat-request.ts +391 -0
  221. package/src/serve/chat-stage.ts +195 -0
  222. package/src/serve/checkpoint-identity.ts +64 -0
  223. package/src/serve/completion-executor.ts +354 -0
  224. package/src/serve/completion-sink.ts +157 -0
  225. package/src/serve/discovery-routes.ts +204 -0
  226. package/src/serve/generation-gateway.ts +211 -243
  227. package/src/serve/http.ts +159 -0
  228. package/src/serve/inference-request.ts +79 -0
  229. package/src/serve/isolate.ts +297 -105
  230. package/src/serve/kv-budget.ts +14 -0
  231. package/src/serve/lab-routes.ts +341 -0
  232. package/src/serve/lane-registry.ts +2 -1
  233. package/src/serve/model-admin-routes.ts +155 -0
  234. package/src/serve/model-binding.ts +43 -0
  235. package/src/serve/model-host.ts +512 -0
  236. package/src/serve/openai-wire.ts +103 -0
  237. package/src/serve/parent-application.ts +56 -0
  238. package/src/serve/pipeline.ts +33 -0
  239. package/src/serve/preparation.ts +33 -0
  240. package/src/serve/prompt-response-trace.ts +147 -0
  241. package/src/serve/request-plan.ts +162 -0
  242. package/src/serve/request-prep.ts +346 -0
  243. package/src/serve/response-lease.ts +24 -0
  244. package/src/serve/responses-client.ts +69 -0
  245. package/src/serve/session-completion-engine.ts +110 -0
  246. package/src/serve/session-search.ts +1 -1
  247. package/src/serve/static-routes.ts +101 -0
  248. package/src/serve/text-completion-stage.ts +104 -0
  249. package/src/serve/token-streams.ts +519 -0
  250. package/src/server.ts +523 -3551
  251. package/src/spec/assistant-source.ts +10 -44
  252. package/src/spec/deepspec-source.ts +6 -6
  253. package/src/spec/dflash-source.ts +9 -8
  254. package/src/spec/dspark/ab-stats.ts +3 -3
  255. package/src/spec/dspark/calibration.ts +2 -2
  256. package/src/spec/dspark/deepspec-module.ts +2 -2
  257. package/src/spec/dspark/generate-dflash.ts +2 -2
  258. package/src/spec/dspark/generate.ts +2 -2
  259. package/src/spec/dspark/loader.ts +1 -1
  260. package/src/spec/dspark/module-dflash.ts +5 -4
  261. package/src/spec/dspark/module.ts +3 -3
  262. package/src/spec/dspark/quantize-drafter.ts +1 -1
  263. package/src/spec/generate.ts +1 -1
  264. package/src/spec/glm52-mtp-source.ts +9 -1
  265. package/src/spec/ngram-source.ts +1 -1
  266. package/src/spec/prefix-state.ts +45 -0
  267. package/src/spec/qwen-mtp-source.ts +528 -0
  268. package/src/spec/serve-loop.ts +179 -213
  269. package/src/spec/source.ts +42 -10
  270. package/src/spec/two-model.ts +1 -1
  271. package/src/ssd-cache.ts +95 -24
  272. package/src/ssd-durability.ts +207 -0
  273. package/src/tool-call.ts +51 -14
  274. package/src/train/flash-cce.ts +14 -13
  275. package/src/train/forward.ts +38 -1
  276. package/src/train/loss.ts +3 -2
  277. package/src/train/prefix-shared.ts +5 -1
  278. package/src/train/segmented.ts +9 -6
  279. package/src/train/trainer.ts +9 -8
  280. package/src/vision/preprocess.ts +1 -1
  281. package/src/vision/prompt.ts +48 -2
  282. package/src/vision/qwen3vl-preprocess.ts +409 -0
  283. package/src/vision/qwen3vl-prompt.ts +196 -0
  284. package/src/vision/qwen3vl-tower.ts +555 -0
  285. package/src/vision/video-frames.ts +148 -0
  286. package/src/web/app.html +1 -1
  287. package/src/web/app.js +15 -6
  288. package/src/web/src/adapters-panel.ts +3 -3
  289. package/src/web/src/assistant.ts +2 -36
  290. package/src/web/src/chat.ts +16 -9
  291. package/src/web/src/composer.ts +4 -4
  292. package/src/web/src/hub.ts +1 -1
  293. package/src/web/src/markdown.ts +0 -0
  294. package/src/web/src/memory-panel.ts +1 -1
  295. package/src/web/src/model-picker.ts +1 -1
  296. package/src/web/src/palette.ts +1 -1
  297. package/src/web/src/protocol.ts +3 -39
  298. package/src/web/src/sessions.ts +1 -1
  299. package/src/web/src/ui-catalog.ts +4 -18
  300. package/src/web-assets.ts +47 -0
  301. package/src/weights.ts +134 -33
  302. package/docs/archive/mlx-bun-lab-report.html +0 -275
  303. package/docs/archive/wwdc-mlx-bun.html +0 -848
  304. package/docs/design/adapters-end-to-end.md +0 -205
  305. package/docs/design/batching-perf-path.md +0 -255
  306. package/docs/design/batching-v2-plan.md +0 -529
  307. package/docs/design/bucketing-stage.md +0 -197
  308. package/docs/design/compat-cli-surface-design.md +0 -230
  309. package/docs/design/docs-reorg-plan.md +0 -165
  310. package/docs/design/dspark-serving-program.md +0 -371
  311. package/docs/design/dspark-speculative-decoding.md +0 -268
  312. package/docs/design/grammar-spec-batching-integration.md +0 -301
  313. package/docs/design/memory-inference-path.md +0 -422
  314. package/docs/design/memory-synthesis.md +0 -287
  315. package/docs/design/memory-system.md +0 -237
  316. package/docs/design/mlx-lm-tool-parity-plan.md +0 -545
  317. package/docs/design/omlx-adoption-map.md +0 -98
  318. package/docs/design/orpo-dynamic-lambda.md +0 -220
  319. package/docs/design/orpo-future-enhancements.md +0 -340
  320. package/docs/design/paged-kv-cache.md +0 -214
  321. package/docs/design/parallel-slots.md +0 -567
  322. package/docs/design/parity-tier-dag.md +0 -161
  323. package/docs/design/repo-cleanup-plan.md +0 -189
  324. package/docs/design/runtime-isolation.md +0 -263
  325. package/docs/design/segmented-backward-training.md +0 -543
  326. package/docs/design/spec-decode-larger-targets.md +0 -229
  327. package/docs/design/ssd-kv-cold-tier.md +0 -288
  328. package/docs/design/structured-output.md +0 -484
  329. package/docs/design/tauri-desktop-app.md +0 -127
  330. package/docs/design/the-dreaming-master-plan.md +0 -445
  331. package/docs/design/turboquant-kv.md +0 -235
  332. package/docs/design/w4a16-compute-precision-spike.md +0 -232
  333. package/docs/design/web-chat-beat-matrix.md +0 -315
  334. package/docs/design/write-pipeline-entity.md +0 -468
  335. package/docs/investigations/curve-runs/bisector-route.json +0 -98
  336. package/docs/investigations/curve-runs/bisector-trace.json +0 -1393
  337. package/docs/investigations/curve-runs/curve-terrain.json +0 -1179
  338. package/docs/investigations/curve-runs/curve-terrain.svg +0 -42
  339. package/docs/investigations/hlg-ab.html +0 -34
  340. package/docs/investigations/hlg-explorer.html +0 -238
  341. package/docs/investigations/hlg-figs/cliff.svg +0 -1
  342. package/docs/investigations/hlg-figs/cross.svg +0 -1
  343. package/docs/investigations/hlg-figs/edges.svg +0 -1
  344. package/docs/investigations/hlg-figs/k20-grids.svg +0 -1
  345. package/docs/investigations/hlg-figs/k20-peaks.svg +0 -1
  346. package/docs/investigations/hlg-figs/lw-curve.svg +0 -1
  347. package/docs/investigations/hlg-figs/terrain.svg +0 -1
  348. package/docs/investigations/hlg-report.html +0 -262
  349. package/docs/investigations/hlg-runs/hlg-ab.json +0 -149
  350. package/docs/investigations/hlg-runs/hlg-blind-prompts.md +0 -87
  351. package/docs/investigations/hlg-runs/hlg-blind.json +0 -107
  352. package/docs/investigations/hlg-runs/hlg-cliff.json +0 -197
  353. package/docs/investigations/hlg-runs/hlg-cross.json +0 -505
  354. package/docs/investigations/hlg-runs/hlg-edge.json +0 -388
  355. package/docs/investigations/hlg-runs/hlg-eval1.json +0 -25
  356. package/docs/investigations/hlg-runs/hlg-frontier.json +0 -86
  357. package/docs/investigations/hlg-runs/hlg-grid.json +0 -242
  358. package/docs/investigations/hlg-runs/hlg-grid2.json +0 -343
  359. package/docs/investigations/hlg-runs/hlg-lw-coarse.json +0 -110
  360. package/docs/investigations/hlg-runs/hlg-map.json +0 -383
  361. package/docs/investigations/hlg-runs/run0-original.log +0 -159
  362. package/docs/investigations/hlg-runs/runA-recommended.log +0 -131
  363. package/docs/investigations/hlg-runs/runAA-cliff-cpm5.log +0 -26
  364. package/docs/investigations/hlg-runs/runAB-grid2-e4b.log +0 -18
  365. package/docs/investigations/hlg-runs/runAC-peak-e4b-partial.log +0 -25
  366. package/docs/investigations/hlg-runs/runAD-ab-samples.log +0 -8
  367. package/docs/investigations/hlg-runs/runAE-edge-e4b.log +0 -51
  368. package/docs/investigations/hlg-runs/runAF-logit-calibration.log +0 -10
  369. package/docs/investigations/hlg-runs/runAG-eval-config2.log +0 -16
  370. package/docs/investigations/hlg-runs/runB-gain-variants.log +0 -192
  371. package/docs/investigations/hlg-runs/runC-pivots.log +0 -162
  372. package/docs/investigations/hlg-runs/runD-oetf.log +0 -132
  373. package/docs/investigations/hlg-runs/runE-eotf.log +0 -162
  374. package/docs/investigations/hlg-runs/runF-shaper-working.log +0 -86
  375. package/docs/investigations/hlg-runs/runG-shaper-sweep.log +0 -101
  376. package/docs/investigations/hlg-runs/runH-wsweep.log +0 -176
  377. package/docs/investigations/hlg-runs/runI-asweep.log +0 -176
  378. package/docs/investigations/hlg-runs/runJ-knobmap.log +0 -49
  379. package/docs/investigations/hlg-runs/runK-frontier.log +0 -19
  380. package/docs/investigations/hlg-runs/runL-widemap.log +0 -65
  381. package/docs/investigations/hlg-runs/runM-frontier-emb.log +0 -15
  382. package/docs/investigations/hlg-runs/runN-verify.log +0 -116
  383. package/docs/investigations/hlg-runs/runO-frontier-seed5000.log +0 -15
  384. package/docs/investigations/hlg-runs/runP-grid-AxSm.log +0 -15
  385. package/docs/investigations/hlg-runs/runQ-grid-tgW.log +0 -15
  386. package/docs/investigations/hlg-runs/runR-blind-transcript.log +0 -79
  387. package/docs/investigations/hlg-runs/runS-cross-coherence.log +0 -33
  388. package/docs/investigations/hlg-runs/runT-cross-cpm5.log +0 -33
  389. package/docs/investigations/hlg-runs/runU-cross-12b.log +0 -33
  390. package/docs/investigations/hlg-runs/runV-lw-e4b.log +0 -22
  391. package/docs/investigations/hlg-runs/runW-lw-12b.log +0 -22
  392. package/docs/investigations/hlg-runs/runX-lw-cpm5.log +0 -22
  393. package/docs/investigations/hlg-runs/runY-cliff-e4b.log +0 -26
  394. package/docs/investigations/hlg-runs/runZ-cliff-12b.log +0 -26
  395. package/docs/planning/IDEAS.md +0 -69
  396. package/docs/reference/embedding.md +0 -197
  397. package/docs/reference/features-matrix.md +0 -100
  398. package/docs/reference/orpo-quickstart.md +0 -135
  399. package/src/flags.ts +0 -13
  400. /package/docs/{investigations → archive/investigations}/chunk-finetune-experiment.md +0 -0
  401. /package/docs/{investigations → archive/investigations}/colibri-oracle-pin.md +0 -0
  402. /package/docs/{investigations → archive/investigations}/curve-bisector-routing.md +0 -0
  403. /package/docs/{investigations → archive/investigations}/decode-roofline-lookagain.md +0 -0
  404. /package/docs/{investigations → archive/investigations}/dspark-handoff.md +0 -0
  405. /package/docs/{investigations → archive/investigations}/expert-offload-single-user-moe.md +0 -0
  406. /package/docs/{investigations → archive/investigations}/indexshare-performance-spike.md +0 -0
  407. /package/docs/{investigations → archive/investigations}/kernel-perf-review-2026-07.md +0 -0
  408. /package/docs/{investigations → archive/investigations}/lab-build-journal.md +0 -0
  409. /package/docs/{investigations → archive/investigations}/openwiki-evaluation.md +0 -0
  410. /package/docs/{investigations → archive/investigations}/orpo-base-uf-experiment-and-directions.md +0 -0
  411. /package/docs/{investigations → archive/investigations}/orpo-flash-cce-pin-leak.md +0 -0
  412. /package/docs/{investigations → archive/investigations}/orpo-uf-testing-handoff.md +0 -0
  413. /package/docs/{investigations → archive/investigations}/pi-builtin-investigation.md +0 -0
  414. /package/docs/{investigations → archive/investigations}/segmented-backward-handoff.md +0 -0
  415. /package/docs/{investigations → archive/investigations}/starter-model-port-handoff.md +0 -0
  416. /package/docs/{investigations → archive/investigations}/steel-flash-cce-handoff.md +0 -0
  417. /package/docs/{investigations → archive/investigations}/trainer-validation-experiment.md +0 -0
  418. /package/docs/{planning → archive/planning}/chat-ui-vision-memory-first.md +0 -0
  419. /package/docs/{planning → archive/planning}/journal.md +0 -0
  420. /package/docs/{planning → archive/planning}/pi-integration-review.md +0 -0
  421. /package/docs/{planning → archive/planning}/release-notes-v0.0.10.md +0 -0
  422. /package/docs/{planning → archive/planning}/release-notes-v0.0.11.md +0 -0
  423. /package/docs/{planning → archive/planning}/release-notes-v0.0.12.md +0 -0
  424. /package/docs/{planning → archive/planning}/release-notes-v0.0.13.md +0 -0
  425. /package/docs/{planning → archive/planning}/web-ui-pass-plan.md +0 -0
  426. /package/{docs/investigations → src/serve/assets}/curve-terrain.html +0 -0
  427. /package/{docs/dag → src/serve/assets}/training-inference-map.html +0 -0
@@ -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
  ]);
@@ -0,0 +1,14 @@
1
+ import type { ModelConfig } from "../config";
2
+ import { kvBytesAt, KvScheme } from "../kv-scheme";
3
+
4
+ /** Project one batch row's worst-case KV bytes using the same per-layer
5
+ * scheme that the server-wide admission ceiling uses. */
6
+ export function batchRowKvBytes(
7
+ config: ModelConfig,
8
+ promptTokens: number,
9
+ maxTokens: number,
10
+ scheme?: KvScheme,
11
+ ): number {
12
+ const tokens = promptTokens + maxTokens;
13
+ return scheme ? scheme.bytesAt(config, tokens) : kvBytesAt(config, tokens);
14
+ }