mlx-bun 0.2.0 → 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 (364) hide show
  1. package/README.md +71 -588
  2. package/docs/README.md +91 -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/{design → archive/investigations}/optimization_plan.md +1 -1
  8. package/docs/{design → archive/investigations}/pre-colibri-stabilization.md +5 -5
  9. package/docs/{design → archive/investigations}/the-dreaming-handoff.md +2 -2
  10. package/docs/design/batching.md +569 -0
  11. package/docs/design/decode-speed-program.md +3178 -169
  12. package/docs/design/dreaming-nightly-pipeline.md +675 -282
  13. package/docs/design/generic-model-support.md +579 -334
  14. package/docs/design/kv-cache.md +680 -0
  15. package/docs/design/orpo-training.md +673 -1239
  16. package/docs/design/speculative-decoding.md +1627 -0
  17. package/docs/design/turboquant.md +2102 -0
  18. package/docs/design/unified-engine-frontier-plan.md +1831 -727
  19. package/docs/design/web-chat-redesign.md +666 -1083
  20. package/docs/planning/PRODUCT_ROADMAP.md +34 -1
  21. package/docs/planning/release-notes-v0.3.0.md +35 -0
  22. package/docs/planning/video-series.md +2 -2
  23. package/docs/reference/benchmarks.md +1162 -0
  24. package/docs/reference/cli.md +394 -159
  25. package/docs/reference/distribution.md +321 -182
  26. package/docs/reference/environment.md +348 -0
  27. package/docs/reference/glossary.md +39 -0
  28. package/docs/reference/library-api.md +566 -168
  29. package/docs/reference/memory.md +227 -64
  30. package/docs/reference/models.md +104 -6
  31. package/docs/reference/server-api.md +886 -425
  32. package/docs/reference/server-config.md +638 -330
  33. package/docs/reference/training.md +393 -254
  34. package/docs/reference/troubleshooting.md +166 -0
  35. package/package.json +9 -4
  36. package/src/audio/decode.ts +2 -2
  37. package/src/audio/features.ts +2 -2
  38. package/src/backends/mlx/autoregressive.ts +108 -0
  39. package/src/backends/mlx/batch-group.ts +1646 -0
  40. package/src/backends/mlx/diffusion.ts +39 -0
  41. package/src/backends/mlx/draft-target.ts +47 -0
  42. package/src/backends/mlx/evaluation.ts +105 -0
  43. package/src/backends/mlx/fill-append.ts +45 -0
  44. package/src/backends/mlx/gateway-binding.ts +71 -0
  45. package/src/backends/mlx/graph.ts +38 -0
  46. package/src/backends/mlx/memory-client.ts +189 -0
  47. package/src/backends/mlx/memory-guard.ts +40 -0
  48. package/src/backends/mlx/methods.ts +90 -0
  49. package/src/backends/mlx/model-prompt.ts +164 -0
  50. package/src/backends/mlx/model-serving.ts +79 -0
  51. package/src/backends/mlx/prefill.ts +33 -0
  52. package/src/backends/mlx/request-policy.ts +20 -0
  53. package/src/backends/mlx/rollback.ts +51 -0
  54. package/src/backends/mlx/serial-executor.ts +273 -0
  55. package/src/backends/mlx/speculative.ts +114 -0
  56. package/src/backends/mlx/state-views.ts +26 -0
  57. package/src/backends/mlx/text-engine.ts +73 -0
  58. package/src/cli.ts +58 -25
  59. package/src/client.ts +66 -0
  60. package/src/config.ts +21 -4
  61. package/src/contracts/completion.ts +16 -0
  62. package/src/contracts/execution.ts +49 -0
  63. package/src/contracts/generation.ts +103 -0
  64. package/src/contracts/host.ts +7 -0
  65. package/src/contracts/jobs.ts +66 -0
  66. package/src/contracts/pi-provider.ts +30 -0
  67. package/src/contracts/pi.ts +259 -0
  68. package/src/contracts/resources.ts +18 -0
  69. package/src/contracts/scheduling.ts +27 -0
  70. package/src/contracts/tsconfig.json +14 -0
  71. package/src/diffusion/diffusion-generate.ts +218 -153
  72. package/src/embed.ts +1 -1
  73. package/src/engine/admission.ts +59 -0
  74. package/src/engine/cancellation.ts +38 -0
  75. package/src/engine/engine.ts +56 -0
  76. package/src/engine/execution-coordinator.ts +70 -0
  77. package/src/engine/execution-plan.ts +51 -0
  78. package/src/engine/resources.ts +42 -0
  79. package/src/engine/scheduler.ts +67 -0
  80. package/src/engine/session.ts +247 -0
  81. package/src/eval/README.md +44 -1
  82. package/src/eval/kl.ts +236 -3
  83. package/src/eval/reference-logits.ts +207 -0
  84. package/src/eval/runner.ts +8 -96
  85. package/src/fill/echo-index.ts +188 -0
  86. package/src/fill/fill-session.ts +452 -0
  87. package/src/fill/proposal.ts +68 -0
  88. package/src/fill/schema-rows.ts +506 -0
  89. package/src/fill/tool-boundary.ts +103 -0
  90. package/src/fit.ts +1 -1
  91. package/src/generate.ts +656 -268
  92. package/src/grammar.ts +12 -7
  93. package/src/harness-pi.ts +14 -33
  94. package/src/hub-rest.ts +4 -4
  95. package/src/index.ts +7 -0
  96. package/src/inference/denoising.ts +17 -0
  97. package/src/inference/graph.ts +33 -0
  98. package/src/inference/prefill.ts +39 -0
  99. package/src/inference/rollback.ts +7 -0
  100. package/src/jobs/db.ts +4 -0
  101. package/src/jobs/index.ts +2 -0
  102. package/src/jobs/job-entry.ts +12 -12
  103. package/src/jobs/runner.ts +98 -13
  104. package/src/jobs/task-client.ts +19 -0
  105. package/src/jobs/types.ts +2 -58
  106. package/src/kv-store.ts +109 -49
  107. package/src/lab/README.md +18 -0
  108. package/src/{assets → lab/curve}/curve-designer.html +2 -2
  109. package/src/{curve-sampler.ts → lab/curve/curve-sampler.ts} +2 -2
  110. package/src/{expert-trace.ts → lab/expert-trace/expert-trace.ts} +5 -5
  111. package/src/{model → lab/paged-kv}/paged-kv.ts +9 -6
  112. package/src/library.ts +40 -0
  113. package/src/lora.ts +18 -0
  114. package/src/media-fetch.ts +1 -1
  115. package/src/memory/article.ts +1 -1
  116. package/src/memory/chunk.ts +1 -1
  117. package/src/memory/model.ts +30 -169
  118. package/src/memory/prompts.ts +1 -1
  119. package/src/memory/rest.ts +1 -1
  120. package/src/memory/schedule.ts +1 -1
  121. package/src/memory/stages.ts +4 -1
  122. package/src/memory/vault.ts +1 -3
  123. package/src/mlx/array.ts +9 -10
  124. package/src/mlx/compile.ts +12 -2
  125. package/src/mlx/ffi.ts +58 -11
  126. package/src/mlx/materialize.ts +23 -0
  127. package/src/mlx/metal-kernel.ts +10 -11
  128. package/src/mlx/ops.ts +33 -13
  129. package/src/mlx/quantized-rows.ts +36 -0
  130. package/src/mlx/token-bitmask.ts +52 -0
  131. package/src/mlx/turboquant-kv-decode.ts +82 -0
  132. package/src/mlx/turboquant-kv-inverse.ts +122 -0
  133. package/src/mlx/turboquant-ops.ts +33 -3
  134. package/src/mlx/turboquant-tables.ts +2 -2
  135. package/src/model/batched-mask.ts +8 -2
  136. package/src/model/batched-quant.ts +1 -1
  137. package/src/model/batched-rotating-quant.ts +2 -2
  138. package/src/model/batched-rotating.ts +1 -1
  139. package/src/model/compiled-decode.ts +1 -1
  140. package/src/model/diffusion-gemma.ts +8 -8
  141. package/src/model/factory.ts +76 -33
  142. package/src/model/fingerprint.ts +1 -1
  143. package/src/model/flash-attention.ts +3 -3
  144. package/src/model/gemma4-base.ts +26 -9
  145. package/src/model/gemma4.ts +6 -6
  146. package/src/model/generated/gemma4-12b.ts +2 -2
  147. package/src/model/generated/gemma4-26b.ts +2 -2
  148. package/src/model/generated/gemma4-e4b.ts +2 -2
  149. package/src/model/glm52-dsa.ts +13 -9
  150. package/src/model/glm52-memory.ts +1 -1
  151. package/src/model/implementation.ts +69 -0
  152. package/src/model/minicpm5.ts +6 -8
  153. package/src/model/profile.ts +12 -2
  154. package/src/model/qwen-append.ts +12 -0
  155. package/src/model/qwen-conv.ts +52 -0
  156. package/src/model/qwen3_5-checkpoint.ts +164 -0
  157. package/src/model/qwen3_5.ts +140 -56
  158. package/src/model/support.ts +1 -1
  159. package/src/model/trellis-balanced-scatter.ts +51 -0
  160. package/src/model/trellis-linear.ts +672 -0
  161. package/src/model/trellis-shared-m.ts +151 -0
  162. package/src/model/trellis-shared-scatter.ts +144 -0
  163. package/src/model/trellis-splitk-prefill.ts +158 -0
  164. package/src/model/trellis-tiled-prefill.ts +136 -0
  165. package/src/model/trellis-vector-expand.ts +62 -0
  166. package/src/model/trellis-wide-prefill.ts +94 -0
  167. package/src/model/universal/dense.ts +1 -1
  168. package/src/model/universal/modules.ts +8 -1
  169. package/src/native-pack.ts +6 -5
  170. package/src/pi-provider.ts +6 -64
  171. package/src/pi-web.ts +8 -315
  172. package/src/prompt-cache.ts +96 -53
  173. package/src/quantize/allocator.ts +1 -1
  174. package/src/quantize/config-writer.ts +10 -3
  175. package/src/quantize/rotate.ts +4 -4
  176. package/src/quantize/trellis.ts +586 -0
  177. package/src/responses.ts +28 -1
  178. package/src/runtime-config.ts +18 -4
  179. package/src/sampler.ts +4 -4
  180. package/src/serve/batch-scheduler.ts +8 -1671
  181. package/src/serve/chat-request.ts +391 -0
  182. package/src/serve/chat-stage.ts +195 -0
  183. package/src/serve/checkpoint-identity.ts +64 -0
  184. package/src/serve/completion-executor.ts +15 -7
  185. package/src/serve/discovery-routes.ts +12 -13
  186. package/src/serve/generation-gateway.ts +154 -193
  187. package/src/serve/http.ts +159 -0
  188. package/src/serve/inference-request.ts +79 -0
  189. package/src/serve/isolate.ts +297 -105
  190. package/src/serve/lab-routes.ts +3 -0
  191. package/src/serve/lane-registry.ts +2 -1
  192. package/src/serve/model-admin-routes.ts +7 -5
  193. package/src/serve/model-binding.ts +43 -0
  194. package/src/serve/model-host.ts +512 -0
  195. package/src/serve/openai-wire.ts +103 -0
  196. package/src/serve/parent-application.ts +56 -0
  197. package/src/serve/pipeline.ts +33 -0
  198. package/src/serve/preparation.ts +33 -0
  199. package/src/serve/request-plan.ts +19 -15
  200. package/src/serve/request-prep.ts +346 -0
  201. package/src/serve/response-lease.ts +24 -0
  202. package/src/serve/responses-client.ts +69 -0
  203. package/src/serve/session-completion-engine.ts +110 -0
  204. package/src/serve/session-search.ts +1 -1
  205. package/src/serve/static-routes.ts +5 -5
  206. package/src/serve/text-completion-stage.ts +104 -0
  207. package/src/serve/token-streams.ts +519 -0
  208. package/src/server.ts +317 -2649
  209. package/src/spec/assistant-source.ts +10 -44
  210. package/src/spec/deepspec-source.ts +6 -6
  211. package/src/spec/dflash-source.ts +8 -8
  212. package/src/spec/dspark/ab-stats.ts +3 -3
  213. package/src/spec/dspark/calibration.ts +2 -2
  214. package/src/spec/dspark/deepspec-module.ts +2 -2
  215. package/src/spec/dspark/generate-dflash.ts +2 -2
  216. package/src/spec/dspark/generate.ts +2 -2
  217. package/src/spec/dspark/loader.ts +1 -1
  218. package/src/spec/dspark/module-dflash.ts +4 -3
  219. package/src/spec/dspark/module.ts +3 -3
  220. package/src/spec/dspark/quantize-drafter.ts +1 -1
  221. package/src/spec/generate.ts +1 -1
  222. package/src/spec/glm52-mtp-source.ts +1 -1
  223. package/src/spec/ngram-source.ts +1 -1
  224. package/src/spec/prefix-state.ts +45 -0
  225. package/src/spec/qwen-mtp-source.ts +166 -69
  226. package/src/spec/serve-loop.ts +154 -156
  227. package/src/spec/source.ts +42 -10
  228. package/src/spec/two-model.ts +1 -1
  229. package/src/ssd-cache.ts +80 -12
  230. package/src/ssd-durability.ts +25 -13
  231. package/src/tool-call.ts +51 -14
  232. package/src/train/flash-cce.ts +2 -2
  233. package/src/train/forward.ts +6 -0
  234. package/src/train/loss.ts +1 -1
  235. package/src/train/prefix-shared.ts +5 -1
  236. package/src/train/segmented.ts +5 -3
  237. package/src/train/trainer.ts +6 -6
  238. package/src/vision/prompt.ts +1 -1
  239. package/src/vision/qwen3vl-tower.ts +52 -10
  240. package/src/web/app.html +1 -1
  241. package/src/web/app.js +5 -3
  242. package/src/web/src/adapters-panel.ts +3 -3
  243. package/src/web/src/assistant.ts +2 -36
  244. package/src/web/src/chat.ts +4 -4
  245. package/src/web/src/composer.ts +4 -4
  246. package/src/web/src/hub.ts +1 -1
  247. package/src/web/src/markdown.ts +0 -0
  248. package/src/web/src/memory-panel.ts +1 -1
  249. package/src/web/src/model-picker.ts +1 -1
  250. package/src/web/src/palette.ts +1 -1
  251. package/src/web/src/protocol.ts +3 -39
  252. package/src/web/src/sessions.ts +1 -1
  253. package/src/web/src/ui-catalog.ts +4 -18
  254. package/src/web-assets.ts +47 -0
  255. package/src/weights.ts +55 -9
  256. package/docs/archive/investigations/curve-runs/bisector-route.json +0 -98
  257. package/docs/archive/investigations/curve-runs/bisector-trace.json +0 -1393
  258. package/docs/archive/investigations/curve-runs/curve-terrain.json +0 -1179
  259. package/docs/archive/investigations/curve-runs/curve-terrain.svg +0 -42
  260. package/docs/archive/investigations/hlg-ab.html +0 -34
  261. package/docs/archive/investigations/hlg-explorer.html +0 -238
  262. package/docs/archive/investigations/hlg-figs/cliff.svg +0 -1
  263. package/docs/archive/investigations/hlg-figs/cross.svg +0 -1
  264. package/docs/archive/investigations/hlg-figs/edges.svg +0 -1
  265. package/docs/archive/investigations/hlg-figs/k20-grids.svg +0 -1
  266. package/docs/archive/investigations/hlg-figs/k20-peaks.svg +0 -1
  267. package/docs/archive/investigations/hlg-figs/lw-curve.svg +0 -1
  268. package/docs/archive/investigations/hlg-figs/terrain.svg +0 -1
  269. package/docs/archive/investigations/hlg-report.html +0 -262
  270. package/docs/archive/investigations/hlg-runs/hlg-ab.json +0 -149
  271. package/docs/archive/investigations/hlg-runs/hlg-blind-prompts.md +0 -87
  272. package/docs/archive/investigations/hlg-runs/hlg-blind.json +0 -107
  273. package/docs/archive/investigations/hlg-runs/hlg-cliff.json +0 -197
  274. package/docs/archive/investigations/hlg-runs/hlg-cross.json +0 -505
  275. package/docs/archive/investigations/hlg-runs/hlg-edge.json +0 -388
  276. package/docs/archive/investigations/hlg-runs/hlg-eval1.json +0 -25
  277. package/docs/archive/investigations/hlg-runs/hlg-frontier.json +0 -86
  278. package/docs/archive/investigations/hlg-runs/hlg-grid.json +0 -242
  279. package/docs/archive/investigations/hlg-runs/hlg-grid2.json +0 -343
  280. package/docs/archive/investigations/hlg-runs/hlg-lw-coarse.json +0 -110
  281. package/docs/archive/investigations/hlg-runs/hlg-map.json +0 -383
  282. package/docs/archive/investigations/hlg-runs/run0-original.log +0 -159
  283. package/docs/archive/investigations/hlg-runs/runA-recommended.log +0 -131
  284. package/docs/archive/investigations/hlg-runs/runAA-cliff-cpm5.log +0 -26
  285. package/docs/archive/investigations/hlg-runs/runAB-grid2-e4b.log +0 -18
  286. package/docs/archive/investigations/hlg-runs/runAC-peak-e4b-partial.log +0 -25
  287. package/docs/archive/investigations/hlg-runs/runAD-ab-samples.log +0 -8
  288. package/docs/archive/investigations/hlg-runs/runAE-edge-e4b.log +0 -51
  289. package/docs/archive/investigations/hlg-runs/runAF-logit-calibration.log +0 -10
  290. package/docs/archive/investigations/hlg-runs/runAG-eval-config2.log +0 -16
  291. package/docs/archive/investigations/hlg-runs/runB-gain-variants.log +0 -192
  292. package/docs/archive/investigations/hlg-runs/runC-pivots.log +0 -162
  293. package/docs/archive/investigations/hlg-runs/runD-oetf.log +0 -132
  294. package/docs/archive/investigations/hlg-runs/runE-eotf.log +0 -162
  295. package/docs/archive/investigations/hlg-runs/runF-shaper-working.log +0 -86
  296. package/docs/archive/investigations/hlg-runs/runG-shaper-sweep.log +0 -101
  297. package/docs/archive/investigations/hlg-runs/runH-wsweep.log +0 -176
  298. package/docs/archive/investigations/hlg-runs/runI-asweep.log +0 -176
  299. package/docs/archive/investigations/hlg-runs/runJ-knobmap.log +0 -49
  300. package/docs/archive/investigations/hlg-runs/runK-frontier.log +0 -19
  301. package/docs/archive/investigations/hlg-runs/runL-widemap.log +0 -65
  302. package/docs/archive/investigations/hlg-runs/runM-frontier-emb.log +0 -15
  303. package/docs/archive/investigations/hlg-runs/runN-verify.log +0 -116
  304. package/docs/archive/investigations/hlg-runs/runO-frontier-seed5000.log +0 -15
  305. package/docs/archive/investigations/hlg-runs/runP-grid-AxSm.log +0 -15
  306. package/docs/archive/investigations/hlg-runs/runQ-grid-tgW.log +0 -15
  307. package/docs/archive/investigations/hlg-runs/runR-blind-transcript.log +0 -79
  308. package/docs/archive/investigations/hlg-runs/runS-cross-coherence.log +0 -33
  309. package/docs/archive/investigations/hlg-runs/runT-cross-cpm5.log +0 -33
  310. package/docs/archive/investigations/hlg-runs/runU-cross-12b.log +0 -33
  311. package/docs/archive/investigations/hlg-runs/runV-lw-e4b.log +0 -22
  312. package/docs/archive/investigations/hlg-runs/runW-lw-12b.log +0 -22
  313. package/docs/archive/investigations/hlg-runs/runX-lw-cpm5.log +0 -22
  314. package/docs/archive/investigations/hlg-runs/runY-cliff-e4b.log +0 -26
  315. package/docs/archive/investigations/hlg-runs/runZ-cliff-12b.log +0 -26
  316. package/docs/archive/mlx-bun-lab-report.html +0 -275
  317. package/docs/archive/wwdc-mlx-bun.html +0 -848
  318. package/docs/design/adapters-end-to-end.md +0 -205
  319. package/docs/design/batching-perf-path.md +0 -255
  320. package/docs/design/batching-v2-plan.md +0 -529
  321. package/docs/design/bucketing-stage.md +0 -197
  322. package/docs/design/compat-cli-surface-design.md +0 -230
  323. package/docs/design/docs-reorg-plan.md +0 -165
  324. package/docs/design/dspark-serving-program.md +0 -371
  325. package/docs/design/dspark-speculative-decoding.md +0 -318
  326. package/docs/design/grammar-spec-batching-integration.md +0 -301
  327. package/docs/design/memory-inference-path.md +0 -422
  328. package/docs/design/memory-synthesis.md +0 -287
  329. package/docs/design/memory-system.md +0 -237
  330. package/docs/design/mlx-lm-serving-execution-seam.md +0 -81
  331. package/docs/design/mlx-lm-tool-parity-plan.md +0 -545
  332. package/docs/design/omlx-adoption-map.md +0 -98
  333. package/docs/design/orpo-dynamic-lambda.md +0 -220
  334. package/docs/design/orpo-future-enhancements.md +0 -340
  335. package/docs/design/paged-kv-cache.md +0 -214
  336. package/docs/design/parallel-slots.md +0 -567
  337. package/docs/design/parity-tier-dag.md +0 -161
  338. package/docs/design/repo-cleanup-plan.md +0 -231
  339. package/docs/design/runtime-isolation.md +0 -263
  340. package/docs/design/segmented-backward-training.md +0 -543
  341. package/docs/design/spec-decode-larger-targets.md +0 -229
  342. package/docs/design/ssd-kv-cold-tier.md +0 -415
  343. package/docs/design/structured-output.md +0 -484
  344. package/docs/design/tauri-desktop-app.md +0 -127
  345. package/docs/design/the-dreaming-master-plan.md +0 -445
  346. package/docs/design/turboquant-kv.md +0 -235
  347. package/docs/design/turboquant-weights.md +0 -467
  348. package/docs/design/w4a16-compute-precision-spike.md +0 -232
  349. package/docs/design/web-chat-beat-matrix.md +0 -315
  350. package/docs/design/write-pipeline-entity.md +0 -468
  351. package/docs/planning/IDEAS.md +0 -69
  352. package/docs/reference/embedding.md +0 -197
  353. package/docs/reference/features-matrix.md +0 -101
  354. package/docs/reference/orpo-quickstart.md +0 -135
  355. package/src/flags.ts +0 -11
  356. /package/docs/{design → archive/investigations}/colibri-glm52-port.md +0 -0
  357. /package/docs/{planning → archive/planning}/chunk-task-roadmap.md +0 -0
  358. /package/docs/{planning → archive/planning}/curve-sampler-research-plan.md +0 -0
  359. /package/docs/{planning → archive/planning}/memory-docs-and-dag-plan.md +0 -0
  360. /package/docs/{planning → archive/planning}/pi-integration-review.md +0 -0
  361. /package/docs/{planning → archive/planning}/release-notes-v0.2.0.md +0 -0
  362. /package/src/{memory-plan.ts → admission-plan.ts} +0 -0
  363. /package/{docs/archive/investigations → src/serve/assets}/curve-terrain.html +0 -0
  364. /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";
50
+ import { maybeQuantizeKv, withModelUsageFlush, withModelWiredLimit, type GenerateOptions, type GenerateStats } from "../generate";
51
+ import { runtimeConfig, withRuntimeConfig } from "../runtime-config";
49
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,9 +79,36 @@ 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
113
  const stepSampler = makeStepSampler(options, {
136
114
  tokenRepresentation: "number",
@@ -142,6 +120,8 @@ export async function specServeRun(
142
120
  sampler,
143
121
  });
144
122
  const gamma = Math.max(1, numDraftTokens);
123
+ const prefillChunk = options.prefillChunkSize ??
124
+ runtimeConfig().number("MLX_BUN_RD_PREFILL_CHUNK", PREFILL_CHUNK);
145
125
 
146
126
  // Allocated INSIDE the try below (2026-07-07 review): provider.open()
147
127
  // throws on a mismatched (target, drafter) pairing — as pre-try consts a
@@ -205,11 +185,48 @@ export async function specServeRun(
205
185
  return flat;
206
186
  };
207
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
+
208
195
  try {
209
- caches = model.makeCache();
210
- 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);
211
205
  source = src;
212
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
+ };
213
230
  // ---- prefill (target, chunked; optionally tapped for DSpark), then seed
214
231
  // the source. Order: the source's prefill needs the tapped context that the
215
232
  // target prefill produces, so target-first (two-model's own draft prefill
@@ -240,77 +257,71 @@ export async function specServeRun(
240
257
  const t0 = performance.now();
241
258
  const tailSplit =
242
259
  src.prefillMode !== "full" &&
243
- flagOn("MLX_BUN_PREFILL_TAIL_SPLIT", true);
260
+ binding.prefillTailSplit;
244
261
  const oracleShape = tailSplit && promptIds.length > 1;
245
262
  if (oracleShape) {
246
263
  let pos = 0;
247
264
  const end = promptIds.length - 1; // last prompt token NEVER prefilled
248
265
  while (pos < end) {
249
- const n = Math.min(PREFILL_CHUNK, end - pos);
266
+ options.signal?.throwIfAborted();
267
+ const n = Math.min(prefillChunk, end - pos);
250
268
  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();
269
+ const { hidden: h, ctxML } = await forwardTokens(chunk, tapLayers);
259
270
  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
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);
266
279
  clearCache();
267
280
  pos += n;
281
+ if (pos < end) await new Promise<void>((resolve) => setImmediate(resolve));
268
282
  }
269
283
  } else {
270
284
  // Legacy shape: full chunked prefill (final chunk included), token0
271
285
  // 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();
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);
282
292
  if (ctxML) ctxParts.push(ctxML);
283
- if (off + PREFILL_CHUNK >= promptIds.length) {
293
+ try { if (end >= promptIds.length) {
284
294
  const L = h.shape[1]!;
285
295
  const H = h.shape[2]!;
286
296
  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();
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);
291
307
  }
292
- h.dispose();
293
308
  clearCache();
309
+ off = end;
310
+ if (off < promptIds.length)
311
+ await new Promise<void>((resolve) => setImmediate(resolve));
294
312
  }
295
313
  }
296
314
  extras.targetCalls++;
315
+ options.signal?.throwIfAborted();
297
316
  // Seed the source: two-model prefills its own draft cache; DSpark seeds
298
317
  // H_ctx from the tapped prompt context (ownership transfers to prefill);
299
318
  // 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
- }
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);
312
323
  stats.prefillMs = performance.now() - t0;
313
- stats.prefillTps = (promptIds.length / Math.max(stats.prefillMs, 1e-6)) * 1000;
324
+ stats.prefillTps = ((promptIds.length - cached) / Math.max(stats.prefillMs, 1e-6)) * 1000;
314
325
 
315
326
  // ---- first pending token ----
316
327
  // Oracle shape: pending = the UNPROCESSED last prompt token — never
@@ -326,11 +337,12 @@ export async function specServeRun(
326
337
  pending = await samplePos(lastLogits!, 0);
327
338
  lastLogits!.dispose();
328
339
  lastLogits = null;
340
+ stats.generatedTokens++;
329
341
  if (eos.includes(pending)) {
342
+ stats.finishReason = "stop";
330
343
  stats.decodeMs = performance.now() - tDecode;
331
344
  return stats;
332
345
  }
333
- stats.generatedTokens++;
334
346
  if (
335
347
  (await onToken(pending)) === false ||
336
348
  stats.generatedTokens >= maxTokens ||
@@ -347,28 +359,24 @@ export async function specServeRun(
347
359
 
348
360
  // ---- rounds ----
349
361
  decode: while (stats.generatedTokens < maxTokens) {
362
+ options.signal?.throwIfAborted();
350
363
  if (!speculating) {
351
364
  // 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();
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(); }
365
373
  extras.targetCalls++;
366
374
  const tok = await samplePos(roundRow, stats.generatedTokens);
367
375
  roundRow.dispose();
368
376
  roundRow = null;
369
377
  clearCache();
370
- if (eos.includes(tok)) break;
371
378
  stats.generatedTokens++;
379
+ if (eos.includes(tok)) { stats.finishReason = "stop"; break; }
372
380
  pending = tok;
373
381
  if ((await onToken(tok)) === false) break;
374
382
  if (grammar?.isTerminated) break; // grammar closed — finish "stop"
@@ -387,11 +395,7 @@ export async function specServeRun(
387
395
  // spec-eligible through the round snapshot/replay contract instead:
388
396
  // specRoundBegin before the verify forward, then commit or a bit-exact
389
397
  // 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
- );
398
+ const roundFits = rollback.canBegin(n);
395
399
  if (!roundFits) {
396
400
  console.warn(
397
401
  "spec: sliding window nearly wrapped — finishing without speculation",
@@ -432,21 +436,20 @@ export async function specServeRun(
432
436
  // the emitted position is the next round's anchor hidden.
433
437
  // Arm recurrent caches' spec round around the verify forward (no-op for
434
438
  // trimmable caches, which roll back via trim() below).
435
- for (const c of caches) c.specRoundBegin?.();
439
+ rollback.begin(d);
436
440
  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);
441
+ let pin: { close(): void } | undefined;
440
442
  try {
441
- const ft = await forwardMaybeTap(model, vIds, caches, tapLayers);
443
+ pin = src.pinTargetKernelFamily ? binding.pinVerify?.() : undefined;
444
+ const ft = await binding.forward(vIds, caches, tapLayers);
442
445
  // Assign these before the lm-head call so the outer cleanup owns them
443
446
  // even if logits construction throws.
444
447
  vHidden = ft.hidden;
445
448
  vCtxML = ft.ctxML;
446
- vLogits = model.logitsFromHidden(vHidden);
449
+ vLogits = binding.projectLogits(vHidden);
447
450
  } finally {
448
451
  vIds.dispose();
449
- if (pinTarget) model.setSpecKernelPinned(false);
452
+ pin?.close();
450
453
  }
451
454
  extras.targetCalls++;
452
455
  // The lm-head remains one logical batched operation. Native GLM MTP's
@@ -502,10 +505,18 @@ export async function specServeRun(
502
505
 
503
506
  // (d) emit the round's tokens through onToken, one at a time
504
507
  for (const tok of emitted) {
508
+ options.signal?.throwIfAborted();
505
509
  stats.generatedTokens++;
506
510
  if ((await onToken(tok)) === false) { halted = true; break; }
507
511
  if (stats.generatedTokens >= maxTokens) break;
508
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
+ }
509
520
  extras.tokensPerForward = extras.rounds > 0
510
521
  ? stats.generatedTokens / extras.rounds
511
522
  : 0;
@@ -526,14 +537,7 @@ export async function specServeRun(
526
537
  // drafts). Trimmable caches drop the rejected tail; recurrent caches
527
538
  // restore their pre-round snapshot and bit-exactly replay those
528
539
  // 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
- }
540
+ rollback.resolve(kAccept);
537
541
  await src.commit(
538
542
  d,
539
543
  kAccept,
@@ -565,21 +569,15 @@ export async function specServeRun(
565
569
  ? (stats.generatedTokens / stats.decodeMs) * 1000
566
570
  : 0;
567
571
  return stats;
572
+ } catch (error) {
573
+ failure = { error };
574
+ throw error;
568
575
  } 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();
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);
584
582
  }
585
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";