@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,840 @@
1
+ // Streaming generation — port of mlx-lm's generate_step:
2
+ // - prefill in chunks; cache state evaluated per chunk (bounded transient
3
+ // memory), logits never computed for non-final prefill positions
4
+ // - decode pipelining via mx.async_eval: step n+1's graph is built and
5
+ // dispatched before step n's token is read back, so the GPU never idles
6
+ // on the JS round-trip
7
+ // - opt-in early token-zero yield reduces latency before that pipeline starts
8
+ // - sampling stays on-device; only the chosen token id crosses to JS
9
+
10
+ import { MlxArray,gpuStream } from "@mlx-bun/mlx/array";
11
+ import {
12
+ Dtype,
13
+ activeMemory,
14
+ clearCache,
15
+ peakMemory,
16
+ synchronize
17
+ } from "@mlx-bun/mlx/ffi";
18
+ import * as ops from "@mlx-bun/mlx/ops";
19
+ import { appendFileSync } from "node:fs";
20
+ import { type Cache } from "../contracts/mlx/cache";
21
+ import { type MlxKnownSpans } from "../contracts/mlx/graph";
22
+ import { nextPrefillStep } from "../contracts/portable/prefill";
23
+ import {
24
+ assertMlxAutoregressiveBinding,
25
+ type MlxAutoregressiveBinding,
26
+ type MlxDecodeStep
27
+ } from "./bindings/autoregressive";
28
+ import { runtimeConfig,type RuntimeConfig } from "../runtime/config";
29
+ import { appendFillHidden } from "./fill-append";
30
+ import { appendGrammarSpan } from "./grammar-step";
31
+ import { adapterScoped,modelNeedsWiredLimit,usageScoped,wiredScoped } from "./scopes";
32
+ import { evalCacheState,executeMlxPrefillStep } from "./prefill";
33
+ import {
34
+ disposeStepExtras,
35
+ makeStepSampler,
36
+ readStepExtras,
37
+ stepExtrasArrays,
38
+ type StepExtras
39
+ } from "../sampling/index";
40
+ import { BF16_KV, COMPOSITION_DEFAULTS, type KvStorageScheme } from "../contracts/portable/composition";
41
+ import { maybePageKv } from "../state/request-policy";
42
+ import { rollbackVerifyBlock, rollsBackVerifyBlock } from "../state/rollback";
43
+ import {
44
+ fillTraceEnabled,
45
+ fillTracePath,
46
+ resolveFillMode,
47
+ type FillTraceRecord,
48
+ type Proposal
49
+ } from "./fill/session";
50
+ import { Generation } from "./result";
51
+ import { GenerateDiagnostics,GenerateOptions,GenerateStats,GeneratedToken } from "./types";
52
+
53
+ /** Jump-forward decoding for this request under a composition whose
54
+ * `grammarJump` is `composed`: grammar requests that return no logprobs. */
55
+ export function shouldUseGrammarJump(
56
+ options: Pick<GenerateOptions, "grammar" | "logprobs" | "topLogprobs">,
57
+ composed: boolean = COMPOSITION_DEFAULTS.grammarJump,
58
+ ): boolean {
59
+ return options.grammar !== undefined &&
60
+ composed &&
61
+ !options.logprobs &&
62
+ !(options.topLogprobs && options.topLogprobs > 0);
63
+ }
64
+
65
+ /** Composition gate for token fast-forwarding, enforced inside the engine
66
+ * The serving planner owns request-level eligibility, including explicit
67
+ * seeds in serial execution and mounted draft models. Shared strict fill
68
+ * has its own grouped method. The composition's KV scheme is no gate: every
69
+ * cache lego takes known tokens, one position at a time or through the
70
+ * graph's committed spans.
71
+ * - grammar: forced-token production is the grammar's job; two producers in
72
+ * one iteration is forbidden (asserted in the loop).
73
+ * - logprobs/top_logprobs: injected tokens are never sampled, so they have
74
+ * no distribution row (same rule as shouldUseGrammarJump).
75
+ * - promptEmbeddings: vision/audio prompts (and mRoPE) — untested shape. */
76
+ export function shouldUseFill(
77
+ options: Pick<GenerateOptions, "fill" | "grammar" | "logprobs" | "topLogprobs" | "promptEmbeddings">,
78
+ runtime: RuntimeConfig = runtimeConfig(),
79
+ ): boolean {
80
+ if (!options.fill) return false;
81
+ if (resolveFillMode(runtime.value("MLX_BUN_FILL") ?? "off") === "off") return false;
82
+ return options.grammar === undefined &&
83
+ !options.logprobs &&
84
+ !(options.topLogprobs && options.topLogprobs > 0) &&
85
+ options.promptEmbeddings === undefined;
86
+ }
87
+
88
+ /** The rewind surface a verify-policy fill needs. `Cache` declares the
89
+ * spec-round trio as optional members, but the cache list is a UNION with
90
+ * GLM's MLACache (which declares none of them), so the union has no such
91
+ * property to read. One narrowing helper keeps the call sites honest — every
92
+ * use is still optional-chained. */
93
+ type RewindableCache = {
94
+ isTrimmable(): boolean;
95
+ trim(n: number, bypass?: boolean): void;
96
+ specRoundBegin?(): void;
97
+ specRoundCommit?(): void;
98
+ specRoundRollback?(keep: number): void;
99
+ };
100
+ const rewindable = (c: unknown): RewindableCache => c as RewindableCache;
101
+
102
+ /** Execute a supplied backend binding through the same AR loop and resource
103
+ * scopes as generate(). Callers serialize shared model/adapter access. Replacing
104
+ * this binding replaces graph, cache construction, media, and optional decode
105
+ * together; it never borrows another model's compiled execution implicitly. */
106
+ export function generateAutoregressive(
107
+ binding: MlxAutoregressiveBinding,
108
+ promptTokens: number[],
109
+ options: GenerateOptions = {},
110
+ diagnostics: GenerateDiagnostics = {},
111
+ ): Generation {
112
+ const runtime = binding.runtime ?? runtimeConfig();
113
+ let inner = generateInner(binding, promptTokens, options, diagnostics, runtime);
114
+ if (options.adapters?.length && binding.adapters) {
115
+ inner = adapterScoped({ loraState: binding.adapters }, options.adapters, inner);
116
+ }
117
+ if (binding.memory.expertRuntime?.finishUsage || binding.memory.expertRuntime?.flushUsage)
118
+ inner = usageScoped(binding.memory, inner);
119
+ return new Generation(modelNeedsWiredLimit(binding.memory, undefined,
120
+ runtime.value("MLX_BUN_FORCE_WIRE") === "1") ? wiredScoped(inner) : inner, runtime);
121
+ }
122
+
123
+ async function* generateInner(
124
+ binding: MlxAutoregressiveBinding,
125
+ promptTokens: number[],
126
+ options: GenerateOptions,
127
+ diagnostics: GenerateDiagnostics,
128
+ runtime: RuntimeConfig,
129
+ ): AsyncGenerator<GeneratedToken, GenerateStats> {
130
+ if (options.signal?.aborted) {
131
+ options.grammar?.dispose();
132
+ options.signal.throwIfAborted();
133
+ }
134
+ const {
135
+ maxTokens = 512,
136
+ eosTokenIds = binding.eosTokenIds,
137
+ prefillChunkSize = binding.prefillPolicy?.chunkSize(promptTokens.length) ?? 2048,
138
+ } = options;
139
+
140
+ try {
141
+ assertMlxAutoregressiveBinding(binding);
142
+ if (options.promptEmbeddings && !binding.prefillEmbeddings)
143
+ throw new Error("AR binding does not support prompt embeddings");
144
+ if (options.adapters?.length && !binding.adapters)
145
+ throw new Error("AR binding does not support adapters");
146
+ } catch (error) {
147
+ options.grammar?.dispose();
148
+ throw error;
149
+ }
150
+ const phases = binding.phases;
151
+
152
+ // logprobs capture (mlx_lm.server semantics — see GenerateOptions.logprobs).
153
+ // Everything below is gated: when neither flag is set, no extra ops, evals,
154
+ // or readbacks happen on the hot path.
155
+ const captureSelLp = options.logprobs === true;
156
+ const captureTopK =
157
+ options.topLogprobs && options.topLogprobs > 0 ? options.topLogprobs : 0;
158
+ const stepSampler = makeStepSampler(options, {
159
+ tokenRepresentation: "device",
160
+ grammarWait: "external",
161
+ historyUpdate: "manual",
162
+ captureSelectedLogprob: captureSelLp,
163
+ captureTopLogprobs: captureTopK,
164
+ });
165
+ const needsTokenHistory = stepSampler.needsHistory;
166
+
167
+ const closePrefill = diagnostics.trace?.begin("prefill.total", {
168
+ mechanism: diagnostics.mechanism ?? "serial",
169
+ promptTokens: promptTokens.length,
170
+ cachedTokens: options.cache?.[0]?.offset ?? 0,
171
+ });
172
+ const closeBatchSetup = diagnostics.trace?.begin("prefill.batch_setup", {
173
+ mechanism: diagnostics.mechanism ?? "serial",
174
+ });
175
+ const ownsCache = !options.cache;
176
+ const resuming = options.initialPendingToken !== undefined;
177
+ let cache: Cache[] = options.cache ?? [];
178
+ // The composition's KV scheme, which every request stores.
179
+ const kv = binding.kv ?? BF16_KV;
180
+ let appender: MlxKnownSpans | null = null;
181
+ try {
182
+ if (ownsCache) cache = binding.makeCache();
183
+ if (!cache.length) throw new Error("AR binding returned an empty cache");
184
+ const cached = cache[0]!.offset;
185
+ if ((!resuming && cached >= promptTokens.length) ||
186
+ (resuming && cached !== promptTokens.length))
187
+ throw new Error(
188
+ resuming
189
+ ? `resumed cache (${cached} tokens) must exactly cover the resume prefix (${promptTokens.length})`
190
+ : `pre-warmed cache (${cached} tokens) must be a strict prefix of the prompt (${promptTokens.length})`,
191
+ );
192
+ // Replace fresh full-attention caches before any forward. The deepest
193
+ // write is prompt + maxTokens - 1, so capacity covers every decode step.
194
+ maybePageKv(cache, kv, binding.pagedKv ?? null, promptTokens.length + maxTokens);
195
+ // Committed known-token spans, where the graph declares them and the run
196
+ // mounts no adapter; otherwise known tokens append one position at a time.
197
+ appender = options.fill && binding.knownSpans && !options.adapters?.length
198
+ ? binding.knownSpans : null;
199
+ } catch (error) {
200
+ if (ownsCache) for (const state of cache) state.dispose();
201
+ stepSampler.dispose();
202
+ options.grammar?.dispose();
203
+ closeBatchSetup?.();
204
+ closePrefill?.();
205
+ throw error;
206
+ }
207
+ const cachedTokens = cache[0]!.offset;
208
+ // Token fast-forwarding gate (see shouldUseFill). Sliding-window layers take
209
+ // part: a multi-token append is one concat write, and a rejected verify tail
210
+ // leaves the ring by the bypass trim that write allows (see rewind below).
211
+ const fillOn = shouldUseFill(options, runtime);
212
+ const fillTrace = fillOn && fillTraceEnabled();
213
+ const fillTraceFile = fillOn ? fillTracePath() : null;
214
+ // Verify-policy proposals (echo tier) need the rejected tail rewound. That
215
+ // is the SAME contract the spec lane's rounds use: trimmable caches drop the
216
+ // tail; recurrent caches (SSMCache — gated-DeltaNet state, untrimmable)
217
+ // restore a pre-round snapshot and bit-exactly replay the accepted prefix.
218
+ // A model whose caches can do neither still gets assert-policy fills; verify
219
+ // proposals are dropped and counted (stats.verifyUnsupported).
220
+ const verifyCapable = fillOn && rollsBackVerifyBlock(kv, cache);
221
+ closeBatchSetup?.();
222
+
223
+ // logits [1,1,V] → sampled token array [1] (+ optional logprob capture,
224
+ // all on-device)
225
+ const sampleStep = (
226
+ logits3d: MlxArray,
227
+ step: number,
228
+ ): { tok: MlxArray; extras: StepExtras | null } => {
229
+ const result = stepSampler.sample(logits3d, step);
230
+ return { tok: result.token, extras: result.extras };
231
+ };
232
+
233
+ const pushHistory = (tok: MlxArray): void => stepSampler.commitDevice(tok);
234
+
235
+ // Decode-loop state lives at function scope so the finally can still
236
+ // report stats and dispose in-flight arrays when the consumer
237
+ // terminates the generator early (break on a stop sequence — the
238
+ // forced .return() resumes at the yield and runs the finally).
239
+ let prefillMs = 0;
240
+ let tDecode = 0;
241
+ let decodeMs = 0;
242
+ let generated = options.initialGeneratedTokens ?? 0;
243
+ let finishReason: GenerateStats["finishReason"];
244
+ const forwarded: number[] = [];
245
+ let pending: MlxArray | null = null;
246
+ let nextPending: MlxArray | null = null;
247
+ let pendingExtras: StepExtras | null = null;
248
+ let nextExtras: StepExtras | null = null;
249
+ let decoder: MlxDecodeStep | null = null;
250
+ let finished = false;
251
+ let threw = false;
252
+ let executionError: unknown;
253
+ let earlyUnforwardedToken: number | null = null;
254
+ let closeTokenZero: (() => void) | undefined;
255
+ const makeStats = (): GenerateStats => ({
256
+ promptTokens: options.originalPromptTokens ?? promptTokens.length,
257
+ cachedTokens: Math.min(cachedTokens, options.originalPromptTokens ?? promptTokens.length),
258
+ generatedTokens: generated,
259
+ ...(finishReason ? { finishReason } : {}),
260
+ prefillMs,
261
+ decodeMs,
262
+ prefillTps: prefillMs > 0
263
+ ? ((promptTokens.length - cachedTokens) / prefillMs) * 1000
264
+ : 0,
265
+ decodeTps: (generated / decodeMs) * 1000,
266
+ cacheTokens: [...promptTokens, ...forwarded],
267
+ ...(fillOn ? { fill: options.fill!.stats } : {}),
268
+ });
269
+
270
+ /** The one invariant that makes fill safe: the caches hold exactly
271
+ * prompt + everything `forwarded` records, so a fill append must forward
272
+ * ONLY the injected ids — the normal step already consumed `cur` and wrote
273
+ * its KV. Forwarding [token, ...ids] would duplicate a position and
274
+ * silently corrupt both the KV and PromptCache.put's key. Checked under
275
+ * MLX_BUN_FILL_TRACE=1 on both sides of the append. */
276
+ const assertFillAlignment = (where: string): void => {
277
+ const offset = cache[0]!.offset;
278
+ const want = promptTokens.length + forwarded.length;
279
+ if (offset !== want)
280
+ throw new Error(
281
+ `[fill] cache misalignment ${where}: cache offset ${offset} != ` +
282
+ `prompt ${promptTokens.length} + forwarded ${forwarded.length}`,
283
+ );
284
+ };
285
+
286
+ /** THE apply primitive for token fast-forwarding — ONE chunked forward
287
+ * carries a proposal into the KV (and the recurrent state), and BOTH
288
+ * policies ride it (packages/inference/src/generation/fill/proposal.ts):
289
+ *
290
+ * - assert: the span is determined (a template scaffold). Append and move
291
+ * on. No readback, no checkpoint, no rewind. The in-flight sample for
292
+ * the position after `token` is dropped UNEXAMINED — a discarded
293
+ * pipeline dispatch, not a rejected draft.
294
+ * - verify: the span is likely (a copy from earlier in the session).
295
+ * Position 0 is checked BEFORE the forward against the in-flight sample
296
+ * (free, and a mismatch costs nothing — nothing has been written).
297
+ * Positions 1..m-1 are checked against the argmax already sitting in
298
+ * THIS forward's logits, so verification adds no pass over the weights.
299
+ * The rejected tail is rewound through the same cache contract the spec
300
+ * lane's rounds use, and decode resumes at the first disagreement.
301
+ *
302
+ * Returns the ids the engine must now emit (empty = no fill happened).
303
+ * Mutates nextPending/nextExtras/forwarded, exactly like the normal step. */
304
+ /** One trace record: the proposal next to the model's own token at every
305
+ * span position (`actual[0]` = in-flight sample, `actual[j]` = argmax after
306
+ * ids[j-1]). Appended as JSONL to MLX_BUN_FILL_TRACE=<file>. */
307
+ const traceProposal = (
308
+ proposal: Proposal, generatedNow: number, accepted: number, actual: number[],
309
+ ): void => {
310
+ const ids = proposal.ids;
311
+ let firstMismatch = -1;
312
+ for (let j = 0; j < ids.length && j < actual.length; j++)
313
+ if (actual[j] !== ids[j]) { firstMismatch = j; break; }
314
+ const dec = options.fill!.decode;
315
+ const rec: FillTraceRecord = {
316
+ ts: new Date().toISOString(), origin: proposal.origin, policy: proposal.policy,
317
+ generated: generatedNow, proposedLen: ids.length, accepted, firstMismatch,
318
+ proposed: ids, actual,
319
+ ...(dec ? { proposedText: dec(ids), actualText: dec(actual) } : {}),
320
+ };
321
+ try { appendFileSync(fillTraceFile!, JSON.stringify(rec) + "\n"); } catch { /* trace is best-effort */ }
322
+ };
323
+
324
+ const applyProposal = async (
325
+ proposal: Proposal, generatedNow: number,
326
+ ): Promise<number[]> => {
327
+ const fill = options.fill!;
328
+ const ids = proposal.ids;
329
+ const verify = proposal.policy === "verify";
330
+ if (verify && !verifyCapable) {
331
+ fill.noteVerifyUnsupported();
332
+ fill.commit(proposal, 0);
333
+ return [];
334
+ }
335
+ // The in-flight sample IS the model's own choice for position 0 (read for
336
+ // the trace under both policies; the served assert path never checks it).
337
+ const inFlight = verify || fillTraceFile ? ops.itemUint32(nextPending!) : -1;
338
+ if (verify) {
339
+ if (inFlight !== ids[0]) {
340
+ if (fillTraceFile) traceProposal(proposal, generatedNow, 0, [inFlight]);
341
+ fill.commit(proposal, 0);
342
+ return []; // nextPending survives; this becomes an ordinary step
343
+ }
344
+ } else {
345
+ fill.noteWastedSample();
346
+ }
347
+ if (fillTrace) assertFillAlignment("at fill entry");
348
+ nextPending!.dispose();
349
+ nextPending = null;
350
+ disposeStepExtras(nextExtras);
351
+ nextExtras = null;
352
+
353
+ let checkpointMs = 0;
354
+ let roundOpen = false;
355
+ if (verify) {
356
+ const t0 = performance.now();
357
+ for (const c of cache) rewindable(c).specRoundBegin?.();
358
+ roundOpen = true;
359
+ checkpointMs += performance.now() - t0;
360
+ }
361
+ let accepted = ids.length;
362
+ let hf: MlxArray | null = null;
363
+ let logitsAll: MlxArray | null = null;
364
+ try {
365
+ // A committed span stops at any composed cache's pending transition,
366
+ // which that cache then runs inside its append.
367
+ const chunkSize = (state: readonly Cache[]) => {
368
+ const maxChunk = Math.min(appender?.maxChunkSize(state) ?? 1,
369
+ ...state.map(c => c.maxAppendTokens?.() ?? Number.POSITIVE_INFINITY));
370
+ return fill.appendChunkSize > 0 ? Math.min(fill.appendChunkSize, maxChunk) : maxChunk;
371
+ };
372
+ // A verified proposal is one verify window (one position decodes); an
373
+ // asserted span appends known tokens, through the committed read under
374
+ // the graph's known spans, one decoded position at a time otherwise.
375
+ const forward = (part: MlxArray, state: Cache[], positions: number) => positions === 1 && (verify || !appender)
376
+ ? phases.decode(1)(part, state, null)
377
+ : verify ? phases.verify(1, positions)(part, state, null) : phases.known(positions)(part, state);
378
+ hf = await appendFillHidden(forward, cache, ids, verify ? () => ids.length : chunkSize);
379
+ const [, Lf, Hf] = hf.shape as [number, number, number];
380
+ let pred: number[] | null = null;
381
+ if (verify || fillTraceFile) {
382
+ // Free logits: this forward already computed every span position's
383
+ // hidden state. argmax at j is the model's continuation after ids[j],
384
+ // so it verifies ids[j+1]. (Under assert this readback is trace-only.)
385
+ logitsAll = phases.logits(ids.length)(hf);
386
+ const argmax = ops.argmaxAxis(logitsAll, -1);
387
+ pred = argmax.toIntTokens();
388
+ argmax.dispose();
389
+ }
390
+ if (verify && pred) {
391
+ for (let j = 0; j + 1 < ids.length; j++) {
392
+ if (pred[j] !== ids[j + 1]) { accepted = j + 1; break; }
393
+ }
394
+ }
395
+ if (fillTraceFile && pred)
396
+ traceProposal(proposal, generatedNow, accepted, [inFlight, ...pred.slice(0, ids.length - 1)]);
397
+ const t1 = performance.now();
398
+ if (accepted < ids.length) {
399
+ // The state drops the rejected tail (trim, a ring's rollback trim, or
400
+ // a recurrence's restore and replay).
401
+ rollbackVerifyBlock(cache, accepted, ids.length);
402
+ roundOpen = false;
403
+ } else if (roundOpen) {
404
+ for (const c of cache) rewindable(c).specRoundCommit?.();
405
+ roundOpen = false;
406
+ }
407
+ if (verify) checkpointMs += performance.now() - t1;
408
+ forwarded.push(...ids.slice(0, accepted));
409
+ if (fillTrace) assertFillAlignment("after fill append");
410
+ // The sampler's token history gets the ACCEPTED ids only, before the
411
+ // resume sample — so logits processors (repetition/presence/frequency)
412
+ // see exactly the sequence that was emitted.
413
+ stepSampler.commitNumbers(ids.slice(0, accepted));
414
+ // Resume from the LAST ACCEPTED position's logits: the bonus token on a
415
+ // full accept, the correction on a partial one. Same sampler and step
416
+ // index as an ordinary step — the correction's KV is written by the next
417
+ // iteration's forward, like any sampled token.
418
+ const willGen = generatedNow + accepted;
419
+ if (willGen < maxTokens) {
420
+ let logits: MlxArray;
421
+ if (logitsAll) {
422
+ const V = logitsAll.shape[2]!;
423
+ logits = logitsAll.slice([0, accepted - 1, 0], [1, accepted, V]);
424
+ } else {
425
+ const hLast = hf.slice([0, Lf - 1, 0], [1, Lf, Hf]);
426
+ logits = phases.logits(1)(hLast);
427
+ hLast.dispose();
428
+ }
429
+ const sn = sampleStep(logits, willGen);
430
+ nextPending = sn.tok;
431
+ nextExtras = sn.extras;
432
+ logits.dispose();
433
+ ops.asyncEvalAll([nextPending, ...stepExtrasArrays(nextExtras)]);
434
+ }
435
+ } finally {
436
+ // A throw mid-round would otherwise leave the recurrent caches armed
437
+ // (their next forward raises "spec round already recorded").
438
+ if (roundOpen) for (const c of cache) rewindable(c).specRoundCommit?.();
439
+ logitsAll?.dispose();
440
+ hf?.dispose();
441
+ }
442
+ if (verify) fill.noteVerifyEvent(checkpointMs);
443
+ fill.commit(proposal, accepted);
444
+ if (fillTrace)
445
+ console.error(
446
+ `[fill] ${proposal.origin}/${proposal.policy} ${accepted}/${ids.length} ` +
447
+ `after ${generatedNow} generated (events ${fill.stats.events}, ` +
448
+ `injected ${fill.stats.injected}, checkpoint ${checkpointMs.toFixed(2)}ms)`,
449
+ );
450
+ return ids.slice(0, accepted);
451
+ };
452
+
453
+ let nextCheckpoint = options.checkpointEveryTokens && options.checkpointEveryTokens > 0
454
+ ? (Math.floor(generated / options.checkpointEveryTokens) + 1) * options.checkpointEveryTokens
455
+ : Number.POSITIVE_INFINITY;
456
+ let tPrefill = performance.now();
457
+
458
+ try {
459
+ if (resuming) {
460
+ // The checkpoint cache already covers the complete replay prefix. Its
461
+ // pending token was sampled from that exact state before the snapshot,
462
+ // so resume starts directly at the decode loop without another forward.
463
+ if (needsTokenHistory) stepSampler.seedHistory(promptTokens);
464
+ // Device samplers return uint32. Preserve that signature on restore so
465
+ // compiled decode does not trace a different signed-token graph first.
466
+ using restoredToken = ops.fromInt32([options.initialPendingToken!], [1]);
467
+ pending = restoredToken.astype(Dtype.uint32);
468
+ ops.asyncEvalAll([pending]);
469
+ prefillMs = 0;
470
+ closePrefill?.();
471
+ tDecode = performance.now();
472
+ } else {
473
+ // ---- prefill ---- (composed caches convert inside their own appends)
474
+ tPrefill = performance.now();
475
+ let h0: MlxArray;
476
+ if (options.promptEmbeddings) {
477
+ if (cachedTokens !== 0)
478
+ throw new Error("promptEmbeddings cannot be combined with a pre-warmed cache");
479
+ if (needsTokenHistory)
480
+ stepSampler.seedHistory(promptTokens);
481
+ // e2b/e4b need the spliced token ids to build per-layer inputs
482
+ // (multimodal soft-token positions zeroed inside forwardEmbeddings).
483
+ const embedIds = ops.fromInt32(promptTokens, [1, promptTokens.length]);
484
+ h0 = binding.prefillEmbeddings!(
485
+ options.promptEmbeddings, cache, options.imageMask ?? null, embedIds,
486
+ options.multimodalMask ?? null, (promptTokens.length),
487
+ );
488
+ embedIds.dispose();
489
+ } else {
490
+ let pos = cachedTokens;
491
+ const snapshotAt = options.onPrefillDone && options.snapshotAt !== undefined
492
+ ? Math.min(Math.max(options.snapshotAt, cachedTokens), promptTokens.length)
493
+ : undefined;
494
+ const tailSplit = binding.prefillTailSplit ?? COMPOSITION_DEFAULTS.prefillTailSplit;
495
+
496
+ while (true) {
497
+ options.signal?.throwIfAborted();
498
+ const step = nextPrefillStep({ length: promptTokens.length, position: pos,
499
+ chunkSize: prefillChunkSize, tailSplit, snapshotAt });
500
+ if (step.kind === "final" && needsTokenHistory) stepSampler.seedHistory(promptTokens);
501
+ const closeChunk = diagnostics.trace?.begin("prefill.chunk", {
502
+ mechanism: diagnostics.mechanism ?? "serial", startToken: step.start,
503
+ tokens: step.end - step.start, ...(step.kind === "final" ? { final: true } : {}),
504
+ });
505
+ let hidden: MlxArray | null;
506
+ try { hidden = await executeMlxPrefillStep(phases, cache, promptTokens, step); }
507
+ finally { closeChunk?.(); }
508
+ pos = step.end;
509
+ if (hidden) { h0 = hidden; break; }
510
+ if (runtime.value("MLX_BUN_PREFILL_MEM_LOG") === "1")
511
+ console.error(`[prefill-mem] ${pos} active ${(activeMemory() / 2 ** 30).toFixed(2)} peak ${(peakMemory() / 2 ** 30).toFixed(2)}`);
512
+ await new Promise<void>((resolve) => setImmediate(resolve));
513
+ if (step.snapshot) options.onPrefillDone?.();
514
+ }
515
+ }
516
+ if (options.snapshotAt === undefined || options.snapshotAt >= promptTokens.length)
517
+ options.onPrefillDone?.();
518
+ // The trace's prefill span is the uncached model-forward portion only.
519
+ // Token-0 lm-head, sampling, and readback are a separate additive stage.
520
+ if (diagnostics.trace && runtime.value("MLX_BUN_P2R_SYNC") === "1")
521
+ synchronize(gpuStream);
522
+ closePrefill?.();
523
+ closeTokenZero = diagnostics.trace?.begin("token_zero.total", {
524
+ mechanism: diagnostics.mechanism ?? "serial",
525
+ });
526
+ const [, L0, H] = h0.shape as [number, number, number];
527
+ const hLast = h0.slice([0, L0 - 1, 0], [1, L0, H]);
528
+ h0.dispose();
529
+ const logits0 = phases.logits(1)(hLast);
530
+ hLast.dispose();
531
+ const s0 = sampleStep(logits0, 0); // token array [1] (+ optional extras)
532
+ pending = s0.tok;
533
+ pendingExtras = s0.extras;
534
+ logits0.dispose();
535
+ // mirror mlx-lm generate_step: async-dispatch the first token's
536
+ // compute; the prefill clock keeps running until the token ARRIVES
537
+ // (first itemUint32 below). mlx-lm stops its prompt clock at the
538
+ // first yielded token, which bills the prefill→decode boundary
539
+ // (allocator reclaim of prefill transients + first-step dispatch)
540
+ // to prompt_time, not decode — replicated so cross-stack decode
541
+ // tok/s measure the same quantity. The boundary cost is real and
542
+ // scales with prompt length; it belongs to "having prefilled".
543
+ ops.asyncEvalAll([pending, ...stepExtrasArrays(pendingExtras)]);
544
+ }
545
+
546
+ // ---- decode (pipelined) ----
547
+ // The compiled step is the graph binding's, chosen once for this run's caches; it has no fallback.
548
+ decoder = options.decodePolicy?.compiledDecode === false ? null
549
+ : binding.createDecode?.({ hasAdapters: !!options.adapters?.length }) ?? null;
550
+ let stop = false;
551
+ /** Token id read eagerly at the top of the loop for grammar (reused for
552
+ * the yield, avoiding a second readback). -1 when grammar is off — the
553
+ * pipelined path keeps its deferred itemUint32 below. */
554
+ let grammarTok = -1;
555
+ // Jump-forward decoding in this direct loop (opt-in, the composition's
556
+ // grammarJump; the scheduler's grammar rows jump through the span method
557
+ // the planner binds): when the grammar forces a unique continuation, emit its retokenized ids without
558
+ // per-token forwards — ONE multi-token forward carries them into the KV
559
+ // (see GrammarController.jumpForward for the contract + the fidelity
560
+ // note on why this is opt-in). Excluded when logprobs are requested
561
+ // (jumped tokens are never sampled, so they'd have no logprobs rows).
562
+ const grammarJump = options.decodePolicy?.grammarJump ?? shouldUseGrammarJump(options, binding.grammarJump);
563
+ // Yield token zero before its own decode forward. The captured runtime
564
+ // keeps this scheduling choice stable across consumer awaits.
565
+ const earlyFirstToken = runtime.flag("MLX_BUN_EARLY_FIRST_TOKEN", true) &&
566
+ maxTokens > 1 && !resuming && !fillOn && !options.grammar;
567
+ while (!stop) {
568
+ options.signal?.throwIfAborted();
569
+ const cur = pending!;
570
+ const stepIndex = generated;
571
+ let curExtras = pendingExtras;
572
+ let yieldedEarly = false;
573
+ if (earlyFirstToken && stepIndex === 0) {
574
+ const firstToken = ops.itemUint32(cur);
575
+ options.signal?.throwIfAborted();
576
+ if (!eosTokenIds.includes(firstToken)) {
577
+ prefillMs = performance.now() - tPrefill;
578
+ closeTokenZero?.();
579
+ closeTokenZero = undefined;
580
+ tDecode = performance.now();
581
+ const logprobs = readStepExtras(curExtras);
582
+ curExtras = null;
583
+ pendingExtras = null;
584
+ // Count before yielding so an early return reports this token. Its
585
+ // KV is still absent; forwarded describes the prompt-only cache.
586
+ generated++;
587
+ yieldedEarly = true;
588
+ earlyUnforwardedToken = firstToken;
589
+ yield { token: firstToken, index: stepIndex, ...(logprobs ? { logprobs } : {}) };
590
+ earlyUnforwardedToken = null;
591
+ options.signal?.throwIfAborted();
592
+ }
593
+ }
594
+ // Keep current extras owned by the run until readback succeeds. A
595
+ // forward/grammar failure must leave them reachable by finally.
596
+ // Grammar advance (packages/inference/src/sampling/grammar.ts): acceptToken needs the token id as a
597
+ // JS number, which the pipelined loop defers (it operates on device
598
+ // arrays). So grammar requests eager-read cur here, advance the matcher,
599
+ // and await the async mask precompute — which overlaps the GPU forward
600
+ // dispatched just below. This trades the readback/forward overlap for
601
+ // correctness (the mask for step n+1 must reflect token n). Non-grammar
602
+ // requests keep the fast pipelined loop untouched.
603
+ //
604
+ // F1 fix (batched-lane plan): always eager-read `grammarTok` when
605
+ // grammar is on, even at the max_tokens boundary — the emitted token
606
+ // reuses it unconditionally. The OLD code gated the READ on
607
+ // generated+1 < maxTokens but emitted grammarTok regardless, so the LAST
608
+ // iteration skipped the refresh and emitted a stale/garbage token (the
609
+ // previous step's token, or -1 when max_tokens=1) → truncated JSON ended
610
+ // on a corrupted token + cacheTokens recorded the wrong id. Now only
611
+ // accept()/ready() (which prepare the NEXT step's mask) are gated.
612
+ /** Forced ids to emit after cur this iteration (jump-forward), else null. */
613
+ let jumpEmit: number[] | null = null;
614
+ if (options.grammar) {
615
+ grammarTok = ops.itemUint32(cur);
616
+ if (stepIndex + 1 < maxTokens) {
617
+ options.grammar.accept(grammarTok);
618
+ await options.grammar.ready();
619
+ if (grammarJump && !options.grammar.isTerminated) {
620
+ jumpEmit = options.grammar.jumpForward(maxTokens - (stepIndex + 1));
621
+ // jumpForward advanced the matcher and fired the post-jump mask
622
+ // fill; it must be ready before this iteration's sampleStep.
623
+ if (jumpEmit) await options.grammar.ready();
624
+ }
625
+ }
626
+ }
627
+ // build step n+1's graph from the *unread* pending token
628
+ nextPending = null;
629
+ nextExtras = null;
630
+ // When the grammar has terminated (a complete valid JSON/schema
631
+ // accepted), there are no valid tokens left — skip building the next
632
+ // step so the sampler never sees an all--inf distribution.
633
+ if (jumpEmit) {
634
+ // JUMP iteration: one [1, 1+m] forward carries cur AND the forced ids
635
+ // into the KV (they are all committed content — jumpForward's
636
+ // contract); the next sampled token, if the budget and grammar allow
637
+ // one, comes from its last position. Compiled decode resumes on the
638
+ // following iteration over the same caches.
639
+ pushHistory(cur);
640
+ stepSampler.commitNumbers(jumpEmit);
641
+ const chunk = [grammarTok, ...jumpEmit];
642
+ const willGen = stepIndex + 1 + jumpEmit.length;
643
+ await appendGrammarSpan(phases, cache, chunk,
644
+ ids => { forwarded.push(...ids); },
645
+ willGen < maxTokens && !options.grammar!.isTerminated ? logits => {
646
+ const sampled = sampleStep(logits, willGen);
647
+ nextPending = sampled.tok;
648
+ nextExtras = sampled.extras;
649
+ } : undefined);
650
+ if (nextPending) ops.asyncEvalAll([nextPending, ...stepExtrasArrays(nextExtras)]);
651
+ } else if (stepIndex + 1 < maxTokens && !options.grammar?.isTerminated) {
652
+ pushHistory(cur);
653
+ let logits: MlxArray;
654
+ let evalWith: MlxArray[] = [];
655
+ if (decoder) ({ logits, evalWith } = decoder.step(cur, cache));
656
+ else {
657
+ const ids = ops.reshape(cur, [1, 1]);
658
+ const h = await phases.decode(1)(ids, cache, null);
659
+ ids.dispose();
660
+ logits = phases.logits(1)(h);
661
+ h.dispose();
662
+ }
663
+ const sn = sampleStep(logits, stepIndex + 1);
664
+ nextPending = sn.tok;
665
+ nextExtras = sn.extras;
666
+ logits.dispose();
667
+ ops.asyncEvalAll([nextPending, ...stepExtrasArrays(nextExtras), ...evalWith]);
668
+ }
669
+
670
+ // sync-read step n's token while n+1 computes
671
+ // sync-read step n's token while n+1 computes (grammar already read it
672
+ // eagerly above — reuse to avoid a second GPU sync)
673
+ const token = options.grammar ? grammarTok : ops.itemUint32(cur);
674
+ options.signal?.throwIfAborted();
675
+ if (generated === 0) {
676
+ // first token arrived: prompt clock stops, decode clock starts
677
+ // (mlx-lm stream_generate's n==0 clock swap; the first token is
678
+ // "free" on the decode clock there too)
679
+ prefillMs = performance.now() - tPrefill;
680
+ closeTokenZero?.();
681
+ closeTokenZero = undefined;
682
+ tDecode = performance.now();
683
+ }
684
+ cur.dispose();
685
+ pending = null;
686
+ if (!yieldedEarly) generated++;
687
+ // if a next-step graph was built, this token's KV entered the cache
688
+ // (jump iterations pushed the whole chunk already)
689
+ if (nextPending !== null && !jumpEmit) forwarded.push(token);
690
+
691
+ if (eosTokenIds.includes(token)) {
692
+ finishReason = "stop";
693
+ disposeStepExtras(curExtras);
694
+ pendingExtras = null;
695
+ nextPending?.dispose();
696
+ nextPending = null;
697
+ disposeStepExtras(nextExtras);
698
+ nextExtras = null;
699
+ stop = true;
700
+ } else {
701
+ // ---- token fast-forwarding (K3): APPEND, then yield ----------------
702
+ // The engine already knows the next m tokens, so it writes them into
703
+ // the KV itself with an append and resumes sampling after them. No
704
+ // draft, no verify, no rollback: an injected token is indistinguishable
705
+ // to the model from one it sampled. The append happens BEFORE any of
706
+ // the burst's yields, which is what makes a consumer break mid-burst
707
+ // safe — `forwarded` already describes the cache exactly.
708
+ let fillEmit: number[] | null = null;
709
+ if (fillOn) {
710
+ if (jumpEmit)
711
+ throw new Error("fill and grammar jump-forward cannot share an iteration");
712
+ // A fill needs an in-flight step to ride; with nextPending null this
713
+ // token's KV was never written and the generation ends here. push()
714
+ // still runs so the session's history (and echo index) stay exact.
715
+ const proposal = options.fill!.push(
716
+ token, nextPending !== null ? maxTokens - generated : 0,
717
+ );
718
+ if (proposal && nextPending !== null) {
719
+ const emitted = await applyProposal(proposal, generated);
720
+ if (emitted.length) fillEmit = emitted;
721
+ } else if (proposal) {
722
+ options.fill!.commit(proposal, 0); // unreachable at budget 0; belt
723
+ }
724
+ }
725
+ // readExtras before the yield: if the consumer breaks at this yield,
726
+ // the extras are already read and disposed.
727
+ const logprobs = readStepExtras(curExtras);
728
+ pendingExtras = null;
729
+ if (!yieldedEarly) yield { token, index: generated - 1, ...(logprobs ? { logprobs } : {}) };
730
+ options.signal?.throwIfAborted();
731
+ // mlx-lm generate_step: clear_cache after token 0 (drops the
732
+ // remaining prefill transients) and every 256 tokens after
733
+ if ((generated - 1) % 256 === 0) clearCache();
734
+ // Jump-forward burst: the forced ids follow cur, one yield each (the
735
+ // consumer's stop-sequence matcher and detokenizer see the same
736
+ // one-at-a-time stream shape as always). Their KV is already in the
737
+ // cache (the chunk forward above); a consumer break mid-burst is
738
+ // safe — `forwarded` already reflects the cache exactly.
739
+ if (jumpEmit) {
740
+ for (const jt of jumpEmit) {
741
+ options.signal?.throwIfAborted();
742
+ generated++;
743
+ yield { token: jt, index: generated - 1 };
744
+ if ((generated - 1) % 256 === 0) clearCache();
745
+ }
746
+ }
747
+ // Fill burst: same one-yield-per-token shape as the jump burst, so
748
+ // CompletionSink / StopMatcher / StreamDecoder see an ordinary stream
749
+ // (a stop sequence inside an injected span fires exactly where it
750
+ // would have in an unfilled run).
751
+ if (fillEmit) {
752
+ for (const ft of fillEmit) {
753
+ options.signal?.throwIfAborted();
754
+ generated++;
755
+ yield { token: ft, index: generated - 1 };
756
+ if ((generated - 1) % 256 === 0) clearCache();
757
+ }
758
+ }
759
+ if (nextPending === null) {
760
+ stop = true;
761
+ } else {
762
+ pending = nextPending;
763
+ nextPending = null;
764
+ pendingExtras = nextExtras;
765
+ nextExtras = null;
766
+ }
767
+ if (
768
+ pending !== null &&
769
+ options.onDecodeCheckpoint &&
770
+ generated >= nextCheckpoint
771
+ ) {
772
+ const pendingToken = ops.itemUint32(pending);
773
+ await options.onDecodeCheckpoint({
774
+ cacheTokens: [...promptTokens, ...forwarded],
775
+ caches: cache,
776
+ generatedTokens: generated,
777
+ pendingToken,
778
+ });
779
+ const every = options.checkpointEveryTokens!;
780
+ while (nextCheckpoint <= generated) nextCheckpoint += every;
781
+ }
782
+ }
783
+ }
784
+ decodeMs = performance.now() - tDecode;
785
+ finished = true;
786
+ return makeStats();
787
+ } catch (e) {
788
+ threw = true;
789
+ executionError = e;
790
+ throw e;
791
+ } finally {
792
+ try {
793
+ // An early consumer return leaves token zero outside the retained KV.
794
+ // Finish that one M=1 forward before handing caller-owned caches back,
795
+ // matching the ordinary pipeline's boundary. Otherwise a later prefix
796
+ // hit folds the token into an M>1 prefill and changes native reductions.
797
+ // Aborted requests and caches owned by this run need no retained state.
798
+ if (!ownsCache && !threw && !options.signal?.aborted && earlyUnforwardedToken !== null) {
799
+ const ids = ops.reshape(pending!, [1, 1]);
800
+ try {
801
+ const hidden = await phases.decode(1)(ids, cache, null);
802
+ try { evalCacheState(cache); }
803
+ finally { hidden.dispose(); }
804
+ } finally { ids.dispose(); }
805
+ forwarded.push(earlyUnforwardedToken);
806
+ }
807
+ } catch (error) {
808
+ threw = true;
809
+ executionError = error;
810
+ throw error;
811
+ } finally {
812
+ if (!finished) {
813
+ pending?.dispose();
814
+ nextPending?.dispose();
815
+ disposeStepExtras(pendingExtras);
816
+ disposeStepExtras(nextExtras);
817
+ }
818
+ try {
819
+ const closing = decoder?.close();
820
+ if (closing) await closing;
821
+ } catch (error) {
822
+ if (threw) throw new AggregateError([executionError, error],
823
+ "AR execution and decoder cleanup failed", { cause: executionError });
824
+ throw error;
825
+ } finally {
826
+ if (ownsCache) for (const c of cache) c.dispose();
827
+ // TokenizerInfo is process-cached; this request owns the matcher.
828
+ options.grammar?.dispose();
829
+ stepSampler.dispose();
830
+ }
831
+ if (!finished && !threw) {
832
+ // forced early return (consumer break at a yield): still report
833
+ // stats — `forwarded` only lists tokens whose KV actually entered
834
+ // the cache, so cacheTokens stays exact for PromptCache.put().
835
+ decodeMs = performance.now() - tDecode;
836
+ return makeStats();
837
+ }
838
+ }
839
+ }
840
+ }