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
@@ -0,0 +1,56 @@
1
+ import { handleStaticRoute } from "./static-routes";
2
+ import { STATIC_ROUTE_ASSETS } from "../web-assets";
3
+ import { createResponsesClient } from "./responses-client";
4
+ import type { DisposableResource } from "../contracts/resources";
5
+ import type { JobStore } from "../jobs/db";
6
+ import { handleLabRoute, matchLabRoute } from "./lab-routes";
7
+ import { handleAdminRoute, matchAdminRoute } from "./admin-routes";
8
+
9
+ /** CPU application state stays in the parent when model workers restart/evict.
10
+ * Native inspection/merge/export routes still execute inside a model worker. */
11
+ export function createParentApplication(options: {
12
+ acquireGpu(signal: AbortSignal): Promise<DisposableResource>;
13
+ serverPort(): number | undefined;
14
+ invalidateLibrary(): void;
15
+ createJobStore?(): Promise<JobStore>;
16
+ }) {
17
+ const responses = createResponsesClient();
18
+ let jobs: Promise<JobStore> | undefined;
19
+ let closed = false;
20
+ const ensureJobs = () => {
21
+ if (closed) return Promise.reject(new Error("application host is closed"));
22
+ return jobs ??= (async () => {
23
+ const { JobStore, registerRunner } = await import("../jobs");
24
+ const store = options.createJobStore ? await options.createJobStore() : new JobStore();
25
+ store.markZombies();
26
+ const { datasetRunner } = await import("../dataset/job");
27
+ registerRunner("dataset", datasetRunner);
28
+ return store;
29
+ })();
30
+ };
31
+ return {
32
+ get responseStats() { return responses.stats; },
33
+ async handle(request: Request, forward?: (request: Request) => Promise<Response>): Promise<Response | null> {
34
+ const url = new URL(request.url);
35
+ const staticResponse = handleStaticRoute(url, request, STATIC_ROUTE_ASSETS);
36
+ if (staticResponse) return staticResponse;
37
+ if (forward && request.method === "POST" && url.pathname === "/v1/responses")
38
+ return responses.forward(request, forward);
39
+ const admin = matchAdminRoute(request.method, url.pathname);
40
+ if (admin) return handleAdminRoute(url, request, { ensureJobs, invalidateLibrary: options.invalidateLibrary });
41
+ const route = matchLabRoute(request.method, url.pathname);
42
+ if (route !== "quantize-submit" && route !== "finetune-submit" &&
43
+ route !== "dataset-submit" && route !== "dataset-templates") return null;
44
+ return handleLabRoute(url, request, { ensureJobs, acquireGpu: options.acquireGpu,
45
+ serverPort: options.serverPort, invalidateLibrary: options.invalidateLibrary });
46
+ },
47
+ async close() {
48
+ closed = true;
49
+ if (!jobs) return;
50
+ const store = await jobs;
51
+ const { closeSubprocessJobs, closeInProcessJobs } = await import("../jobs");
52
+ await Promise.all([closeSubprocessJobs(store), closeInProcessJobs(store)]);
53
+ store.close();
54
+ },
55
+ };
56
+ }
@@ -0,0 +1,33 @@
1
+ // Shared contracts of the request pipeline. Each stage is one configured
2
+ // program — `run(input) → output` — and the server composes them in order,
3
+ // like a shell pipe:
4
+ //
5
+ // new ChatRequest(body) → ChatStage.run → InferenceRequest
6
+ // → InferenceStage.run → InferenceResult → wire JSON/SSE
7
+ //
8
+ // A stage knows only the interface it reads and the one it writes.
9
+ import type { CompletionEvent } from "./completion-sink";
10
+ import type { CompletionUsage } from "./completion-executor";
11
+ import type { PromptResponseTrace } from "./prompt-response-trace";
12
+
13
+ /** A request a stage refuses. `status` is the HTTP status the wire adapter
14
+ * answers with; `body` the protocol-neutral error object (adapters reshape
15
+ * it: OpenAI `{error}`, Anthropic `{type:"error"}`, Responses `{error}`). */
16
+ export class RequestError extends Error {
17
+ constructor(
18
+ readonly status: number,
19
+ message: string,
20
+ readonly body: Record<string, unknown> = { message },
21
+ ) {
22
+ super(message);
23
+ }
24
+ }
25
+
26
+ /** What flows back to the caller while a request is in flight: cancellation,
27
+ * tracing, and (for streaming surfaces) events + live usage. */
28
+ export interface RunControl {
29
+ signal?: AbortSignal;
30
+ trace?: PromptResponseTrace;
31
+ onEvents?(events: readonly CompletionEvent[]): void | false | Promise<void | false>;
32
+ onUsageProgress?(usage: Readonly<CompletionUsage>): void;
33
+ }
@@ -0,0 +1,33 @@
1
+ /** Host-side admission before grammar/media preparation can allocate native
2
+ * resources. The implementation shares the generation execution domain. */
3
+ export interface PreparationExecutor {
4
+ reserve?(kind: "media" | "constraint", signal?: AbortSignal): Promise<import("../contracts/resources").DisposableResource>;
5
+ run<T>(prepare: () => Promise<T>, signal?: AbortSignal): Promise<T>;
6
+ }
7
+
8
+ import { AdmissionPool } from "../engine/admission";
9
+ import { CancellationSource } from "../engine/cancellation";
10
+
11
+ export async function acquireReservation(pool: Pick<AdmissionPool, "acquire">, signal?: AbortSignal) {
12
+ signal?.throwIfAborted();
13
+ const cancellation = new CancellationSource();
14
+ const abort = () => cancellation.cancel("requested");
15
+ signal?.addEventListener("abort", abort, { once: true });
16
+ try { return await pool.acquire(cancellation); }
17
+ finally { signal?.removeEventListener("abort", abort); }
18
+ }
19
+
20
+ /** One retained media preparation at a time; grammar rows may form a batch. */
21
+ export function createPreparationExecutor(
22
+ run: PreparationExecutor["run"], grammarCapacity: number,
23
+ ): PreparationExecutor & { close(): void } {
24
+ const media = new AdmissionPool(1);
25
+ const constraints = new AdmissionPool(Math.max(1, grammarCapacity));
26
+ return {
27
+ run,
28
+ async reserve(kind, signal) {
29
+ return acquireReservation(kind === "media" ? media : constraints, signal);
30
+ },
31
+ close() { media.close(); constraints.close(); },
32
+ };
33
+ }
@@ -1,28 +1,31 @@
1
+ import { snapshotGenerationPolicy } from "../backends/mlx/request-policy";
1
2
  import type { GenerateOptions } from "../generate";
2
3
  import type { RequestShape } from "./generation-gateway";
3
-
4
- export interface DisposableResource {
5
- dispose(): void;
6
- }
4
+ import type { DisposableResource } from "../contracts/resources";
5
+ import { disposeResources, ownResource } from "../engine/resources";
6
+ export type { DisposableResource } from "../contracts/resources";
7
7
 
8
8
  export class RequestOwnership {
9
- #resources = new Set<DisposableResource>();
10
- #transferred = false;
9
+ #owner = ownResource(new Set<DisposableResource>(), disposeResources);
10
+ #reservations = ownResource(new Set<DisposableResource>(), disposeResources);
11
+
12
+ /** Reservations live until completion, including after native ownership transfers. */
13
+ retain<T extends DisposableResource>(reservation: T): T {
14
+ this.#reservations.borrow().add(reservation);
15
+ return reservation;
16
+ }
11
17
 
12
18
  own<T extends DisposableResource | null | undefined>(resource: T): T {
13
- if (resource) this.#resources.add(resource);
19
+ if (resource) this.#owner.borrow().add(resource);
14
20
  return resource;
15
21
  }
16
22
 
17
23
  transfer(): void {
18
- this.#transferred = true;
19
- this.#resources.clear();
24
+ this.#owner.transfer();
20
25
  }
21
26
 
22
27
  dispose(): void {
23
- if (this.#transferred) return;
24
- for (const resource of this.#resources) resource.dispose();
25
- this.#resources.clear();
28
+ disposeResources([{ dispose: () => this.#owner.close() }, { dispose: () => this.#reservations.close() }]);
26
29
  }
27
30
  }
28
31
 
@@ -30,7 +33,7 @@ export interface RequestPlanInput {
30
33
  promptIds: number[];
31
34
  options: GenerateOptions & { stopSequences: string[] };
32
35
  requestedMaxTokens: number;
33
- maxSafeContext: number;
36
+ contextLimit: number | null;
34
37
  stream: boolean;
35
38
  wantLogprobs: boolean;
36
39
  topLogprobs: number;
@@ -111,11 +114,12 @@ export type PlanRequestResult = RequestPlan | RequestRejection;
111
114
  * options, adapter selection, and lane shape are derived here once for chat
112
115
  * and raw completions. */
113
116
  export function planRequest(input: RequestPlanInput): PlanRequestResult {
114
- const available = input.maxSafeContext - input.promptIds.length;
117
+ const available = input.contextLimit === null ? Infinity
118
+ : input.contextLimit - input.promptIds.length;
115
119
  if (available < 1) {
116
120
  return new RequestRejection(
117
121
  `prompt is ${input.promptIds.length} tokens but the memory budget caps ` +
118
- `safe context at ${input.maxSafeContext} — no room to generate; ` +
122
+ `safe context at ${input.contextLimit} — no room to generate; ` +
119
123
  "shorten the prompt or raise --memory-budget",
120
124
  input.ownership,
121
125
  );
@@ -123,6 +127,7 @@ export function planRequest(input: RequestPlanInput): PlanRequestResult {
123
127
 
124
128
  const options = { ...input.options };
125
129
  options.maxTokens = Math.min(input.requestedMaxTokens, available);
130
+ options.seedWasExplicit = input.userSeed;
126
131
  if (input.adapterIds.length) options.adapters = [...input.adapterIds];
127
132
  const captureLogprobs = !input.stream && (input.wantLogprobs || input.topLogprobs > 0);
128
133
  if (captureLogprobs) {
@@ -147,9 +152,9 @@ export function planRequest(input: RequestPlanInput): PlanRequestResult {
147
152
  };
148
153
 
149
154
  return new RequestPlan({
150
- promptIds: input.promptIds,
151
- options,
152
- shape,
155
+ promptIds: Object.freeze([...input.promptIds]) as number[],
156
+ options: snapshotGenerationPolicy(options),
157
+ shape: Object.freeze(shape),
153
158
  captureLogprobs,
154
159
  wantLogprobs: input.wantLogprobs,
155
160
  topLogprobs: input.topLogprobs,
@@ -0,0 +1,344 @@
1
+ // Per-request preparation shared by every completion surface (chat,
2
+ // raw text, /signal, /generate): request → GenerateOptions with the
3
+ // server/model defaults folded in, chat-template rendering + prompt ids,
4
+ // the stable prompt-cache boundary probe, grammar compilation, and the
5
+ // tool-call stream router. Resolved ONCE per server (createRequestPrep) and
6
+ // captured by the route handlers. Extracted from src/server.ts (repo-taming
7
+ // Phase 4).
8
+ import type { ToolDefinition } from "../chat-template";
9
+ import {
10
+ fillEchoConfig,
11
+ fillMaxSpan,
12
+ FillSession,
13
+ resolveFillMode,
14
+ } from "../fill/fill-session";
15
+ import { compileStrictFillRows, type StrictRowPlan } from "../fill/schema-rows";
16
+ import type { GenerateOptions } from "../generate";
17
+ import { compileGrammarRequest, grammarEnabled, type GrammarRequest } from "../grammar";
18
+ import type { KvSchemeOptions } from "../kv-scheme";
19
+ import { isMiniCPM5Config } from "../model/support";
20
+ import type { HlgConfig } from "../sampler";
21
+ import {
22
+ nextDefaultSeed,
23
+ normalizeMessages,
24
+ parseLogitBias,
25
+ promptEndsInOpenThink,
26
+ resolveHlg,
27
+ type ChatRequestParams,
28
+ } from "./chat-request";
29
+ import type { ServingContext } from "./model-host";
30
+ import { selectToolStreamMode, ToolAwareStream, type ToolStreamMode } from "./token-streams";
31
+
32
+ /** The ServerOptions fields request preparation reads (server-wide
33
+ * defaults a request field overrides). */
34
+ export interface RequestPrepOptions {
35
+ defaultThinking?: boolean;
36
+ defaultTemperature?: number;
37
+ defaultTopP?: number;
38
+ defaultTopK?: number;
39
+ hlg?: HlgConfig;
40
+ pagedKv?: { blockSize?: number };
41
+ }
42
+
43
+ export function createRequestPrep(input: {
44
+ ctx: ServingContext;
45
+ serverOptions: RequestPrepOptions;
46
+ /** The server-wide KV scheme (resolveKvScheme(...).generationOptions),
47
+ * spread into every request's GenerateOptions. */
48
+ kvScheme: KvSchemeOptions;
49
+ /** `--max-tokens` / the GLM memory plan's generation cap; undefined = the
50
+ * surface's own default (chat 65 536, raw completion 512). */
51
+ defaultGeneratedTokens: number | undefined;
52
+ }) {
53
+ const { ctx, serverOptions, kvScheme, defaultGeneratedTokens } = input;
54
+
55
+ // XTC never removes EOS or the newline token: mlx_lm.server passes
56
+ // [tokenizer.eos_token_id, tokenizer.encode("\n")] as xtc_special_tokens.
57
+ // We pass the flat equivalent — ALL configured EOS ids plus the bare newline
58
+ // id(s), encoded WITHOUT special tokens (mlx-lm's encode("\n") can drag a
59
+ // BOS along on some tokenizers; protecting BOS from XTC is a no-op, so the
60
+ // flat/no-BOS form is behaviorally identical and cleaner).
61
+ const xtcSpecialTokens = [
62
+ ...ctx.model.config.eosTokenIds,
63
+ ...ctx.tokenizer.encode("\n", false),
64
+ ];
65
+
66
+ // Effective enable_thinking for a request, with the same precedence the chat
67
+ // template uses (extracted so sampling and template rendering can't disagree):
68
+ // explicit chat_template_kwargs.enable_thinking → reasoning_effort ("none" =
69
+ // off) → server --thinking default → model default (MiniCPM5 → off). undefined
70
+ // means "not a switchable-thinking model / leave the template default".
71
+ const resolveEnableThinking = (req: ChatRequestParams): boolean | undefined => {
72
+ const explicit = req.chat_template_kwargs?.enable_thinking;
73
+ if (typeof explicit === "boolean") return explicit;
74
+ const effort = req.reasoning_effort;
75
+ if (effort !== undefined) return effort !== "none";
76
+ if (serverOptions.defaultThinking !== undefined) return serverOptions.defaultThinking;
77
+ return isMiniCPM5Config(ctx.model.config) ? false : undefined;
78
+ };
79
+
80
+ const toOptions = (req: ChatRequestParams): GenerateOptions & { stopSequences: string[] } => {
81
+ // Sampling follows the thinking state — which the web UI's thinking button
82
+ // drives via enable_thinking. Model authors publish a SINGLE
83
+ // generation_config temperature (the think-mode value) but recommend a
84
+ // cooler one for direct, no-think replies (MiniCPM5 card: 0.9 think / 0.7
85
+ // no-think, top_p 0.95 for both). So with no explicit temperature set, a
86
+ // no-think turn runs at most NO_THINK_TEMPERATURE while a think turn keeps
87
+ // the model's hotter configured default. An explicit request/CLI
88
+ // temperature always wins. top_p is unchanged by mode.
89
+ const NO_THINK_TEMPERATURE = 0.7;
90
+ const genTemp = ctx.genDefaults.temperature ?? 0.7;
91
+ const defaultTemp = resolveEnableThinking(req) === false
92
+ ? Math.min(genTemp, NO_THINK_TEMPERATURE)
93
+ : genTemp;
94
+ return {
95
+ // Unset means generate until EOS/stop. Only a client cap or an explicit
96
+ // operator limit constrains completion length; fit estimates do not.
97
+ maxTokens: req.max_completion_tokens ?? req.max_tokens ??
98
+ defaultGeneratedTokens,
99
+ temperature: req.temperature ?? serverOptions.defaultTemperature ?? defaultTemp,
100
+ topP: req.top_p ?? serverOptions.defaultTopP ?? ctx.genDefaults.topP ?? 0,
101
+ topK: req.top_k ?? serverOptions.defaultTopK ?? ctx.genDefaults.topK ?? 0,
102
+ seed: req.seed ?? nextDefaultSeed(),
103
+ repetitionPenalty: req.repetition_penalty ?? ctx.genDefaults.repetitionPenalty,
104
+ // mlx_lm.server sampling extensions (defaults mirror server.py: min_p /
105
+ // xtc off, context windows 20). parseLogitBias throws on non-numeric
106
+ // input — callers surface it as a 400 (mlx-lm's coercion error).
107
+ minP: req.min_p ?? 0,
108
+ xtcProbability: req.xtc_probability ?? 0,
109
+ xtcThreshold: req.xtc_threshold ?? 0,
110
+ ...(req.xtc_probability ? { xtcSpecialTokens } : {}),
111
+ logitBias: parseLogitBias(req.logit_bias),
112
+ repetitionContextSize: req.repetition_context_size ?? 20,
113
+ presencePenalty: req.presence_penalty ?? 0,
114
+ presenceContextSize: req.presence_context_size ?? 20,
115
+ frequencyPenalty: req.frequency_penalty ?? 0,
116
+ frequencyContextSize: req.frequency_context_size ?? 20,
117
+ hlg: resolveHlg(req.hlg, serverOptions.hlg),
118
+ // generate() yields token ids; stop sequences match on decoded text
119
+ // (they can span token boundaries), so the StopMatcher sits at the
120
+ // decode layer below and halts the loop via onToken → false.
121
+ stopSequences: (typeof req.stop === "string" ? [req.stop] : req.stop ?? [])
122
+ .filter((s) => typeof s === "string" && s.length > 0),
123
+ ...kvScheme,
124
+ // Paged KV is a server-wide mode like the kv scheme above (v1:
125
+ // serial bf16 gemma4 — the createServer refusals hold the invariants).
126
+ ...(serverOptions.pagedKv ? { pagedKv: serverOptions.pagedKv } : {}),
127
+ };
128
+ };
129
+
130
+ /** Compile a grammar controller from the request's structured-output fields
131
+ * (response_format / guided_* / structured_outputs). Returns null when no
132
+ * constraint is requested OR when compile failed (degrade path). On degrade,
133
+ * `degradeHint` carries a human description for the system-prompt injection
134
+ * + Warning header (oMLX parity — never 500). Honors MLX_BUN_GRAMMAR=0. */
135
+ const compileGrammarForRequest = async (
136
+ req: ChatRequestParams,
137
+ ): Promise<{ controller: import("../grammar").GrammarController | null; degradeHint: string | null }> => {
138
+ if (!grammarEnabled()) {
139
+ return {
140
+ controller: null,
141
+ degradeHint: "grammar compilation disabled by MLX_BUN_GRAMMAR=0",
142
+ };
143
+ }
144
+ const r = await compileGrammarRequest(
145
+ req as GrammarRequest,
146
+ ctx.tokenizer,
147
+ ctx.model.config.text.vocabSize,
148
+ );
149
+ if (!r) return { controller: null, degradeHint: null };
150
+ return { controller: r.controller, degradeHint: r.degradeHint };
151
+ };
152
+
153
+ // Map OpenAI reasoning_effort levels onto the Qwen3.8 template's supported
154
+ // set (xhigh|medium|low — the template raises on anything else). "none"
155
+ // means thinking off (handled by resolveEnableThinking), so no depth is
156
+ // passed. Only consumed for templates with readsReasoningEffort.
157
+ const qwenReasoningEffort = (
158
+ effort: ChatRequestParams["reasoning_effort"],
159
+ ): "xhigh" | "medium" | "low" | undefined => {
160
+ switch (effort) {
161
+ case "minimal":
162
+ case "low":
163
+ return "low";
164
+ case "medium":
165
+ return "medium";
166
+ case "high":
167
+ case "xhigh":
168
+ return "xhigh";
169
+ default:
170
+ return undefined; // "none" / unset → template default (xhigh)
171
+ }
172
+ };
173
+
174
+ const templateOptionsFor = (req: ChatRequestParams, tools: ToolDefinition[] | null) => {
175
+ // enableThinking resolution (and its precedence) lives in
176
+ // resolveEnableThinking so template rendering and sampling stay in sync.
177
+ return {
178
+ tools,
179
+ enableThinking: resolveEnableThinking(req),
180
+ reasoningEffort: qwenReasoningEffort(req.reasoning_effort),
181
+ preserveThinking: req.chat_template_kwargs?.preserve_thinking,
182
+ };
183
+ };
184
+
185
+ const promptIdsFor = (
186
+ req: ChatRequestParams,
187
+ tools: ToolDefinition[] | null,
188
+ ): { ids: number[]; startInThinking: boolean } => {
189
+ const opts = templateOptionsFor(req, tools);
190
+ const rendered = ctx.template.render(normalizeMessages(req.messages), opts);
191
+ const ids = ctx.tokenizer.encode(rendered);
192
+ // template includes <bos>; tokenizer post-processor also prepends one
193
+ const trimmed = ids[0] === ids[1] && ids[0] === ctx.tokenizer.bosTokenId ? ids.slice(1) : ids;
194
+ return { ids: trimmed, startInThinking: promptEndsInOpenThink(rendered) };
195
+ };
196
+
197
+ /** STABLE cache boundary: the prompt's tail is a generation primer (e.g.
198
+ * 12B's `<|channel>thought` tokens) that the NEXT turn's re-render does
199
+ * not contain — a cache entry ending there can never prefix-match again
200
+ * once the rings wrap. Probe by rendering this conversation as if a
201
+ * reply existed; the common prefix is what every future turn preserves.
202
+ * The probe is EXPENSIVE (a second full render + encode — ~150 ms at
203
+ * 16k with our tokenizer; 30-50% of the 12B ctx-repeat TTFT,
204
+ * 2026-07-06b), but the PRIMER LENGTH it measures is constant per
205
+ * template mode: the divergence sits after a special-token turn
206
+ * delimiter, and special tokens break BPE merges, so message content
207
+ * can't shift it. So the full probe runs ONCE per mode and later
208
+ * requests pay a subtraction. (A wrong boundary is a QUALITY knob, not
209
+ * a correctness one — any prefix ≤ len-1 is a valid snapshot point.) */
210
+ const primerLenByMode = new Map<string, number>();
211
+ const stableLenFor = (
212
+ req: ChatRequestParams,
213
+ tools: ToolDefinition[] | null,
214
+ trimmed: number[],
215
+ ): number => {
216
+ const opts = templateOptionsFor(req, tools);
217
+ const mode = `${opts.enableThinking}|${!!tools?.length}|${opts.reasoningEffort ?? ""}|${opts.preserveThinking ?? ""}`;
218
+ const primer = primerLenByMode.get(mode);
219
+ if (primer !== undefined) return Math.max(0, trimmed.length - primer);
220
+ let stableLen = trimmed.length;
221
+ try {
222
+ const probe = ctx.tokenizer.encode(
223
+ ctx.template.render(
224
+ [...normalizeMessages(req.messages), { role: "assistant", content: "x" }],
225
+ opts,
226
+ ),
227
+ );
228
+ const probeTrimmed =
229
+ probe[0] === probe[1] && probe[0] === ctx.tokenizer.bosTokenId ? probe.slice(1) : probe;
230
+ let i = 0;
231
+ const n = Math.min(trimmed.length, probeTrimmed.length);
232
+ while (i < n && trimmed[i] === probeTrimmed[i]) i++;
233
+ stableLen = i;
234
+ // Memoize the mode's primer length (sanity-capped: a "primer" longer
235
+ // than 64 tokens means the probe diverged for content reasons —
236
+ // don't generalize that).
237
+ const p = trimmed.length - stableLen;
238
+ if (p >= 0 && p <= 64) primerLenByMode.set(mode, p);
239
+ } catch { /* probe is best-effort; full boundary is the fallback */ }
240
+ return stableLen;
241
+ };
242
+
243
+ const toolStreamMode = (tools: ToolDefinition[] | null): ToolStreamMode =>
244
+ // Gemma-4 keeps the token-level sentinel path; every other family
245
+ // (MiniCPM5, Qwen3/3.5, Tier-0 generics) emits tool calls as DECODED TEXT
246
+ // — see selectToolStreamMode for why sentinel ids must stay family-gated.
247
+ selectToolStreamMode(ctx.model.config.modelType, !!tools?.length);
248
+
249
+ const toolRouter = (
250
+ tools: ToolDefinition[] | null,
251
+ onParseFailure?: () => void,
252
+ ): ToolAwareStream =>
253
+ new ToolAwareStream(ctx.tokenizer, toolStreamMode(tools), tools, onParseFailure);
254
+
255
+ /** Strict fill rows are a function of (template mode, tool signature) only —
256
+ * they are token spans of the assistant turn, which opens with a special
257
+ * token, so message content cannot shift them (the same argument that lets
258
+ * stableLenFor memoize its primer length). Compiling costs several template
259
+ * renders + encodes, so it happens ONCE per mode. */
260
+ const fillRowsByMode = new Map<string, StrictRowPlan>();
261
+ const toolsSignature = (tools: ToolDefinition[]): string =>
262
+ JSON.stringify(tools.map((t) => [
263
+ t.function?.name,
264
+ Object.keys((t.function?.parameters?.properties ?? {}) as Record<string, unknown>),
265
+ (t.function?.parameters?.required as unknown) ?? null,
266
+ (t.function?.parameters?.additionalProperties as unknown) ?? null,
267
+ ]));
268
+
269
+ /** Token fast-forwarding (docs/design/speculative-decoding.md "Token
270
+ * fast-forwarding"): compile this request's determined-span table, or null
271
+ * when the feature is off or the request's shape refuses it.
272
+ *
273
+ * Refusals owned here (body-level); the ones only ChatStage can see —
274
+ * a compiled grammar, media prompts, a mounted draft model, a quantized KV
275
+ * scheme — are applied there. Continuous (batch) placement needs no
276
+ * refusal: generate() is the only fill site, so a batched request simply
277
+ * does not fill.
278
+ *
279
+ * MLX_BUN_FILL=echo additionally arms the echo index (K3c, Lab tier): the
280
+ * strict rows keep policy "assert", and copied spans ride the same
281
+ * mechanism under policy "verify". */
282
+ const fillPlanFor = (
283
+ req: ChatRequestParams,
284
+ tools: ToolDefinition[] | null,
285
+ promptIds: number[],
286
+ ): FillSession | null => {
287
+ const mode = resolveFillMode();
288
+ if (mode === "off") return null;
289
+ if (!tools?.length) return null;
290
+ // A user-fixed seed means reproducibility: the sampler's step index would
291
+ // skip the injected positions, so an identical request could not replay.
292
+ if (req.seed !== undefined) return null;
293
+ // Injected tokens are never sampled — they have no logprob row.
294
+ if (req.logprobs === true) return null;
295
+ if (typeof req.top_logprobs === "number" && req.top_logprobs > 0) return null;
296
+ // Structured output owns forced-token production (grammar jump-forward).
297
+ if (req.response_format != null || req.guided_grammar || req.guided_regex ||
298
+ req.guided_choice?.length || req.structured_outputs != null) return null;
299
+
300
+ const opts = templateOptionsFor(req, tools);
301
+ const key = `${opts.enableThinking}|${opts.reasoningEffort ?? ""}|` +
302
+ `${opts.preserveThinking ?? ""}|${toolsSignature(tools)}`;
303
+ let plan = fillRowsByMode.get(key);
304
+ if (plan === undefined) {
305
+ plan = compileStrictFillRows({
306
+ template: ctx.template,
307
+ tokenizer: ctx.tokenizer,
308
+ messages: normalizeMessages(req.messages),
309
+ tools,
310
+ renderOptions: opts,
311
+ });
312
+ fillRowsByMode.set(key, plan);
313
+ }
314
+ // The echo tier can carry a request whose template yields no strict rows;
315
+ // strict-only mode cannot.
316
+ if (!plan.rows.length && mode !== "echo") return null;
317
+ return new FillSession(
318
+ {
319
+ rows: plan.rows,
320
+ echo: mode === "echo" ? fillEchoConfig() : null,
321
+ delimiters: new Set(plan.delimiters),
322
+ eos: ctx.model.config.eosTokenIds,
323
+ },
324
+ promptIds,
325
+ {
326
+ maxSpan: fillMaxSpan(), decode: (ids) => ctx.tokenizer.decode([...ids]),
327
+ strictContext: plan.createContext?.(promptIds),
328
+ },
329
+ );
330
+ };
331
+
332
+ return {
333
+ resolveEnableThinking,
334
+ toOptions,
335
+ compileGrammarForRequest,
336
+ templateOptionsFor,
337
+ promptIdsFor,
338
+ stableLenFor,
339
+ toolRouter,
340
+ fillPlanFor,
341
+ };
342
+ }
343
+
344
+ export type RequestPrep = ReturnType<typeof createRequestPrep>;
@@ -0,0 +1,24 @@
1
+ import type { DisposableResource } from "../contracts/resources";
2
+
3
+ /** Keep host activity owned through response EOF/error/cancel, without buffering
4
+ * the response. The parent additionally drains worker cleanup before GPU jobs. */
5
+ export function retainResponseLease(response: Response, lease: DisposableResource): Response {
6
+ if (!response.body) { lease.dispose(); return response; }
7
+ const reader = response.body.getReader();
8
+ let closed = false;
9
+ const release = () => {
10
+ if (closed) return;
11
+ closed = true;
12
+ reader.releaseLock(); lease.dispose();
13
+ };
14
+ return new Response(new ReadableStream<Uint8Array>({
15
+ async pull(controller) {
16
+ try {
17
+ const item = await reader.read();
18
+ if (item.done) { release(); controller.close(); }
19
+ else controller.enqueue(item.value);
20
+ } catch (error) { release(); controller.error(error); }
21
+ },
22
+ async cancel(reason) { try { await reader.cancel(reason); } finally { release(); } },
23
+ }), { status: response.status, statusText: response.statusText, headers: response.headers });
24
+ }
@@ -0,0 +1,69 @@
1
+ import { ResponseStore, resolveResponsesConversation, type ResponseHistory, type ResponsesRequest } from "../responses";
2
+
3
+ /** Application-owned conversation history over a data-only HTTP host. Workers
4
+ * translate/run the request; the parent stores only completed response records. */
5
+ export function createResponsesClient(store: ResponseHistory = new ResponseStore()) {
6
+ return {
7
+ get stats() { return { entries: store.size, bytes: store.totalBytes, max_bytes: store.maxBytes, ttl_ms: store.ttlMs }; },
8
+ async forward(request: Request, forward: (request: Request) => Promise<Response>): Promise<Response> {
9
+ const error = (status: number, message: string) => Response.json({ error: {
10
+ message, type: "invalid_request_error", param: null, code: null,
11
+ } }, { status });
12
+ let body: ResponsesRequest;
13
+ try { body = await request.json() as ResponsesRequest; }
14
+ catch { return error(400, "invalid JSON body"); }
15
+ if (!body || typeof body !== "object" || Array.isArray(body)) return error(400, "invalid JSON body");
16
+ let conversation: ReturnType<typeof resolveResponsesConversation>;
17
+ try { conversation = resolveResponsesConversation(body, store); }
18
+ catch (e) { return error(404, (e as Error).message); }
19
+ const headers = new Headers(request.headers);
20
+ headers.delete("content-length");
21
+ headers.set("x-mlx-bun-response-owner", "parent");
22
+ const response = await forward(new Request(request.url, {
23
+ method: "POST", headers, signal: request.signal,
24
+ body: JSON.stringify({ ...conversation.body, previous_response_id: undefined }),
25
+ }));
26
+ if (!response.ok) return response;
27
+ const remember = (value: Record<string, unknown>) => {
28
+ if (typeof value.id === "string" && Array.isArray(value.output))
29
+ store.put(value.id, { input: conversation.input, output: value.output, instructions: conversation.instructions });
30
+ };
31
+ const restorePrevious = (value: Record<string, unknown>) => ({ ...value, previous_response_id: conversation.previousId });
32
+ if (!body.stream) {
33
+ const value = await response.json() as Record<string, unknown>;
34
+ remember(value);
35
+ const headers = new Headers(response.headers); headers.delete("content-length");
36
+ return Response.json(restorePrevious(value), { status: response.status, headers });
37
+ }
38
+ if (!response.body) return response;
39
+ const decoder = new TextDecoder(), encoder = new TextEncoder();
40
+ let pending = "";
41
+ const rewrite = (frame: string): string => frame.replace(/^data: (.+)$/m, (line, data: string) => {
42
+ if (data === "[DONE]") return line;
43
+ const event = JSON.parse(data) as Record<string, unknown>;
44
+ if (event.response && typeof event.response === "object") {
45
+ const value = event.response as Record<string, unknown>;
46
+ if (event.type === "response.completed") remember(value);
47
+ event.response = restorePrevious(value);
48
+ }
49
+ return `data: ${JSON.stringify(event)}`;
50
+ });
51
+ const outputHeaders = new Headers(response.headers); outputHeaders.delete("content-length");
52
+ return new Response(response.body.pipeThrough(new TransformStream<Uint8Array, Uint8Array>({
53
+ transform(chunk, controller) {
54
+ pending += decoder.decode(chunk, { stream: true });
55
+ let split: number;
56
+ while ((split = pending.indexOf("\n\n")) !== -1) {
57
+ const frame = pending.slice(0, split);
58
+ pending = pending.slice(split + 2);
59
+ controller.enqueue(encoder.encode(`${rewrite(frame)}\n\n`));
60
+ }
61
+ },
62
+ flush(controller) {
63
+ pending += decoder.decode();
64
+ if (pending) controller.enqueue(encoder.encode(rewrite(pending)));
65
+ },
66
+ })), { status: response.status, headers: outputHeaders });
67
+ },
68
+ };
69
+ }