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
@@ -0,0 +1,164 @@
1
+ import { ensureWav } from "../../audio/transcode";
2
+ import { DiffusionGemmaModel } from "../../model/diffusion-gemma";
3
+ import { Gemma4Model } from "../../model/gemma4";
4
+ import { Qwen35Model } from "../../model/qwen3_5";
5
+ import { spliceImageTokens } from "../../vision/diffusion-vision";
6
+ import { buildMultimodalPrompt, buildVisionPrompt, extractAudio, extractImages, extractVideos,
7
+ type VisionEncoder, type VisionTokenIds } from "../../vision/prompt";
8
+ import { buildQwen3VLVisionPrompt } from "../../vision/qwen3vl-prompt";
9
+ import type { Qwen3VLVisionTower } from "../../vision/qwen3vl-tower";
10
+ import { normalizeMessages, type ChatRequestParams } from "../../serve/chat-request";
11
+ import { getAudioTower, getVisionTower, type ServerContext } from "../../serve/model-host";
12
+ import { RequestError } from "../../serve/pipeline";
13
+ import type { RequestOwnership } from "../../serve/request-plan";
14
+ import type { RequestPrep } from "../../serve/request-prep";
15
+ import type { BuiltPrompt } from "../../serve/model-binding";
16
+
17
+ export async function buildModelPrompt(
18
+ ctx: ServerContext,
19
+ prep: RequestPrep,
20
+ body: ChatRequestParams,
21
+ tools: ChatRequestParams["tools"] | null,
22
+ ownership: RequestOwnership,
23
+ ): Promise<BuiltPrompt> {
24
+ const toolList = tools ?? null;
25
+ const partOf = (types: string[]) => body.messages.some(
26
+ (m) => Array.isArray(m.content) &&
27
+ m.content.some((p: any) => types.includes(p.type)),
28
+ );
29
+ const hasImages = partOf(["image_url", "image"]);
30
+ // Same shapes extractAudio accepts: OpenAI-canonical input_audio plus
31
+ // optiq's audio / audio_url aliases (docs/design/generic-model-support.md §3.2).
32
+ const hasAudio = partOf(["input_audio", "audio", "audio_url"]);
33
+ // Video content parts (video_url / video with base64 data) —
34
+ // Qwen3.5-family only (decoded to sampled frames via the
35
+ // AVFoundation sidecar, vision/video-frames.ts).
36
+ const hasVideos = partOf(["video_url", "video"]);
37
+ const noMedia: Pick<BuiltPrompt, "startInThinking" | "probeStableLen" | "diffusionPixels"> =
38
+ { startInThinking: false, probeStableLen: false, diffusionPixels: null };
39
+
40
+ // Video is Qwen3.5-family only and never composes with audio —
41
+ // one early guard so no downstream branch can silently drop a
42
+ // video part (the gemma/diffusion builders don't know the type).
43
+ if (hasVideos && (hasAudio || !(ctx.model instanceof Qwen35Model))) {
44
+ throw new RequestError(400, hasAudio
45
+ ? "video and audio content parts cannot be combined"
46
+ : `model ${ctx.modelId} does not accept video input — video ` +
47
+ `content parts need a Qwen3.5-family model (e.g. Qwen3.8-27B)`);
48
+ }
49
+ if (hasAudio) {
50
+ // Audio (and MIXED image+audio) input — A4 of
51
+ // docs/design/generic-model-support.md. One buildMultimodalPrompt call
52
+ // splices both media kinds in document order. A request WITH
53
+ // audio on a model whose tower is unavailable is an explicit 400
54
+ // — never a silent text-only degrade (that leniency is only for
55
+ // requests without the media, getVisionTower's contract).
56
+ const audioTower = getAudioTower(ctx);
57
+ if (!audioTower || !ctx.audioTokenIds) {
58
+ throw new RequestError(400,
59
+ `model ${ctx.modelId} has no audio tower — audio input needs ` +
60
+ `a model whose config.json carries audio_config and whose ` +
61
+ `sidecar ships the audio tensors (e.g. gemma-4 e4b OptiQ)`);
62
+ }
63
+ let visionSide: { tower: VisionEncoder; tokenIds: VisionTokenIds } | undefined;
64
+ if (hasImages) {
65
+ const tower = getVisionTower(ctx);
66
+ if (!tower) throw new RequestError(400, "model has no vision sidecar");
67
+ visionSide = { tower, tokenIds: ctx.visionTokenIds };
68
+ }
69
+ const { messages: withAudioParts, images } =
70
+ await extractImages(normalizeMessages(body.messages));
71
+ const { messages, audio } = await extractAudio(withAudioParts);
72
+ // Non-WAV containers (mp3/m4a/flac/ogg/aiff/…) transcode to
73
+ // 16 kHz WAV via CoreAudio; RIFF bytes pass through untouched.
74
+ // Failures throw into the prompt-build 400.
75
+ const wavs = await Promise.all(audio.map(ensureWav));
76
+ // The towers are only ever non-null for Gemma4 (loader gates).
77
+ const mp = await buildMultimodalPrompt(
78
+ ctx.model as Gemma4Model,
79
+ {
80
+ ...(visionSide ? { vision: visionSide } : {}),
81
+ audio: { tower: audioTower, tokenIds: ctx.audioTokenIds },
82
+ },
83
+ ctx.tokenizer, ctx.template, messages, images, wavs, toolList,
84
+ );
85
+ // bidirMask is null whenever audio is present (§3.3 Q1: mixed
86
+ // prompts run fully causal); the union mask does the per-layer
87
+ // id zeroing either way.
88
+ return {
89
+ ...noMedia,
90
+ promptIds: mp.ids,
91
+ vision: {
92
+ embeddings: mp.embeddings,
93
+ ...(mp.bidirMask ? { imageMask: mp.bidirMask } : {}),
94
+ multimodalMask: mp.multimodalMask,
95
+ },
96
+ };
97
+ }
98
+ if (hasImages && ctx.model instanceof DiffusionGemmaModel) {
99
+ // DiffusionGemma image-text-to-text: its OWN dedicated SigLIP tower +
100
+ // encoder vision merge feed the denoising engine (NOT the AR
101
+ // forwardEmbeddings path). v1 supports a single image.
102
+ const dm = ctx.model;
103
+ if (!dm.visionTower) throw new RequestError(400, "this checkpoint has no vision tower");
104
+ const { messages, images } = await extractImages(normalizeMessages(body.messages));
105
+ if (images.length !== 1)
106
+ throw new RequestError(400, "DiffusionGemma image input supports exactly one image");
107
+ const rendered = ctx.template.render(messages, { tools: toolList, addGenerationPrompt: true });
108
+ const rawIds = ctx.tokenizer.encode(rendered, /* addSpecialTokens */ false);
109
+ const { pixels, softTokens } = await dm.visionTower.preprocess(images[0]!);
110
+ const diffusionPixels = ownership.own(pixels);
111
+ const promptIds = spliceImageTokens(rawIds, [softTokens], {
112
+ image: ctx.visionTokenIds.imageTokenId,
113
+ boi: ctx.visionTokenIds.boiTokenId,
114
+ eoi: ctx.visionTokenIds.eoiTokenId,
115
+ });
116
+ return { ...noMedia, promptIds, vision: undefined, diffusionPixels };
117
+ }
118
+ if ((hasImages || hasVideos) && ctx.model instanceof Qwen35Model) {
119
+ // Qwen3.8 vision + video: the tower is a Qwen3VLVisionTower
120
+ // riding the shared lazy slot (makeVisionLoader's qwen branch);
121
+ // image/video spans splice into input embeddings and the request
122
+ // carries mRoPE positions + delta (PLAN 14v/14w). Videos decode
123
+ // to sampled frames via the AVFoundation sidecar.
124
+ const tower = getVisionTower(ctx) as unknown as Qwen3VLVisionTower | null;
125
+ if (!tower) throw new RequestError(400, "model has no vision sidecar");
126
+ const { messages: withVideos, images } =
127
+ await extractImages(normalizeMessages(body.messages));
128
+ const { messages, videos } = await extractVideos(withVideos);
129
+ const vp = await buildQwen3VLVisionPrompt(
130
+ ctx.model, tower, ctx.tokenizer, ctx.template, messages, images,
131
+ {
132
+ imageTokenId: (ctx.model.config.raw.image_token_id as number) ?? 248056,
133
+ videoTokenId: (ctx.model.config.raw.video_token_id as number) ?? 248057,
134
+ visionStartId: (ctx.model.config.raw.vision_start_token_id as number) ?? 248053,
135
+ visionEndId: (ctx.model.config.raw.vision_end_token_id as number) ?? 248054,
136
+ },
137
+ prep.templateOptionsFor(body, toolList),
138
+ videos,
139
+ );
140
+ return { ...noMedia, promptIds: vp.ids, vision: { embeddings: vp.embeddings, mrope: vp.mrope } };
141
+ }
142
+ if (hasImages) {
143
+ // Loads (and caches) the tower on first image request — text-only
144
+ // sessions never pay for it.
145
+ const tower = getVisionTower(ctx);
146
+ if (!tower) throw new RequestError(400, "model has no vision sidecar");
147
+ const { messages, images } = await extractImages(normalizeMessages(body.messages));
148
+ // The tower is only ever non-null for Gemma4 (sidecar gate in
149
+ // makeVisionLoader), so the model narrow is safe here.
150
+ const vp = await buildVisionPrompt(
151
+ ctx.model as Gemma4Model, tower, ctx.tokenizer, ctx.template,
152
+ messages, images, ctx.visionTokenIds, toolList,
153
+ );
154
+ return { ...noMedia, promptIds: vp.ids, vision: { embeddings: vp.embeddings, imageMask: vp.imageMask } };
155
+ }
156
+ const text = prep.promptIdsFor(body, toolList);
157
+ return {
158
+ promptIds: text.ids,
159
+ vision: undefined,
160
+ startInThinking: text.startInThinking,
161
+ probeStableLen: true,
162
+ diffusionPixels: null,
163
+ };
164
+ }
@@ -0,0 +1,79 @@
1
+ import type { ServerContext, ServingContext } from "../../serve/model-host";
2
+ import type { ModelServingBinding } from "../../serve/model-binding";
3
+ import { Glm52Model } from "../../model/glm52";
4
+ import { embedMany, isEmbeddingModel } from "../../embed";
5
+ import { bindMlxGateway } from "./gateway-binding";
6
+ import { bindLegacySerialModel, createMlxSerialExecutor } from "./serial-executor";
7
+ import { buildModelPrompt } from "./model-prompt";
8
+ import { MLX_VERSION, deviceArchitecture } from "../../mlx/ffi";
9
+
10
+ export function modelPromptBuilder(context: ServingContext): ModelServingBinding["buildPrompt"] {
11
+ return context.serving?.buildPrompt ?? ((body, tools, ownership, prep) =>
12
+ buildModelPrompt(context as ServerContext, prep, body, tools, ownership));
13
+ }
14
+
15
+ /** Existing model classes enter the interface once here. New implementations
16
+ * provide ctx.serving directly and need no forward/makeCache methods. */
17
+ export function modelServingBinding(context: ServingContext): ModelServingBinding {
18
+ if (context.serving) return context.serving;
19
+ const ctx = context as ServerContext;
20
+ const model = ctx.model;
21
+ if (typeof model.makeCache !== "function" || typeof model.forward !== "function")
22
+ throw new Error("model implementation must supply its serving binding");
23
+ const serial = bindLegacySerialModel(model, ctx.draft ?? undefined);
24
+ const glm = model instanceof Glm52Model;
25
+ const embedding = isEmbeddingModel(model);
26
+ return {
27
+ stateCompatibility: `mlx-${MLX_VERSION}-${deviceArchitecture()}`,
28
+ gateway: bindMlxGateway(model),
29
+ createSerial: (services) => createMlxSerialExecutor(serial, services),
30
+ buildPrompt: (body, tools, ownership, prep) => buildModelPrompt(ctx, prep, body, tools, ownership),
31
+ restore: (store, entry) => store.restore(entry, model),
32
+ async signal(ids, count, minimum) {
33
+ const caches = model.makeCache();
34
+ try {
35
+ const logits = model.forward(ids, caches);
36
+ let f: Float32Array;
37
+ let vocab: number;
38
+ try {
39
+ const [, length, width] = logits.shape as [number, number, number];
40
+ vocab = width;
41
+ const last = logits.slice([0, length - 1, 0], [1, length, width]);
42
+ try { f = last.toFloat32(); } finally { last.dispose(); }
43
+ } finally { logits.dispose(); }
44
+ let mx = -Infinity;
45
+ for (const value of f) if (value > mx) mx = value;
46
+ let sum = 0;
47
+ for (const value of f) sum += Math.exp(value - mx);
48
+ const lse = mx + Math.log(sum);
49
+ const bins = new Array<number>(count).fill(0);
50
+ for (const value of f) {
51
+ const t = Math.max(0, Math.min(1, (value - lse - minimum) / -minimum));
52
+ const bin = Math.min(count - 1, Math.floor(t * count));
53
+ bins[bin] = (bins[bin] ?? 0) + 1;
54
+ }
55
+ return { bins, vocab };
56
+ } finally { for (const cache of caches) cache.dispose(); }
57
+ },
58
+ discovery: { adapters: !glm, training: !glm, dsa: glm && model.capabilities.dsa, embeddings: embedding },
59
+ ...(embedding ? { embed: (inputs: string[], instruction?: string) => embedMany(model, ctx.tokenizer, inputs, instruction) } : {}),
60
+ diagnostics() {
61
+ const plan = ctx.glmMemoryPlan;
62
+ if (!plan) return {};
63
+ const runtime = glm ? model.expertRuntime : undefined;
64
+ return { glm52: {
65
+ preset: plan.preset, planned_process_bytes: plan.plannedProcessBytes,
66
+ process_limit_bytes: plan.processLimitBytes, context_tokens: plan.contextTokens,
67
+ max_generation_tokens: plan.maxGenerationTokens, batch_size: plan.batchSize,
68
+ dsa: glm && model.capabilities.dsa, mtp: ctx.draft?.provider.id === "glm52-native-mtp",
69
+ mtp_draft_tokens: plan.mtpDraftTokens, resident_weight_bytes: plan.lineItems.residentWeightsBytes,
70
+ main_expert_slab_bytes: plan.lineItems.mainExpertSlabBytes,
71
+ mtp_expert_slab_bytes: plan.lineItems.mtpExpertSlabBytes,
72
+ expert_runtime: runtime ? {
73
+ main_residency: runtime.manager.snapshot(), mtp_residency: runtime.mtp?.manager.snapshot() ?? null,
74
+ last_turn: runtime.lastTelemetry, last_repin: runtime.lastRepin,
75
+ } : null,
76
+ } };
77
+ },
78
+ };
79
+ }
@@ -0,0 +1,33 @@
1
+ import type { PrefillStep } from "../../inference/prefill";
2
+ import type { MlxArray } from "../../mlx/array";
3
+ import type { Cache } from "../../model/gemma4";
4
+ import * as ops from "../../mlx/ops";
5
+ import { clearCache } from "../../mlx/ffi";
6
+ import { leaseCacheStates } from "./state-views";
7
+ import { withResource } from "../../engine/resources";
8
+
9
+ /** State views with explicit ownership are released even on deferred errors.
10
+ * Owned views precede borrowed state, preserving the serial eval ordering. */
11
+ export function evalCacheState(cache: Cache[]): void {
12
+ withResource(leaseCacheStates(cache), (state) => ops.evalAll([...state]));
13
+ }
14
+
15
+ /** Forward once. Drains evaluate state, apply KV maintenance, then clear the
16
+ * allocator cache. Finals return owned hidden state without forcing eval. */
17
+ export async function executeMlxPrefillStep(
18
+ forward: (ids: MlxArray, cache: Cache[]) => MlxArray | Promise<MlxArray>,
19
+ cache: Cache[], prompt: readonly number[], step: PrefillStep,
20
+ maintain: () => void,
21
+ ): Promise<MlxArray | null> {
22
+ const chunk = prompt.slice(step.start, step.end);
23
+ const ids = ops.fromInt32(chunk, [1, chunk.length]);
24
+ let hidden: MlxArray;
25
+ try { hidden = await forward(ids, cache); }
26
+ finally { ids.dispose(); }
27
+ if (step.kind === "final") return hidden;
28
+ hidden.dispose();
29
+ evalCacheState(cache);
30
+ maintain();
31
+ clearCache();
32
+ return null;
33
+ }
@@ -0,0 +1,20 @@
1
+ import type { GenerateOptions } from "../../generate";
2
+
3
+ /** Copy policy data without cloning native caches, controllers or callbacks. */
4
+ export function snapshotGenerationPolicy<T extends GenerateOptions & { stopSequences?: readonly string[] }>(options: T): T {
5
+ const snapshot = { ...options };
6
+ const freeze = (value: unknown): void => {
7
+ if (!value || typeof value !== "object") return;
8
+ for (const item of Object.values(value)) freeze(item);
9
+ Object.freeze(value);
10
+ };
11
+ for (const key of ["stopSequences", "eosTokenIds", "adapters", "xtcSpecialTokens", "logitBias",
12
+ "kvConfig", "turboQuant", "pagedKv", "hlg", "curve", "decodePolicy"] as const) {
13
+ if (snapshot[key] === undefined) continue;
14
+ const value = structuredClone(snapshot[key]);
15
+ freeze(value);
16
+ Object.assign(snapshot, { [key]: value });
17
+ }
18
+ return Object.freeze(snapshot);
19
+ }
20
+
@@ -0,0 +1,51 @@
1
+ import type { SpeculativeTransaction } from "../../inference/rollback";
2
+ import type { Cache } from "../../model/gemma4-base";
3
+
4
+ /** Bind the full legacy recurrent transaction or the trim implementation once.
5
+ * Ring-capacity checks still happen at each safe boundary before any writes. */
6
+ export function bindCacheRollback(caches: readonly Cache[]): SpeculativeTransaction {
7
+ const layers = caches.map((cache) => {
8
+ const { specRoundBegin: begin, specRoundCommit: commit, specRoundRollback: rollback } = cache;
9
+ const hasRound = [begin, commit, rollback].some((method) => method !== undefined);
10
+ if (hasRound && !(typeof begin === "function" && typeof commit === "function" && typeof rollback === "function"))
11
+ throw new Error("cache exposes an incomplete speculative transaction");
12
+ return {
13
+ fits(drafts: number) {
14
+ if ("maxSize" in cache && typeof cache.maxSize === "number")
15
+ return cache.offset + drafts + 1 < cache.maxSize;
16
+ return hasRound || cache.isTrimmable();
17
+ },
18
+ begin: () => begin?.call(cache),
19
+ resolve(accepted: number, drafts: number) {
20
+ if (accepted < drafts) {
21
+ if (rollback) rollback.call(cache, accepted + 1);
22
+ else cache.trim(drafts - accepted);
23
+ } else commit?.call(cache);
24
+ },
25
+ };
26
+ });
27
+ let active: number | undefined;
28
+ let invalid = false;
29
+ const validate = (drafts: number) => {
30
+ if (invalid) throw new Error("speculative state is invalid and must be discarded");
31
+ if (!Number.isSafeInteger(drafts) || drafts < 0) throw new Error("invalid speculative draft count");
32
+ };
33
+ return {
34
+ canBegin(drafts) { validate(drafts); return active === undefined && layers.every((layer) => layer.fits(drafts)); },
35
+ begin(drafts) {
36
+ validate(drafts);
37
+ if (active !== undefined || !layers.every((layer) => layer.fits(drafts)))
38
+ throw new Error("speculative state cannot begin this round");
39
+ active = drafts;
40
+ try { for (const layer of layers) layer.begin(); }
41
+ catch (error) { invalid = true; throw error; }
42
+ },
43
+ resolve(accepted) {
44
+ validate(accepted);
45
+ if (active === undefined || accepted > active) throw new Error("invalid speculative acceptance count");
46
+ try { for (const layer of layers) layer.resolve(accepted, active); }
47
+ catch (error) { invalid = true; throw error; }
48
+ active = undefined;
49
+ },
50
+ };
51
+ }
@@ -0,0 +1,273 @@
1
+ import { cleanupFailure, disposeResources, ownResource } from "../../engine/resources";
2
+ import { bindGeneration } from "../../generate";
3
+ import type { RuntimeModel } from "../../model/factory";
4
+ import { Qwen35Model } from "../../model/qwen3_5";
5
+ import type { Cache } from "../../model/gemma4";
6
+ import type { PromptCache } from "../../prompt-cache";
7
+ import type { SsdCacheStore } from "../../ssd-cache";
8
+ import type { DraftProvider } from "../../spec/source";
9
+ import type { SerialRun, Vision } from "../../serve/generation-gateway";
10
+ import { generationCheckpointKey } from "../../serve/checkpoint-identity";
11
+ import { runtimeConfig, withRuntimeConfig, type RuntimeConfig } from "../../runtime-config";
12
+ import { bindLegacySpeculativeModel } from "./speculative";
13
+ import { enterMlxMemoryGuard, type MlxMemoryBudget } from "./memory-guard";
14
+
15
+ /** Native serial execution depends on this bound port, never a model union.
16
+ * Weights remain borrowed. The gateway supplies the exclusive runtime lease. */
17
+ export interface MlxSerialBinding {
18
+ readonly runtime: RuntimeConfig;
19
+ readonly generate: ReturnType<typeof bindGeneration>;
20
+ readonly speculate?: SerialRun;
21
+ makeCache(): Cache[];
22
+ enterMedia?(vision?: Vision): () => void;
23
+ enterMemoryGuard?(budget: SerialMemoryBudget, promptTokens: number,
24
+ prefillChunkSize?: number): { check(): void; close(): void };
25
+ }
26
+
27
+ /** Admission's ceiling and the cache that can release memory under the GPU lease. */
28
+ export type SerialMemoryBudget = MlxMemoryBudget;
29
+
30
+ /** Family-specific context is bound once at the compatibility boundary. */
31
+ export function bindLegacySerialModel(
32
+ model: RuntimeModel,
33
+ draft?: { provider: DraftProvider; numDraftTokens: number },
34
+ ): MlxSerialBinding {
35
+ const speculative = draft ? bindLegacySpeculativeModel(model, draft.provider) : undefined;
36
+ return {
37
+ runtime: runtimeConfig(),
38
+ generate: bindGeneration(model),
39
+ makeCache: model.makeCache.bind(model),
40
+ enterMemoryGuard(budget, promptTokens, prefillChunkSize) {
41
+ return enterMlxMemoryGuard(model, budget, promptTokens, prefillChunkSize);
42
+ },
43
+ ...(speculative && draft ? { speculate: (async (prompt, options, onToken) => {
44
+ const { specRun } = await import("../../spec/serve-loop");
45
+ return specRun(speculative, draft.numDraftTokens, prompt, options, onToken);
46
+ }) satisfies SerialRun } : {}),
47
+ ...(model instanceof Qwen35Model ? { enterMedia(vision?: Vision) {
48
+ const previous = model.mrope;
49
+ if (vision?.mrope) model.mrope = vision.mrope;
50
+ return () => { model.mrope = previous; };
51
+ } } : {}),
52
+ };
53
+ }
54
+
55
+ export interface MlxSerialServices {
56
+ readonly promptCache: Pick<PromptCache, "take" | "put"> & Partial<Pick<PromptCache, "maxBytes">>;
57
+ readonly checkpoints: Pick<SsdCacheStore, "findGenerationCheckpoint" | "restore" |
58
+ "storeGenerationCheckpoint" | "removeGenerationCheckpoints"> | null;
59
+ readonly checkpointEveryTokens?: number;
60
+ readonly memoryBudget?: SerialMemoryBudget;
61
+ /** Artifact, implementation, state ABI and codec identity captured at load. */
62
+ readonly identity: unknown;
63
+ adapterNamespace(adapters: string[]): string;
64
+ cloneState(caches: Cache[]): Cache[];
65
+ }
66
+
67
+ /** Cache lookup, replay, prompt snapshots and checkpoint persistence belong to
68
+ * native execution. HTTP only composes this service and frames its output.
69
+ * The caller transfers prepared native inputs; caches created/taken here are
70
+ * owned until returned to the prefix store or disposed after execution. */
71
+ export function createMlxSerialExecutor(binding: MlxSerialBinding, services: MlxSerialServices): SerialRun {
72
+ const { promptCache, checkpoints: ssdStore } = services;
73
+ return (promptIds, options, onToken, vision, trace, execution) => withRuntimeConfig(binding.runtime, async () => {
74
+ let caches: Cache[] = [];
75
+ let retain: (() => void) | undefined;
76
+ let closeMedia: (() => void) | undefined;
77
+ let memoryGuard: ReturnType<NonNullable<MlxSerialBinding["enterMemoryGuard"]>> | undefined;
78
+ const cleanup = ownResource(null, () => disposeResources([
79
+ { dispose() {
80
+ disposeResources(caches);
81
+ retain?.(); // backing release requires successful cache disposal
82
+ } },
83
+ { dispose: () => closeMedia?.() },
84
+ { dispose: () => memoryGuard?.close() },
85
+ ...[options.grammar, vision?.embeddings, vision?.imageMask,
86
+ vision?.multimodalMask, options.visionPixels].filter((value) => value != null),
87
+ ]));
88
+ try {
89
+ options.signal?.throwIfAborted();
90
+ if (!execution) throw new Error("serial execution requires a resolved plan");
91
+ if (execution.method === "speculative") {
92
+ if (!binding.speculate) throw new Error("resolved speculation requires a bound verifier");
93
+ return await binding.speculate(promptIds, {
94
+ ...options,
95
+ speculativeCacheBytes: binding.runtime.flag("MLX_BUN_MTP_PROMPT_CACHE", false)
96
+ ? services.promptCache.maxBytes ?? 0 : 0,
97
+ }, onToken);
98
+ }
99
+ // Cache entries are adapter-specific: KV computed under one adapter
100
+ // must never seed another's (or the base's) prefill.
101
+ const cacheNs = options.adapters?.length ? services.adapterNamespace(options.adapters) : "";
102
+ // Paged-KV request scope (docs/design/kv-cache.md): media
103
+ // prompts (bidir overlay) and LoRA-adapter requests are v1 non-goals —
104
+ // they run the PLAIN cache path even under --paged-kv (scope the flag
105
+ // per request, never 400). Effective value computed ONCE so the
106
+ // prompt-cache bypass below and the generate() options can't disagree.
107
+ const pagedKv = execution.pagedKv ? options.pagedKv : undefined;
108
+ // Paged requests bypass the prompt cache entirely (v1 non-goal:
109
+ // PagedKVCache has no cloneKvCaches/restore path — the vision
110
+ // precedent). Fresh caches per request, disposed on completion.
111
+ const skipPromptCache = !execution.promptCache;
112
+ const checkpointEvery = services.checkpointEveryTokens;
113
+ const checkpointEligible = execution.checkpoint;
114
+ const checkpointKey = checkpointEligible
115
+ ? generationCheckpointKey(promptIds, options, cacheNs, execution, services.identity)
116
+ : null;
117
+ // Both tiers in one call (Layer 0): take() prefers a strictly-longer
118
+ // SSD prefix, restores it zero-copy, and trims — see PromptCache.take.
119
+ const closeCacheLookup = trace?.begin("cache.lookup_restore", {
120
+ mechanism: "serial",
121
+ bypassed: skipPromptCache,
122
+ });
123
+ const checkpointEntry = checkpointKey
124
+ ? ssdStore!.findGenerationCheckpoint(promptIds, checkpointKey, cacheNs)
125
+ : null;
126
+ const restoredCheckpoint = checkpointEntry
127
+ ? ssdStore!.restore(checkpointEntry, binding)
128
+ : null;
129
+ caches = restoredCheckpoint?.caches ?? [];
130
+ const checkpoint = restoredCheckpoint?.header.generationCheckpoint;
131
+ if (restoredCheckpoint && !checkpoint)
132
+ throw new Error("restored generation checkpoint has no continuation metadata");
133
+ const resuming = Boolean(restoredCheckpoint && checkpoint);
134
+ const generationPromptIds = resuming ? restoredCheckpoint!.tokens : promptIds;
135
+ const entry = skipPromptCache || resuming
136
+ ? null
137
+ : promptCache.take(promptIds, cacheNs);
138
+ if (entry) { caches = entry.caches; retain = entry.retain; }
139
+ if (!restoredCheckpoint && !entry) caches = binding.makeCache();
140
+ closeCacheLookup?.();
141
+ // Prompt-boundary snapshot (the multi-turn agent fix, 2026-07-04): the
142
+ // prompt+gen entry put() below is UNTRIMMABLE at context > sliding
143
+ // window (wrapped rings) and under quantized KV (mid-group), so any
144
+ // decode→encode roundtrip drift in the reply the client sends back
145
+ // turns the next turn into a total miss (measured: 12B turn-2 TTFT
146
+ // 8.9 s instead of ~0.2 s). A prompt-ONLY entry is always an exact
147
+ // prefix of the next turn's rendering regardless of reply drift.
148
+ // Zero-copy (cloneKvCaches = slice views); only for substantial cold
149
+ // prefills, where the re-prefill it saves is worth an extra entry.
150
+ // The oracle invariant (mlx-lm insert_segments): a trim-free STRICT
151
+ // prefix of the prompt exists for EVERY substantial request — cap the
152
+ // boundary at len-1 so even a stableLen == len prompt (e4b: the
153
+ // template tail survives the probe render) snapshots prompt[:-1]. An
154
+ // exact repeat then matches with trimNeeded == 0, bypassing
155
+ // isTrimmable() entirely — the only reuse path a wrapped ring has.
156
+ const boundary = Math.min(options.snapshotAt ?? promptIds.length, promptIds.length - 1);
157
+ // Re-snapshot on EVERY substantial request whose stable boundary extends
158
+ // past the cached prefix; the clone is zero-copy views, so re-putting
159
+ // is ~free.
160
+ const snapshotBoundary =
161
+ !skipPromptCache && !resuming && boundary >= 256 &&
162
+ boundary > (entry?.tokens.length ?? 0);
163
+ if (services.memoryBudget) {
164
+ memoryGuard = binding.enterMemoryGuard?.(
165
+ services.memoryBudget, generationPromptIds.length, options.prefillChunkSize,
166
+ );
167
+ memoryGuard?.check();
168
+ }
169
+ closeMedia = binding.enterMedia?.(vision);
170
+ if (resuming) {
171
+ const replay = generationPromptIds.slice(promptIds.length);
172
+ console.log(
173
+ `[generation-checkpoint] resuming ${checkpointKey} at ` +
174
+ `${replay.length} emitted tokens`,
175
+ );
176
+ for (const token of replay) {
177
+ options.signal?.throwIfAborted();
178
+ if ((await onToken(token)) === false)
179
+ throw new Error("saved generation prefix triggered a terminal stop while replaying");
180
+ }
181
+ }
182
+ const gen = binding.generate(generationPromptIds, {
183
+ ...options,
184
+ decodePolicy: execution,
185
+ ...(resuming ? { seed: checkpoint!.seed } : {}),
186
+ fill: execution.fill ? options.fill : undefined,
187
+ pagedKv, // request-scoped (undefined strips the server-wide flag)
188
+ cache: caches,
189
+ ...(resuming
190
+ ? {
191
+ initialPendingToken: checkpoint!.pendingToken,
192
+ initialGeneratedTokens: checkpoint!.generatedTokens,
193
+ originalPromptTokens: checkpoint!.originalPromptTokens,
194
+ }
195
+ : {}),
196
+ ...(checkpointEligible
197
+ ? {
198
+ checkpointEveryTokens: checkpointEvery,
199
+ onDecodeCheckpoint: async (state: {
200
+ cacheTokens: number[];
201
+ caches: Cache[];
202
+ generatedTokens: number;
203
+ pendingToken: number;
204
+ }) => {
205
+ const stored = await ssdStore!.storeGenerationCheckpoint(
206
+ state.cacheTokens,
207
+ state.caches,
208
+ {
209
+ key: checkpointKey!,
210
+ cacheNs,
211
+ originalPromptTokens: promptIds.length,
212
+ generatedTokens: state.generatedTokens,
213
+ pendingToken: state.pendingToken,
214
+ seed: options.seed ?? 0,
215
+ seedWasExplicit: options.seedWasExplicit === true,
216
+ },
217
+ );
218
+ if (stored)
219
+ console.log(
220
+ `[generation-checkpoint] saved ${state.generatedTokens} emitted tokens`,
221
+ );
222
+ },
223
+ }
224
+ : {}),
225
+ ...(snapshotBoundary
226
+ ? {
227
+ // snapshotAt MUST travel with the hook: generate() splits the
228
+ // prefill at exactly this many tokens and fires the hook while
229
+ // the caches hold exactly that prefix — putting boundary
230
+ // tokens against caches at any other offset is silent KV
231
+ // corruption.
232
+ snapshotAt: boundary,
233
+ onPrefillDone: () => {
234
+ try {
235
+ const snapshot = ownResource(services.cloneState(caches), disposeResources);
236
+ try {
237
+ promptCache.put(promptIds.slice(0, boundary), snapshot.borrow(), cacheNs);
238
+ snapshot.transfer();
239
+ } finally { snapshot.close(); }
240
+ } catch (e) {
241
+ console.warn(`prompt-boundary snapshot skipped: ${(e as Error).message}`);
242
+ }
243
+ },
244
+ }
245
+ : {}),
246
+ ...(vision
247
+ ? {
248
+ promptEmbeddings: vision.embeddings,
249
+ ...(vision.imageMask ? { imageMask: vision.imageMask } : {}),
250
+ ...(vision.multimodalMask ? { multimodalMask: vision.multimodalMask } : {}),
251
+ }
252
+ : {}),
253
+ }, { trace, mechanism: "serial" });
254
+ for await (const t of gen) {
255
+ if (t.index % 256 === 0) memoryGuard?.check();
256
+ if ((await onToken(t.token, t.logprobs)) === false) break;
257
+ }
258
+ const s = gen.stats!; // set on completion AND on early break
259
+ if (checkpointKey) ssdStore!.removeGenerationCheckpoints(checkpointKey);
260
+ if (!skipPromptCache) {
261
+ // put() fires onPut → the debounced write-behind SSD snapshot
262
+ // (wired below), covering the batch lane's puts too.
263
+ promptCache.put(s.cacheTokens, caches, cacheNs, retain);
264
+ caches = []; retain = undefined; // ownership returned to the prefix store
265
+ }
266
+ return s;
267
+ } catch (error) {
268
+ cleanupFailure(error, () => cleanup.close());
269
+ } finally {
270
+ cleanup.close();
271
+ }
272
+ });
273
+ }