@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,225 @@
1
+ import { MlxArray } from "@mlx-bun/mlx/array";
2
+ import { type MlxForwardWork,type MlxPreparationWork } from "../contracts/mlx/forward-work";
3
+ import type { ExecutionContext } from "../contracts/portable/scheduling";
4
+ import { type RuntimeConfig } from "../runtime/config";
5
+ import type { PromptResponseTrace } from "../runtime/trace";
6
+ import type { GrammarController } from "../sampling/grammar";
7
+ import type { KvMaintenance } from "../contracts/mlx/cache";
8
+ import type { RequestStateCache } from "../state/cache-service";
9
+ import type { OrdinaryContinuation } from "./continuation-types";
10
+
11
+ // NOTE — oMLX-style "adaptive burst decode" (several steps per event-loop
12
+ // yield, omlx/engine_core.py _step_burst) was ported here 2026-07-02 and
13
+ // REFUTED by measurement: on this runtime it REGRESSED cpm5 B=4 aggregate
14
+ // 345→289 tok/s, batch-lane B=1 149→121, and TTFT ~+100 ms (first-token SSE
15
+ // flush waits out the burst budget). Their win exists because each Python
16
+ // step hand-off ping-pongs the GIL with asyncio/uvicorn (~1 ms/token); Bun's
17
+ // setImmediate hop costs microseconds, so bursting here only delays socket
18
+ // flushes. Don't re-add without new evidence — the per-yield step below is
19
+ // the measured optimum (`02d723a:docs/design/batching.md` §2).
20
+
21
+ /** A token sampler for one row: (logits [1,V], step) → token array [1] on
22
+ * device. Greedy is `(l) => ops.argmaxAxis(l, -1)`; richer closures fold in
23
+ * temperature / top-p / logits processors + history (built per request from
24
+ * its sampler options, exactly like generate()'s sampleStep). Called once per
25
+ * step 0..maxTokens-1 for this row (never beyond maxTokens). */
26
+ export type RowSampler = (logits1V: MlxArray, step: number) => MlxArray;
27
+
28
+ /** A method prepares and advances rows; the shared executor owns queueing. */
29
+ export interface MlxGroupPreparation {
30
+ readonly rows: readonly Row[];
31
+ readonly tokenWeight?: number;
32
+ readonly canAdmit?: boolean;
33
+ readonly supportsMixedWork?: boolean;
34
+ admit?(row: Row): void;
35
+ advance(work?: MlxPreparationWork): Promise<boolean>;
36
+ dispose(): void;
37
+ }
38
+
39
+ export interface MlxGroupMethodHost {
40
+ readonly rows: readonly Row[];
41
+ readonly runtime: RuntimeConfig;
42
+ readonly prefillChunkSize: number;
43
+ /** The composition's `prefillTailSplit`: a prompt's last token prefills alone. */
44
+ readonly prefillTailSplit: boolean;
45
+ /** The composition's cache service: every row's state comes from it and returns to it. */
46
+ readonly cache: RequestStateCache;
47
+ /** The row-layout maintenance of the composition's KV scheme (where a row's
48
+ * composed caches join row layouts); absent under bf16. */
49
+ readonly kvMaintenance: KvMaintenance | undefined;
50
+ join(row: Row): void;
51
+ filterRows(keep: number[]): void;
52
+ publish(row: Row, token: number, logprobs?: import("../generation/index").TokenLogprobs): Promise<void | boolean>;
53
+ finish(row: Row, reason: "stop" | "length"): void;
54
+ }
55
+
56
+ export interface MlxGroupedMethod {
57
+ /** Maximum target tokens in the next iteration, including candidates. */
58
+ readonly runningTokens?: number;
59
+ prepare(row: Row): MlxGroupPreparation;
60
+ advance(work?: MlxForwardWork): Promise<void>;
61
+ filterRows(keep: readonly number[], discard: boolean): void;
62
+ dispose(): void;
63
+ }
64
+
65
+ export interface MlxGroupMethodRequest {
66
+ /** Equal keys declare compatible method state, independent of queue size. */
67
+ readonly key: string;
68
+ readonly data: unknown;
69
+ open(host: MlxGroupMethodHost): MlxGroupedMethod;
70
+ }
71
+
72
+ export type BatchRequest = BatchRequestFields & (
73
+ | { method: MlxGroupMethodRequest; sample?: RowSampler }
74
+ | { method?: undefined; sample: RowSampler }
75
+ );
76
+
77
+ export interface BatchRequestFields {
78
+ /** Borrowed prepared input; its owner retains it through preparation. */
79
+ promptInput?: import("../contracts/mlx/media").MlxPromptInput;
80
+ cacheSessionId?: string;
81
+ /** Optional ordinary continuation: its checkpoint key and sampler recovery;
82
+ * the cache service resumes and checkpoints it. */
83
+ continuation?: OrdinaryContinuation;
84
+ /** False for a request whose prefix has no identity (prepared media without
85
+ * one): it takes fresh state and its state is not kept. Rows that differ here
86
+ * do not share a group. */
87
+ reusesPrefix?: boolean;
88
+ context?: ExecutionContext;
89
+ /** Resolve loaded-state identity under the execution lease, after any
90
+ * queued adapter replacement has finished. */
91
+ cacheNamespace?: string | (() => string);
92
+ /** Resolved replay permission; standalone group callers retain auto mode. */
93
+ compiledDecode?: boolean;
94
+ promptIds: number[];
95
+ /** Request policy overrides the execution group's captured prefill default. */
96
+ prefillChunkSize?: number;
97
+ maxTokens: number;
98
+ eosTokenIds: number[];
99
+ /** Called per emitted (non-EOS) token, in order. Returning `false` halts this
100
+ * row (a decoded-text stop sequence fired) — matches generate()'s onToken
101
+ * contract. EOS terminates the row WITHOUT an onToken call. Throwing evicts
102
+ * THIS row only (its submit promise rejects; siblings continue). May be
103
+ * async; keep it cheap — it runs inline in the step loop. */
104
+ onToken: (token: number, logprobs?: import("../generation/index").TokenLogprobs) => void | boolean | Promise<void | boolean>;
105
+ /** Diagnostic-only request-local trace. */
106
+ trace?: PromptResponseTrace;
107
+ /** Closes the gateway's queue/admission span when this row leaves pending. */
108
+ onAdmitted?: () => void;
109
+ /** Client/request lifetime. An aborted pending row is removed before
110
+ * admission; a row in prefill or decode is evicted at the next safe
111
+ * scheduler boundary. */
112
+ signal?: AbortSignal;
113
+ /** The row's sampler is PLAIN GREEDY (temperature 0, no curve) with no
114
+ * logits processors and no grammar — set by the gateway when true. Lets
115
+ * the scheduler take the vectorized sampling fast path (ONE
116
+ * log-softmax+argmax over [B,V] instead of B slice/sample/concat graphs)
117
+ * when EVERY live row qualifies. Numerically identical per row
118
+ * (row-independent ops, same per-row shapes); the per-row closure path
119
+ * is the fallback and the composition's `vectorGreedySampling: false`. */
120
+ plainGreedy?: boolean;
121
+ /** Grammar controller for this row (B1: per-row matchers). When set, the
122
+ * scheduler drives it: `accept()` after each emitted token (fires the async
123
+ * bitmask fill), `await ready()` before the row's next sample (the sample
124
+ * closure applies `applyMask`). Termination (grammar satisfied) is an
125
+ * additional per-row stop source. The gateway OWNS disposal (finally around
126
+ * submit()); the scheduler uses but never owns. Null on the degrade path. */
127
+ grammar?: GrammarController;
128
+ /** Stable cache boundary from the server's template probe (the serial
129
+ * lane's options.snapshotAt): the prompt prefix the NEXT turn's render
130
+ * preserves. The scheduler snapshots a trim-free prompt-cache entry at
131
+ * min(this, promptIds.length-1) during the solo prefill — the oracle
132
+ * invariant (mlx-lm insert_segments) that gives wrapped-ring models a
133
+ * reuse path. Absent = still capped at promptIds.length-1. */
134
+ snapshotAt?: number;
135
+ }
136
+
137
+ export interface BatchStats {
138
+ spec?: import("../generation/index").GenerateStats["spec"];
139
+ fill?: import("../generation/index").GenerateStats["fill"];
140
+ promptTokens: number;
141
+ generatedTokens: number;
142
+ /** Prompt tokens served from the prompt cache: a joiner's prefill starts
143
+ * from the longest usable cached prefix the cache service acquires. 0 on a
144
+ * cold prefill. */
145
+ cachedTokens: number;
146
+ finishReason: "stop" | "length";
147
+ /** Wall-clock from admission (leaving the pending queue) to the first
148
+ * emitted token — the row's prefill span on the batch lane. */
149
+ prefillMs: number;
150
+ /** Wall-clock from the first emitted token to finish. */
151
+ decodeMs: number;
152
+ }
153
+
154
+ export interface Row {
155
+ spec?: import("../generation/index").GenerateStats["spec"];
156
+ fill?: import("../generation/index").GenerateStats["fill"];
157
+ req: BatchRequest;
158
+ resolve: (s: BatchStats) => void;
159
+ reject: (e: unknown) => void;
160
+ current: number; // last emitted token, fed at the next step (pipeline cold)
161
+ generated: number; // tokens emitted so far (incl. a terminating EOS)
162
+ sampled: number; // sample() calls so far (leads `generated` by 1 in-pipeline)
163
+ promptTokens: number;
164
+ /** Prompt tokens restored from the prompt cache at admission (stats). */
165
+ cachedTokens: number;
166
+ /** performance.now() marks for BatchStats timing (0 = not reached). */
167
+ admittedAt: number;
168
+ firstTokenAt: number;
169
+ /** A method that computes its whole result before publishing reports that
170
+ * work span; finish() then reports it as decode time, excluding admission
171
+ * wait and output delivery. Denoising sets it. */
172
+ decodeSpan?: { start: number; end: number };
173
+ closeTokenZero?: () => void;
174
+ cacheNamespace?: string;
175
+ /** Generated tokens whose KV actually entered the cache (fed as a step
176
+ * input) — serial generate()'s `forwarded` list. promptIds+fed is the
177
+ * exact token coverage of the row's caches, the put() entry key.
178
+ * PER-ROW EXACT for every row: pushes are gated on the pending
179
+ * register's per-slot real/placeholder flags (#pendingReal), so a
180
+ * doomed slot's placeholder is never recorded — and if one ever DID
181
+ * feed a forward, `fedTainted` flips instead (see #step's read). */
182
+ fed: number[];
183
+ /** A placeholder slot value fed this row's KV (junk token in the cache):
184
+ * the coverage key is no longer derivable — extraction refuses.
185
+ * Structurally unreachable today (a doomed row always evicts at the
186
+ * emit that follows its placeholder, filtering the slot before the
187
+ * next forward; cold steps never contain doomed rows because a flush
188
+ * emits every in-flight token) — this flag makes `fed` provably exact
189
+ * instead of resting on that timing chain. */
190
+ fedTainted: boolean;
191
+ /** Ever merged into a multi-row batch (or poisoned by a reject): its KV
192
+ * is interleaved in batched buffers — a finish put() must EXTRACT the
193
+ * row (#extractRowCaches) rather than adopt the inners. */
194
+ merged: boolean;
195
+ }
196
+
197
+ /** Held while the batch is active; the serial fallback acquires the same lock. */
198
+ export interface ExclusiveLock {
199
+ acquire(): Promise<() => void>;
200
+ }
201
+
202
+ export interface MlxBatchExecutionGroupOptions {
203
+ /** Scheduling work budget for extending an existing preparation cohort. */
204
+ prefillBatchTokenLimit?: number;
205
+ runtime?: RuntimeConfig;
206
+ maxQueued?: number;
207
+ /** Max rows in the running batch (mlx-lm `--decode-concurrency`). */
208
+ maxBatch: number;
209
+ lock?: ExclusiveLock;
210
+ /** Drain signal: while true, no NEW rows are admitted (running rows finish,
211
+ * the lock is released) so a waiting serial-lane request can run — mlx-lm's
212
+ * drain_batch. Pair with kick() when it flips back to false. */
213
+ admissionHeld?: () => boolean;
214
+ /** Joiner prefill chunk length (default 2048, the serial loop's constant). */
215
+ prefillChunkSize?: number;
216
+ /** The composition's cache service (`state/cache-service`): admission under
217
+ * its KV budget (a joiner that would push the admitted rows' projected KV
218
+ * over `--kv-budget` WAITS in the queue, FIFO and head-of-line, until rows
219
+ * finish; one that can never fit is rejected), each row's state at
220
+ * admission (a reused prefix, a resumed generation, or fresh state), and
221
+ * the state rows offer back when they finish. Runs under the gateway mutex
222
+ * domain. Default: a service over the graph's composition that reuses and
223
+ * keeps nothing and admits without a budget. */
224
+ cache?: RequestStateCache;
225
+ }
@@ -0,0 +1,38 @@
1
+ import type { CancelReason, Cancellation } from "../contracts/portable/generation";
2
+
3
+ export class GenerationCancelled extends Error {
4
+ constructor(readonly reason: CancelReason) {
5
+ super(`generation cancelled: ${reason}`);
6
+ this.name = "GenerationCancelled";
7
+ }
8
+ }
9
+
10
+ export function throwIfCancelled(cancellation: Cancellation): void {
11
+ if (cancellation.reason !== undefined) throw new GenerationCancelled(cancellation.reason);
12
+ }
13
+
14
+ export class CancellationSource implements Cancellation {
15
+ #reason: CancelReason | undefined;
16
+ #listeners = new Set<(reason: CancelReason) => void>();
17
+ get reason(): CancelReason | undefined { return this.#reason; }
18
+
19
+ subscribe(listener: (reason: CancelReason) => void): () => void {
20
+ if (this.#reason !== undefined) {
21
+ listener(this.#reason);
22
+ return () => {};
23
+ }
24
+ this.#listeners.add(listener);
25
+ return () => { this.#listeners.delete(listener); };
26
+ }
27
+
28
+ cancel(reason: CancelReason): void {
29
+ if (this.#reason !== undefined) return;
30
+ this.#reason = reason;
31
+ const listeners = [...this.#listeners];
32
+ this.#listeners.clear();
33
+ // A faulty observer cannot prevent the remaining owners from cancelling.
34
+ for (const listener of listeners) {
35
+ try { listener(reason); } catch { /* Observers do not own settlement. */ }
36
+ }
37
+ }
38
+ }
@@ -0,0 +1,45 @@
1
+ import { snapshotGenerationPolicy } from "./request-policy";
2
+ import type { GenerateOptions } from "../generation/index";
3
+ import type { ResolvedExecution } from "../contracts/portable/execution";
4
+ import { generationCheckpointKey } from "../generation/checkpoint-identity";
5
+ import { makeStepSampler } from "../sampling/index";
6
+ import { createRowSampling } from "./row-sampling";
7
+ import { type OrdinaryContinuation } from "./continuation-types";
8
+
9
+ /** Optional policy and sampler recovery for the existing ordinary driver. The
10
+ * cache service resumes and checkpoints the generation under the key this
11
+ * request names; KV layout and delayed conversion stay with the caches. */
12
+ export function createOrdinaryContinuationRequest(input: {
13
+ options: GenerateOptions; prompt: number[]; execution: ResolvedExecution;
14
+ /** Checkpoint every this many generated tokens (the cache service's `checkpointEveryTokens`). */
15
+ interval: number;
16
+ onToken: Parameters<typeof createRowSampling>[1];
17
+ }) {
18
+ const options: GenerateOptions = snapshotGenerationPolicy(input.options);
19
+ if (!Number.isSafeInteger(input.interval) || input.interval < 1) throw new Error("continuation interval must be positive");
20
+ if (options.grammar || options.logprobs || options.topLogprobs || options.fill ||
21
+ input.execution.method !== "autoregressive")
22
+ throw new Error("ordinary continuation requires replayable ordinary sampling");
23
+ const make = (seed = options.seed, history: readonly number[] = input.prompt) => createRowSampling(makeStepSampler(
24
+ { ...options, seed }, { tokenRepresentation: "device", grammarWait: "external", historyUpdate: "after-sample",
25
+ initialHistory: [...history] }), input.onToken);
26
+ let sampling = make();
27
+ const continuation: OrdinaryContinuation = {
28
+ interval: input.interval,
29
+ key: (namespace, identity) => generationCheckpointKey(input.prompt, options, namespace, input.execution, identity),
30
+ seed: options.seed ?? 0,
31
+ seedWasExplicit: options.seedWasExplicit === true,
32
+ resumeSampling(state) {
33
+ const next = make(state.seed, [...state.cacheTokens, state.pendingToken]);
34
+ const previous = sampling; sampling = next; previous.dispose();
35
+ },
36
+ };
37
+ return {
38
+ continuation,
39
+ sample: ((...args) => sampling.sample(...args)) as ReturnType<typeof createRowSampling>["sample"],
40
+ // History-free greedy sampling needs no recovery work in the fast path.
41
+ plainGreedy: sampling.plainGreedy,
42
+ onToken: input.onToken,
43
+ dispose: () => { sampling.dispose(); },
44
+ };
45
+ }
@@ -0,0 +1,10 @@
1
+ import type { ContinuationRequest, ContinuationState } from "../state/cache-service";
2
+
3
+ /** A replayable ordinary request: what the cache service checkpoints it under
4
+ * (`ContinuationRequest`), how often the scheduler offers a checkpoint, and
5
+ * the sampler recovery a resumed generation runs. No store knowledge enters
6
+ * the driver; the cache service resumes and checkpoints it. */
7
+ export interface OrdinaryContinuation extends ContinuationRequest {
8
+ readonly interval: number;
9
+ resumeSampling(state: Omit<ContinuationState, "caches">): void;
10
+ }
@@ -0,0 +1,70 @@
1
+ import type { Cancellation } from "../contracts/portable/generation";
2
+ import type { DisposableResource } from "../contracts/portable/resources";
3
+ import { AdmissionRejected } from "./admission";
4
+ import { throwIfCancelled } from "./cancellation";
5
+
6
+ type Mode = "shared" | "exclusive";
7
+ interface Waiter {
8
+ mode: Mode;
9
+ resolve(lease: DisposableResource): void;
10
+ reject(error: unknown): void;
11
+ unsubscribe(): void;
12
+ }
13
+
14
+ /** Shared worker activity; exclusive managed jobs. FIFO writers cannot starve
15
+ * behind newly arriving inference. This coordinates execution, not memory size. */
16
+ export class ExecutionCoordinator {
17
+ #readers = 0;
18
+ #writer = false;
19
+ #closed = false;
20
+ #queue: Waiter[] = [];
21
+ constructor(readonly maxQueued = 64) {
22
+ if (!Number.isSafeInteger(maxQueued) || maxQueued < 0) throw new Error("invalid coordination queue bound");
23
+ }
24
+ acquire(mode: Mode, cancellation?: Cancellation): Promise<DisposableResource> {
25
+ if (this.#closed) return Promise.reject(new Error("execution coordinator is closed"));
26
+ try { if (cancellation) throwIfCancelled(cancellation); }
27
+ catch (error) { return Promise.reject(error); }
28
+ if (!this.#queue.length && !this.#writer && (mode === "shared" || this.#readers === 0))
29
+ return Promise.resolve(this.#lease(mode));
30
+ if (this.#queue.length >= this.maxQueued) return Promise.reject(new AdmissionRejected());
31
+ return new Promise((resolve, reject) => {
32
+ const waiter: Waiter = { mode, resolve, reject, unsubscribe() {} };
33
+ this.#queue.push(waiter);
34
+ waiter.unsubscribe = cancellation?.subscribe((reason) => {
35
+ const index = this.#queue.indexOf(waiter);
36
+ if (index < 0) return;
37
+ this.#queue.splice(index, 1);
38
+ waiter.unsubscribe();
39
+ reject(new Error(`execution admission cancelled: ${reason}`));
40
+ this.#drain();
41
+ }) ?? (() => {});
42
+ if (!this.#queue.includes(waiter)) waiter.unsubscribe();
43
+ });
44
+ }
45
+ #lease(mode: Mode): DisposableResource {
46
+ if (mode === "shared") this.#readers++;
47
+ else this.#writer = true;
48
+ let disposed = false;
49
+ return { dispose: () => {
50
+ if (disposed) return;
51
+ disposed = true;
52
+ if (mode === "shared") this.#readers--;
53
+ else this.#writer = false;
54
+ this.#drain();
55
+ } };
56
+ }
57
+ #drain(): void {
58
+ while (!this.#writer && this.#queue.length) {
59
+ const next = this.#queue[0]!;
60
+ if (next.mode === "exclusive" && this.#readers > 0) return;
61
+ this.#queue.shift(); next.unsubscribe(); next.resolve(this.#lease(next.mode));
62
+ }
63
+ }
64
+ close(): void {
65
+ this.#closed = true;
66
+ for (const next of this.#queue.splice(0)) {
67
+ next.unsubscribe(); next.reject(new Error("execution coordinator is closed"));
68
+ }
69
+ }
70
+ }
@@ -0,0 +1,180 @@
1
+ import type { MlxArray } from "@mlx-bun/mlx/array";
2
+ import type { GenerateOptions } from "../generation/index";
3
+ import { denoisingRequestOptions } from "../generation/denoising";
4
+ import { openDenoisingRun, type DenoisingRun, type DiffusionGenOptions, type DiffusionGenResult } from "../generation/diffusion";
5
+ import type { MlxDenoisingBinding } from "../generation/bindings/denoising";
6
+ import { cleanupFailure, disposeResources } from "../runtime/resources";
7
+ import { snapshotGenerationPolicy } from "./request-policy";
8
+ import type { MlxGroupedMethod, MlxGroupMethodHost, MlxGroupMethodRequest, MlxGroupPreparation, Row } from "./batch-types";
9
+
10
+ interface RequestState {
11
+ readonly run: DenoisingRun;
12
+ readonly adapters: string[];
13
+ readonly maxTokens: number;
14
+ result?: DiffusionGenResult;
15
+ /** performance.now() when the final unit returned the result. */
16
+ computedAt?: number;
17
+ }
18
+
19
+ /** Interleaved shared denoising: each row keeps its own canvas, feedback,
20
+ * encoder state and key sequence, and advances by one bounded unit at a time
21
+ * in round-robin order. Graph calls stay single-row; canvases are not stacked. */
22
+ export function bindDenoisingGroupRequests<State>(
23
+ binding: MlxDenoisingBinding<State>,
24
+ policy: (options: GenerateOptions) => DiffusionGenOptions = denoisingRequestOptions,
25
+ ) {
26
+ return (input: GenerateOptions): MlxGroupMethodRequest => {
27
+ const options = snapshotGenerationPolicy(input);
28
+ return {
29
+ key: JSON.stringify(["denoising", binding.graph.descriptor.id]),
30
+ data: options,
31
+ open: host => new DenoisingGroup(host, binding, policy),
32
+ };
33
+ };
34
+ }
35
+
36
+ class DenoisingGroup<State> implements MlxGroupedMethod {
37
+ readonly #requests = new Map<Row, RequestState>();
38
+ /** One immutable base-weight table shared by this group's rows. Rows borrow
39
+ * it; the group releases it after the last row's run has closed. */
40
+ #table: MlxArray | null = null;
41
+ #tableUsers = 0;
42
+ #next = 0;
43
+
44
+ constructor(readonly host: MlxGroupMethodHost, readonly binding: MlxDenoisingBinding<State>,
45
+ readonly policy: (options: GenerateOptions) => DiffusionGenOptions) {}
46
+
47
+ prepare(first: Row): MlxGroupPreparation {
48
+ let row: Row | null = first;
49
+ const preparation: MlxGroupPreparation = {
50
+ get rows() { return row ? [row] : []; },
51
+ canAdmit: false,
52
+ supportsMixedWork: false,
53
+ get tokenWeight() { return row?.promptTokens ?? 0; },
54
+ // The whole prompt prefill and first canvas draw form one unit.
55
+ advance: async () => {
56
+ const current = row!;
57
+ if (current.req.signal?.aborted) throw current.req.signal.reason;
58
+ this.#advanceRun(this.#requests.get(current)!);
59
+ row = null;
60
+ this.host.join(current);
61
+ return true;
62
+ },
63
+ dispose: () => {
64
+ if (row && !this.host.rows.includes(row)) this.#close(row);
65
+ row = null;
66
+ },
67
+ };
68
+ try { this.#open(first); return preparation; }
69
+ catch (error) { return cleanupFailure(error, () => preparation.dispose()); }
70
+ }
71
+
72
+ async advance(): Promise<void> {
73
+ const rows = this.host.rows;
74
+ const live = rows.flatMap((row, index) => row.req.signal?.aborted ? [] : [index]);
75
+ if (live.length !== rows.length) {
76
+ for (const row of rows) if (row.req.signal?.aborted) row.reject(row.req.signal.reason);
77
+ this.host.filterRows(live);
78
+ }
79
+ if (!this.host.rows.length) return;
80
+ const index = this.#next % this.host.rows.length;
81
+ const row = this.host.rows[index]!;
82
+ const state = this.#requests.get(row)!;
83
+ try {
84
+ if (!state.result) this.#advanceRun(state);
85
+ } catch (error) {
86
+ this.#retire(index);
87
+ row.reject(error);
88
+ return;
89
+ }
90
+ this.#next = index + 1;
91
+ if (state.result) await this.#publish(row, index, state);
92
+ }
93
+
94
+ filterRows(keep: readonly number[], _discard: boolean): void {
95
+ const rows = this.host.rows;
96
+ const retained = new Set(keep.map(index => rows[index]!));
97
+ for (const row of rows) if (!retained.has(row)) this.#close(row);
98
+ this.#next = keep.filter(index => index < this.#next).length;
99
+ }
100
+
101
+ dispose(): void {
102
+ disposeResources([...this.#requests.keys()].map(row => ({ dispose: () => this.#close(row) })));
103
+ }
104
+
105
+ /** Image pixels stay borrowed from the request owner. Only the row's first
106
+ * unit reads them; the owner releases them after the row has settled. */
107
+ #open(row: Row): void {
108
+ const options = row.req.method!.data as GenerateOptions;
109
+ const denoise = this.policy(options);
110
+ const table = this.#acquireTable();
111
+ let run: DenoisingRun;
112
+ try { run = openDenoisingRun(this.binding.graph, row.req.promptIds, { ...denoise, embedWeight: table }); }
113
+ catch (error) { return cleanupFailure(error, () => this.#releaseTable()); }
114
+ this.#requests.set(row, { run, maxTokens: denoise.maxTokens,
115
+ adapters: options.adapters?.length ? [...options.adapters] : [] });
116
+ }
117
+
118
+ #advanceRun(state: RequestState): void {
119
+ const result = this.#unit(state);
120
+ if (!result) return;
121
+ state.result = result;
122
+ state.computedAt = performance.now();
123
+ }
124
+
125
+ /** One synchronous unit with only this row's adapters active. Units never
126
+ * await, so no other row can observe them. */
127
+ #unit(state: RequestState): DiffusionGenResult | undefined {
128
+ const adapters = this.binding.adapters;
129
+ if (!adapters) return state.run.advance();
130
+ const previous = adapters.active;
131
+ adapters.active = state.adapters;
132
+ try { return state.run.advance(); }
133
+ finally { adapters.active = previous; }
134
+ }
135
+
136
+ /** Publish the finished result only; no canvas token leaves before it. */
137
+ async #publish(row: Row, index: number, state: RequestState): Promise<void> {
138
+ try {
139
+ for (const token of state.result!.tokens) {
140
+ row.generated++; row.sampled++;
141
+ if (await this.host.publish(row, token) === false) break;
142
+ }
143
+ } catch (error) {
144
+ this.#retire(index);
145
+ row.reject(error);
146
+ return;
147
+ }
148
+ // The whole result exists before its first token is published. Report the
149
+ // work from admission to the final unit as decode time, as the serial
150
+ // method did, excluding output delivery.
151
+ row.decodeSpan = { start: row.admittedAt, end: state.computedAt! };
152
+ this.host.finish(row, row.generated >= state.maxTokens ? "length" : "stop");
153
+ this.#retire(index);
154
+ }
155
+
156
+ #retire(index: number): void {
157
+ this.host.filterRows(this.host.rows.flatMap((_row, position) => position === index ? [] : [position]));
158
+ }
159
+
160
+ /** The run's cleanup completes before the shared table can be released. */
161
+ #close(row: Row): void {
162
+ const state = this.#requests.get(row);
163
+ if (!state) return;
164
+ this.#requests.delete(row);
165
+ disposeResources([{ dispose: () => state.run.close() }, { dispose: () => this.#releaseTable() }]);
166
+ }
167
+
168
+ #acquireTable(): MlxArray {
169
+ this.#table ??= this.binding.graph.dequantEmbedWeight();
170
+ this.#tableUsers++;
171
+ return this.#table;
172
+ }
173
+
174
+ #releaseTable(): void {
175
+ if (--this.#tableUsers > 0) return;
176
+ const table = this.#table;
177
+ this.#table = null;
178
+ table?.dispose();
179
+ }
180
+ }
@@ -0,0 +1,56 @@
1
+ import type { ExecutionPlanner, GenerationSession, InferenceEngine, RunControl, Timer } from "../contracts/portable/generation";
2
+ import { EngineSession, type SessionResources } from "./session";
3
+
4
+ export interface EngineOptions {
5
+ readonly timer: Timer;
6
+ /** Deadline for an unread stream or a producer blocked by an idle consumer. */
7
+ readonly idleTimeoutMs?: number;
8
+ readonly maxQueuedEvents?: number;
9
+ readonly maxQueuedTokens?: number;
10
+ readonly maxTopLogprobs?: number;
11
+ /** Aggregate output buffers for active collecting sessions, in tokens (4 bytes each).
12
+ * Once terminal, output storage belongs to the caller. */
13
+ readonly maxCollectTokens?: number;
14
+ }
15
+
16
+ export function createInferenceEngine<Request, Metrics>(
17
+ planner: ExecutionPlanner<Request, Metrics>, options: EngineOptions,
18
+ ): InferenceEngine<Request, Metrics> {
19
+ const idleTimeoutMs = options.idleTimeoutMs ?? 30_000;
20
+ const maxQueuedEvents = options.maxQueuedEvents ?? 32;
21
+ const maxQueuedTokens = options.maxQueuedTokens ?? 256;
22
+ const maxCollectTokens = options.maxCollectTokens ?? 65_536;
23
+ const maxTopLogprobs = options.maxTopLogprobs ?? 20;
24
+ for (const [name, value] of Object.entries({ idleTimeoutMs, maxQueuedEvents, maxQueuedTokens, maxCollectTokens, maxTopLogprobs }))
25
+ if (!Number.isSafeInteger(value) || value <= 0) throw new Error(`${name} must be a positive safe integer`);
26
+ const sessions = new Set<GenerationSession<Metrics>>();
27
+ let closed = false;
28
+ let closing: Promise<void> | undefined;
29
+ let nextId = 0;
30
+ let reserved = 0;
31
+ const resources: SessionResources = {
32
+ timer: options.timer, idleTimeoutMs, maxQueuedEvents, maxQueuedTokens, maxTopLogprobs,
33
+ reserveOutput(tokens) {
34
+ if (tokens > maxCollectTokens - reserved) throw new Error("collection output capacity exceeded");
35
+ const buffer = new Uint32Array(tokens);
36
+ reserved += tokens;
37
+ let released = false;
38
+ return { buffer, release() { if (!released) { released = true; reserved -= tokens; } } };
39
+ },
40
+ };
41
+ return {
42
+ async open(request: Request, control: RunControl) {
43
+ if (closed) throw new Error("inference engine is closed");
44
+ let session: EngineSession<Metrics> | undefined;
45
+ session = new EngineSession(String(++nextId), (cancellation) => planner.plan(request, cancellation),
46
+ { ...control }, resources,
47
+ () => { if (session) sessions.delete(session); });
48
+ if (session.state !== "terminal") sessions.add(session);
49
+ return session;
50
+ },
51
+ close() {
52
+ closed = true;
53
+ return closing ??= Promise.all([...sessions].map((session) => session.cancel("engine_closed"))).then(() => {});
54
+ },
55
+ };
56
+ }