mlx-bun 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (433) hide show
  1. package/README.md +71 -588
  2. package/docs/README.md +94 -0
  3. package/docs/{design → archive/investigations}/audio-input-plan.md +4 -4
  4. package/docs/{design → archive/investigations}/diffusion-gemma-port.md +3 -3
  5. package/docs/{design → archive/investigations}/faithful-l1-consolidation.md +4 -4
  6. package/docs/{design → archive/investigations}/minicpm5-decode-megakernel.md +11 -11
  7. package/docs/archive/investigations/openjarvis-fit.md +198 -0
  8. package/docs/{design → archive/investigations}/optimization_plan.md +1 -1
  9. package/docs/{design → archive/investigations}/pre-colibri-stabilization.md +5 -5
  10. package/docs/archive/investigations/qwen38-27b-campaign-log.md +1092 -0
  11. package/docs/{design → archive/investigations}/the-dreaming-handoff.md +2 -2
  12. package/docs/archive/planning/release-notes-v0.3.0.md +35 -0
  13. package/docs/design/batching.md +1841 -0
  14. package/docs/design/decode-speed-program.md +2346 -169
  15. package/docs/design/dreaming-nightly-pipeline.md +675 -282
  16. package/docs/design/generic-model-support.md +655 -334
  17. package/docs/design/kv-cache.md +824 -0
  18. package/docs/design/orpo-training.md +673 -1239
  19. package/docs/design/speculative-decoding.md +1990 -0
  20. package/docs/design/turboquant.md +2212 -0
  21. package/docs/design/unified-engine-frontier-plan.md +1833 -727
  22. package/docs/design/web-chat-redesign.md +666 -1083
  23. package/docs/planning/PRODUCT_ROADMAP.md +50 -1
  24. package/docs/planning/release-notes-v0.4.0.md +45 -0
  25. package/docs/planning/video-series.md +2 -2
  26. package/docs/reference/benchmarks.md +3613 -0
  27. package/docs/reference/cli.md +434 -160
  28. package/docs/reference/distribution.md +328 -182
  29. package/docs/reference/environment.md +398 -0
  30. package/docs/reference/glossary.md +39 -0
  31. package/docs/reference/library-api.md +566 -168
  32. package/docs/reference/memory.md +227 -64
  33. package/docs/reference/models.md +113 -6
  34. package/docs/reference/server-api.md +944 -429
  35. package/docs/reference/server-config.md +740 -338
  36. package/docs/reference/training.md +393 -254
  37. package/docs/reference/troubleshooting.md +158 -0
  38. package/package.json +9 -4
  39. package/src/audio/decode.ts +2 -2
  40. package/src/audio/features.ts +2 -2
  41. package/src/backends/mlx/assistant-target.ts +46 -0
  42. package/src/backends/mlx/autoregressive-draft-rows.ts +120 -0
  43. package/src/backends/mlx/autoregressive.ts +108 -0
  44. package/src/backends/mlx/batch-group.ts +1790 -0
  45. package/src/backends/mlx/cache-attachment.ts +67 -0
  46. package/src/backends/mlx/cache-layout.ts +31 -0
  47. package/src/backends/mlx/checkpoint-state.ts +53 -0
  48. package/src/backends/mlx/continuation-persistence.ts +106 -0
  49. package/src/backends/mlx/continuation-request.ts +43 -0
  50. package/src/backends/mlx/continuation.ts +77 -0
  51. package/src/backends/mlx/diffusion.ts +39 -0
  52. package/src/backends/mlx/draft-target.ts +32 -0
  53. package/src/backends/mlx/evaluation.ts +105 -0
  54. package/src/backends/mlx/fill-append.ts +45 -0
  55. package/src/backends/mlx/full-kv-row-append.ts +32 -0
  56. package/src/backends/mlx/full-kv-row-donor.ts +54 -0
  57. package/src/backends/mlx/full-transitioning-kv-rows.ts +65 -0
  58. package/src/backends/mlx/gateway-binding.ts +130 -0
  59. package/src/backends/mlx/graph.ts +38 -0
  60. package/src/backends/mlx/kv-codec.ts +9 -0
  61. package/src/backends/mlx/kv-maintenance.ts +133 -0
  62. package/src/backends/mlx/kv-tensor-rows.ts +212 -0
  63. package/src/backends/mlx/memory-client.ts +189 -0
  64. package/src/backends/mlx/methods.ts +90 -0
  65. package/src/backends/mlx/model-prompt.ts +164 -0
  66. package/src/backends/mlx/model-serving.ts +79 -0
  67. package/src/backends/mlx/prefill-cohort.ts +89 -0
  68. package/src/backends/mlx/prefill-rows.ts +200 -0
  69. package/src/backends/mlx/prefill.ts +33 -0
  70. package/src/backends/mlx/projected-context-rows.ts +72 -0
  71. package/src/backends/mlx/request-policy.ts +20 -0
  72. package/src/backends/mlx/request-state-policy.ts +41 -0
  73. package/src/backends/mlx/rollback.ts +89 -0
  74. package/src/backends/mlx/row-sampling.ts +49 -0
  75. package/src/backends/mlx/serial-executor.ts +242 -0
  76. package/src/backends/mlx/speculative-group.ts +359 -0
  77. package/src/backends/mlx/speculative-round.ts +161 -0
  78. package/src/backends/mlx/speculative.ts +114 -0
  79. package/src/backends/mlx/state-rows.ts +104 -0
  80. package/src/backends/mlx/state-views.ts +46 -0
  81. package/src/backends/mlx/target-layout-capability.ts +35 -0
  82. package/src/backends/mlx/text-engine.ts +73 -0
  83. package/src/backends/mlx/transitioning-kv-rows.ts +165 -0
  84. package/src/cli.ts +66 -58
  85. package/src/client.ts +66 -0
  86. package/src/config.ts +21 -4
  87. package/src/contracts/completion.ts +16 -0
  88. package/src/contracts/execution.ts +60 -0
  89. package/src/contracts/generation.ts +103 -0
  90. package/src/contracts/host.ts +7 -0
  91. package/src/contracts/jobs.ts +66 -0
  92. package/src/contracts/pi-provider.ts +30 -0
  93. package/src/contracts/pi.ts +259 -0
  94. package/src/contracts/prefix-cache.ts +23 -0
  95. package/src/contracts/resources.ts +25 -0
  96. package/src/contracts/sampling.ts +23 -0
  97. package/src/contracts/scheduling.ts +45 -0
  98. package/src/contracts/tsconfig.json +14 -0
  99. package/src/diffusion/diffusion-generate.ts +218 -153
  100. package/src/embed.ts +1 -1
  101. package/src/engine/admission.ts +59 -0
  102. package/src/engine/cancellation.ts +38 -0
  103. package/src/engine/engine.ts +56 -0
  104. package/src/engine/execution-coordinator.ts +70 -0
  105. package/src/engine/execution-plan.ts +53 -0
  106. package/src/engine/resources.ts +53 -0
  107. package/src/engine/scheduler.ts +72 -0
  108. package/src/engine/session.ts +247 -0
  109. package/src/eval/README.md +44 -1
  110. package/src/eval/kl.ts +236 -3
  111. package/src/eval/reference-logits.ts +207 -0
  112. package/src/eval/runner.ts +8 -96
  113. package/src/fill/echo-index.ts +188 -0
  114. package/src/fill/fill-session.ts +452 -0
  115. package/src/fill/proposal.ts +68 -0
  116. package/src/fill/schema-rows.ts +506 -0
  117. package/src/fill/tool-boundary.ts +103 -0
  118. package/src/fit.ts +30 -16
  119. package/src/generate.ts +709 -417
  120. package/src/grammar.ts +12 -7
  121. package/src/harness-pi.ts +14 -33
  122. package/src/hub-rest.ts +4 -4
  123. package/src/index.ts +7 -0
  124. package/src/inference/denoising.ts +17 -0
  125. package/src/inference/draft-acceptance.ts +41 -0
  126. package/src/inference/draft-output.ts +49 -0
  127. package/src/inference/graph.ts +33 -0
  128. package/src/inference/prefill.ts +39 -0
  129. package/src/inference/rollback.ts +7 -0
  130. package/src/jobs/db.ts +4 -0
  131. package/src/jobs/index.ts +2 -0
  132. package/src/jobs/job-entry.ts +12 -12
  133. package/src/jobs/runner.ts +98 -13
  134. package/src/jobs/task-client.ts +19 -0
  135. package/src/jobs/types.ts +2 -58
  136. package/src/kv-scheme.ts +70 -12
  137. package/src/kv-store.ts +177 -63
  138. package/src/lab/README.md +18 -0
  139. package/src/{assets → lab/curve}/curve-designer.html +2 -2
  140. package/src/{curve-sampler.ts → lab/curve/curve-sampler.ts} +2 -2
  141. package/src/{expert-trace.ts → lab/expert-trace/expert-trace.ts} +5 -5
  142. package/src/lab/paged-kv/paged-kv-rows.ts +83 -0
  143. package/src/{model → lab/paged-kv}/paged-kv.ts +32 -15
  144. package/src/library.ts +40 -0
  145. package/src/lora.ts +18 -0
  146. package/src/media-fetch.ts +1 -1
  147. package/src/memory/article.ts +1 -1
  148. package/src/memory/chunk.ts +1 -1
  149. package/src/memory/model.ts +30 -169
  150. package/src/memory/prompts.ts +1 -1
  151. package/src/memory/rest.ts +1 -1
  152. package/src/memory/schedule.ts +1 -1
  153. package/src/memory/stages.ts +4 -1
  154. package/src/memory/vault.ts +1 -3
  155. package/src/mlx/array.ts +11 -11
  156. package/src/mlx/compile.ts +12 -2
  157. package/src/mlx/ffi.ts +58 -11
  158. package/src/mlx/materialize.ts +23 -0
  159. package/src/mlx/metal-kernel.ts +10 -11
  160. package/src/mlx/ops.ts +33 -13
  161. package/src/mlx/quantized-rows.ts +36 -0
  162. package/src/mlx/token-bitmask.ts +52 -0
  163. package/src/mlx/turboquant-kv-decode.ts +82 -0
  164. package/src/mlx/turboquant-kv-inverse.ts +122 -0
  165. package/src/mlx/turboquant-ops.ts +33 -3
  166. package/src/mlx/turboquant-tables.ts +2 -2
  167. package/src/model/artifact-identity.ts +73 -0
  168. package/src/model/batched-kv.ts +235 -0
  169. package/src/model/batched-mask.ts +20 -6
  170. package/src/model/batched-quant.ts +9 -3
  171. package/src/model/batched-quantized-kv.ts +144 -0
  172. package/src/model/batched-rotating-quant.ts +78 -17
  173. package/src/model/batched-rotating-state.ts +76 -0
  174. package/src/model/batched-rotating.ts +65 -23
  175. package/src/model/batched-row-storage.ts +52 -0
  176. package/src/model/batched-ssm.ts +52 -0
  177. package/src/model/batched-turboquant-kv.ts +93 -0
  178. package/src/model/compiled-decode.ts +8 -2
  179. package/src/model/decoded-kv-donor.ts +12 -0
  180. package/src/model/delayed-quantized-kv.ts +72 -0
  181. package/src/model/delayed-rotating-quantized-kv.ts +93 -0
  182. package/src/model/delayed-turboquant-kv.ts +55 -0
  183. package/src/model/diffusion-gemma.ts +8 -8
  184. package/src/model/factory.ts +76 -33
  185. package/src/model/fingerprint.ts +1 -1
  186. package/src/model/flash-attention.ts +3 -3
  187. package/src/model/full-prefill-padding.ts +56 -0
  188. package/src/model/full-prefill-row.ts +105 -0
  189. package/src/model/gemma4-base.ts +179 -80
  190. package/src/model/gemma4.ts +40 -32
  191. package/src/model/generated/gemma4-12b.ts +6 -3
  192. package/src/model/generated/gemma4-26b.ts +6 -3
  193. package/src/model/generated/gemma4-e4b.ts +6 -3
  194. package/src/model/glm52-cache.ts +80 -4
  195. package/src/model/glm52-dsa.ts +13 -9
  196. package/src/model/glm52-memory.ts +1 -1
  197. package/src/model/glm52-mla.ts +12 -6
  198. package/src/model/glm52.ts +19 -40
  199. package/src/model/implementation.ts +69 -0
  200. package/src/model/kv-attention-view.ts +67 -0
  201. package/src/model/minicpm5.ts +17 -15
  202. package/src/model/profile.ts +12 -2
  203. package/src/model/qwen-append.ts +12 -0
  204. package/src/model/qwen-conv.ts +52 -0
  205. package/src/model/qwen3-delta-state.ts +74 -0
  206. package/src/model/qwen3-delta.ts +59 -36
  207. package/src/model/qwen3_5-checkpoint.ts +164 -0
  208. package/src/model/qwen3_5.ts +202 -78
  209. package/src/model/rotating-kv-donor.ts +35 -0
  210. package/src/model/rotating-kv-layout.ts +262 -0
  211. package/src/model/rotating-row-transaction.ts +35 -0
  212. package/src/model/speculative-rotating-kv.ts +101 -0
  213. package/src/model/ssm-prefill-padding.ts +39 -0
  214. package/src/model/support.ts +1 -1
  215. package/src/model/trellis-balanced-scatter.ts +51 -0
  216. package/src/model/trellis-linear.ts +672 -0
  217. package/src/model/trellis-shared-m.ts +151 -0
  218. package/src/model/trellis-shared-scatter.ts +144 -0
  219. package/src/model/trellis-splitk-prefill.ts +158 -0
  220. package/src/model/trellis-tiled-prefill.ts +136 -0
  221. package/src/model/trellis-vector-expand.ts +62 -0
  222. package/src/model/trellis-wide-prefill.ts +94 -0
  223. package/src/model/turboquant-codec.ts +83 -0
  224. package/src/model/universal/dense.ts +15 -14
  225. package/src/model/universal/modules.ts +8 -1
  226. package/src/native-pack.ts +6 -5
  227. package/src/pi-provider.ts +6 -64
  228. package/src/pi-web.ts +8 -315
  229. package/src/preflight.ts +31 -15
  230. package/src/prompt-cache.ts +110 -69
  231. package/src/quantize/allocator.ts +1 -1
  232. package/src/quantize/config-writer.ts +10 -3
  233. package/src/quantize/rotate.ts +4 -4
  234. package/src/quantize/trellis.ts +586 -0
  235. package/src/responses.ts +28 -1
  236. package/src/runtime-config.ts +18 -4
  237. package/src/sampler.ts +40 -29
  238. package/src/serve/batch-scheduler.ts +8 -1671
  239. package/src/serve/chat-request.ts +391 -0
  240. package/src/serve/chat-stage.ts +195 -0
  241. package/src/serve/checkpoint-identity.ts +64 -0
  242. package/src/serve/completion-executor.ts +15 -7
  243. package/src/serve/discovery-routes.ts +12 -13
  244. package/src/serve/generation-gateway.ts +183 -213
  245. package/src/serve/http.ts +159 -0
  246. package/src/serve/inference-request.ts +79 -0
  247. package/src/serve/isolate.ts +297 -105
  248. package/src/serve/lab-routes.ts +3 -0
  249. package/src/serve/lane-registry.ts +2 -1
  250. package/src/serve/model-admin-routes.ts +7 -5
  251. package/src/serve/model-binding.ts +43 -0
  252. package/src/serve/model-host.ts +511 -0
  253. package/src/serve/openai-wire.ts +103 -0
  254. package/src/serve/parent-application.ts +56 -0
  255. package/src/serve/pipeline.ts +33 -0
  256. package/src/serve/preparation.ts +33 -0
  257. package/src/serve/request-plan.ts +23 -18
  258. package/src/serve/request-prep.ts +344 -0
  259. package/src/serve/response-lease.ts +24 -0
  260. package/src/serve/responses-client.ts +69 -0
  261. package/src/serve/session-completion-engine.ts +110 -0
  262. package/src/serve/session-search.ts +1 -1
  263. package/src/serve/static-routes.ts +5 -5
  264. package/src/serve/text-completion-stage.ts +104 -0
  265. package/src/serve/token-streams.ts +519 -0
  266. package/src/server.ts +335 -2711
  267. package/src/spec/assistant-rows.ts +92 -0
  268. package/src/spec/assistant-source.ts +31 -55
  269. package/src/spec/cache-identity.ts +22 -0
  270. package/src/spec/deepspec-rows.ts +29 -0
  271. package/src/spec/deepspec-source.ts +16 -14
  272. package/src/spec/dflash-source.ts +36 -12
  273. package/src/spec/drafter.ts +153 -142
  274. package/src/spec/dspark/ab-stats.ts +3 -3
  275. package/src/spec/dspark/calibration.ts +2 -2
  276. package/src/spec/dspark/deepspec-module.ts +97 -122
  277. package/src/spec/dspark/generate-dflash.ts +2 -2
  278. package/src/spec/dspark/generate.ts +2 -2
  279. package/src/spec/dspark/loader.ts +1 -1
  280. package/src/spec/dspark/module-dflash.ts +129 -172
  281. package/src/spec/dspark/module.ts +3 -3
  282. package/src/spec/dspark/quantize-drafter.ts +1 -1
  283. package/src/spec/generate.ts +1 -1
  284. package/src/spec/glm52-mtp-graph.ts +29 -0
  285. package/src/spec/glm52-mtp-rows.ts +132 -0
  286. package/src/spec/glm52-mtp-source.ts +54 -137
  287. package/src/spec/glm52-mtp-state.ts +33 -0
  288. package/src/spec/ngram-source.ts +93 -21
  289. package/src/spec/projected-draft-rows.ts +73 -0
  290. package/src/spec/qwen-mtp-module.ts +171 -0
  291. package/src/spec/qwen-mtp-rows.ts +219 -0
  292. package/src/spec/qwen-mtp-source.ts +147 -195
  293. package/src/spec/qwen-mtp-state.ts +30 -0
  294. package/src/spec/serve-loop.ts +201 -201
  295. package/src/spec/source.ts +110 -13
  296. package/src/spec/two-model-rows.ts +61 -0
  297. package/src/spec/two-model.ts +44 -102
  298. package/src/ssd-cache.ts +99 -19
  299. package/src/ssd-durability.ts +36 -20
  300. package/src/tool-call.ts +51 -14
  301. package/src/train/flash-cce.ts +2 -2
  302. package/src/train/forward.ts +6 -0
  303. package/src/train/loss.ts +1 -1
  304. package/src/train/prefix-shared.ts +5 -1
  305. package/src/train/segmented.ts +5 -3
  306. package/src/train/trainer.ts +6 -6
  307. package/src/vision/prompt.ts +1 -1
  308. package/src/vision/qwen3vl-tower.ts +52 -10
  309. package/src/web/app.html +1 -1
  310. package/src/web/app.js +62 -60
  311. package/src/web/src/adapters-panel.ts +3 -3
  312. package/src/web/src/assistant.ts +2 -36
  313. package/src/web/src/chat.ts +4 -4
  314. package/src/web/src/composer.ts +4 -4
  315. package/src/web/src/hub.ts +1 -1
  316. package/src/web/src/markdown.ts +0 -0
  317. package/src/web/src/memory-panel.ts +1 -1
  318. package/src/web/src/model-picker.ts +1 -1
  319. package/src/web/src/palette.ts +1 -1
  320. package/src/web/src/protocol.ts +3 -39
  321. package/src/web/src/sessions.ts +1 -1
  322. package/src/web/src/ui-catalog.ts +4 -18
  323. package/src/web-assets.ts +47 -0
  324. package/src/weights.ts +55 -9
  325. package/docs/archive/investigations/curve-runs/bisector-route.json +0 -98
  326. package/docs/archive/investigations/curve-runs/bisector-trace.json +0 -1393
  327. package/docs/archive/investigations/curve-runs/curve-terrain.json +0 -1179
  328. package/docs/archive/investigations/curve-runs/curve-terrain.svg +0 -42
  329. package/docs/archive/investigations/hlg-ab.html +0 -34
  330. package/docs/archive/investigations/hlg-explorer.html +0 -238
  331. package/docs/archive/investigations/hlg-figs/cliff.svg +0 -1
  332. package/docs/archive/investigations/hlg-figs/cross.svg +0 -1
  333. package/docs/archive/investigations/hlg-figs/edges.svg +0 -1
  334. package/docs/archive/investigations/hlg-figs/k20-grids.svg +0 -1
  335. package/docs/archive/investigations/hlg-figs/k20-peaks.svg +0 -1
  336. package/docs/archive/investigations/hlg-figs/lw-curve.svg +0 -1
  337. package/docs/archive/investigations/hlg-figs/terrain.svg +0 -1
  338. package/docs/archive/investigations/hlg-report.html +0 -262
  339. package/docs/archive/investigations/hlg-runs/hlg-ab.json +0 -149
  340. package/docs/archive/investigations/hlg-runs/hlg-blind-prompts.md +0 -87
  341. package/docs/archive/investigations/hlg-runs/hlg-blind.json +0 -107
  342. package/docs/archive/investigations/hlg-runs/hlg-cliff.json +0 -197
  343. package/docs/archive/investigations/hlg-runs/hlg-cross.json +0 -505
  344. package/docs/archive/investigations/hlg-runs/hlg-edge.json +0 -388
  345. package/docs/archive/investigations/hlg-runs/hlg-eval1.json +0 -25
  346. package/docs/archive/investigations/hlg-runs/hlg-frontier.json +0 -86
  347. package/docs/archive/investigations/hlg-runs/hlg-grid.json +0 -242
  348. package/docs/archive/investigations/hlg-runs/hlg-grid2.json +0 -343
  349. package/docs/archive/investigations/hlg-runs/hlg-lw-coarse.json +0 -110
  350. package/docs/archive/investigations/hlg-runs/hlg-map.json +0 -383
  351. package/docs/archive/investigations/hlg-runs/run0-original.log +0 -159
  352. package/docs/archive/investigations/hlg-runs/runA-recommended.log +0 -131
  353. package/docs/archive/investigations/hlg-runs/runAA-cliff-cpm5.log +0 -26
  354. package/docs/archive/investigations/hlg-runs/runAB-grid2-e4b.log +0 -18
  355. package/docs/archive/investigations/hlg-runs/runAC-peak-e4b-partial.log +0 -25
  356. package/docs/archive/investigations/hlg-runs/runAD-ab-samples.log +0 -8
  357. package/docs/archive/investigations/hlg-runs/runAE-edge-e4b.log +0 -51
  358. package/docs/archive/investigations/hlg-runs/runAF-logit-calibration.log +0 -10
  359. package/docs/archive/investigations/hlg-runs/runAG-eval-config2.log +0 -16
  360. package/docs/archive/investigations/hlg-runs/runB-gain-variants.log +0 -192
  361. package/docs/archive/investigations/hlg-runs/runC-pivots.log +0 -162
  362. package/docs/archive/investigations/hlg-runs/runD-oetf.log +0 -132
  363. package/docs/archive/investigations/hlg-runs/runE-eotf.log +0 -162
  364. package/docs/archive/investigations/hlg-runs/runF-shaper-working.log +0 -86
  365. package/docs/archive/investigations/hlg-runs/runG-shaper-sweep.log +0 -101
  366. package/docs/archive/investigations/hlg-runs/runH-wsweep.log +0 -176
  367. package/docs/archive/investigations/hlg-runs/runI-asweep.log +0 -176
  368. package/docs/archive/investigations/hlg-runs/runJ-knobmap.log +0 -49
  369. package/docs/archive/investigations/hlg-runs/runK-frontier.log +0 -19
  370. package/docs/archive/investigations/hlg-runs/runL-widemap.log +0 -65
  371. package/docs/archive/investigations/hlg-runs/runM-frontier-emb.log +0 -15
  372. package/docs/archive/investigations/hlg-runs/runN-verify.log +0 -116
  373. package/docs/archive/investigations/hlg-runs/runO-frontier-seed5000.log +0 -15
  374. package/docs/archive/investigations/hlg-runs/runP-grid-AxSm.log +0 -15
  375. package/docs/archive/investigations/hlg-runs/runQ-grid-tgW.log +0 -15
  376. package/docs/archive/investigations/hlg-runs/runR-blind-transcript.log +0 -79
  377. package/docs/archive/investigations/hlg-runs/runS-cross-coherence.log +0 -33
  378. package/docs/archive/investigations/hlg-runs/runT-cross-cpm5.log +0 -33
  379. package/docs/archive/investigations/hlg-runs/runU-cross-12b.log +0 -33
  380. package/docs/archive/investigations/hlg-runs/runV-lw-e4b.log +0 -22
  381. package/docs/archive/investigations/hlg-runs/runW-lw-12b.log +0 -22
  382. package/docs/archive/investigations/hlg-runs/runX-lw-cpm5.log +0 -22
  383. package/docs/archive/investigations/hlg-runs/runY-cliff-e4b.log +0 -26
  384. package/docs/archive/investigations/hlg-runs/runZ-cliff-12b.log +0 -26
  385. package/docs/archive/mlx-bun-lab-report.html +0 -275
  386. package/docs/archive/wwdc-mlx-bun.html +0 -848
  387. package/docs/design/adapters-end-to-end.md +0 -205
  388. package/docs/design/batching-perf-path.md +0 -255
  389. package/docs/design/batching-v2-plan.md +0 -529
  390. package/docs/design/bucketing-stage.md +0 -197
  391. package/docs/design/compat-cli-surface-design.md +0 -230
  392. package/docs/design/docs-reorg-plan.md +0 -165
  393. package/docs/design/dspark-serving-program.md +0 -371
  394. package/docs/design/dspark-speculative-decoding.md +0 -318
  395. package/docs/design/grammar-spec-batching-integration.md +0 -301
  396. package/docs/design/memory-inference-path.md +0 -422
  397. package/docs/design/memory-synthesis.md +0 -287
  398. package/docs/design/memory-system.md +0 -237
  399. package/docs/design/mlx-lm-serving-execution-seam.md +0 -81
  400. package/docs/design/mlx-lm-tool-parity-plan.md +0 -545
  401. package/docs/design/omlx-adoption-map.md +0 -98
  402. package/docs/design/orpo-dynamic-lambda.md +0 -220
  403. package/docs/design/orpo-future-enhancements.md +0 -340
  404. package/docs/design/paged-kv-cache.md +0 -214
  405. package/docs/design/parallel-slots.md +0 -567
  406. package/docs/design/parity-tier-dag.md +0 -161
  407. package/docs/design/repo-cleanup-plan.md +0 -231
  408. package/docs/design/runtime-isolation.md +0 -263
  409. package/docs/design/segmented-backward-training.md +0 -543
  410. package/docs/design/spec-decode-larger-targets.md +0 -229
  411. package/docs/design/ssd-kv-cold-tier.md +0 -415
  412. package/docs/design/structured-output.md +0 -484
  413. package/docs/design/tauri-desktop-app.md +0 -127
  414. package/docs/design/the-dreaming-master-plan.md +0 -445
  415. package/docs/design/turboquant-kv.md +0 -235
  416. package/docs/design/turboquant-weights.md +0 -467
  417. package/docs/design/w4a16-compute-precision-spike.md +0 -232
  418. package/docs/design/web-chat-beat-matrix.md +0 -315
  419. package/docs/design/write-pipeline-entity.md +0 -468
  420. package/docs/planning/IDEAS.md +0 -69
  421. package/docs/reference/embedding.md +0 -197
  422. package/docs/reference/features-matrix.md +0 -101
  423. package/docs/reference/orpo-quickstart.md +0 -135
  424. package/src/flags.ts +0 -11
  425. /package/docs/{design → archive/investigations}/colibri-glm52-port.md +0 -0
  426. /package/docs/{planning → archive/planning}/chunk-task-roadmap.md +0 -0
  427. /package/docs/{planning → archive/planning}/curve-sampler-research-plan.md +0 -0
  428. /package/docs/{planning → archive/planning}/memory-docs-and-dag-plan.md +0 -0
  429. /package/docs/{planning → archive/planning}/pi-integration-review.md +0 -0
  430. /package/docs/{planning → archive/planning}/release-notes-v0.2.0.md +0 -0
  431. /package/src/{memory-plan.ts → admission-plan.ts} +0 -0
  432. /package/{docs/archive/investigations → src/serve/assets}/curve-terrain.html +0 -0
  433. /package/{docs/dag → src/serve/assets}/training-inference-map.html +0 -0
@@ -1,6 +1,6 @@
1
1
  // Serve-time speculative decoding — the ONE verify/accept executor behind
2
- // `serve --draft-model` (docs/design/mlx-lm-tool-parity-plan.md §7; sequenced
3
- // by docs/design/grammar-spec-batching-integration.md Phase B).
2
+ // `serve --draft-model` (docs/reference/cli.md §7; sequenced
3
+ // by docs/design/batching.md Phase B).
4
4
  //
5
5
  // Faithful to mlx-lm's speculative_generate_step (generate.py:473-654, read
6
6
  // from the oracle venv): per round, draft n tokens from the DraftSource, run
@@ -17,11 +17,9 @@
17
17
  // trimmable mid-generation; a serve endpoint must not 500 mid-stream, so
18
18
  // we STOP SPECULATING and finish the generation with plain single-token
19
19
  // decode (bit-equivalent continuation — the target's own samples).
20
- // - Prompt-cache reuse is BYPASSED in v1 (fresh caches per spec request,
21
- // cachedTokens=0). mlx-lm composes spec with its LRU prompt cache
22
- // (target+draft caches per entry); wiring that through our PromptCache +
23
- // SSD tier is a tracked follow-up in the integration plan, not silently
24
- // absent.
20
+ // - Sources with checkpoint support use the injected prefix-cache service.
21
+ // Target and companion state share the existing RAM/SSD lifecycle. Other
22
+ // sources begin with fresh state until they supply a checkpoint codec.
25
23
  //
26
24
  // Grammar × spec (Phase C, the constrained verify walk — novel: NO runtime
27
25
  // serves both; mlx-lm has no grammar, oMLX no spec): the drafter runs FREE
@@ -31,7 +29,7 @@
31
29
  // so rejected drafts never touch grammar state and no matcher rollback is
32
30
  // needed. Grammar termination mid-burst truncates the round (the all--inf
33
31
  // guarantee). Gates (no oracle): greedy grammar+spec ≡ greedy grammar-only
34
- // long-prefix + 100% schema validity (tests/spec-serve.test.ts).
32
+ // long-prefix + 100% schema validity (tests/parity/spec-serve.test.ts).
35
33
  //
36
34
  // Emission discipline: tokens flow through the caller's onToken ONE AT A
37
35
  // TIME, in order (bursts of ≤ n+1 per round) — the server's stop-sequence
@@ -40,70 +38,25 @@
40
38
  // false halts the generation mid-burst.
41
39
 
42
40
  import { MlxArray } from "../mlx/array";
41
+ import { sampleSpeculativeRows } from "../backends/mlx/speculative-round";
43
42
  import * as ops from "../mlx/ops";
44
43
  import { clearCache } from "../mlx/ffi";
45
- import { flagOn } from "../flags";
44
+ import { cleanupFailure, disposeResources } from "../engine/resources";
45
+ import type { DisposableResource } from "../contracts/resources";
46
+ import { assertMlxSpeculativeBinding, bindLegacySpeculativeModel, type MlxSpeculativeBinding } from "../backends/mlx/speculative";
46
47
  import type { RuntimeModel } from "../model/factory";
48
+ import { cloneKvCaches } from "../kv-store";
49
+ import { disposeAttachments, type CheckpointAttachment } from "../backends/mlx/checkpoint-state";
47
50
  import type { Cache } from "../model/gemma4";
48
- import type { GenerateOptions, GenerateStats } from "../generate";
51
+ import { withModelUsageFlush, withModelWiredLimit, type GenerateOptions, type GenerateStats } from "../generate";
52
+ import { createKvMaintenance } from "../backends/mlx/kv-maintenance";
53
+ import { runtimeConfig, withRuntimeConfig } from "../runtime-config";
49
54
  import { makeSampler, makeStepSampler } from "../sampler";
50
55
  import type { OnToken } from "../serve/generation-gateway";
51
56
  import type { DraftProvider } from "./source";
52
57
 
53
58
  const PREFILL_CHUNK = 2048;
54
59
 
55
- /** Run one target forward, and when `tapLayers` is set (⟹ a Gemma4 target — a
56
- * DSpark-style source), also return the captured multi-layer context
57
- * [1,L,m*H] (tapLayers concatenated on the feature axis; index nLayers is the
58
- * post-finalNorm sentinel). Non-tapping sources get ctxML=null and never
59
- * touch model.hiddenTap. Mirrors generate-dflash.ts forwardTapped. */
60
- async function forwardMaybeTap(
61
- model: RuntimeModel,
62
- ids: MlxArray,
63
- caches: Cache[],
64
- tapLayers: number[] | undefined,
65
- ): Promise<{ hidden: MlxArray; ctxML: MlxArray | null }> {
66
- if (!tapLayers) {
67
- const asyncModel = model as RuntimeModel & {
68
- forwardHiddenAsync?: (
69
- ids: MlxArray,
70
- caches: Cache[],
71
- ) => Promise<MlxArray>;
72
- };
73
- const hidden = typeof asyncModel.forwardHiddenAsync === "function"
74
- ? await asyncModel.forwardHiddenAsync(ids, caches)
75
- : model.forwardHidden(ids, caches);
76
- return { hidden, ctxML: null };
77
- }
78
- const m = model as unknown as {
79
- hiddenTap: { layers: Set<number>; captured: Map<number, MlxArray> } | null;
80
- };
81
- const cap = new Map<number, MlxArray>();
82
- m.hiddenTap = { layers: new Set(tapLayers), captured: cap };
83
- let hidden: MlxArray | null = null;
84
- try {
85
- hidden = model.forwardHidden(ids, caches);
86
- const perLayer = tapLayers.map((li) => {
87
- const a = cap.get(li);
88
- if (!a) throw new Error(`spec tap: layer ${li} not captured`);
89
- return a;
90
- });
91
- const ctxML = ops.concatAxis(perLayer, 2); // [1,L,m*H]
92
- for (const [, a] of cap) a.dispose();
93
- cap.clear(); // consumed — the finally must not double-dispose
94
- const out = { hidden, ctxML };
95
- hidden = null; // ownership returned to the caller
96
- return out;
97
- } finally {
98
- // On any throw (forward mid-capture, missing layer, concat), free the
99
- // partially-captured tap tensors and the orphaned hidden. On success both
100
- // are already gone (cap cleared, hidden nulled).
101
- hidden?.dispose();
102
- for (const [, a] of cap) a.dispose();
103
- m.hiddenTap = null;
104
- }
105
- }
106
-
107
60
  export interface SpecServeExtras {
108
61
  drafted: number;
109
62
  accepted: number;
@@ -115,12 +68,19 @@ export interface SpecServeExtras {
115
68
  forwardsSaved: number;
116
69
  /** Per-draft-position counters (index = position within a round's block,
117
70
  * 0..γ-1): how many rounds drafted/accepted at that position. Drives the
118
- * Phase-1c per-position acceptance report (scripts/dspark-drafter-ab.ts)
71
+ * Phase-1c per-position acceptance report (scripts/dspark.ts ab)
119
72
  * — near-zero cost, always populated. */
120
73
  draftedByPos: number[];
121
74
  acceptedByPos: number[];
122
75
  }
123
76
 
77
+ export interface SpecRunServices {
78
+ readonly prefixCache?: import("../backends/mlx/checkpoint-state").MlxPrefixCache;
79
+ /** Mounted adapter revision, resolved while the execution lease is held. */
80
+ readonly cacheNamespace?: string;
81
+ readonly cloneState?: (caches: Cache[]) => Cache[];
82
+ }
83
+
124
84
  export async function specServeRun(
125
85
  model: RuntimeModel,
126
86
  provider: DraftProvider,
@@ -128,9 +88,40 @@ export async function specServeRun(
128
88
  promptIds: number[],
129
89
  options: GenerateOptions & { stopSequences?: string[] },
130
90
  onToken: OnToken,
91
+ services: SpecRunServices = {},
92
+ ): Promise<GenerateStats> {
93
+ return specRun(bindLegacySpeculativeModel(model, provider), numDraftTokens, promptIds, options, onToken, services);
94
+ }
95
+
96
+ /** The verifier consumes a bound target and source factory, independent of
97
+ * concrete model classes. Compatibility callers use specServeRun above. */
98
+ export async function specRun(
99
+ binding: MlxSpeculativeBinding,
100
+ numDraftTokens: number,
101
+ promptIds: number[],
102
+ options: GenerateOptions & { stopSequences?: string[] },
103
+ onToken: OnToken,
104
+ services: SpecRunServices = {},
105
+ ): Promise<GenerateStats> {
106
+ return withRuntimeConfig(binding.runtime ?? runtimeConfig(), () => {
107
+ const run = () => specRunInner(binding, numDraftTokens, promptIds, options, onToken, services);
108
+ return binding.memory
109
+ ? withModelWiredLimit(binding.memory, () => withModelUsageFlush(binding.memory!, run))
110
+ : run();
111
+ });
112
+ }
113
+
114
+ async function specRunInner(
115
+ binding: MlxSpeculativeBinding,
116
+ numDraftTokens: number,
117
+ promptIds: number[],
118
+ options: GenerateOptions & { stopSequences?: string[] },
119
+ onToken: OnToken,
120
+ services: SpecRunServices = {},
131
121
  ): Promise<GenerateStats> {
132
122
  const maxTokens = options.maxTokens ?? 512;
133
- const eos = options.eosTokenIds ?? model.config.eosTokenIds;
123
+ const maintainKv = createKvMaintenance(options);
124
+ const eos = options.eosTokenIds ?? [...binding.eosTokenIds];
134
125
  const sampler = makeSampler(options);
135
126
  const stepSampler = makeStepSampler(options, {
136
127
  tokenRepresentation: "number",
@@ -142,6 +133,8 @@ export async function specServeRun(
142
133
  sampler,
143
134
  });
144
135
  const gamma = Math.max(1, numDraftTokens);
136
+ const prefillChunk = options.prefillChunkSize ??
137
+ runtimeConfig().number("MLX_BUN_RD_PREFILL_CHUNK", PREFILL_CHUNK);
145
138
 
146
139
  // Allocated INSIDE the try below (2026-07-07 review): provider.open()
147
140
  // throws on a mismatched (target, drafter) pairing — as pre-try consts a
@@ -149,6 +142,7 @@ export async function specServeRun(
149
142
  // and turned a config error into a per-request 500. loadContext now
150
143
  // probe-opens the pairing at startup, so a throw here is belt+suspenders.
151
144
  let caches: Cache[] = [];
145
+ let prefixRetain: (() => void) | undefined;
152
146
  let source: import("./source").DraftSource | null = null;
153
147
  let tapLayers: number[] | undefined;
154
148
  // Target final hidden [1,1,H] at the anchor position — the assistant source
@@ -195,21 +189,73 @@ export async function specServeRun(
195
189
  const samplePos = async (logits1V: MlxArray, step: number): Promise<number> =>
196
190
  (await stepSampler.sample(logits1V, step)).token;
197
191
 
198
- /** The [1,V] logits row at position `pos` of a hidden window — batched
199
- * lm-head is applied by the caller ONCE; this slices its output. */
200
- const logitsRow = (logitsWindow: MlxArray, pos: number): MlxArray => {
201
- const V = logitsWindow.shape[logitsWindow.shape.length - 1]!;
202
- const sl = logitsWindow.slice([0, pos, 0], [1, pos + 1, V]);
203
- const flat = ops.reshape(sl, [1, V]);
204
- sl.dispose();
205
- return flat;
192
+ const forwardTokens = async (tokens: number[], taps?: number[]) => {
193
+ const ids = ops.fromInt32(tokens, [1, tokens.length]);
194
+ try { return await binding.forward(ids, caches, taps); }
195
+ finally { ids.dispose(); }
206
196
  };
197
+ let failure: { error: unknown } | undefined;
207
198
 
208
199
  try {
209
- caches = model.makeCache();
210
- const src = provider.open({ sampler, target: { model, caches } });
200
+ options.signal?.throwIfAborted();
201
+ if (!Number.isSafeInteger(prefillChunk) || prefillChunk < 1)
202
+ throw new Error("speculative prefill chunk must be a positive integer");
203
+ assertMlxSpeculativeBinding(binding);
204
+ if (options.kvConfig?.length ||
205
+ (options.turboQuant && (options.quantizedKvStart ?? 0) !== 0) ||
206
+ (options.kvBits && ((options.kvBits !== 4 && options.kvBits !== 8) || options.quantizedKvStart !== 0)))
207
+ throw new Error("speculative KV requires uniform KV4/KV8 or TurboQuant start=0");
208
+ caches = binding.makeCache();
209
+ const src = binding.openDraft(sampler, caches);
211
210
  source = src;
212
211
  tapLayers = src.tapLayers;
212
+ const checkpoint = src.prefillMode === "full" ? src.checkpoint : undefined;
213
+ const prefixCache = checkpoint ? services.prefixCache : undefined;
214
+ const prefixNamespace = JSON.stringify({ method: checkpoint?.namespace,
215
+ adapters: services.cacheNamespace ?? options.adapters ?? [], kvBits: options.kvBits ?? null,
216
+ kvGroupSize: options.kvGroupSize ?? 64, quantizedKvStart: options.quantizedKvStart ?? null,
217
+ turboQuant: options.turboQuant ?? null });
218
+ let cached = 0;
219
+ const hit = prefixCache?.take(promptIds, prefixNamespace);
220
+ if (hit) {
221
+ try {
222
+ disposeResources(caches);
223
+ caches.splice(0, caches.length, ...hit.caches.splice(0));
224
+ prefixRetain = hit.retain;
225
+ hit.retain = undefined;
226
+ checkpoint!.restore(hit.tokens.length, hit.attachments![0]!);
227
+ cached = hit.tokens.length;
228
+ } finally {
229
+ disposeResources([...hit.caches, { dispose: () => disposeAttachments(hit.attachments) },
230
+ { dispose: () => hit.retain?.() }]);
231
+ }
232
+ }
233
+ stats.cachedTokens = cached;
234
+ const capture = (tokens: number[]) => {
235
+ let state: Cache[] = [];
236
+ let attachments: CheckpointAttachment[] = [];
237
+ try {
238
+ state = (services.cloneState ?? cloneKvCaches)(caches);
239
+ attachments.push(checkpoint!.capture(tokens.length));
240
+ prefixCache!.put(tokens, state, prefixNamespace, undefined, attachments);
241
+ state = []; attachments = [];
242
+ } finally {
243
+ disposeResources([...state, { dispose: () => disposeAttachments(attachments) }]);
244
+ }
245
+ };
246
+ const snapshotAt = prefixCache
247
+ ? Math.min(options.snapshotAt ?? promptIds.length, promptIds.length - 1) : -1;
248
+ const seedSource = async (end: number) => {
249
+ if (ctxParts.length === 1) prefillCtx = ctxParts[0]!;
250
+ else if (ctxParts.length > 1) {
251
+ prefillCtx = ops.concatAxis(ctxParts, 1);
252
+ for (const part of ctxParts) part.dispose();
253
+ }
254
+ ctxParts.length = 0;
255
+ const context = prefillCtx;
256
+ prefillCtx = null; // source.prefill owns context on both success and failure
257
+ await src.prefill(promptIds.slice(0, end), context ?? undefined);
258
+ };
213
259
  // ---- prefill (target, chunked; optionally tapped for DSpark), then seed
214
260
  // the source. Order: the source's prefill needs the tapped context that the
215
261
  // target prefill produces, so target-first (two-model's own draft prefill
@@ -240,77 +286,71 @@ export async function specServeRun(
240
286
  const t0 = performance.now();
241
287
  const tailSplit =
242
288
  src.prefillMode !== "full" &&
243
- flagOn("MLX_BUN_PREFILL_TAIL_SPLIT", true);
289
+ binding.prefillTailSplit;
244
290
  const oracleShape = tailSplit && promptIds.length > 1;
245
291
  if (oracleShape) {
246
292
  let pos = 0;
247
293
  const end = promptIds.length - 1; // last prompt token NEVER prefilled
248
294
  while (pos < end) {
249
- const n = Math.min(PREFILL_CHUNK, end - pos);
295
+ options.signal?.throwIfAborted();
296
+ const n = Math.min(prefillChunk, end - pos);
250
297
  const chunk = promptIds.slice(pos, pos + n);
251
- const ids = ops.fromInt32(chunk, [1, chunk.length]);
252
- const { hidden: h, ctxML } = await forwardMaybeTap(
253
- model,
254
- ids,
255
- caches,
256
- tapLayers,
257
- );
258
- ids.dispose();
298
+ const { hidden: h, ctxML } = await forwardTokens(chunk, tapLayers);
259
299
  if (ctxML) ctxParts.push(ctxML);
260
- if (pos + n >= end) {
261
- const L = h.shape[1]!;
262
- const H = h.shape[2]!;
263
- anchorHidden = h.slice([0, L - 1, 0], [1, L, H]); // position len-2
264
- }
265
- h.dispose(); // logits never computed during the drain
300
+ try {
301
+ if (pos + n >= end) {
302
+ const L = h.shape[1]!;
303
+ const H = h.shape[2]!;
304
+ anchorHidden = h.slice([0, L - 1, 0], [1, L, H]); // position len-2
305
+ }
306
+ } finally { h.dispose(); } // no logits during the drain
307
+ maintainKv(caches);
266
308
  clearCache();
267
309
  pos += n;
310
+ if (pos < end) await new Promise<void>((resolve) => setImmediate(resolve));
268
311
  }
269
312
  } else {
270
313
  // Legacy shape: full chunked prefill (final chunk included), token0
271
314
  // sampled from the last position below.
272
- for (let off = 0; off < promptIds.length; off += PREFILL_CHUNK) {
273
- const chunk = promptIds.slice(off, off + PREFILL_CHUNK);
274
- const ids = ops.fromInt32(chunk, [1, chunk.length]);
275
- const { hidden: h, ctxML } = await forwardMaybeTap(
276
- model,
277
- ids,
278
- caches,
279
- tapLayers,
280
- );
281
- ids.dispose();
315
+ for (let off = cached; off < promptIds.length;) {
316
+ options.signal?.throwIfAborted();
317
+ const end = Math.min(off + prefillChunk, promptIds.length,
318
+ snapshotAt > off ? snapshotAt : Infinity);
319
+ const chunk = promptIds.slice(off, end);
320
+ const { hidden: h, ctxML } = await forwardTokens(chunk, tapLayers);
282
321
  if (ctxML) ctxParts.push(ctxML);
283
- if (off + PREFILL_CHUNK >= promptIds.length) {
322
+ try { if (end >= promptIds.length) {
284
323
  const L = h.shape[1]!;
285
324
  const H = h.shape[2]!;
286
325
  anchorHidden = h.slice([0, L - 1, 0], [1, L, H]); // [1,1,H], retained
287
- const lg = model.logitsFromHidden(anchorHidden);
288
- const V = lg.shape[lg.shape.length - 1]!;
289
- lastLogits = ops.reshape(lg, [1, V]);
290
- lg.dispose();
326
+ const lg = binding.projectLogits(anchorHidden);
327
+ try {
328
+ const V = lg.shape[lg.shape.length - 1]!;
329
+ lastLogits = ops.reshape(lg, [1, V]);
330
+ } finally { lg.dispose(); }
331
+ } } finally { h.dispose(); }
332
+ maintainKv(caches);
333
+ if (end === snapshotAt && snapshotAt > cached) {
334
+ await seedSource(end);
335
+ capture(promptIds.slice(0, end));
291
336
  }
292
- h.dispose();
293
337
  clearCache();
338
+ off = end;
339
+ if (off < promptIds.length)
340
+ await new Promise<void>((resolve) => setImmediate(resolve));
294
341
  }
295
342
  }
296
343
  extras.targetCalls++;
344
+ options.signal?.throwIfAborted();
297
345
  // Seed the source: two-model prefills its own draft cache; DSpark seeds
298
346
  // H_ctx from the tapped prompt context (ownership transfers to prefill);
299
347
  // the assistant is a no-op.
300
- if (tapLayers) {
301
- if (ctxParts.length === 1) prefillCtx = ctxParts[0]!;
302
- else if (ctxParts.length > 1) {
303
- prefillCtx = ops.concatAxis(ctxParts, 1); // [1,Lp,m*H]
304
- for (const p of ctxParts) p.dispose();
305
- }
306
- ctxParts.length = 0; // parts consumed (transferred as prefillCtx or disposed)
307
- await src.prefill(promptIds, prefillCtx ?? undefined); // takes ownership of prefillCtx
308
- prefillCtx = null; // ownership transferred
309
- } else {
310
- await src.prefill(promptIds);
311
- }
348
+ await seedSource(promptIds.length);
349
+ // Conversion replaces plain cache objects. Bind only after prefill has
350
+ // converted every attention layer; rollback must own the active objects.
351
+ const rollback = binding.bindRollback(caches);
312
352
  stats.prefillMs = performance.now() - t0;
313
- stats.prefillTps = (promptIds.length / Math.max(stats.prefillMs, 1e-6)) * 1000;
353
+ stats.prefillTps = ((promptIds.length - cached) / Math.max(stats.prefillMs, 1e-6)) * 1000;
314
354
 
315
355
  // ---- first pending token ----
316
356
  // Oracle shape: pending = the UNPROCESSED last prompt token — never
@@ -326,11 +366,12 @@ export async function specServeRun(
326
366
  pending = await samplePos(lastLogits!, 0);
327
367
  lastLogits!.dispose();
328
368
  lastLogits = null;
369
+ stats.generatedTokens++;
329
370
  if (eos.includes(pending)) {
371
+ stats.finishReason = "stop";
330
372
  stats.decodeMs = performance.now() - tDecode;
331
373
  return stats;
332
374
  }
333
- stats.generatedTokens++;
334
375
  if (
335
376
  (await onToken(pending)) === false ||
336
377
  stats.generatedTokens >= maxTokens ||
@@ -347,28 +388,24 @@ export async function specServeRun(
347
388
 
348
389
  // ---- rounds ----
349
390
  decode: while (stats.generatedTokens < maxTokens) {
391
+ options.signal?.throwIfAborted();
350
392
  if (!speculating) {
351
393
  // plain single-token continuation (post-ring-wrap fallback)
352
- const ids = ops.fromInt32([pending], [1, 1]);
353
- const { hidden: h } = await forwardMaybeTap(
354
- model,
355
- ids,
356
- caches,
357
- undefined,
358
- );
359
- ids.dispose();
360
- const lg = model.logitsFromHidden(h);
361
- h.dispose();
362
- const V = lg.shape[lg.shape.length - 1]!;
363
- roundRow = ops.reshape(lg, [1, V]);
364
- lg.dispose();
394
+ const { hidden: h } = await forwardTokens([pending], undefined);
395
+ try {
396
+ const lg = binding.projectLogits(h);
397
+ try {
398
+ const V = lg.shape[lg.shape.length - 1]!;
399
+ roundRow = ops.reshape(lg, [1, V]);
400
+ } finally { lg.dispose(); }
401
+ } finally { h.dispose(); }
365
402
  extras.targetCalls++;
366
403
  const tok = await samplePos(roundRow, stats.generatedTokens);
367
404
  roundRow.dispose();
368
405
  roundRow = null;
369
406
  clearCache();
370
- if (eos.includes(tok)) break;
371
407
  stats.generatedTokens++;
408
+ if (eos.includes(tok)) { stats.finishReason = "stop"; break; }
372
409
  pending = tok;
373
410
  if ((await onToken(tok)) === false) break;
374
411
  if (grammar?.isTerminated) break; // grammar closed — finish "stop"
@@ -387,11 +424,7 @@ export async function specServeRun(
387
424
  // spec-eligible through the round snapshot/replay contract instead:
388
425
  // specRoundBegin before the verify forward, then commit or a bit-exact
389
426
  // rollback(keep) after the accept walk.
390
- const roundFits = caches.every((c) =>
391
- "maxSize" in c && typeof (c as { maxSize?: number }).maxSize === "number"
392
- ? c.offset + n + 1 < (c as { maxSize: number }).maxSize
393
- : c.isTrimmable() || typeof c.specRoundBegin === "function",
394
- );
427
+ const roundFits = rollback.canBegin(n);
395
428
  if (!roundFits) {
396
429
  console.warn(
397
430
  "spec: sliding window nearly wrapped — finishing without speculation",
@@ -432,21 +465,20 @@ export async function specServeRun(
432
465
  // the emitted position is the next round's anchor hidden.
433
466
  // Arm recurrent caches' spec round around the verify forward (no-op for
434
467
  // trimmable caches, which roll back via trim() below).
435
- for (const c of caches) c.specRoundBegin?.();
468
+ rollback.begin(d);
436
469
  const vIds = ops.fromInt32([pending, ...drafts], [1, d + 1]);
437
- const pinTarget = src.pinTargetKernelFamily === true &&
438
- "setSpecKernelPinned" in model;
439
- if (pinTarget) model.setSpecKernelPinned(true);
470
+ let pin: { close(): void } | undefined;
440
471
  try {
441
- const ft = await forwardMaybeTap(model, vIds, caches, tapLayers);
472
+ pin = src.pinTargetKernelFamily ? binding.pinVerify?.() : undefined;
473
+ const ft = await binding.forward(vIds, caches, tapLayers);
442
474
  // Assign these before the lm-head call so the outer cleanup owns them
443
475
  // even if logits construction throws.
444
476
  vHidden = ft.hidden;
445
477
  vCtxML = ft.ctxML;
446
- vLogits = model.logitsFromHidden(vHidden);
478
+ vLogits = binding.projectLogits(vHidden);
447
479
  } finally {
448
480
  vIds.dispose();
449
- if (pinTarget) model.setSpecKernelPinned(false);
481
+ pin?.close();
450
482
  }
451
483
  extras.targetCalls++;
452
484
  // The lm-head remains one logical batched operation. Native GLM MTP's
@@ -457,39 +489,12 @@ export async function specServeRun(
457
489
  // accept while it reproduces the draft. Sampling is sequential because
458
490
  // the processor history (and, in Phase C, the grammar mask) at position
459
491
  // i depends on the tokens accepted at positions < i.
460
- let kAccept = 0;
461
- let correction: number | null = null; // target's token at first mismatch (or bonus)
462
- let sawEos = false;
492
+ const [result] = await sampleSpeculativeRows([{
493
+ pending, step: stats.generatedTokens, remaining: maxTokens - stats.generatedTokens,
494
+ eosTokenIds: eos, sampling: stepSampler, grammarDone: () => grammar?.isTerminated ?? false,
495
+ }], [drafts], vLogits!);
496
+ const { accepted: kAccept, correction, sawEos, emitted, grammarDone } = result!.acceptance;
463
497
  let halted = false;
464
- const emitted: number[] = [];
465
- let grammarDone = false;
466
- for (let i = 0; i <= d; i++) {
467
- roundRow = logitsRow(vLogits!, i);
468
- const tok = await samplePos(roundRow, stats.generatedTokens + emitted.length);
469
- roundRow.dispose();
470
- roundRow = null;
471
- if (i < d && tok === drafts[i]) {
472
- kAccept++;
473
- // EOS is never content — even when it arrives as an ACCEPTED DRAFT
474
- // (the correction/bonus branch below always excluded it; this
475
- // branch pushed-then-broke, leaking the EOS through onToken —
476
- // caught by the 2026-07-07 live oracle gate: streams were
477
- // bit-identical, ours emitted one extra "content" token: <|eot_id|>).
478
- if (eos.includes(tok)) { sawEos = true; break; }
479
- emitted.push(tok);
480
- // grammar termination mid-burst truncates the round — nothing may
481
- // be sampled past a satisfied grammar (the all--inf guarantee).
482
- if (grammar?.isTerminated) { grammarDone = true; break; }
483
- if (stats.generatedTokens + emitted.length >= maxTokens) break;
484
- continue;
485
- }
486
- // mismatch (target replaces the draft) or bonus (i === n)
487
- correction = tok;
488
- if (!eos.includes(tok)) emitted.push(tok);
489
- else sawEos = true;
490
- if (grammar?.isTerminated) grammarDone = true;
491
- break;
492
- }
493
498
  vLogits!.dispose();
494
499
  vLogits = null;
495
500
  extras.accepted += kAccept;
@@ -502,10 +507,18 @@ export async function specServeRun(
502
507
 
503
508
  // (d) emit the round's tokens through onToken, one at a time
504
509
  for (const tok of emitted) {
510
+ options.signal?.throwIfAborted();
505
511
  stats.generatedTokens++;
506
512
  if ((await onToken(tok)) === false) { halted = true; break; }
507
513
  if (stats.generatedTokens >= maxTokens) break;
508
514
  }
515
+ // Native generation and mlx-lm count the stopping EOS in usage while
516
+ // keeping it out of content. A callback or budget may stop earlier in
517
+ // this verified burst; that unused suffix must not enter usage.
518
+ if (sawEos && !halted && stats.generatedTokens < maxTokens) {
519
+ stats.generatedTokens++;
520
+ stats.finishReason = "stop";
521
+ }
509
522
  extras.tokensPerForward = extras.rounds > 0
510
523
  ? stats.generatedTokens / extras.rounds
511
524
  : 0;
@@ -526,14 +539,7 @@ export async function specServeRun(
526
539
  // drafts). Trimmable caches drop the rejected tail; recurrent caches
527
540
  // restore their pre-round snapshot and bit-exactly replay those
528
541
  // kAccept+1 kept tokens. On full accept the round just commits.
529
- if (kAccept < d) {
530
- for (const c of caches) {
531
- if (c.specRoundRollback) c.specRoundRollback(kAccept + 1);
532
- else c.trim(d - kAccept);
533
- }
534
- } else {
535
- for (const c of caches) c.specRoundCommit?.();
536
- }
542
+ rollback.resolve(kAccept);
537
543
  await src.commit(
538
544
  d,
539
545
  kAccept,
@@ -565,21 +571,15 @@ export async function specServeRun(
565
571
  ? (stats.generatedTokens / stats.decodeMs) * 1000
566
572
  : 0;
567
573
  return stats;
574
+ } catch (error) {
575
+ failure = { error };
576
+ throw error;
568
577
  } finally {
569
- // Dispose whatever a mid-round/prefill throw left live (each is nulled the
570
- // instant its normal disposal or ownership transfer ran, so no double-free).
571
- anchorHidden?.dispose();
572
- lastLogits?.dispose();
573
- prefillCtx?.dispose();
574
- for (const p of ctxParts) p.dispose();
575
- vLogits?.dispose();
576
- roundRow?.dispose();
577
- vHidden?.dispose();
578
- vCtxML?.dispose();
579
- for (const c of caches) c.dispose();
580
- source?.dispose();
581
- stepSampler.dispose();
582
- options.grammar?.dispose(); // Phase C will consume it; never leak either way
583
- clearCache();
578
+ const resources = [anchorHidden, lastLogits, prefillCtx, ...ctxParts,
579
+ vLogits, roundRow, vHidden, vCtxML, ...caches, source, stepSampler,
580
+ options.grammar, { dispose: () => prefixRetain?.() }, { dispose: clearCache }]
581
+ .filter((resource): resource is DisposableResource => resource != null);
582
+ if (failure) cleanupFailure(failure.error, () => disposeResources(resources));
583
+ else disposeResources(resources);
584
584
  }
585
585
  }