mlx-bun 0.0.13 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (427) hide show
  1. package/README.md +71 -578
  2. package/bin/mlx-bun.mjs +1 -1
  3. package/docs/README.md +91 -0
  4. package/docs/{design → archive}/hlg-sampling.md +3 -3
  5. package/docs/{design → archive/investigations}/audio-input-plan.md +4 -4
  6. package/docs/{design → archive/investigations}/colibri-glm52-port.md +1 -1
  7. package/docs/{design → archive/investigations}/diffusion-gemma-port.md +3 -3
  8. package/docs/{design → archive/investigations}/faithful-l1-consolidation.md +4 -4
  9. package/docs/{investigations → archive/investigations}/hlg-sampling-investigation.md +1 -1
  10. package/docs/{design → archive/investigations}/minicpm5-decode-megakernel.md +11 -11
  11. package/docs/{design → archive/investigations}/optimization_plan.md +1 -1
  12. package/docs/archive/investigations/pareto-specialized-runtime-findings.md +92 -0
  13. package/docs/{design → archive/investigations}/pre-colibri-stabilization.md +5 -5
  14. package/docs/{design → archive/investigations}/the-dreaming-handoff.md +2 -2
  15. package/docs/{planning → archive/planning}/chat-ui-vision.md +1 -1
  16. package/docs/{planning → archive/planning}/chunk-task-roadmap.md +1 -1
  17. package/docs/{planning → archive/planning}/curve-sampler-research-plan.md +4 -4
  18. package/docs/{planning → archive/planning}/memory-docs-and-dag-plan.md +1 -1
  19. package/docs/{planning → archive/planning}/release-notes-v0.0.9.md +1 -1
  20. package/docs/archive/planning/release-notes-v0.2.0.md +49 -0
  21. package/docs/{planning → archive/planning}/website-readme-pass-plan.md +1 -1
  22. package/docs/design/batching.md +569 -0
  23. package/docs/design/decode-speed-program.md +3178 -169
  24. package/docs/design/dreaming-nightly-pipeline.md +675 -282
  25. package/docs/design/generic-model-support.md +579 -334
  26. package/docs/design/kv-cache.md +680 -0
  27. package/docs/design/orpo-training.md +673 -1239
  28. package/docs/design/speculative-decoding.md +1627 -0
  29. package/docs/design/turboquant.md +2102 -0
  30. package/docs/design/unified-engine-frontier-plan.md +1831 -727
  31. package/docs/design/web-chat-redesign.md +666 -1083
  32. package/docs/planning/PRODUCT_ROADMAP.md +34 -1
  33. package/docs/planning/release-notes-v0.3.0.md +35 -0
  34. package/docs/planning/video-series.md +421 -0
  35. package/docs/reference/benchmarks.md +1162 -0
  36. package/docs/reference/cli.md +395 -149
  37. package/docs/reference/distribution.md +321 -181
  38. package/docs/reference/environment.md +348 -0
  39. package/docs/reference/glossary.md +39 -0
  40. package/docs/reference/library-api.md +580 -142
  41. package/docs/reference/memory.md +227 -64
  42. package/docs/reference/models.md +137 -6
  43. package/docs/reference/server-api.md +938 -422
  44. package/docs/reference/server-config.md +652 -320
  45. package/docs/reference/training.md +393 -254
  46. package/docs/reference/troubleshooting.md +166 -0
  47. package/package.json +10 -5
  48. package/src/admission-plan.ts +18 -0
  49. package/src/anthropic.ts +92 -3
  50. package/src/audio/decode.ts +2 -2
  51. package/src/audio/features.ts +2 -2
  52. package/src/backends/mlx/autoregressive.ts +108 -0
  53. package/src/backends/mlx/batch-group.ts +1646 -0
  54. package/src/backends/mlx/diffusion.ts +39 -0
  55. package/src/backends/mlx/draft-target.ts +47 -0
  56. package/src/backends/mlx/evaluation.ts +105 -0
  57. package/src/backends/mlx/fill-append.ts +45 -0
  58. package/src/backends/mlx/gateway-binding.ts +71 -0
  59. package/src/backends/mlx/graph.ts +38 -0
  60. package/src/backends/mlx/memory-client.ts +189 -0
  61. package/src/backends/mlx/memory-guard.ts +40 -0
  62. package/src/backends/mlx/methods.ts +90 -0
  63. package/src/backends/mlx/model-prompt.ts +164 -0
  64. package/src/backends/mlx/model-serving.ts +79 -0
  65. package/src/backends/mlx/prefill.ts +33 -0
  66. package/src/backends/mlx/request-policy.ts +20 -0
  67. package/src/backends/mlx/rollback.ts +51 -0
  68. package/src/backends/mlx/serial-executor.ts +273 -0
  69. package/src/backends/mlx/speculative.ts +114 -0
  70. package/src/backends/mlx/state-views.ts +26 -0
  71. package/src/backends/mlx/text-engine.ts +73 -0
  72. package/src/cli.ts +189 -61
  73. package/src/client.ts +66 -0
  74. package/src/config.ts +21 -4
  75. package/src/contracts/completion.ts +16 -0
  76. package/src/contracts/execution.ts +49 -0
  77. package/src/contracts/generation.ts +103 -0
  78. package/src/contracts/host.ts +7 -0
  79. package/src/contracts/jobs.ts +66 -0
  80. package/src/contracts/pi-provider.ts +30 -0
  81. package/src/contracts/pi.ts +259 -0
  82. package/src/contracts/resources.ts +18 -0
  83. package/src/contracts/scheduling.ts +27 -0
  84. package/src/contracts/tsconfig.json +14 -0
  85. package/src/diffusion/diffusion-generate.ts +218 -153
  86. package/src/embed.ts +1 -1
  87. package/src/engine/admission.ts +59 -0
  88. package/src/engine/cancellation.ts +38 -0
  89. package/src/engine/engine.ts +56 -0
  90. package/src/engine/execution-coordinator.ts +70 -0
  91. package/src/engine/execution-plan.ts +51 -0
  92. package/src/engine/resources.ts +42 -0
  93. package/src/engine/scheduler.ts +67 -0
  94. package/src/engine/session.ts +247 -0
  95. package/src/eval/README.md +44 -1
  96. package/src/eval/kl.ts +263 -23
  97. package/src/eval/reference-logits.ts +207 -0
  98. package/src/eval/runner.ts +10 -79
  99. package/src/eval/tasks/bfcl.ts +2 -1
  100. package/src/eval/tasks/gsm8k.ts +2 -1
  101. package/src/eval/tasks/hashhop.ts +2 -1
  102. package/src/eval/tasks/humaneval.ts +5 -4
  103. package/src/eval/tasks/ifeval.ts +2 -1
  104. package/src/eval/tasks/mmlu.ts +2 -1
  105. package/src/expert-io.ts +3 -1
  106. package/src/expert-offload.ts +3 -1
  107. package/src/fill/echo-index.ts +188 -0
  108. package/src/fill/fill-session.ts +452 -0
  109. package/src/fill/proposal.ts +68 -0
  110. package/src/fill/schema-rows.ts +506 -0
  111. package/src/fill/tool-boundary.ts +103 -0
  112. package/src/fit.ts +24 -43
  113. package/src/generate.ts +740 -337
  114. package/src/grammar.ts +14 -8
  115. package/src/harness-pi.ts +15 -34
  116. package/src/hub-rest.ts +4 -4
  117. package/src/index.ts +27 -0
  118. package/src/inference/denoising.ts +17 -0
  119. package/src/inference/graph.ts +33 -0
  120. package/src/inference/prefill.ts +39 -0
  121. package/src/inference/rollback.ts +7 -0
  122. package/src/jobs/db.ts +4 -0
  123. package/src/jobs/index.ts +2 -0
  124. package/src/jobs/job-entry.ts +15 -14
  125. package/src/jobs/runner.ts +98 -13
  126. package/src/jobs/task-client.ts +19 -0
  127. package/src/jobs/types.ts +2 -58
  128. package/src/kv-scheme.ts +199 -0
  129. package/src/kv-store.ts +459 -234
  130. package/src/lab/README.md +18 -0
  131. package/src/{assets → lab/curve}/curve-designer.html +2 -2
  132. package/src/{curve-sampler.ts → lab/curve/curve-sampler.ts} +2 -2
  133. package/src/{expert-trace.ts → lab/expert-trace/expert-trace.ts} +7 -5
  134. package/src/{model → lab/paged-kv}/paged-kv.ts +9 -6
  135. package/src/library.ts +40 -0
  136. package/src/lora.ts +18 -0
  137. package/src/media-fetch.ts +20 -4
  138. package/src/memory/article.ts +1 -1
  139. package/src/memory/chunk.ts +1 -1
  140. package/src/memory/model.ts +32 -170
  141. package/src/memory/prompts.ts +1 -1
  142. package/src/memory/rest.ts +1 -1
  143. package/src/memory/schedule.ts +1 -1
  144. package/src/memory/stages.ts +4 -1
  145. package/src/memory/vault.ts +3 -4
  146. package/src/mlx/array.ts +9 -10
  147. package/src/mlx/compile.ts +12 -2
  148. package/src/mlx/ffi.ts +66 -22
  149. package/src/mlx/materialize.ts +23 -0
  150. package/src/mlx/metal-kernel.ts +10 -11
  151. package/src/mlx/ops.ts +64 -19
  152. package/src/mlx/quantized-rows.ts +36 -0
  153. package/src/mlx/token-bitmask.ts +52 -0
  154. package/src/mlx/turboquant-kv-decode.ts +82 -0
  155. package/src/mlx/turboquant-kv-inverse.ts +122 -0
  156. package/src/mlx/turboquant-ops.ts +33 -3
  157. package/src/mlx/turboquant-tables.ts +2 -2
  158. package/src/model/batched-mask.ts +9 -3
  159. package/src/model/batched-quant.ts +2 -2
  160. package/src/model/batched-rotating-quant.ts +45 -99
  161. package/src/model/batched-rotating-state.ts +73 -0
  162. package/src/model/batched-rotating.ts +57 -106
  163. package/src/model/batched-row-storage.ts +127 -0
  164. package/src/model/compiled-decode.ts +4 -3
  165. package/src/model/diffusion-gemma.ts +8 -8
  166. package/src/model/factory.ts +87 -49
  167. package/src/model/fingerprint.ts +1 -1
  168. package/src/model/flash-attention.ts +3 -3
  169. package/src/model/gemma4-base.ts +187 -17
  170. package/src/model/gemma4.ts +6 -6
  171. package/src/model/generated/gemma4-12b.ts +52 -148
  172. package/src/model/generated/gemma4-26b.ts +34 -94
  173. package/src/model/generated/gemma4-e4b.ts +28 -76
  174. package/src/model/glm52-cache.ts +19 -9
  175. package/src/model/glm52-dsa.ts +13 -9
  176. package/src/model/glm52-memory.ts +28 -1
  177. package/src/model/implementation.ts +69 -0
  178. package/src/model/minicpm5.ts +7 -8
  179. package/src/model/profile.ts +443 -0
  180. package/src/model/qwen-append.ts +12 -0
  181. package/src/model/qwen-conv.ts +52 -0
  182. package/src/model/qwen3-delta.ts +98 -2
  183. package/src/model/qwen3-mrope.ts +252 -0
  184. package/src/model/qwen3_5-checkpoint.ts +164 -0
  185. package/src/model/qwen3_5.ts +336 -60
  186. package/src/model/support.ts +1 -1
  187. package/src/model/trellis-balanced-scatter.ts +51 -0
  188. package/src/model/trellis-linear.ts +672 -0
  189. package/src/model/trellis-shared-m.ts +151 -0
  190. package/src/model/trellis-shared-scatter.ts +144 -0
  191. package/src/model/trellis-splitk-prefill.ts +158 -0
  192. package/src/model/trellis-tiled-prefill.ts +136 -0
  193. package/src/model/trellis-vector-expand.ts +62 -0
  194. package/src/model/trellis-wide-prefill.ts +94 -0
  195. package/src/model/universal/dense.ts +1 -1
  196. package/src/model/universal/modules.ts +8 -1
  197. package/src/native/frame_extract.swift +140 -0
  198. package/src/native-pack.ts +15 -7
  199. package/src/pi-provider.ts +6 -64
  200. package/src/pi-web.ts +14 -320
  201. package/src/prompt-cache.ts +97 -54
  202. package/src/quantize/allocator.ts +1 -1
  203. package/src/quantize/atomic-output.ts +38 -0
  204. package/src/quantize/calibration.ts +2 -1
  205. package/src/quantize/config-writer.ts +16 -3
  206. package/src/quantize/index.ts +28 -1
  207. package/src/quantize/job.ts +11 -0
  208. package/src/quantize/quantizer.ts +265 -100
  209. package/src/quantize/rotate.ts +603 -0
  210. package/src/quantize/safetensors-writer.ts +79 -1
  211. package/src/quantize/sensitivity.ts +41 -59
  212. package/src/quantize/trellis.ts +586 -0
  213. package/src/quantize/weight-transform.ts +292 -0
  214. package/src/responses.ts +130 -3
  215. package/src/runtime-config.ts +81 -0
  216. package/src/sampler.ts +276 -4
  217. package/src/serve/admin-routes.ts +184 -0
  218. package/src/serve/aux-routes.ts +132 -0
  219. package/src/serve/batch-scheduler.ts +8 -1558
  220. package/src/serve/chat-request.ts +391 -0
  221. package/src/serve/chat-stage.ts +195 -0
  222. package/src/serve/checkpoint-identity.ts +64 -0
  223. package/src/serve/completion-executor.ts +354 -0
  224. package/src/serve/completion-sink.ts +157 -0
  225. package/src/serve/discovery-routes.ts +204 -0
  226. package/src/serve/generation-gateway.ts +211 -243
  227. package/src/serve/http.ts +159 -0
  228. package/src/serve/inference-request.ts +79 -0
  229. package/src/serve/isolate.ts +297 -105
  230. package/src/serve/kv-budget.ts +14 -0
  231. package/src/serve/lab-routes.ts +341 -0
  232. package/src/serve/lane-registry.ts +2 -1
  233. package/src/serve/model-admin-routes.ts +155 -0
  234. package/src/serve/model-binding.ts +43 -0
  235. package/src/serve/model-host.ts +512 -0
  236. package/src/serve/openai-wire.ts +103 -0
  237. package/src/serve/parent-application.ts +56 -0
  238. package/src/serve/pipeline.ts +33 -0
  239. package/src/serve/preparation.ts +33 -0
  240. package/src/serve/prompt-response-trace.ts +147 -0
  241. package/src/serve/request-plan.ts +162 -0
  242. package/src/serve/request-prep.ts +346 -0
  243. package/src/serve/response-lease.ts +24 -0
  244. package/src/serve/responses-client.ts +69 -0
  245. package/src/serve/session-completion-engine.ts +110 -0
  246. package/src/serve/session-search.ts +1 -1
  247. package/src/serve/static-routes.ts +101 -0
  248. package/src/serve/text-completion-stage.ts +104 -0
  249. package/src/serve/token-streams.ts +519 -0
  250. package/src/server.ts +523 -3551
  251. package/src/spec/assistant-source.ts +10 -44
  252. package/src/spec/deepspec-source.ts +6 -6
  253. package/src/spec/dflash-source.ts +9 -8
  254. package/src/spec/dspark/ab-stats.ts +3 -3
  255. package/src/spec/dspark/calibration.ts +2 -2
  256. package/src/spec/dspark/deepspec-module.ts +2 -2
  257. package/src/spec/dspark/generate-dflash.ts +2 -2
  258. package/src/spec/dspark/generate.ts +2 -2
  259. package/src/spec/dspark/loader.ts +1 -1
  260. package/src/spec/dspark/module-dflash.ts +5 -4
  261. package/src/spec/dspark/module.ts +3 -3
  262. package/src/spec/dspark/quantize-drafter.ts +1 -1
  263. package/src/spec/generate.ts +1 -1
  264. package/src/spec/glm52-mtp-source.ts +9 -1
  265. package/src/spec/ngram-source.ts +1 -1
  266. package/src/spec/prefix-state.ts +45 -0
  267. package/src/spec/qwen-mtp-source.ts +528 -0
  268. package/src/spec/serve-loop.ts +179 -213
  269. package/src/spec/source.ts +42 -10
  270. package/src/spec/two-model.ts +1 -1
  271. package/src/ssd-cache.ts +95 -24
  272. package/src/ssd-durability.ts +207 -0
  273. package/src/tool-call.ts +51 -14
  274. package/src/train/flash-cce.ts +14 -13
  275. package/src/train/forward.ts +38 -1
  276. package/src/train/loss.ts +3 -2
  277. package/src/train/prefix-shared.ts +5 -1
  278. package/src/train/segmented.ts +9 -6
  279. package/src/train/trainer.ts +9 -8
  280. package/src/vision/preprocess.ts +1 -1
  281. package/src/vision/prompt.ts +48 -2
  282. package/src/vision/qwen3vl-preprocess.ts +409 -0
  283. package/src/vision/qwen3vl-prompt.ts +196 -0
  284. package/src/vision/qwen3vl-tower.ts +555 -0
  285. package/src/vision/video-frames.ts +148 -0
  286. package/src/web/app.html +1 -1
  287. package/src/web/app.js +15 -6
  288. package/src/web/src/adapters-panel.ts +3 -3
  289. package/src/web/src/assistant.ts +2 -36
  290. package/src/web/src/chat.ts +16 -9
  291. package/src/web/src/composer.ts +4 -4
  292. package/src/web/src/hub.ts +1 -1
  293. package/src/web/src/markdown.ts +0 -0
  294. package/src/web/src/memory-panel.ts +1 -1
  295. package/src/web/src/model-picker.ts +1 -1
  296. package/src/web/src/palette.ts +1 -1
  297. package/src/web/src/protocol.ts +3 -39
  298. package/src/web/src/sessions.ts +1 -1
  299. package/src/web/src/ui-catalog.ts +4 -18
  300. package/src/web-assets.ts +47 -0
  301. package/src/weights.ts +134 -33
  302. package/docs/archive/mlx-bun-lab-report.html +0 -275
  303. package/docs/archive/wwdc-mlx-bun.html +0 -848
  304. package/docs/design/adapters-end-to-end.md +0 -205
  305. package/docs/design/batching-perf-path.md +0 -255
  306. package/docs/design/batching-v2-plan.md +0 -529
  307. package/docs/design/bucketing-stage.md +0 -197
  308. package/docs/design/compat-cli-surface-design.md +0 -230
  309. package/docs/design/docs-reorg-plan.md +0 -165
  310. package/docs/design/dspark-serving-program.md +0 -371
  311. package/docs/design/dspark-speculative-decoding.md +0 -268
  312. package/docs/design/grammar-spec-batching-integration.md +0 -301
  313. package/docs/design/memory-inference-path.md +0 -422
  314. package/docs/design/memory-synthesis.md +0 -287
  315. package/docs/design/memory-system.md +0 -237
  316. package/docs/design/mlx-lm-tool-parity-plan.md +0 -545
  317. package/docs/design/omlx-adoption-map.md +0 -98
  318. package/docs/design/orpo-dynamic-lambda.md +0 -220
  319. package/docs/design/orpo-future-enhancements.md +0 -340
  320. package/docs/design/paged-kv-cache.md +0 -214
  321. package/docs/design/parallel-slots.md +0 -567
  322. package/docs/design/parity-tier-dag.md +0 -161
  323. package/docs/design/repo-cleanup-plan.md +0 -189
  324. package/docs/design/runtime-isolation.md +0 -263
  325. package/docs/design/segmented-backward-training.md +0 -543
  326. package/docs/design/spec-decode-larger-targets.md +0 -229
  327. package/docs/design/ssd-kv-cold-tier.md +0 -288
  328. package/docs/design/structured-output.md +0 -484
  329. package/docs/design/tauri-desktop-app.md +0 -127
  330. package/docs/design/the-dreaming-master-plan.md +0 -445
  331. package/docs/design/turboquant-kv.md +0 -235
  332. package/docs/design/w4a16-compute-precision-spike.md +0 -232
  333. package/docs/design/web-chat-beat-matrix.md +0 -315
  334. package/docs/design/write-pipeline-entity.md +0 -468
  335. package/docs/investigations/curve-runs/bisector-route.json +0 -98
  336. package/docs/investigations/curve-runs/bisector-trace.json +0 -1393
  337. package/docs/investigations/curve-runs/curve-terrain.json +0 -1179
  338. package/docs/investigations/curve-runs/curve-terrain.svg +0 -42
  339. package/docs/investigations/hlg-ab.html +0 -34
  340. package/docs/investigations/hlg-explorer.html +0 -238
  341. package/docs/investigations/hlg-figs/cliff.svg +0 -1
  342. package/docs/investigations/hlg-figs/cross.svg +0 -1
  343. package/docs/investigations/hlg-figs/edges.svg +0 -1
  344. package/docs/investigations/hlg-figs/k20-grids.svg +0 -1
  345. package/docs/investigations/hlg-figs/k20-peaks.svg +0 -1
  346. package/docs/investigations/hlg-figs/lw-curve.svg +0 -1
  347. package/docs/investigations/hlg-figs/terrain.svg +0 -1
  348. package/docs/investigations/hlg-report.html +0 -262
  349. package/docs/investigations/hlg-runs/hlg-ab.json +0 -149
  350. package/docs/investigations/hlg-runs/hlg-blind-prompts.md +0 -87
  351. package/docs/investigations/hlg-runs/hlg-blind.json +0 -107
  352. package/docs/investigations/hlg-runs/hlg-cliff.json +0 -197
  353. package/docs/investigations/hlg-runs/hlg-cross.json +0 -505
  354. package/docs/investigations/hlg-runs/hlg-edge.json +0 -388
  355. package/docs/investigations/hlg-runs/hlg-eval1.json +0 -25
  356. package/docs/investigations/hlg-runs/hlg-frontier.json +0 -86
  357. package/docs/investigations/hlg-runs/hlg-grid.json +0 -242
  358. package/docs/investigations/hlg-runs/hlg-grid2.json +0 -343
  359. package/docs/investigations/hlg-runs/hlg-lw-coarse.json +0 -110
  360. package/docs/investigations/hlg-runs/hlg-map.json +0 -383
  361. package/docs/investigations/hlg-runs/run0-original.log +0 -159
  362. package/docs/investigations/hlg-runs/runA-recommended.log +0 -131
  363. package/docs/investigations/hlg-runs/runAA-cliff-cpm5.log +0 -26
  364. package/docs/investigations/hlg-runs/runAB-grid2-e4b.log +0 -18
  365. package/docs/investigations/hlg-runs/runAC-peak-e4b-partial.log +0 -25
  366. package/docs/investigations/hlg-runs/runAD-ab-samples.log +0 -8
  367. package/docs/investigations/hlg-runs/runAE-edge-e4b.log +0 -51
  368. package/docs/investigations/hlg-runs/runAF-logit-calibration.log +0 -10
  369. package/docs/investigations/hlg-runs/runAG-eval-config2.log +0 -16
  370. package/docs/investigations/hlg-runs/runB-gain-variants.log +0 -192
  371. package/docs/investigations/hlg-runs/runC-pivots.log +0 -162
  372. package/docs/investigations/hlg-runs/runD-oetf.log +0 -132
  373. package/docs/investigations/hlg-runs/runE-eotf.log +0 -162
  374. package/docs/investigations/hlg-runs/runF-shaper-working.log +0 -86
  375. package/docs/investigations/hlg-runs/runG-shaper-sweep.log +0 -101
  376. package/docs/investigations/hlg-runs/runH-wsweep.log +0 -176
  377. package/docs/investigations/hlg-runs/runI-asweep.log +0 -176
  378. package/docs/investigations/hlg-runs/runJ-knobmap.log +0 -49
  379. package/docs/investigations/hlg-runs/runK-frontier.log +0 -19
  380. package/docs/investigations/hlg-runs/runL-widemap.log +0 -65
  381. package/docs/investigations/hlg-runs/runM-frontier-emb.log +0 -15
  382. package/docs/investigations/hlg-runs/runN-verify.log +0 -116
  383. package/docs/investigations/hlg-runs/runO-frontier-seed5000.log +0 -15
  384. package/docs/investigations/hlg-runs/runP-grid-AxSm.log +0 -15
  385. package/docs/investigations/hlg-runs/runQ-grid-tgW.log +0 -15
  386. package/docs/investigations/hlg-runs/runR-blind-transcript.log +0 -79
  387. package/docs/investigations/hlg-runs/runS-cross-coherence.log +0 -33
  388. package/docs/investigations/hlg-runs/runT-cross-cpm5.log +0 -33
  389. package/docs/investigations/hlg-runs/runU-cross-12b.log +0 -33
  390. package/docs/investigations/hlg-runs/runV-lw-e4b.log +0 -22
  391. package/docs/investigations/hlg-runs/runW-lw-12b.log +0 -22
  392. package/docs/investigations/hlg-runs/runX-lw-cpm5.log +0 -22
  393. package/docs/investigations/hlg-runs/runY-cliff-e4b.log +0 -26
  394. package/docs/investigations/hlg-runs/runZ-cliff-12b.log +0 -26
  395. package/docs/planning/IDEAS.md +0 -69
  396. package/docs/reference/embedding.md +0 -197
  397. package/docs/reference/features-matrix.md +0 -100
  398. package/docs/reference/orpo-quickstart.md +0 -135
  399. package/src/flags.ts +0 -13
  400. /package/docs/{investigations → archive/investigations}/chunk-finetune-experiment.md +0 -0
  401. /package/docs/{investigations → archive/investigations}/colibri-oracle-pin.md +0 -0
  402. /package/docs/{investigations → archive/investigations}/curve-bisector-routing.md +0 -0
  403. /package/docs/{investigations → archive/investigations}/decode-roofline-lookagain.md +0 -0
  404. /package/docs/{investigations → archive/investigations}/dspark-handoff.md +0 -0
  405. /package/docs/{investigations → archive/investigations}/expert-offload-single-user-moe.md +0 -0
  406. /package/docs/{investigations → archive/investigations}/indexshare-performance-spike.md +0 -0
  407. /package/docs/{investigations → archive/investigations}/kernel-perf-review-2026-07.md +0 -0
  408. /package/docs/{investigations → archive/investigations}/lab-build-journal.md +0 -0
  409. /package/docs/{investigations → archive/investigations}/openwiki-evaluation.md +0 -0
  410. /package/docs/{investigations → archive/investigations}/orpo-base-uf-experiment-and-directions.md +0 -0
  411. /package/docs/{investigations → archive/investigations}/orpo-flash-cce-pin-leak.md +0 -0
  412. /package/docs/{investigations → archive/investigations}/orpo-uf-testing-handoff.md +0 -0
  413. /package/docs/{investigations → archive/investigations}/pi-builtin-investigation.md +0 -0
  414. /package/docs/{investigations → archive/investigations}/segmented-backward-handoff.md +0 -0
  415. /package/docs/{investigations → archive/investigations}/starter-model-port-handoff.md +0 -0
  416. /package/docs/{investigations → archive/investigations}/steel-flash-cce-handoff.md +0 -0
  417. /package/docs/{investigations → archive/investigations}/trainer-validation-experiment.md +0 -0
  418. /package/docs/{planning → archive/planning}/chat-ui-vision-memory-first.md +0 -0
  419. /package/docs/{planning → archive/planning}/journal.md +0 -0
  420. /package/docs/{planning → archive/planning}/pi-integration-review.md +0 -0
  421. /package/docs/{planning → archive/planning}/release-notes-v0.0.10.md +0 -0
  422. /package/docs/{planning → archive/planning}/release-notes-v0.0.11.md +0 -0
  423. /package/docs/{planning → archive/planning}/release-notes-v0.0.12.md +0 -0
  424. /package/docs/{planning → archive/planning}/release-notes-v0.0.13.md +0 -0
  425. /package/docs/{planning → archive/planning}/web-ui-pass-plan.md +0 -0
  426. /package/{docs/investigations → src/serve/assets}/curve-terrain.html +0 -0
  427. /package/{docs/dag → src/serve/assets}/training-inference-map.html +0 -0
@@ -1,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
@@ -31,7 +31,7 @@
31
31
  // so rejected drafts never touch grammar state and no matcher rollback is
32
32
  // needed. Grammar termination mid-burst truncates the round (the all--inf
33
33
  // guarantee). Gates (no oracle): greedy grammar+spec ≡ greedy grammar-only
34
- // long-prefix + 100% schema validity (tests/spec-serve.test.ts).
34
+ // long-prefix + 100% schema validity (tests/parity/spec-serve.test.ts).
35
35
  //
36
36
  // Emission discipline: tokens flow through the caller's onToken ONE AT A
37
37
  // TIME, in order (bursts of ≤ n+1 per round) — the server's stop-sequence
@@ -42,68 +42,19 @@
42
42
  import { MlxArray } from "../mlx/array";
43
43
  import * as ops from "../mlx/ops";
44
44
  import { clearCache } from "../mlx/ffi";
45
- import { flagOn } from "../flags";
45
+ import { cleanupFailure, disposeResources } from "../engine/resources";
46
+ import type { DisposableResource } from "../contracts/resources";
47
+ import { assertMlxSpeculativeBinding, bindLegacySpeculativeModel, type MlxSpeculativeBinding } from "../backends/mlx/speculative";
46
48
  import type { RuntimeModel } from "../model/factory";
47
49
  import type { Cache } from "../model/gemma4";
48
- import type { GenerateOptions, GenerateStats } from "../generate";
49
- import { makeSampler, makeLogitsProcessors, toLogprobs } from "../sampler";
50
+ import { maybeQuantizeKv, withModelUsageFlush, withModelWiredLimit, type GenerateOptions, type GenerateStats } from "../generate";
51
+ import { runtimeConfig, withRuntimeConfig } from "../runtime-config";
52
+ import { makeSampler, makeStepSampler } from "../sampler";
50
53
  import type { OnToken } from "../serve/generation-gateway";
51
54
  import type { DraftProvider } from "./source";
52
55
 
53
56
  const PREFILL_CHUNK = 2048;
54
57
 
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
58
  export interface SpecServeExtras {
108
59
  drafted: number;
109
60
  accepted: number;
@@ -115,7 +66,7 @@ export interface SpecServeExtras {
115
66
  forwardsSaved: number;
116
67
  /** Per-draft-position counters (index = position within a round's block,
117
68
  * 0..γ-1): how many rounds drafted/accepted at that position. Drives the
118
- * Phase-1c per-position acceptance report (scripts/dspark-drafter-ab.ts)
69
+ * Phase-1c per-position acceptance report (scripts/dspark.ts ab)
119
70
  * — near-zero cost, always populated. */
120
71
  draftedByPos: number[];
121
72
  acceptedByPos: number[];
@@ -128,12 +79,49 @@ export async function specServeRun(
128
79
  promptIds: number[],
129
80
  options: GenerateOptions & { stopSequences?: string[] },
130
81
  onToken: OnToken,
82
+ ): Promise<GenerateStats> {
83
+ return specRun(bindLegacySpeculativeModel(model, provider), numDraftTokens, promptIds, options, onToken);
84
+ }
85
+
86
+ /** The verifier consumes a bound target and source factory, independent of
87
+ * concrete model classes. Compatibility callers use specServeRun above. */
88
+ export async function specRun(
89
+ binding: MlxSpeculativeBinding,
90
+ numDraftTokens: number,
91
+ promptIds: number[],
92
+ options: GenerateOptions & { stopSequences?: string[] },
93
+ onToken: OnToken,
94
+ ): Promise<GenerateStats> {
95
+ return withRuntimeConfig(binding.runtime ?? runtimeConfig(), () => {
96
+ const run = () => specRunInner(binding, numDraftTokens, promptIds, options, onToken);
97
+ return binding.memory
98
+ ? withModelWiredLimit(binding.memory, () => withModelUsageFlush(binding.memory!, run))
99
+ : run();
100
+ });
101
+ }
102
+
103
+ async function specRunInner(
104
+ binding: MlxSpeculativeBinding,
105
+ numDraftTokens: number,
106
+ promptIds: number[],
107
+ options: GenerateOptions & { stopSequences?: string[] },
108
+ onToken: OnToken,
131
109
  ): Promise<GenerateStats> {
132
110
  const maxTokens = options.maxTokens ?? 512;
133
- const eos = options.eosTokenIds ?? model.config.eosTokenIds;
111
+ const eos = options.eosTokenIds ?? [...binding.eosTokenIds];
134
112
  const sampler = makeSampler(options);
135
- const processors = makeLogitsProcessors(options);
113
+ const stepSampler = makeStepSampler(options, {
114
+ tokenRepresentation: "number",
115
+ grammarWait: "before-sample",
116
+ historyUpdate: "after-sample",
117
+ initialHistory: promptIds,
118
+ acceptGrammar: true,
119
+ eosTokenIds: eos,
120
+ sampler,
121
+ });
136
122
  const gamma = Math.max(1, numDraftTokens);
123
+ const prefillChunk = options.prefillChunkSize ??
124
+ runtimeConfig().number("MLX_BUN_RD_PREFILL_CHUNK", PREFILL_CHUNK);
137
125
 
138
126
  // Allocated INSIDE the try below (2026-07-07 review): provider.open()
139
127
  // throws on a mismatched (target, drafter) pairing — as pre-try consts a
@@ -159,11 +147,6 @@ export async function specServeRun(
159
147
  let vCtxML: MlxArray | null = null;
160
148
  let vLogits: MlxArray | null = null;
161
149
  let roundRow: MlxArray | null = null; // the in-flight verify/continuation row
162
- // Device-side token history for the logits processors (repetition/presence/
163
- // frequency penalties, logit_bias) — generate()'s pushHistory discipline.
164
- let history: MlxArray | null =
165
- processors.length > 0 ? ops.fromInt32(promptIds, [promptIds.length]) : null;
166
-
167
150
  const extras: SpecServeExtras = {
168
151
  drafted: 0,
169
152
  accepted: 0,
@@ -185,65 +168,12 @@ export async function specServeRun(
185
168
  spec: extras,
186
169
  };
187
170
 
188
- /** Sample ONE verify position from a [1,V] logits row: processors →
189
- * grammar mask (Phase C: the constrained verify walk — the mask has the
190
- * final say, same ordering as serial sampleStep and the batch closure) →
191
- * log-softmax → sampler. The GRAMMAR walk is inherently sequential: the
192
- * mask at position i reflects every token emitted before it, so this
193
- * awaits the matcher's async fill (fired by the previous accept) before
194
- * masking, samples, then advances the matcher — the matcher only ever
195
- * moves on tokens that are (about to be) EMITTED, which is what makes
196
- * rollback-free v1 correct: rejected DRAFTS never touch grammar state.
197
- * Appends the sampled token to the processor history. */
171
+ /** The speculative walk needs eager token ids and waits for each grammar
172
+ * mask. StepSampler keeps its processor, mask, sample, and history ordering
173
+ * identical to the serial and batched lanes. */
198
174
  const grammar = options.grammar;
199
- const samplePos = async (logits1V: MlxArray, step: number): Promise<number> => {
200
- // Owned intermediates drained in the finally on any throw — grammar
201
- // fill reject, applyMask/toLogprobs/sampler error (2026-07-07 review:
202
- // these were the one spot the header's hoist discipline missed; a
203
- // grammar+penalties request whose matcher fill rejects orphaned one
204
- // [1,V] row per occurrence). `cur` is owned only once it diverges from
205
- // the caller's row.
206
- let cur: MlxArray = logits1V;
207
- let lp: MlxArray | null = null;
208
- let tokArr: MlxArray | null = null;
209
- try {
210
- for (const p of processors) {
211
- const next = p(history, cur);
212
- if (cur !== logits1V) cur.dispose();
213
- cur = next;
214
- }
215
- if (grammar && !grammar.isTerminated) {
216
- await grammar.ready();
217
- const masked = grammar.applyMask(cur);
218
- if (cur !== logits1V) cur.dispose();
219
- cur = masked;
220
- }
221
- lp = toLogprobs(cur);
222
- if (cur !== logits1V) cur.dispose();
223
- cur = logits1V; // consumed — nothing owned under this name now
224
- tokArr = sampler(lp, step);
225
- lp.dispose();
226
- lp = null;
227
- const tok = ops.itemUint32(tokArr);
228
- tokArr.dispose();
229
- tokArr = null;
230
- // Advance the matcher on the emitted token (fires the next async fill).
231
- // EOS is never content and never grammar-valid — don't feed it.
232
- if (grammar && !eos.includes(tok)) grammar.accept(tok);
233
- if (history) {
234
- const t1 = ops.fromInt32([tok], [1]);
235
- const prev = history;
236
- history = ops.concatAxis([prev, t1], 0);
237
- prev.dispose();
238
- t1.dispose();
239
- }
240
- return tok;
241
- } finally {
242
- if (cur !== logits1V) cur.dispose();
243
- lp?.dispose();
244
- tokArr?.dispose();
245
- }
246
- };
175
+ const samplePos = async (logits1V: MlxArray, step: number): Promise<number> =>
176
+ (await stepSampler.sample(logits1V, step)).token;
247
177
 
248
178
  /** The [1,V] logits row at position `pos` of a hidden window — batched
249
179
  * lm-head is applied by the caller ONCE; this slices its output. */
@@ -255,11 +185,48 @@ export async function specServeRun(
255
185
  return flat;
256
186
  };
257
187
 
188
+ const forwardTokens = async (tokens: number[], taps?: number[]) => {
189
+ const ids = ops.fromInt32(tokens, [1, tokens.length]);
190
+ try { return await binding.forward(ids, caches, taps); }
191
+ finally { ids.dispose(); }
192
+ };
193
+ let failure: { error: unknown } | undefined;
194
+
258
195
  try {
259
- caches = model.makeCache();
260
- const src = provider.open({ sampler, target: { model, caches } });
196
+ options.signal?.throwIfAborted();
197
+ if (!Number.isSafeInteger(prefillChunk) || prefillChunk < 1)
198
+ throw new Error("speculative prefill chunk must be a positive integer");
199
+ assertMlxSpeculativeBinding(binding);
200
+ if (options.turboQuant || options.kvConfig?.length ||
201
+ (options.kvBits && (options.kvBits !== 4 || options.quantizedKvStart !== 0)))
202
+ throw new Error("speculative KV requires the qualified uniform KV4 start=0 policy");
203
+ caches = binding.makeCache();
204
+ const src = binding.openDraft(sampler, caches);
261
205
  source = src;
262
206
  tapLayers = src.tapLayers;
207
+ const prefixBudget = Math.max(0, options.speculativeCacheBytes ?? 0);
208
+ const prefix = src.prefillMode === "full" ? src.prefix : undefined;
209
+ const prefixNamespace = JSON.stringify({ adapters: options.adapters ?? [],
210
+ kvBits: options.kvBits ?? null, kvGroupSize: options.kvGroupSize ?? 64,
211
+ quantizedKvStart: options.quantizedKvStart ?? null });
212
+ const cached = prefix?.restore(promptIds, caches, prefixNamespace, prefixBudget) ?? 0;
213
+ stats.cachedTokens = cached;
214
+ const snapshotAt = prefix && prefixBudget > 0
215
+ ? Math.min(options.snapshotAt ?? promptIds.length, promptIds.length - 1)
216
+ : -1;
217
+ if (prefix && prefixBudget > 0 && cached > 0 && snapshotAt <= cached)
218
+ prefix.capture(promptIds.slice(0, cached), caches, prefixNamespace, prefixBudget);
219
+ const seedSource = async (end: number) => {
220
+ if (ctxParts.length === 1) prefillCtx = ctxParts[0]!;
221
+ else if (ctxParts.length > 1) {
222
+ prefillCtx = ops.concatAxis(ctxParts, 1);
223
+ for (const part of ctxParts) part.dispose();
224
+ }
225
+ ctxParts.length = 0;
226
+ const context = prefillCtx;
227
+ prefillCtx = null; // source.prefill owns context on both success and failure
228
+ await src.prefill(promptIds.slice(0, end), context ?? undefined);
229
+ };
263
230
  // ---- prefill (target, chunked; optionally tapped for DSpark), then seed
264
231
  // the source. Order: the source's prefill needs the tapped context that the
265
232
  // target prefill produces, so target-first (two-model's own draft prefill
@@ -290,77 +257,71 @@ export async function specServeRun(
290
257
  const t0 = performance.now();
291
258
  const tailSplit =
292
259
  src.prefillMode !== "full" &&
293
- flagOn("MLX_BUN_PREFILL_TAIL_SPLIT", true);
260
+ binding.prefillTailSplit;
294
261
  const oracleShape = tailSplit && promptIds.length > 1;
295
262
  if (oracleShape) {
296
263
  let pos = 0;
297
264
  const end = promptIds.length - 1; // last prompt token NEVER prefilled
298
265
  while (pos < end) {
299
- const n = Math.min(PREFILL_CHUNK, end - pos);
266
+ options.signal?.throwIfAborted();
267
+ const n = Math.min(prefillChunk, end - pos);
300
268
  const chunk = promptIds.slice(pos, pos + n);
301
- const ids = ops.fromInt32(chunk, [1, chunk.length]);
302
- const { hidden: h, ctxML } = await forwardMaybeTap(
303
- model,
304
- ids,
305
- caches,
306
- tapLayers,
307
- );
308
- ids.dispose();
269
+ const { hidden: h, ctxML } = await forwardTokens(chunk, tapLayers);
309
270
  if (ctxML) ctxParts.push(ctxML);
310
- if (pos + n >= end) {
311
- const L = h.shape[1]!;
312
- const H = h.shape[2]!;
313
- anchorHidden = h.slice([0, L - 1, 0], [1, L, H]); // position len-2
314
- }
315
- h.dispose(); // logits never computed during the drain
271
+ try {
272
+ if (pos + n >= end) {
273
+ const L = h.shape[1]!;
274
+ const H = h.shape[2]!;
275
+ anchorHidden = h.slice([0, L - 1, 0], [1, L, H]); // position len-2
276
+ }
277
+ } finally { h.dispose(); } // no logits during the drain
278
+ maybeQuantizeKv(caches, options);
316
279
  clearCache();
317
280
  pos += n;
281
+ if (pos < end) await new Promise<void>((resolve) => setImmediate(resolve));
318
282
  }
319
283
  } else {
320
284
  // Legacy shape: full chunked prefill (final chunk included), token0
321
285
  // sampled from the last position below.
322
- for (let off = 0; off < promptIds.length; off += PREFILL_CHUNK) {
323
- const chunk = promptIds.slice(off, off + PREFILL_CHUNK);
324
- const ids = ops.fromInt32(chunk, [1, chunk.length]);
325
- const { hidden: h, ctxML } = await forwardMaybeTap(
326
- model,
327
- ids,
328
- caches,
329
- tapLayers,
330
- );
331
- ids.dispose();
286
+ for (let off = cached; off < promptIds.length;) {
287
+ options.signal?.throwIfAborted();
288
+ const end = Math.min(off + prefillChunk, promptIds.length,
289
+ snapshotAt > off ? snapshotAt : Infinity);
290
+ const chunk = promptIds.slice(off, end);
291
+ const { hidden: h, ctxML } = await forwardTokens(chunk, tapLayers);
332
292
  if (ctxML) ctxParts.push(ctxML);
333
- if (off + PREFILL_CHUNK >= promptIds.length) {
293
+ try { if (end >= promptIds.length) {
334
294
  const L = h.shape[1]!;
335
295
  const H = h.shape[2]!;
336
296
  anchorHidden = h.slice([0, L - 1, 0], [1, L, H]); // [1,1,H], retained
337
- const lg = model.logitsFromHidden(anchorHidden);
338
- const V = lg.shape[lg.shape.length - 1]!;
339
- lastLogits = ops.reshape(lg, [1, V]);
340
- lg.dispose();
297
+ const lg = binding.projectLogits(anchorHidden);
298
+ try {
299
+ const V = lg.shape[lg.shape.length - 1]!;
300
+ lastLogits = ops.reshape(lg, [1, V]);
301
+ } finally { lg.dispose(); }
302
+ } } finally { h.dispose(); }
303
+ maybeQuantizeKv(caches, options);
304
+ if (end === snapshotAt && snapshotAt > cached) {
305
+ await seedSource(end);
306
+ prefix!.capture(promptIds.slice(0, end), caches, prefixNamespace, prefixBudget);
341
307
  }
342
- h.dispose();
343
308
  clearCache();
309
+ off = end;
310
+ if (off < promptIds.length)
311
+ await new Promise<void>((resolve) => setImmediate(resolve));
344
312
  }
345
313
  }
346
314
  extras.targetCalls++;
315
+ options.signal?.throwIfAborted();
347
316
  // Seed the source: two-model prefills its own draft cache; DSpark seeds
348
317
  // H_ctx from the tapped prompt context (ownership transfers to prefill);
349
318
  // the assistant is a no-op.
350
- if (tapLayers) {
351
- if (ctxParts.length === 1) prefillCtx = ctxParts[0]!;
352
- else if (ctxParts.length > 1) {
353
- prefillCtx = ops.concatAxis(ctxParts, 1); // [1,Lp,m*H]
354
- for (const p of ctxParts) p.dispose();
355
- }
356
- ctxParts.length = 0; // parts consumed (transferred as prefillCtx or disposed)
357
- await src.prefill(promptIds, prefillCtx ?? undefined); // takes ownership of prefillCtx
358
- prefillCtx = null; // ownership transferred
359
- } else {
360
- await src.prefill(promptIds);
361
- }
319
+ await seedSource(promptIds.length);
320
+ // Conversion replaces plain cache objects. Bind only after prefill has
321
+ // converted every attention layer; rollback must own the active objects.
322
+ const rollback = binding.bindRollback(caches);
362
323
  stats.prefillMs = performance.now() - t0;
363
- stats.prefillTps = (promptIds.length / Math.max(stats.prefillMs, 1e-6)) * 1000;
324
+ stats.prefillTps = ((promptIds.length - cached) / Math.max(stats.prefillMs, 1e-6)) * 1000;
364
325
 
365
326
  // ---- first pending token ----
366
327
  // Oracle shape: pending = the UNPROCESSED last prompt token — never
@@ -376,11 +337,12 @@ export async function specServeRun(
376
337
  pending = await samplePos(lastLogits!, 0);
377
338
  lastLogits!.dispose();
378
339
  lastLogits = null;
340
+ stats.generatedTokens++;
379
341
  if (eos.includes(pending)) {
342
+ stats.finishReason = "stop";
380
343
  stats.decodeMs = performance.now() - tDecode;
381
344
  return stats;
382
345
  }
383
- stats.generatedTokens++;
384
346
  if (
385
347
  (await onToken(pending)) === false ||
386
348
  stats.generatedTokens >= maxTokens ||
@@ -397,28 +359,24 @@ export async function specServeRun(
397
359
 
398
360
  // ---- rounds ----
399
361
  decode: while (stats.generatedTokens < maxTokens) {
362
+ options.signal?.throwIfAborted();
400
363
  if (!speculating) {
401
364
  // plain single-token continuation (post-ring-wrap fallback)
402
- const ids = ops.fromInt32([pending], [1, 1]);
403
- const { hidden: h } = await forwardMaybeTap(
404
- model,
405
- ids,
406
- caches,
407
- undefined,
408
- );
409
- ids.dispose();
410
- const lg = model.logitsFromHidden(h);
411
- h.dispose();
412
- const V = lg.shape[lg.shape.length - 1]!;
413
- roundRow = ops.reshape(lg, [1, V]);
414
- lg.dispose();
365
+ const { hidden: h } = await forwardTokens([pending], undefined);
366
+ try {
367
+ const lg = binding.projectLogits(h);
368
+ try {
369
+ const V = lg.shape[lg.shape.length - 1]!;
370
+ roundRow = ops.reshape(lg, [1, V]);
371
+ } finally { lg.dispose(); }
372
+ } finally { h.dispose(); }
415
373
  extras.targetCalls++;
416
374
  const tok = await samplePos(roundRow, stats.generatedTokens);
417
375
  roundRow.dispose();
418
376
  roundRow = null;
419
377
  clearCache();
420
- if (eos.includes(tok)) break;
421
378
  stats.generatedTokens++;
379
+ if (eos.includes(tok)) { stats.finishReason = "stop"; break; }
422
380
  pending = tok;
423
381
  if ((await onToken(tok)) === false) break;
424
382
  if (grammar?.isTerminated) break; // grammar closed — finish "stop"
@@ -433,11 +391,11 @@ export async function specServeRun(
433
391
  // KV would be woven into the live window (unrecoverable, upstream
434
392
  // RAISES here, generate.py:529-533). Degrade to plain decode while the
435
393
  // caches are still clean instead of 500ing a stream.
436
- const roundFits = caches.every((c) =>
437
- "maxSize" in c && typeof (c as { maxSize?: number }).maxSize === "number"
438
- ? c.offset + n + 1 < (c as { maxSize: number }).maxSize
439
- : c.isTrimmable(),
440
- );
394
+ // Non-trimmable recurrent caches (SSMCache — gated-DeltaNet state) are
395
+ // spec-eligible through the round snapshot/replay contract instead:
396
+ // specRoundBegin before the verify forward, then commit or a bit-exact
397
+ // rollback(keep) after the accept walk.
398
+ const roundFits = rollback.canBegin(n);
441
399
  if (!roundFits) {
442
400
  console.warn(
443
401
  "spec: sliding window nearly wrapped — finishing without speculation",
@@ -476,20 +434,22 @@ export async function specServeRun(
476
434
  // (b) ONE target forward over [pending, ...drafts] (optionally tapped for
477
435
  // DSpark's H_ctx). vHidden is retained past the accept walk: its slice at
478
436
  // the emitted position is the next round's anchor hidden.
437
+ // Arm recurrent caches' spec round around the verify forward (no-op for
438
+ // trimmable caches, which roll back via trim() below).
439
+ rollback.begin(d);
479
440
  const vIds = ops.fromInt32([pending, ...drafts], [1, d + 1]);
480
- const pinTarget = src.pinTargetKernelFamily === true &&
481
- "setSpecKernelPinned" in model;
482
- if (pinTarget) model.setSpecKernelPinned(true);
441
+ let pin: { close(): void } | undefined;
483
442
  try {
484
- const ft = await forwardMaybeTap(model, vIds, caches, tapLayers);
443
+ pin = src.pinTargetKernelFamily ? binding.pinVerify?.() : undefined;
444
+ const ft = await binding.forward(vIds, caches, tapLayers);
485
445
  // Assign these before the lm-head call so the outer cleanup owns them
486
446
  // even if logits construction throws.
487
447
  vHidden = ft.hidden;
488
448
  vCtxML = ft.ctxML;
489
- vLogits = model.logitsFromHidden(vHidden);
449
+ vLogits = binding.projectLogits(vHidden);
490
450
  } finally {
491
451
  vIds.dispose();
492
- if (pinTarget) model.setSpecKernelPinned(false);
452
+ pin?.close();
493
453
  }
494
454
  extras.targetCalls++;
495
455
  // The lm-head remains one logical batched operation. Native GLM MTP's
@@ -545,10 +505,18 @@ export async function specServeRun(
545
505
 
546
506
  // (d) emit the round's tokens through onToken, one at a time
547
507
  for (const tok of emitted) {
508
+ options.signal?.throwIfAborted();
548
509
  stats.generatedTokens++;
549
510
  if ((await onToken(tok)) === false) { halted = true; break; }
550
511
  if (stats.generatedTokens >= maxTokens) break;
551
512
  }
513
+ // Native generation and mlx-lm count the stopping EOS in usage while
514
+ // keeping it out of content. A callback or budget may stop earlier in
515
+ // this verified burst; that unused suffix must not enter usage.
516
+ if (sawEos && !halted && stats.generatedTokens < maxTokens) {
517
+ stats.generatedTokens++;
518
+ stats.finishReason = "stop";
519
+ }
552
520
  extras.tokensPerForward = extras.rounds > 0
553
521
  ? stats.generatedTokens / extras.rounds
554
522
  : 0;
@@ -565,7 +533,11 @@ export async function specServeRun(
565
533
  // position (the assistant borrows it; generate.ts:230 pattern — the slice
566
534
  // outlives its parent's dispose).
567
535
  if (!stop) {
568
- if (kAccept < d) for (const c of caches) c.trim(d - kAccept);
536
+ // The verify window kept positions 0..kAccept (pending + accepted
537
+ // drafts). Trimmable caches drop the rejected tail; recurrent caches
538
+ // restore their pre-round snapshot and bit-exactly replay those
539
+ // kAccept+1 kept tokens. On full accept the round just commits.
540
+ rollback.resolve(kAccept);
569
541
  await src.commit(
570
542
  d,
571
543
  kAccept,
@@ -597,21 +569,15 @@ export async function specServeRun(
597
569
  ? (stats.generatedTokens / stats.decodeMs) * 1000
598
570
  : 0;
599
571
  return stats;
572
+ } catch (error) {
573
+ failure = { error };
574
+ throw error;
600
575
  } finally {
601
- // Dispose whatever a mid-round/prefill throw left live (each is nulled the
602
- // instant its normal disposal or ownership transfer ran, so no double-free).
603
- anchorHidden?.dispose();
604
- lastLogits?.dispose();
605
- prefillCtx?.dispose();
606
- for (const p of ctxParts) p.dispose();
607
- vLogits?.dispose();
608
- roundRow?.dispose();
609
- vHidden?.dispose();
610
- vCtxML?.dispose();
611
- for (const c of caches) c.dispose();
612
- source?.dispose();
613
- history?.dispose();
614
- options.grammar?.dispose(); // Phase C will consume it; never leak either way
615
- clearCache();
576
+ const resources = [anchorHidden, lastLogits, prefillCtx, ...ctxParts,
577
+ vLogits, roundRow, vHidden, vCtxML, ...caches, source, stepSampler,
578
+ options.grammar, { dispose: clearCache }]
579
+ .filter((resource): resource is DisposableResource => resource != null);
580
+ if (failure) cleanupFailure(failure.error, () => disposeResources(resources));
581
+ else disposeResources(resources);
616
582
  }
617
583
  }
@@ -1,6 +1,6 @@
1
1
  // DraftSource — the seam between the serve-time speculative verify loop
2
2
  // (src/spec/serve-loop.ts) and whatever produces draft tokens. Designed in
3
- // docs/design/mlx-lm-tool-parity-plan.md §7 so every drafter shares ONE
3
+ // docs/reference/cli.md §7 so every drafter shares ONE
4
4
  // verify/accept executor:
5
5
  // - TwoModelSource (src/spec/two-model.ts) — mlx-lm parity (L1 oracle:
6
6
  // mlx_lm.server --draft-model), a full second model. Ignores `target`.
@@ -9,7 +9,7 @@
9
9
  // Reads the target's donor K/V + anchor hidden each step.
10
10
  // - DflashSource (src/spec/dflash-source.ts) — DSpark (L3, KL/quality-gated).
11
11
  // Taps the target's multi-layer hiddens (prefill + verify) into a growing
12
- // H_ctx (docs/design/dspark-speculative-decoding.md).
12
+ // H_ctx (docs/design/speculative-decoding.md).
13
13
  // - NgramSource (src/spec/ngram-source.ts) — model-free prompt lookup.
14
14
  // - Glm52NativeMtpSource (src/spec/glm52-mtp-source.ts) — the target
15
15
  // artifact's native Colibri MTP row.
@@ -21,21 +21,53 @@
21
21
  // two-model). See [[dspark-seam-kv-borrowing]].
22
22
 
23
23
  import type { MlxArray } from "../mlx/array";
24
- import type { RuntimeModel } from "../model/factory";
24
+ import type { Dtype } from "../mlx/ffi";
25
25
  import type { Cache } from "../model/gemma4";
26
26
 
27
- /** Read-only handle to the target's live decode state, handed to every source
28
- * at open(). Two-model ignores it; KV-borrowing sources read the target's
29
- * model (embed / lm-head / the draft module) and its LIVE cache array (donor
30
- * K/V views + offset — the SAME caches the serve loop drives). */
27
+ /** Numerical ports are backend-specific; returned arrays are caller-owned. */
28
+ export interface DraftProjection {
29
+ readonly embed: {
30
+ encode(ids: MlxArray): MlxArray;
31
+ readonly scales: { readonly dtype: Dtype };
32
+ };
33
+ logitsFromHidden(hidden: MlxArray): MlxArray;
34
+ }
35
+
36
+ export interface AssistantTarget {
37
+ position(): number;
38
+ embedScaled(token: number): MlxArray;
39
+ /** Retained temporal views; the draft round disposes every returned array. */
40
+ readDonors(): { sliding: [MlxArray, MlxArray]; full: [MlxArray, MlxArray] };
41
+ }
42
+
43
+ export interface QwenMtpTarget {
44
+ readonly hiddenSize: number;
45
+ readonly layerCount: number;
46
+ embed(ids: MlxArray): MlxArray;
47
+ logitsFromHidden(hidden: MlxArray): MlxArray;
48
+ }
49
+
50
+ /** Graph-declared extensions over the target's live state. Sources request
51
+ * only the ports they consume; they never inspect a concrete model or cache.
52
+ * Absence refuses an unsupported pairing before draft-side allocation. */
31
53
  export interface TargetView {
32
- readonly model: RuntimeModel;
33
- readonly caches: Cache[];
54
+ /** Opaque identity for providers borrowing weights from one exact target. */
55
+ readonly identity: object;
56
+ readonly assistant?: AssistantTarget;
57
+ readonly gemmaTaps?: { readonly layerCount: number; readonly projection: DraftProjection };
58
+ readonly qwenMtp?: QwenMtpTarget;
34
59
  }
35
60
 
36
61
  /** A per-request draft-token producer. Created per generation (owns its own
37
62
  * draft-side state), disposed by the serve loop's finally. */
38
63
  export interface DraftSource {
64
+ /** Optional paired target/draft prefill state. Restore transfers matching
65
+ * target state into the supplied array. Capture borrows evaluated state;
66
+ * the source owns its retained snapshot. Only full-prefill sources opt in. */
67
+ readonly prefix?: {
68
+ restore(prompt: readonly number[], caches: Cache[], namespace: string, maxBytes: number): number;
69
+ capture(tokens: number[], caches: Cache[], namespace: string, maxBytes: number): void;
70
+ };
39
71
  /** Target prefill shape required by this source's oracle. Most mlx-lm
40
72
  * sources leave the final prompt token pending; native Colibri MTP starts
41
73
  * from a full-prompt target forward. */
@@ -111,7 +143,7 @@ export interface DraftProvider {
111
143
  * sampler as the target — mlx-lm parity; greedy drafting under a
112
144
  * temperature>0 request is NOT parity). */
113
145
  sampler: (logprobs: MlxArray, step: number) => MlxArray;
114
- /** The target model + its live caches (donor views / offset). */
146
+ /** Bound target capabilities, with explicit donor-view ownership. */
115
147
  target: TargetView;
116
148
  }): DraftSource;
117
149
  dispose(): void;
@@ -16,7 +16,7 @@
16
16
  import { MlxArray } from "../mlx/array";
17
17
  import * as ops from "../mlx/ops";
18
18
  import { clearCache } from "../mlx/ffi";
19
- import { flagOn } from "../flags";
19
+ import { flagOn } from "../runtime-config";
20
20
  import { loadModelConfig } from "../config";
21
21
  import { Weights } from "../weights";
22
22
  import { createModel, type RuntimeModel } from "../model/factory";