@mlx-bun/inference 0.0.0-stage → 1.0.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 (467) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1475 -2
  3. package/THIRD_PARTY_NOTICES.md +3404 -0
  4. package/dist/native/libmlx_bun_ane.dylib +0 -0
  5. package/dist/native/libmlx_bun_expert_io.dylib +0 -0
  6. package/dist/native/mlx-bun-frame-extract +0 -0
  7. package/examples/fit-model.ts +25 -0
  8. package/examples/qwen3-forward.ts +42 -0
  9. package/examples/qwen3-generate.ts +28 -0
  10. package/examples/trellis-expand.ts +23 -0
  11. package/package.json +129 -4
  12. package/src/adapters/available.ts +110 -0
  13. package/src/adapters/index.ts +3 -0
  14. package/src/adapters/manager.ts +316 -0
  15. package/src/artifacts/auxiliary-files.ts +45 -0
  16. package/src/artifacts/config-dialects.ts +94 -0
  17. package/src/artifacts/config.ts +341 -0
  18. package/src/artifacts/draft-vocabulary.ts +30 -0
  19. package/src/artifacts/expert-offload-build.ts +75 -0
  20. package/src/artifacts/expert-offload.ts +89 -0
  21. package/src/artifacts/experts/index.ts +3 -0
  22. package/src/artifacts/experts/io.ts +386 -0
  23. package/src/artifacts/experts/residency.ts +1134 -0
  24. package/src/artifacts/experts/usage.ts +415 -0
  25. package/src/artifacts/fingerprint.ts +44 -0
  26. package/src/artifacts/glm52/container.ts +305 -0
  27. package/src/artifacts/glm52/expert-layout.ts +249 -0
  28. package/src/artifacts/glm52/index.ts +7 -0
  29. package/src/artifacts/glm52/kernel-layout.ts +124 -0
  30. package/src/artifacts/glm52/layout.ts +152 -0
  31. package/src/artifacts/glm52/memory.ts +500 -0
  32. package/src/artifacts/glm52/resident-weights.ts +135 -0
  33. package/src/artifacts/glm52/weights.ts +503 -0
  34. package/src/artifacts/glm52-config.ts +262 -0
  35. package/src/artifacts/identity.ts +94 -0
  36. package/src/artifacts/index.ts +15 -0
  37. package/src/artifacts/mmap.ts +90 -0
  38. package/src/artifacts/safetensors-writer.ts +249 -0
  39. package/src/artifacts/safetensors.ts +164 -0
  40. package/src/artifacts/turboquant-fold.ts +42 -0
  41. package/src/artifacts/weight-audit.ts +43 -0
  42. package/src/artifacts/weights.ts +255 -0
  43. package/src/contracts/index.ts +2 -0
  44. package/src/contracts/mlx/attention.ts +8 -0
  45. package/src/contracts/mlx/cache.ts +510 -0
  46. package/src/contracts/mlx/checkpoint.ts +10 -0
  47. package/src/contracts/mlx/draft-checkpoint.ts +14 -0
  48. package/src/contracts/mlx/draft-projection.ts +27 -0
  49. package/src/contracts/mlx/draft-target.ts +84 -0
  50. package/src/contracts/mlx/drafter.ts +176 -0
  51. package/src/contracts/mlx/forward-work.ts +23 -0
  52. package/src/contracts/mlx/glm52-weights.ts +25 -0
  53. package/src/contracts/mlx/graph.ts +309 -0
  54. package/src/contracts/mlx/head.ts +6 -0
  55. package/src/contracts/mlx/index.ts +15 -0
  56. package/src/contracts/mlx/kv-codec.ts +9 -0
  57. package/src/contracts/mlx/media.ts +113 -0
  58. package/src/contracts/mlx/positions.ts +16 -0
  59. package/src/contracts/mlx/row-table.ts +2 -0
  60. package/src/contracts/mlx/trainable.ts +103 -0
  61. package/src/contracts/mlx/whisper.ts +96 -0
  62. package/src/contracts/portable/composition.ts +106 -0
  63. package/src/contracts/portable/denoising.ts +17 -0
  64. package/src/contracts/portable/execution.ts +79 -0
  65. package/src/contracts/portable/generation.ts +103 -0
  66. package/src/contracts/portable/graph.ts +80 -0
  67. package/src/contracts/portable/index.ts +15 -0
  68. package/src/contracts/portable/memory-plan.ts +26 -0
  69. package/src/contracts/portable/object-cache.ts +8 -0
  70. package/src/contracts/portable/prefill.ts +63 -0
  71. package/src/contracts/portable/prefix-cache.ts +33 -0
  72. package/src/contracts/portable/prompt.ts +2 -0
  73. package/src/contracts/portable/resources.ts +25 -0
  74. package/src/contracts/portable/rollback.ts +7 -0
  75. package/src/contracts/portable/sampling.ts +44 -0
  76. package/src/contracts/portable/scheduling.ts +54 -0
  77. package/src/contracts/portable/token-delivery.ts +4 -0
  78. package/src/contracts/portable/tsconfig.json +14 -0
  79. package/src/embeddings/index.ts +1 -0
  80. package/src/embeddings/text.ts +85 -0
  81. package/src/execution/admission.ts +59 -0
  82. package/src/execution/batch-group.ts +1240 -0
  83. package/src/execution/batch-types.ts +225 -0
  84. package/src/execution/cancellation.ts +38 -0
  85. package/src/execution/continuation-request.ts +45 -0
  86. package/src/execution/continuation-types.ts +10 -0
  87. package/src/execution/coordinator.ts +70 -0
  88. package/src/execution/denoising-group.ts +180 -0
  89. package/src/execution/engine.ts +56 -0
  90. package/src/execution/fill-group.ts +505 -0
  91. package/src/execution/fit.ts +184 -0
  92. package/src/execution/gateway-binding.ts +178 -0
  93. package/src/execution/grammar-group.ts +224 -0
  94. package/src/execution/index.ts +18 -0
  95. package/src/execution/methods.ts +75 -0
  96. package/src/execution/mixed-iteration.ts +96 -0
  97. package/src/execution/plan.ts +65 -0
  98. package/src/execution/prefill-cohort.ts +100 -0
  99. package/src/execution/prefill-rows.ts +263 -0
  100. package/src/execution/request-policy.ts +19 -0
  101. package/src/execution/row-sampling.ts +49 -0
  102. package/src/execution/scheduler.ts +92 -0
  103. package/src/execution/session.ts +247 -0
  104. package/src/execution/speculative-group.ts +797 -0
  105. package/src/execution/tasks.ts +44 -0
  106. package/src/generation/autoregressive.ts +840 -0
  107. package/src/generation/bindings/autoregressive.ts +104 -0
  108. package/src/generation/bindings/denoising.ts +60 -0
  109. package/src/generation/bindings/prefill-policy.ts +36 -0
  110. package/src/generation/checkpoint-identity.ts +60 -0
  111. package/src/generation/denoising.ts +85 -0
  112. package/src/generation/diffusion.ts +583 -0
  113. package/src/generation/fill/echo-index.ts +188 -0
  114. package/src/generation/fill/index.ts +3 -0
  115. package/src/generation/fill/proposal.ts +69 -0
  116. package/src/generation/fill/schema-rows.ts +506 -0
  117. package/src/generation/fill/session.ts +464 -0
  118. package/src/generation/fill/tool-boundary.ts +103 -0
  119. package/src/generation/fill-append.ts +58 -0
  120. package/src/generation/generate.ts +44 -0
  121. package/src/generation/grammar-step.ts +39 -0
  122. package/src/generation/index.ts +14 -0
  123. package/src/generation/phase-dispatch.ts +115 -0
  124. package/src/generation/prefill.ts +33 -0
  125. package/src/generation/result.ts +49 -0
  126. package/src/generation/scopes.ts +157 -0
  127. package/src/generation/speculative/acceptance.ts +41 -0
  128. package/src/generation/speculative/adaptive-draft-gate.ts +166 -0
  129. package/src/generation/speculative/bindings/assistant-rows.ts +102 -0
  130. package/src/generation/speculative/bindings/autoregressive-rows.ts +124 -0
  131. package/src/generation/speculative/bindings/binding.ts +106 -0
  132. package/src/generation/speculative/bindings/deepspec-rows.ts +33 -0
  133. package/src/generation/speculative/bindings/draft-target.ts +10 -0
  134. package/src/generation/speculative/bindings/glm52-mtp-rows.ts +128 -0
  135. package/src/generation/speculative/bindings/projected-context-rows.ts +78 -0
  136. package/src/generation/speculative/bindings/projected-draft-rows.ts +95 -0
  137. package/src/generation/speculative/bindings/qwen-mtp-rows.ts +336 -0
  138. package/src/generation/speculative/bindings/two-model-rows.ts +63 -0
  139. package/src/generation/speculative/cache-identity.ts +16 -0
  140. package/src/generation/speculative/draft-kind.ts +149 -0
  141. package/src/generation/speculative/draft-policy.ts +41 -0
  142. package/src/generation/speculative/draft-registry.ts +118 -0
  143. package/src/generation/speculative/draft-vocab.ts +110 -0
  144. package/src/generation/speculative/index.ts +17 -0
  145. package/src/generation/speculative/output.ts +49 -0
  146. package/src/generation/speculative/round.ts +307 -0
  147. package/src/generation/speculative/source.ts +144 -0
  148. package/src/generation/speculative/sources/assistant-source.ts +64 -0
  149. package/src/generation/speculative/sources/deepspec-source.ts +70 -0
  150. package/src/generation/speculative/sources/dflash-source.ts +84 -0
  151. package/src/generation/speculative/sources/dflash2-source.ts +86 -0
  152. package/src/generation/speculative/sources/glm52-mtp-source.ts +66 -0
  153. package/src/generation/speculative/sources/ngram-source.ts +165 -0
  154. package/src/generation/speculative/sources/qwen-mtp-source.ts +154 -0
  155. package/src/generation/speculative/sources/two-model.ts +75 -0
  156. package/src/generation/types.ts +194 -0
  157. package/src/index.ts +20 -0
  158. package/src/input/audio/audiotoolbox.ts +112 -0
  159. package/src/input/audio/decode.ts +133 -0
  160. package/src/input/audio/features.ts +202 -0
  161. package/src/input/audio/index.ts +6 -0
  162. package/src/input/audio/transcode.ts +67 -0
  163. package/src/input/audio/whisper-mel.ts +185 -0
  164. package/src/input/audio/whisper-tokenizer.ts +238 -0
  165. package/src/input/chat-template.ts +389 -0
  166. package/src/input/index.ts +6 -0
  167. package/src/input/media-fetch.ts +439 -0
  168. package/src/input/packed-mixed.ts +43 -0
  169. package/src/input/special-tokens.ts +75 -0
  170. package/src/input/token-groups.ts +49 -0
  171. package/src/input/tokenizer.ts +336 -0
  172. package/src/input/tool-call.ts +689 -0
  173. package/src/input/vision/index.ts +3 -0
  174. package/src/input/vision/preprocess.ts +247 -0
  175. package/src/input/vision/prompt.ts +398 -0
  176. package/src/input/vision/qwen3vl-preprocess.ts +409 -0
  177. package/src/input/vision/qwen3vl-prompt.ts +237 -0
  178. package/src/input/vision/video-frames.ts +100 -0
  179. package/src/kernels/ane/fill.ts +101 -0
  180. package/src/kernels/ane/linear.ts +416 -0
  181. package/src/kernels/attention/flash.ts +694 -0
  182. package/src/kernels/attention/kvq4-decode.ts +155 -0
  183. package/src/kernels/attention/masks.ts +40 -0
  184. package/src/kernels/attention/multi-query.ts +193 -0
  185. package/src/kernels/attention/paged.ts +130 -0
  186. package/src/kernels/delta/gated.ts +306 -0
  187. package/src/kernels/delta/index.ts +3 -0
  188. package/src/kernels/delta/silu-rms-heads.ts +69 -0
  189. package/src/kernels/delta/state.ts +74 -0
  190. package/src/kernels/gated-rms-norm.ts +64 -0
  191. package/src/kernels/glm52/layout.ts +37 -0
  192. package/src/kernels/glm52/streamed-experts.ts +360 -0
  193. package/src/kernels/logits.ts +38 -0
  194. package/src/kernels/quantization/affine-row.ts +154 -0
  195. package/src/kernels/quantization/affine-rows.ts +36 -0
  196. package/src/kernels/quantization/affine-split-mma.ts +144 -0
  197. package/src/kernels/quantization/affine3-mma.ts +117 -0
  198. package/src/kernels/quantization/affine3-rows.ts +103 -0
  199. package/src/kernels/quantization/affine4-mma.ts +113 -0
  200. package/src/kernels/quantization/affine4-rows.ts +102 -0
  201. package/src/kernels/residual-rms-norm.ts +65 -0
  202. package/src/kernels/sampling/index.ts +2 -0
  203. package/src/kernels/sampling/normalized-argmax.ts +116 -0
  204. package/src/kernels/sampling/token-bitmask.ts +52 -0
  205. package/src/kernels/softcap.ts +35 -0
  206. package/src/kernels/trellis/codebook.ts +123 -0
  207. package/src/kernels/trellis/down-codebook.ts +184 -0
  208. package/src/kernels/trellis/down-factored.ts +180 -0
  209. package/src/kernels/trellis/down-k3-interleaved-factored.ts +146 -0
  210. package/src/kernels/trellis/down-mma-wide.ts +195 -0
  211. package/src/kernels/trellis/down-mma.ts +145 -0
  212. package/src/kernels/trellis/expand.ts +42 -0
  213. package/src/kernels/trellis/gate-up-codebook-mma.ts +141 -0
  214. package/src/kernels/trellis/gate-up-codebook.ts +137 -0
  215. package/src/kernels/trellis/gate-up-factored.ts +197 -0
  216. package/src/kernels/trellis/gate-up-mma-wide.ts +147 -0
  217. package/src/kernels/trellis/gate-up-mma.ts +148 -0
  218. package/src/kernels/trellis/gate-up.ts +248 -0
  219. package/src/kernels/trellis/geometry.ts +22 -0
  220. package/src/kernels/trellis/index.ts +103 -0
  221. package/src/kernels/trellis/launch.ts +12 -0
  222. package/src/kernels/trellis/mixed-gate-up-factored.ts +119 -0
  223. package/src/kernels/trellis/mixed-gate-up.ts +146 -0
  224. package/src/kernels/trellis/qualified.ts +34 -0
  225. package/src/kernels/trellis/reduce.ts +150 -0
  226. package/src/kernels/trellis/scatter.ts +407 -0
  227. package/src/kernels/trellis/splitk-prefill.ts +152 -0
  228. package/src/kernels/trellis/tiled-prefill.ts +131 -0
  229. package/src/kernels/trellis/vector-expand.ts +58 -0
  230. package/src/kernels/trellis/wide-prefill.ts +89 -0
  231. package/src/kernels/turboquant/decode.ts +82 -0
  232. package/src/kernels/turboquant/index.ts +5 -0
  233. package/src/kernels/turboquant/inverse.ts +122 -0
  234. package/src/kernels/turboquant/ops.ts +860 -0
  235. package/src/kernels/turboquant/signs.ts +130 -0
  236. package/src/kernels/turboquant/tables.ts +124 -0
  237. package/src/layers/affine-verify-linear.ts +75 -0
  238. package/src/layers/ane-prefill-split.ts +283 -0
  239. package/src/layers/compiled-normalization.ts +41 -0
  240. package/src/layers/delta-heads.ts +23 -0
  241. package/src/layers/dense-embedding.ts +29 -0
  242. package/src/layers/dense-linear.ts +32 -0
  243. package/src/layers/dequantized-linear.ts +72 -0
  244. package/src/layers/folded-attention.ts +42 -0
  245. package/src/layers/geglu.ts +130 -0
  246. package/src/layers/helpers.ts +7 -0
  247. package/src/layers/index.ts +36 -0
  248. package/src/layers/kv4-decode-attention.ts +15 -0
  249. package/src/layers/layer-norm.ts +24 -0
  250. package/src/layers/loading.ts +92 -0
  251. package/src/layers/lora.ts +150 -0
  252. package/src/layers/normalization.ts +9 -0
  253. package/src/layers/prefix-layout.ts +33 -0
  254. package/src/layers/quantized-append-attention.ts +32 -0
  255. package/src/layers/quantized-attention.ts +353 -0
  256. package/src/layers/quantized-embedding.ts +39 -0
  257. package/src/layers/quantized-linear.ts +137 -0
  258. package/src/layers/quantized-switch-linear.ts +37 -0
  259. package/src/layers/qwen-append.ts +12 -0
  260. package/src/layers/qwen-mrope.ts +238 -0
  261. package/src/layers/rope-2d.ts +110 -0
  262. package/src/layers/rope.ts +389 -0
  263. package/src/layers/swiglu.ts +23 -0
  264. package/src/layers/trellis-codebook-down.ts +52 -0
  265. package/src/layers/trellis-codebook-gate-up.ts +42 -0
  266. package/src/layers/trellis-down.ts +117 -0
  267. package/src/layers/trellis-gate-up.ts +71 -0
  268. package/src/layers/trellis-linear.ts +211 -0
  269. package/src/layers/trellis-prompt.ts +102 -0
  270. package/src/layers/trellis-reduce-gate-up.ts +73 -0
  271. package/src/layers/trellis-scatter-down.ts +98 -0
  272. package/src/models/ane-install.ts +74 -0
  273. package/src/models/audio/conformer.ts +656 -0
  274. package/src/models/cache-composition.ts +44 -0
  275. package/src/models/capabilities.ts +89 -0
  276. package/src/models/chat-template.ts +10 -0
  277. package/src/models/composition.ts +107 -0
  278. package/src/models/diffusion-gemma/experts.ts +59 -0
  279. package/src/models/diffusion-gemma/family.ts +21 -0
  280. package/src/models/diffusion-gemma/model.ts +900 -0
  281. package/src/models/diffusion-gemma/vision.ts +549 -0
  282. package/src/models/drafter-loaders.ts +49 -0
  283. package/src/models/drafters.ts +39 -0
  284. package/src/models/factory.ts +351 -0
  285. package/src/models/families.ts +70 -0
  286. package/src/models/family.ts +125 -0
  287. package/src/models/gemma4/assistant-target.ts +46 -0
  288. package/src/models/gemma4/assistant.ts +327 -0
  289. package/src/models/gemma4/blocks.ts +755 -0
  290. package/src/models/gemma4/compiled-decode.ts +452 -0
  291. package/src/models/gemma4/family.ts +39 -0
  292. package/src/models/gemma4/gemma4-12b.ts +677 -0
  293. package/src/models/gemma4/gemma4-26b.ts +544 -0
  294. package/src/models/gemma4/gemma4-e4b.ts +599 -0
  295. package/src/models/gemma4/graphs.ts +147 -0
  296. package/src/models/gemma4/media.ts +88 -0
  297. package/src/models/gemma4/model.ts +860 -0
  298. package/src/models/gemma4/serving-blocks.ts +72 -0
  299. package/src/models/gemma4/trainable.ts +186 -0
  300. package/src/models/gemma4/written-blocks.ts +232 -0
  301. package/src/models/gemma4/written-forward.ts +159 -0
  302. package/src/models/gemma4/written-input.ts +29 -0
  303. package/src/models/glm52/atlas.ts +507 -0
  304. package/src/models/glm52/coupling.ts +388 -0
  305. package/src/models/glm52/dsa.ts +601 -0
  306. package/src/models/glm52/family.ts +20 -0
  307. package/src/models/glm52/mla.ts +812 -0
  308. package/src/models/glm52/model.ts +1127 -0
  309. package/src/models/glm52/moe.ts +223 -0
  310. package/src/models/glm52/mtp.ts +60 -0
  311. package/src/models/glm52/pilot.ts +537 -0
  312. package/src/models/glm52/reference.ts +445 -0
  313. package/src/models/glm52/residency.ts +557 -0
  314. package/src/models/glm52/streamed-experts.ts +447 -0
  315. package/src/models/graph.ts +59 -0
  316. package/src/models/implementation.ts +230 -0
  317. package/src/models/index.ts +15 -0
  318. package/src/models/media-input.ts +15 -0
  319. package/src/models/memory-plan.ts +39 -0
  320. package/src/models/minicpm5/family.ts +29 -0
  321. package/src/models/minicpm5/model.ts +394 -0
  322. package/src/models/minicpm5/trainable.ts +90 -0
  323. package/src/models/profile.ts +337 -0
  324. package/src/models/qwen/blocks.ts +658 -0
  325. package/src/models/qwen/checkpoint.ts +190 -0
  326. package/src/models/qwen/families.ts +57 -0
  327. package/src/models/qwen/media-input.ts +161 -0
  328. package/src/models/qwen/mtp.ts +101 -0
  329. package/src/models/qwen/qwen3-moe.ts +355 -0
  330. package/src/models/qwen/qwen3.ts +323 -0
  331. package/src/models/qwen/qwen38-27b-trellis-m1max.ts +1532 -0
  332. package/src/models/qwen/qwen38-27b-trellis-m4pro.ts +1092 -0
  333. package/src/models/qwen/qwen3_5.ts +433 -0
  334. package/src/models/qwen/written-layers.ts +285 -0
  335. package/src/models/row-table.ts +1 -0
  336. package/src/models/runtime.ts +73 -0
  337. package/src/models/speculative/deepspec-artifact.ts +10 -0
  338. package/src/models/speculative/deepspec.ts +744 -0
  339. package/src/models/speculative/dflash.ts +553 -0
  340. package/src/models/speculative/dflash2-artifact.ts +9 -0
  341. package/src/models/speculative/dflash2-operations.ts +1 -0
  342. package/src/models/speculative/dflash2.ts +576 -0
  343. package/src/models/speculative/loader.ts +29 -0
  344. package/src/models/support.ts +79 -0
  345. package/src/models/universal/archs.ts +365 -0
  346. package/src/models/universal/blocks.ts +479 -0
  347. package/src/models/universal/dense.ts +255 -0
  348. package/src/models/universal/family.ts +17 -0
  349. package/src/models/vision/qwen3vl.ts +566 -0
  350. package/src/models/vision/siglip.ts +520 -0
  351. package/src/models/vision/unified.ts +176 -0
  352. package/src/models/whisper/family.ts +19 -0
  353. package/src/models/whisper/fast.ts +734 -0
  354. package/src/models/whisper/model.ts +430 -0
  355. package/src/runtime/compiled-trace.ts +16 -0
  356. package/src/runtime/config.ts +77 -0
  357. package/src/runtime/expert-trace.ts +71 -0
  358. package/src/runtime/graph-forward.ts +65 -0
  359. package/src/runtime/integers.ts +6 -0
  360. package/src/runtime/native.ts +16 -0
  361. package/src/runtime/resources.ts +53 -0
  362. package/src/runtime/row-operations.ts +42 -0
  363. package/src/runtime/trace.ts +161 -0
  364. package/src/sampling/curve.ts +122 -0
  365. package/src/sampling/draft.ts +55 -0
  366. package/src/sampling/extras.ts +45 -0
  367. package/src/sampling/filters.ts +134 -0
  368. package/src/sampling/grammar.ts +630 -0
  369. package/src/sampling/hlg.ts +290 -0
  370. package/src/sampling/index.ts +9 -0
  371. package/src/sampling/processors.ts +142 -0
  372. package/src/sampling/sampler.ts +120 -0
  373. package/src/sampling/step.ts +343 -0
  374. package/src/sampling/types.ts +161 -0
  375. package/src/sampling/window-filters.ts +46 -0
  376. package/src/scoring/decisions.ts +96 -0
  377. package/src/scoring/full-sequence.ts +103 -0
  378. package/src/scoring/index.ts +9 -0
  379. package/src/scoring/kl.ts +29 -0
  380. package/src/scoring/perplexity.ts +165 -0
  381. package/src/scoring/response-logps.ts +65 -0
  382. package/src/state/affine-attention.ts +180 -0
  383. package/src/state/attachment.ts +68 -0
  384. package/src/state/attention-read.ts +215 -0
  385. package/src/state/batched-kv.ts +294 -0
  386. package/src/state/batched-mask.ts +445 -0
  387. package/src/state/batched-quant.ts +389 -0
  388. package/src/state/batched-quantized-kv.ts +209 -0
  389. package/src/state/batched-rotating-quant.ts +457 -0
  390. package/src/state/batched-rotating-state.ts +149 -0
  391. package/src/state/batched-rotating.ts +430 -0
  392. package/src/state/batched-row-storage.ts +179 -0
  393. package/src/state/batched-ssm.ts +54 -0
  394. package/src/state/batched-turboquant-kv.ts +144 -0
  395. package/src/state/bf16-first-kv.ts +663 -0
  396. package/src/state/cache-service.ts +592 -0
  397. package/src/state/capabilities.ts +65 -0
  398. package/src/state/checkpoint.ts +46 -0
  399. package/src/state/continuation-persistence.ts +126 -0
  400. package/src/state/decode-step-inputs.ts +31 -0
  401. package/src/state/decoded-kv-donor.ts +12 -0
  402. package/src/state/delayed-quantized-kv.ts +141 -0
  403. package/src/state/delayed-rotating-quantized-kv.ts +178 -0
  404. package/src/state/delayed-turboquant-kv.ts +123 -0
  405. package/src/state/durability.ts +214 -0
  406. package/src/state/encoder-cache.ts +26 -0
  407. package/src/state/full-kv-row-append.ts +33 -0
  408. package/src/state/full-kv-row-donor.ts +54 -0
  409. package/src/state/full-prefill-padding.ts +56 -0
  410. package/src/state/full-prefill-row.ts +112 -0
  411. package/src/state/full-transitioning-kv-rows.ts +70 -0
  412. package/src/state/glm52-cache.ts +783 -0
  413. package/src/state/grouped-heads-kv4-cache.ts +26 -0
  414. package/src/state/index.ts +38 -0
  415. package/src/state/kv-attention-view.ts +77 -0
  416. package/src/state/kv-budget.ts +14 -0
  417. package/src/state/kv-scheme.ts +315 -0
  418. package/src/state/kv-storage.ts +57 -0
  419. package/src/state/kv-tensor-rows.ts +212 -0
  420. package/src/state/kv.ts +324 -0
  421. package/src/state/kv4-head256-cache.ts +46 -0
  422. package/src/state/latent-legos.ts +895 -0
  423. package/src/state/layout.ts +277 -0
  424. package/src/state/leases.ts +26 -0
  425. package/src/state/paged/cache.ts +689 -0
  426. package/src/state/paged/codec.ts +36 -0
  427. package/src/state/paged/rows.ts +223 -0
  428. package/src/state/persistence-types.ts +173 -0
  429. package/src/state/persistence-worker.ts +83 -0
  430. package/src/state/persistence.ts +1036 -0
  431. package/src/state/persistence.worker.d.ts +3 -0
  432. package/src/state/persistence.worker.js +264 -0
  433. package/src/state/prefix-cache.ts +685 -0
  434. package/src/state/quantized-kv.ts +369 -0
  435. package/src/state/quantized-tensor.ts +26 -0
  436. package/src/state/request-policy.ts +48 -0
  437. package/src/state/retention-policy.ts +37 -0
  438. package/src/state/rollback.ts +131 -0
  439. package/src/state/rotating-kv-donor.ts +35 -0
  440. package/src/state/rotating-kv-layout.ts +341 -0
  441. package/src/state/rotating-kv.ts +529 -0
  442. package/src/state/rotating-quantized-kv.ts +616 -0
  443. package/src/state/rotating-row-transaction.ts +35 -0
  444. package/src/state/rows.ts +104 -0
  445. package/src/state/softcap-attention.ts +118 -0
  446. package/src/state/speculative/glm52-mtp-state.ts +38 -0
  447. package/src/state/speculative/qwen-mtp-state.ts +34 -0
  448. package/src/state/speculative-rotating-kv.ts +113 -0
  449. package/src/state/ssd-cache.ts +434 -0
  450. package/src/state/ssm-prefill-padding.ts +39 -0
  451. package/src/state/ssm.ts +422 -0
  452. package/src/state/target-layout.ts +7 -0
  453. package/src/state/tiered-prefix-cache.ts +45 -0
  454. package/src/state/training-cache.ts +300 -0
  455. package/src/state/transitioning-kv-rows.ts +267 -0
  456. package/src/state/turboquant-attention.ts +97 -0
  457. package/src/state/turboquant-codec.ts +90 -0
  458. package/src/state/turboquant-kv.ts +442 -0
  459. package/src/state/views.ts +26 -0
  460. package/src/transcription/index.ts +3 -0
  461. package/src/transcription/silero-vad.ts +379 -0
  462. package/src/transcription/whisper/decode.ts +773 -0
  463. package/src/transcription/whisper/format.ts +74 -0
  464. package/src/transcription/whisper/index.ts +4 -0
  465. package/src/transcription/whisper/timing.ts +274 -0
  466. package/src/transcription/whisper/transcribe.ts +394 -0
  467. package/src/transcription/whisper/types.ts +22 -0
@@ -0,0 +1,41 @@
1
+ // Draft-selection policy for the speculative engine.
2
+ //
3
+ // Verification is exact token match: at each window position the TARGET
4
+ // samples its own token with the step key (seed, step), independent of the
5
+ // draft, and the draft is accepted only when equal. Draft policy therefore
6
+ // cannot change the output distribution, only acceptance and draft cost.
7
+ // It is NOT output-neutral trajectory by trajectory: a token's bf16 logits
8
+ // depend on which row of the verify window computed it, so a different
9
+ // acceptance pattern re-rolls some seeded trajectories. Compare draft-side
10
+ // arms by ms per round and tokens per round; compare tok/s only on items whose
11
+ // outputs are byte-identical.
12
+ //
13
+ // The default draft uses the request sampler with the SAME step key as the
14
+ // target position it predicts. MLX's categorical over a [1, V] input draws by
15
+ // INVERSE CDF in token-id order from ONE uniform of that key (see
16
+ // draft-vocab.ts), so draft and target read the same point of their own CDFs:
17
+ // a coupled draw whose match rate is far above independent sampling. Every
18
+ // knob here keeps that key; each is default off and needs a paired microbench
19
+ // win before any default (flag-and-try-both).
20
+ import { runtimeFlag, runtimeValue } from "../../runtime/config";
21
+ import type { SamplerOptions } from "../../sampling/index";
22
+
23
+ /** Sampler options for the DRAFT of a request. `greedy` is the composition's
24
+ * `greedyDraft`: argmax drafts, which drop the key coupling.
25
+ * `MLX_BUN_SPEC_DRAFT_TEMPERATURE=<t>` samples the draft at its own
26
+ * temperature: a head flatter than its target matches more often when
27
+ * sharpened (t < request temperature); t=0 is argmax.
28
+ * `MLX_BUN_SPEC_DRAFT_FILTER=0` drops top-p/top-k/min-p/XTC from the draft:
29
+ * the coupled inverse-CDF draw over the full vocabulary, a quarter of the
30
+ * per-step sampling cost, which at low temperature rarely leaves the
31
+ * filtered set. */
32
+ export function draftSamplerOptions<T extends SamplerOptions>(options: T, greedy: boolean): T {
33
+ if (greedy) return { ...options, temperature: 0 };
34
+ let next = options;
35
+ const temperature = runtimeValue("MLX_BUN_SPEC_DRAFT_TEMPERATURE");
36
+ if (temperature !== undefined && Number.isFinite(Number(temperature)) && Number(temperature) >= 0)
37
+ next = { ...next, temperature: Number(temperature) };
38
+ if (!runtimeFlag("MLX_BUN_SPEC_DRAFT_FILTER", true))
39
+ next = { ...next, topP: 0, topK: 0, minP: 0, xtcProbability: 0 };
40
+ return next;
41
+ }
@@ -0,0 +1,118 @@
1
+ // The draft providers a server can load, each owning how its artifact is
2
+ // recognized and how it is loaded. The registry is model-free: it never loads
3
+ // MLX itself, so callers detect kinds cheaply and pay for a provider only when
4
+ // they load one. Applications use the library's default registry
5
+ // (`defaultDraftProviders`), extend it, or hand a ready DraftProvider in
6
+ // instead; none names a provider class.
7
+ import { declaredGraph } from "../../models/capabilities";
8
+ import type { DraftProvider } from "./source";
9
+
10
+ /** A draft artifact directory, read lazily and at most once. */
11
+ export interface DraftArtifact {
12
+ readonly dir: string;
13
+ has(file: string): Promise<boolean>;
14
+ /** config.json, or null when absent or unreadable. */
15
+ config(): Promise<Record<string, unknown> | null>;
16
+ }
17
+
18
+ export function draftArtifact(dir: string): DraftArtifact {
19
+ let config: Promise<Record<string, unknown> | null> | undefined;
20
+ return {
21
+ dir,
22
+ has: file => Bun.file(`${dir}/${file}`).exists(),
23
+ config: () => config ??= Bun.file(`${dir}/config.json`).json().then(
24
+ value => value as Record<string, unknown>, () => null),
25
+ };
26
+ }
27
+
28
+ /** What the target offers a draft that must agree with it. */
29
+ export interface DraftTarget {
30
+ readonly vocabSize: number;
31
+ readonly tokenizer: { encode(text: string): readonly number[] };
32
+ }
33
+
34
+ export interface DraftLoadRequest {
35
+ /** The artifact directory; undefined for a kind with no artifact. */
36
+ readonly dir: string | undefined;
37
+ readonly target: DraftTarget;
38
+ /** Requested draft tokens per round; each kind applies its own default and bound. */
39
+ readonly numDraftTokens?: number;
40
+ /** Prompt-lookup window, for the kind that has one. */
41
+ readonly ngram?: { readonly max?: number; readonly min?: number };
42
+ }
43
+
44
+ export interface LoadedDraft {
45
+ readonly provider: DraftProvider;
46
+ readonly numDraftTokens: number;
47
+ /** The registered kind that loaded it (a graph may declare an adaptive width for it). */
48
+ readonly kind?: string;
49
+ }
50
+
51
+ export interface DraftProviderKind {
52
+ readonly kind: string;
53
+ /** False for a model-free kind: it takes no artifact and is chosen only by name. */
54
+ readonly artifact: boolean;
55
+ /** Subdirectory of a target artifact that bundles this kind's companion. */
56
+ readonly bundledCompanion?: string;
57
+ /** Whether the artifact is this kind's. Absent: the kind is chosen only by
58
+ * name. Kinds are asked in registration order. */
59
+ detect?(artifact: DraftArtifact): Promise<boolean>;
60
+ /** A catch-all: asked only after every other kind declined, wherever it was
61
+ * registered, so a kind added later is still detected ahead of it. */
62
+ readonly fallback?: boolean;
63
+ /** Load the provider and settle its round width; the provider is owned by the
64
+ * caller on return and disposed here on failure. */
65
+ load(request: DraftLoadRequest): Promise<LoadedDraft>;
66
+ }
67
+
68
+ /** A provider that binds to graphs declaring `GraphCapabilities.nativeDraft`
69
+ * with this kind: the draft head the graph's own checkpoint carries. */
70
+ export interface NativeDraftKind {
71
+ readonly kind: string;
72
+ create(graph: object): Promise<DraftProvider>;
73
+ }
74
+
75
+ export class DraftProviderRegistry {
76
+ readonly #kinds = new Map<string, DraftProviderKind>();
77
+ readonly #native = new Map<string, NativeDraftKind>();
78
+
79
+ register(kind: DraftProviderKind): this {
80
+ if (this.#kinds.has(kind.kind)) throw new Error(`draft provider kind ${kind.kind} is already registered`);
81
+ this.#kinds.set(kind.kind, kind);
82
+ return this;
83
+ }
84
+
85
+ registerNative(kind: NativeDraftKind): this {
86
+ if (this.#native.has(kind.kind)) throw new Error(`native draft kind ${kind.kind} is already registered`);
87
+ this.#native.set(kind.kind, kind);
88
+ return this;
89
+ }
90
+
91
+ kinds(): string[] { return [...this.#kinds.keys()]; }
92
+
93
+ get(kind: string): DraftProviderKind | undefined { return this.#kinds.get(kind); }
94
+
95
+ /** The first registered kind that recognizes the artifact. Reads files only. */
96
+ async detect(dir: string): Promise<string> {
97
+ const artifact = draftArtifact(dir);
98
+ const asked = [...this.#kinds.values()].filter(kind => kind.detect);
99
+ for (const kind of [...asked.filter(kind => !kind.fallback), ...asked.filter(kind => kind.fallback)])
100
+ if (await kind.detect!(artifact)) return kind.kind;
101
+ throw new Error(`${dir}: no registered draft provider recognizes this artifact`);
102
+ }
103
+
104
+ async load(kind: string, request: DraftLoadRequest): Promise<LoadedDraft> {
105
+ const entry = this.#kinds.get(kind);
106
+ if (!entry) throw new Error(`unknown draft provider kind ${kind} (registered: ${this.kinds().join(", ")})`);
107
+ return { ...await entry.load(request), kind };
108
+ }
109
+
110
+ /** The draft the graph declares it carries, or null when it declares none. */
111
+ async native(graph: object): Promise<LoadedDraft | null> {
112
+ const declared = declaredGraph(graph).graphCapabilities.nativeDraft;
113
+ if (!declared) return null;
114
+ const kind = this.#native.get(declared.kind);
115
+ if (!kind) throw new Error(`the graph declares native draft ${declared.kind}, which no provider is registered for`);
116
+ return { provider: await kind.create(graph), numDraftTokens: declared.numDraftTokens, kind: declared.kind };
117
+ }
118
+ }
@@ -0,0 +1,110 @@
1
+ // Frequency-ranked draft vocabulary (FR-Spec, Zhao et al. 2025).
2
+ //
3
+ // A native MTP head spends most of each draft step in the target's vocabulary
4
+ // projection (Qwen3.8: 248,320 rows). Drafts only need to be LIKELY tokens, so
5
+ // the draft projects onto the N most frequent rows of the same quantized head.
6
+ // The target still verifies and samples over the full vocabulary: the output
7
+ // distribution is unchanged (a different acceptance pattern can re-roll a seeded
8
+ // trajectory), a token outside the list simply cannot be drafted (one miss).
9
+ //
10
+ // Coupling is preserved. For a [1, V] input MLX's categorical draws by INVERSE
11
+ // CDF in token-id order with ONE uniform from the step key (random.cpp
12
+ // categorical_inverse_cdf), and the default draft uses the same key as the target
13
+ // position it predicts, so both read the same point of their own CDFs. The list
14
+ // is kept in ascending id order, so a categorical draw over the listed rows IS a
15
+ // full-vocabulary draw with zero mass outside the list: same uniform, same order,
16
+ // same coupling. No noise reconstruction is involved.
17
+ //
18
+ // A list shipped beside the draft companion (`draft_vocab.json`) loads
19
+ // automatically; MLX_BUN_SPEC_DRAFT_VOCAB=<path to json {"ids":[...]}> selects
20
+ // another list and =0/=off disables it (see configuredDraftVocabulary).
21
+ import { existsSync } from "node:fs";
22
+ import { join } from "node:path";
23
+ import { MlxArray } from "@mlx-bun/mlx/array";
24
+ import * as ops from "@mlx-bun/mlx/ops";
25
+ import { DRAFT_VOCAB_FILE, readDraftVocabulary } from "../../artifacts/draft-vocabulary";
26
+ import { runtimeValue } from "../../runtime/config";
27
+ import { applyMinP, applyTopK, applyTopP, stepKey, toLogprobs, type SamplerOptions } from "../../sampling/index";
28
+
29
+ export { DRAFT_VOCAB_FILE };
30
+
31
+ export interface QuantizedHeadState {
32
+ readonly w: MlxArray;
33
+ readonly scales: MlxArray;
34
+ readonly biases: MlxArray | null;
35
+ readonly spec: ops.QuantSpec;
36
+ }
37
+
38
+ /** The draft vocabulary for this target, validated against its vocabulary size,
39
+ * or null. Precedence: `MLX_BUN_SPEC_DRAFT_VOCAB=<path>` selects a list,
40
+ * `=0`/`=off` disables, and otherwise the list shipped beside the draft
41
+ * companion (`<companionDir>/draft_vocab.json`) is used when present. */
42
+ export function configuredDraftVocabulary(vocabSize: number, companionDir?: string): readonly number[] | null {
43
+ const setting = runtimeValue("MLX_BUN_SPEC_DRAFT_VOCAB");
44
+ if (setting === "0" || setting === "off") return null;
45
+ let path = setting;
46
+ if (!path && companionDir) {
47
+ const shipped = join(companionDir, DRAFT_VOCAB_FILE);
48
+ if (existsSync(shipped)) path = shipped;
49
+ }
50
+ return path ? readDraftVocabulary(path, vocabSize) : null;
51
+ }
52
+
53
+ /** Rows of a quantized head (affine packs along the input axis, so a row
54
+ * gather is exact). Owns its gathered arrays; borrows nothing afterwards. */
55
+ export class DraftVocabularyHead {
56
+ readonly ids: MlxArray; // [N] int32, ascending token ids
57
+ readonly size: number;
58
+ readonly #w: MlxArray;
59
+ readonly #scales: MlxArray;
60
+ readonly #biases: MlxArray | null;
61
+ readonly #spec: ops.QuantSpec;
62
+
63
+ constructor(head: QuantizedHeadState, ids: readonly number[]) {
64
+ this.size = ids.length;
65
+ this.ids = ops.fromInt32([...ids], [ids.length]);
66
+ const rows = (a: MlxArray) => { using taken = ops.takeAxis(a, this.ids, 0); return ops.contiguous(taken); };
67
+ this.#w = rows(head.w);
68
+ this.#scales = rows(head.scales);
69
+ this.#biases = head.biases ? rows(head.biases) : null;
70
+ this.#spec = head.spec;
71
+ ops.evalAll([this.ids, this.#w, this.#scales, ...(this.#biases ? [this.#biases] : [])]);
72
+ }
73
+
74
+ /** hidden [..., H] → logits [..., N] over the listed ids. */
75
+ project(hidden: MlxArray): MlxArray {
76
+ return ops.quantizedMatmul(hidden, this.#w, this.#scales, this.#biases, this.#spec, true);
77
+ }
78
+
79
+ dispose(): void {
80
+ this.ids.dispose(); this.#w.dispose(); this.#scales.dispose(); this.#biases?.dispose();
81
+ }
82
+ }
83
+
84
+ export type SubsetDraftSampler = (logits: MlxArray, head: DraftVocabularyHead, step: number) => MlxArray;
85
+
86
+ /** One request's coupled draw over a draft vocabulary: subset logits [1, N] →
87
+ * owned token id [1] (a FULL-vocabulary id). Null when the options need a
88
+ * sampler this path does not reproduce (curve, HLG, XTC). */
89
+ export function makeSubsetDraftSampler(options: SamplerOptions): SubsetDraftSampler | null {
90
+ const { temperature = 0, topP = 0, topK = 0, minP = 0, minTokensToKeep = 1, xtcProbability = 0, seed = 0 } = options;
91
+ if (options.curve || options.hlg?.enabled === true || xtcProbability > 0) return null;
92
+ const pick = (index: MlxArray, head: DraftVocabularyHead): MlxArray => {
93
+ using flat = ops.reshape(index, [1]);
94
+ return ops.takeAxis(head.ids, flat, 0);
95
+ };
96
+ if (temperature === 0) return (logits, head) => { using index = ops.argmaxAxis(logits, -1); return pick(index, head); };
97
+ return (logits, head, step) => {
98
+ const owned: MlxArray[] = [];
99
+ try {
100
+ let current = toLogprobs(logits); owned.push(current);
101
+ if (topP > 0 && topP < 1) { current = applyTopP(current, topP); owned.push(current); }
102
+ if (minP !== 0) { current = applyMinP(current, minP, minTokensToKeep); owned.push(current); }
103
+ if (topK > 0 && topK < head.size) { current = applyTopK(current, topK); owned.push(current); }
104
+ using scaled = ops.mulScalar(current, 1 / temperature);
105
+ using key = stepKey(seed, step);
106
+ using index = ops.randomCategorical(scaled, key);
107
+ return pick(index, head);
108
+ } finally { for (const array of owned) array.dispose(); }
109
+ };
110
+ }
@@ -0,0 +1,17 @@
1
+ export * from "./source";
2
+ export * from "./acceptance";
3
+ export * from "./output";
4
+ export * from "./draft-policy";
5
+ export * from "./draft-registry";
6
+ export * from "./draft-kind";
7
+ export * from "./draft-vocab";
8
+ export * from "./adaptive-draft-gate";
9
+ export * from "./sources/assistant-source";
10
+ export * from "./sources/two-model";
11
+ export * from "./sources/qwen-mtp-source";
12
+ export * from "./sources/glm52-mtp-source";
13
+ export * from "./sources/deepspec-source";
14
+ export * from "./sources/dflash-source";
15
+ export * from "./sources/dflash2-source";
16
+ export * from "./sources/ngram-source";
17
+ export { projectedDraftGroups, type ProjectedDraftMethod } from "./bindings/projected-draft-rows";
@@ -0,0 +1,49 @@
1
+ import type { Cancellation, CancelReason, GenerationOutput, TokenLogprobs } from "../../contracts/portable/generation";
2
+ import type { DraftAcceptance } from "./acceptance";
3
+
4
+ export interface DraftOutputRow {
5
+ readonly acceptance: DraftAcceptance;
6
+ readonly logprobs?: readonly (TokenLogprobs | undefined)[];
7
+ readonly output: Pick<GenerationOutput, "commit">;
8
+ readonly cancellation?: Cancellation;
9
+ }
10
+
11
+ /** Counts apply to this round. Accepted counts describe processed proposed
12
+ * inputs, excluding an unprocessed correction/bonus and any unpublished EOS.
13
+ * Failed/cancelled requests must be discarded, never checkpointed. */
14
+ export type DraftOutputResult = { readonly generated: number; readonly accepted: number } & (
15
+ | { readonly kind: "continue"; readonly pending: number }
16
+ | { readonly kind: "stop" | "length" }
17
+ | { readonly kind: "cancelled"; readonly reason: CancelReason }
18
+ | { readonly kind: "failed"; readonly error: unknown }
19
+ );
20
+
21
+ /** Deliver verified content through the output interface before resolving
22
+ * retained state. A stopped or failed consumer affects only its own request.
23
+ * Token-sized publications preserve callbacks that stop inside a draft burst. */
24
+ export async function deliverDraftOutputs(rows: readonly DraftOutputRow[]): Promise<DraftOutputResult[]> {
25
+ const results: DraftOutputResult[] = [];
26
+ for (const row of rows) results.push(await deliver(row));
27
+ return results;
28
+ }
29
+
30
+ async function deliver(row: DraftOutputRow): Promise<DraftOutputResult> {
31
+ const { acceptance } = row;
32
+ let generated = 0;
33
+ const retained = () => ({ generated, accepted: Math.min(generated, acceptance.accepted) });
34
+ try {
35
+ for (const token of acceptance.emitted) {
36
+ const reason = row.cancellation?.reason;
37
+ if (reason) return { kind: "cancelled", reason, generated, accepted: 0 };
38
+ const metadata = row.logprobs?.[generated];
39
+ const more = await row.output.commit([token], metadata ? [metadata] : undefined);
40
+ generated++;
41
+ if (more === false) return { kind: "stop", ...retained() };
42
+ }
43
+ const reason = row.cancellation?.reason;
44
+ if (reason) return { kind: "cancelled", reason, generated, accepted: 0 };
45
+ if (acceptance.sawEos || acceptance.grammarDone) return { kind: "stop", ...retained() };
46
+ if (generated >= acceptance.remaining) return { kind: "length", ...retained() };
47
+ return { kind: "continue", pending: acceptance.correction!, ...retained() };
48
+ } catch (error) { return { kind: "failed", error, generated, accepted: 0 }; }
49
+ }
@@ -0,0 +1,307 @@
1
+ import type { MlxArray } from "@mlx-bun/mlx/array";
2
+ import * as ops from "@mlx-bun/mlx/ops";
3
+ import type { MlxVerifyWindow } from "../phase-dispatch";
4
+ import type { Cancellation, GenerationOutput, TokenLogprobs } from "../../contracts/portable/generation";
5
+ import type { DisposableResource } from "../../contracts/portable/resources";
6
+ import { cleanupFailure, disposeResources } from "../../runtime/resources";
7
+ import type { SpeculativeTransaction } from "../../contracts/portable/rollback";
8
+ import { DraftAcceptance } from "./acceptance";
9
+ import { deliverDraftOutputs, type DraftOutputResult } from "./output";
10
+ import { readStepExtras, type NumberStepSampler } from "../../sampling/index";
11
+ import { runtimeFlag } from "../../runtime/config";
12
+ import type { SpecPhaseMs } from "../index";
13
+
14
+ /** Diagnostic phase timing: evaluates the verify logits before sampling so
15
+ * draft / verify / sample / commit wall time can be attributed separately.
16
+ * Off by default because that evaluation removes the draft-verify overlap. */
17
+ export function specPhaseTiming(): boolean {
18
+ return runtimeFlag("MLX_BUN_SPEC_PHASE_TIMING", false);
19
+ }
20
+
21
+ /** Draft state owns proposal generation and its committed hidden/KV state.
22
+ * It borrows verification context and knows nothing about target storage. */
23
+ export interface MlxDraftRows<Context = MlxArray> {
24
+ /** Each row returns 0..depth proposals. The method verifies the returned
25
+ * lengths; an empty row still consumes its pending token. */
26
+ draft(pending: readonly number[], depth: number, steps: readonly number[]): number[][] | Promise<number[][]>;
27
+ /** Optional: the same proposals left on the device as [B, depth] ids, resolved
28
+ * from one later host readback. Lets the round build its verify graph while
29
+ * the GPU is still drafting. Null when unavailable for this call. */
30
+ draftDevice?(pending: readonly number[], depth: number, steps: readonly number[]):
31
+ { tokens: MlxArray; resolve(read: readonly number[]): number[][] } | null;
32
+ commit(accepted: readonly number[], context: Context): void | Promise<void>;
33
+ /** Diagnostic barrier for retained lazy state; never called by normal rounds. */
34
+ materialize?(): void;
35
+ }
36
+
37
+ /** A bound graph transfers both tensors on success and releases partial
38
+ * allocations on failure. State retention is a separate injected interface. */
39
+ export interface MlxRowVerification<Context extends DisposableResource = MlxArray> {
40
+ readonly transaction: SpeculativeTransaction<readonly number[]>;
41
+ /** The verify forward over `ids` [sequences, positions], the window the round built. */
42
+ forward(ids: MlxArray, window: MlxVerifyWindow): Promise<{ logits: MlxArray; context: Context }>;
43
+ /** Diagnostic barrier for retained target state after verification/rollback. */
44
+ materialize?(): void;
45
+ }
46
+
47
+ export interface MlxSpeculativeRow {
48
+ readonly pending: number;
49
+ readonly step: number;
50
+ readonly remaining: number;
51
+ readonly eosTokenIds: readonly number[];
52
+ readonly sampling: NumberStepSampler;
53
+ readonly grammarDone?: () => boolean;
54
+ }
55
+
56
+ export interface MlxSpeculativeRowResult {
57
+ readonly drafts: readonly number[];
58
+ readonly acceptance: DraftAcceptance;
59
+ /** One entry per content token; stopping EOS is excluded. */
60
+ readonly logprobs: readonly (TokenLogprobs | undefined)[];
61
+ }
62
+
63
+ export interface MlxSpeculativeOutputRow extends MlxSpeculativeRow {
64
+ readonly output: Pick<GenerationOutput, "commit">;
65
+ readonly cancellation?: Cancellation;
66
+ }
67
+
68
+ /** Execute one method step through output delivery and aligned state commit.
69
+ * The caller retires terminal rows and publishes eligible completed state
70
+ * before changing membership. Consumer failures are returned per request;
71
+ * a graph/state failure throws and requires discarding the whole group. */
72
+ export async function advanceSpeculativeOutputs<Context extends DisposableResource = MlxArray>(
73
+ rows: readonly MlxSpeculativeOutputRow[], depth: number,
74
+ draft: MlxDraftRows<NoInfer<Context>>, target: MlxRowVerification<Context>,
75
+ ): Promise<{ rounds: readonly MlxSpeculativeRowResult[]; outputs: readonly DraftOutputResult[]; phaseMs?: SpecPhaseMs }> {
76
+ const round = await prepareSpeculativeRows(rows, depth, draft, target);
77
+ let outputs: DraftOutputResult[];
78
+ try {
79
+ outputs = await deliverDraftOutputs(round.results.map((result, row) => ({
80
+ acceptance: result.acceptance, logprobs: result.logprobs,
81
+ output: rows[row]!.output, cancellation: rows[row]!.cancellation,
82
+ })));
83
+ await round.commit(outputs.map(output => output.accepted));
84
+ } catch (error) { return cleanupFailure(error, () => round.dispose()); }
85
+ round.dispose();
86
+ return { rounds: round.results, outputs, ...(round.phaseMs ? { phaseMs: round.phaseMs } : {}) };
87
+ }
88
+
89
+ /** Retention stays unresolved while the method delivers output. A terminal
90
+ * row may keep fewer accepted inputs for an output-aligned checkpoint;
91
+ * continuing rows retain their complete accepted prefix. Commit exactly once,
92
+ * then dispose; disposing an uncommitted round requires discarding its state. */
93
+ export interface MlxPreparedSpeculativeRound extends DisposableResource {
94
+ readonly results: readonly MlxSpeculativeRowResult[];
95
+ commit(keepAccepted?: readonly number[]): Promise<void>;
96
+ /** Present under `MLX_BUN_SPEC_PHASE_TIMING=1`; `commit` fills in after commit(). */
97
+ readonly phaseMs?: SpecPhaseMs;
98
+ }
99
+
100
+ /** One committed method step at B=1 or B>1. The caller supplies live rows with
101
+ * a positive token budget and a common nonnegative draft depth. Admission,
102
+ * cancellation boundaries, output delivery and cache publication stay outside
103
+ * this step. On failure the caller discards the participating method state. */
104
+ export async function advanceSpeculativeRows<Context extends DisposableResource = MlxArray>(
105
+ rows: readonly MlxSpeculativeRow[], depth: number,
106
+ draft: MlxDraftRows<NoInfer<Context>>, target: MlxRowVerification<Context>,
107
+ ): Promise<readonly MlxSpeculativeRowResult[]> {
108
+ const round = await prepareSpeculativeRows(rows, depth, draft, target);
109
+ try { await round.commit(); }
110
+ catch (error) { return cleanupFailure(error, () => round.dispose()); }
111
+ round.dispose();
112
+ return round.results;
113
+ }
114
+
115
+ /** Draft, verify and sample without deciding which output-aligned state to
116
+ * retain. Graph-specific context is opaque to this operation. */
117
+ export async function prepareSpeculativeRows<Context extends DisposableResource = MlxArray>(
118
+ rows: readonly MlxSpeculativeRow[], depth: number,
119
+ draft: MlxDraftRows<NoInfer<Context>>, target: MlxRowVerification<Context>,
120
+ ): Promise<MlxPreparedSpeculativeRound> {
121
+ const timing = specPhaseTiming();
122
+ const phaseMs: SpecPhaseMs | undefined = timing ? { draft: 0, verify: 0, sample: 0, commit: 0, rounds: 1 } : undefined;
123
+ let mark = timing ? performance.now() : 0;
124
+ const lap = (): number => { const now = performance.now(); const elapsed = now - mark; mark = now; return elapsed; };
125
+ // Device-first round: leave the draft tokens on the GPU, assemble the verify
126
+ // ids there, start the GPU on the draft chain, and build the ~2.5k-node verify
127
+ // graph while it runs. Drafts and window samples then come back in ONE read.
128
+ // Same ops and values as the host-first order, so outputs are unchanged.
129
+ // Diagnostics (phase timing) keep the host-first order so phases stay separable.
130
+ const lazy = !phaseMs && depth > 0 && runtimeFlag("MLX_BUN_SPEC_DEVICE_ROUND", true) && draft.draftDevice &&
131
+ rows.every(row => row.sampling.independent || row.sampling.positional)
132
+ ? draft.draftDevice(rows.map(row => row.pending), depth, rows.map(row => row.step)) : null;
133
+ if (lazy) return prepareDeviceRound(rows, depth, lazy, draft, target);
134
+ const inventory = phaseMs && runtimeFlag("MLX_BUN_SPEC_OP_INVENTORY", false);
135
+ const counting = globalThis as Record<string, unknown>;
136
+ if (inventory) counting.__opCount = {};
137
+ let proposals: number[][];
138
+ try { proposals = await draft.draft(rows.map(row => row.pending), depth, rows.map(row => row.step)); }
139
+ finally { if (inventory && phaseMs) { phaseMs.draftOps = counting.__opCount as Record<string, number>; counting.__opCount = undefined; } }
140
+ if (phaseMs) phaseMs.draft = lap();
141
+ const B = rows.length;
142
+ const verifyDepth = proposals.reduce((max, tokens) => Math.max(max, tokens.length), 0);
143
+ using ids = ops.fromInt32(proposals.flatMap((tokens, row) => {
144
+ const window = [rows[row]!.pending, ...tokens];
145
+ // Causal right padding only fills the rectangular graph. Sampling stops
146
+ // at this row's own bonus position; commit discards every padded input.
147
+ while (window.length <= verifyDepth) window.push(rows[row]!.pending);
148
+ return window;
149
+ }), [B, verifyDepth + 1]);
150
+ target.transaction.begin(verifyDepth);
151
+ // Component attribution INSIDE the served verify forward. The model's
152
+ // profiler hooks add an evaluation barrier per op, so this distorts the
153
+ // verify total; it is only ever on with phase timing, for diagnosis.
154
+ const layers: Record<string, number> | undefined =
155
+ phaseMs && runtimeFlag("MLX_BUN_SPEC_LAYER_PROFILE", false) ? {} : undefined;
156
+ const profiled = globalThis as Record<string, unknown>;
157
+ if (layers) profiled.__deltaProf = layers;
158
+ if (inventory) counting.__opCount = {};
159
+ let verification: Awaited<ReturnType<typeof target.forward>>;
160
+ try { verification = await target.forward(ids, { sequences: B, positions: verifyDepth + 1 }); }
161
+ finally {
162
+ if (layers) profiled.__deltaProf = undefined;
163
+ if (inventory && phaseMs) { phaseMs.verifyOps = counting.__opCount as Record<string, number>; counting.__opCount = undefined; }
164
+ }
165
+ if (layers && phaseMs) phaseMs.layers = layers;
166
+ const dispose = () => disposeResources([verification.context, verification.logits]);
167
+ try {
168
+ if (phaseMs) {
169
+ // Force the verify forward to completion so its time is not attributed
170
+ // to the sampling readback. Production leaves it lazy on purpose.
171
+ ops.evalAll([verification.logits]);
172
+ target.materialize?.();
173
+ phaseMs.verify = lap();
174
+ }
175
+ const results = await sampleSpeculativeRows(rows, proposals, verification.logits, ids);
176
+ if (phaseMs) phaseMs.sample = lap();
177
+ return {
178
+ results,
179
+ async commit(accepted = results.map(result => result.acceptance.accepted)) {
180
+ if (phaseMs) lap();
181
+ target.transaction.resolve(accepted);
182
+ await draft.commit(accepted, verification.context);
183
+ if (phaseMs) {
184
+ target.materialize?.();
185
+ draft.materialize?.();
186
+ phaseMs.commit = lap();
187
+ }
188
+ },
189
+ dispose,
190
+ ...(phaseMs ? { phaseMs } : {}),
191
+ };
192
+ } catch (error) { return cleanupFailure(error, dispose); }
193
+ }
194
+
195
+ async function prepareDeviceRound<Context extends DisposableResource>(
196
+ rows: readonly MlxSpeculativeRow[], depth: number,
197
+ lazy: { tokens: MlxArray; resolve(read: readonly number[]): number[][] },
198
+ draft: MlxDraftRows<NoInfer<Context>>, target: MlxRowVerification<Context>,
199
+ ): Promise<MlxPreparedSpeculativeRound> {
200
+ const B = rows.length;
201
+ let verification: { logits: MlxArray; context: Context } | null = null;
202
+ const dispose = () => { if (verification) disposeResources([verification.context, verification.logits]); };
203
+ try {
204
+ ops.asyncEvalAll([lazy.tokens]); // the GPU drafts while the verify graph is built
205
+ using pendingIds = ops.fromInt32(rows.map(row => row.pending), [B, 1]);
206
+ using pending = pendingIds.astype(lazy.tokens.dtype);
207
+ using ids = ops.concatAxis([pending, lazy.tokens], 1);
208
+ target.transaction.begin(depth);
209
+ verification = await target.forward(ids, { sequences: B, positions: depth + 1 });
210
+ const sampled = sampleWindowOnDevice(rows, verification.logits, ids);
211
+ if (!sampled) throw new Error("device round requires stateless window sampling");
212
+ let read: number[];
213
+ try {
214
+ using drafted = ops.reshape(lazy.tokens, [B * depth]);
215
+ using draftedSameType = drafted.astype(sampled.dtype);
216
+ using both = ops.concatAxis([draftedSameType, sampled], 0);
217
+ read = both.toIntTokens();
218
+ } finally { sampled.dispose(); }
219
+ const proposals = lazy.resolve(read.slice(0, B * depth));
220
+ const results = await walkSpeculativeRows(rows, proposals, verification.logits, read.slice(B * depth));
221
+ const held = verification;
222
+ return {
223
+ results,
224
+ async commit(accepted = results.map(result => result.acceptance.accepted)) {
225
+ target.transaction.resolve(accepted);
226
+ await draft.commit(accepted, held.context);
227
+ },
228
+ dispose,
229
+ };
230
+ } catch (error) { return cleanupFailure(error, dispose); }
231
+ finally { lazy.tokens.dispose(); }
232
+ }
233
+
234
+ /** Sample a borrowed verify window over its borrowed inputs `ids` [B, width]. Each request owns its processor
235
+ * history, grammar and RNG stream; completing one walk never samples its unused suffix.
236
+ * The existing request adapter and grouped method use this same operation. */
237
+ export async function sampleSpeculativeRows(
238
+ rows: readonly MlxSpeculativeRow[], proposals: readonly (readonly number[])[], logits: MlxArray, ids: MlxArray,
239
+ ): Promise<MlxSpeculativeRowResult[]> {
240
+ let tokens: number[] | undefined;
241
+ const sampled = sampleWindowOnDevice(rows, logits, ids);
242
+ if (sampled) { try { tokens = sampled.toIntTokens(); } finally { sampled.dispose(); } }
243
+ return walkSpeculativeRows(rows, proposals, logits, tokens);
244
+ }
245
+
246
+ /** Every window position of every row as ONE owned device array [B * width]
247
+ * (row-major), or null when some row's sampling depends on a grammar or
248
+ * probability capture (those walk position by position).
249
+ * Plain greedy rows share one stateless argmax; other rows (temperature/top-p/top-k,
250
+ * processor penalties) draw every position with its own (seed, step) key over
251
+ * the history the window's inputs `ids` [B, width] (borrowed) give it, so the
252
+ * walk still stops at each row's first rejection with the draws sequential
253
+ * sampling would make, and unused suffix draws consume nothing. */
254
+ export function sampleWindowOnDevice(rows: readonly MlxSpeculativeRow[], logits: MlxArray, ids: MlxArray): MlxArray | null {
255
+ const V = logits.shape[2]!, width = logits.shape[1]!;
256
+ const independent = rows[0]?.sampling.independent;
257
+ if (independent && rows.every(row => row.sampling.independent === independent)) {
258
+ using flat = ops.reshape(logits, [rows.length * width, V]);
259
+ return independent.sample(flat);
260
+ }
261
+ if (rows.length === 0 || !rows.every(row => row.sampling.positional)) return null;
262
+ const perRow: MlxArray[] = [];
263
+ try {
264
+ for (const [row, request] of rows.entries()) {
265
+ using slice = logits.slice([row, 0, 0], [row + 1, width, V]);
266
+ using scores = ops.reshape(slice, [width, V]);
267
+ using inputs = request.sampling.needsHistory ? ids.slice([row, 0], [row + 1, width]) : null;
268
+ using window = inputs ? ops.reshape(inputs, [width]) : null;
269
+ perRow.push(request.sampling.positional!.sample(
270
+ scores, Array.from({ length: width }, (_, position) => request.step + position), window ?? undefined));
271
+ }
272
+ return perRow.length === 1 ? perRow.pop()! : ops.concatAxis(perRow, 0);
273
+ } finally { for (const sampled of perRow) sampled.dispose(); }
274
+ }
275
+
276
+ /** The host accept walk over already-read window samples (`tokens`, row-major
277
+ * [B * width]), whose used draws it commits to each row's history, or, without
278
+ * them, position-by-position sampling. */
279
+ export async function walkSpeculativeRows(
280
+ rows: readonly MlxSpeculativeRow[], proposals: readonly (readonly number[])[], logits: MlxArray,
281
+ tokens: readonly number[] | undefined,
282
+ ): Promise<MlxSpeculativeRowResult[]> {
283
+ const V = logits.shape[2]!, width = logits.shape[1]!;
284
+ const results: MlxSpeculativeRowResult[] = [];
285
+ for (let row = 0; row < rows.length; row++) {
286
+ const request = rows[row]!;
287
+ const acceptance = new DraftAcceptance(proposals[row]!, request.remaining, request.eosTokenIds);
288
+ const logprobs: (TokenLogprobs | undefined)[] = [];
289
+ while (!acceptance.done) {
290
+ const position = acceptance.position;
291
+ let token: number, metadata: TokenLogprobs | undefined;
292
+ if (tokens) token = tokens[row * width + position]!;
293
+ else {
294
+ using slice = logits.slice([row, position, 0], [row + 1, position + 1, V]);
295
+ using scores = ops.reshape(slice, [1, V]);
296
+ const sampled = await request.sampling.sample(scores, request.step + acceptance.emitted.length);
297
+ token = sampled.token;
298
+ metadata = readStepExtras(sampled.extras);
299
+ }
300
+ acceptance.accept(token, request.grammarDone?.() ?? false);
301
+ if (!acceptance.sawEos) logprobs.push(metadata);
302
+ }
303
+ if (tokens) request.sampling.positional?.commit(tokens.slice(row * width, row * width + acceptance.position));
304
+ results.push({ drafts: proposals[row]!, acceptance, logprobs });
305
+ }
306
+ return results;
307
+ }