@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,734 @@
1
+ // Whisper FAST execution path — the optimized graphs behind the same
2
+ // decoding loop as the faithful port (packages/inference/src/models/whisper/model.ts /
3
+ // packages/inference/src/transcription/whisper/decode.ts).
4
+ // Gated token-exact against the faithful path on the fixture clips
5
+ // (`02d723a:tests/parity/whisper-fast.test.ts`); never bit-exact by design.
6
+ //
7
+ // What it changes (the reference-engine techniques):
8
+ // S1 fused SDPA (mx.fast.scaled_dot_product_attention) for encoder
9
+ // self-attention, decoder self-attention (causal prefill, cached
10
+ // steps) and cross-attention — whisper.cpp's flash_attn.
11
+ // S3 cross-attention K/V for ALL decoder layers projected by ONE matmul
12
+ // pair per window (concatenated weights), stored head-split
13
+ // [B, H, 1500, hd] once — whisper.cpp's kv_cross / vLLM's
14
+ // encoder-prefill-only cross KV.
15
+ // S2 the single-token decoder step — embed → 4 layers → ln → logits →
16
+ // logit filters — is ONE mx.compile'd, shapeless closure replayed
17
+ // natively (packages/mlx/src/compile.ts; the LLM compiled-decode pattern).
18
+ // Per-step state crosses as small int32 arrays (position, last two
19
+ // tokens, last timestamp, at-sample-begin) so the graph never
20
+ // changes; the growing self-KV enters as inputs (concat in-graph).
21
+ // GELU: the four separate ops instead of the compiled fused kernel the
22
+ // faithful path needs for bit-exactness (measured equal; fewer traces).
23
+ // S4 logit filters (suppress sets, blank, timestamp rules incl. the
24
+ // openai monotonic rule and the timestamp-probability rule) are
25
+ // device-side broadcast ops inside that closure — no 200 KB host
26
+ // mask upload per step.
27
+ // Beam rows attend to the single encoder output through broadcasting;
28
+ // cache reorders are `take` on axis 0 between steps (openai/whisper.cpp
29
+ // seq_cp equivalent).
30
+
31
+ import { MlxArray } from "@mlx-bun/mlx/array";
32
+ import { CompiledFunction } from "@mlx-bun/mlx/compile";
33
+ import { Dtype } from "@mlx-bun/mlx/ffi";
34
+ import * as ops from "@mlx-bun/mlx/ops";
35
+ import type { FastFilterConfig, WhisperDims, WhisperFastCache, WhisperFastPath as WhisperFastPathContract } from "../../contracts/mlx/whisper";
36
+
37
+ /** Numerical operations needed by the fused path; model loading stays outside. */
38
+ export interface WhisperFastModel {
39
+ readonly dims: WhisperDims;
40
+ readonly dtype: Dtype;
41
+ weight(name: string): MlxArray;
42
+ weightT(name: string): MlxArray;
43
+ layerNorm(input: MlxArray, prefix: string): MlxArray;
44
+ linear(input: MlxArray, prefix: string, bias?: boolean): MlxArray;
45
+ }
46
+
47
+ /** Cache layout of the fast path: head-split self K/V [B, H, T, hd] per
48
+ * layer; cross K/V [Bc, H, 1500, hd] per layer (Bc = 1 for one window). */
49
+ export class FastKvCache implements WhisperFastCache {
50
+ k: MlxArray[] = [];
51
+ v: MlxArray[] = [];
52
+ crossK: MlxArray[] = [];
53
+ crossV: MlxArray[] = [];
54
+ get offset(): number {
55
+ return this.k[0]?.shape[2] ?? 0;
56
+ }
57
+ rearrange(indices: number[]): void {
58
+ if (indices.every((x, i) => x === i)) return;
59
+ const idx = ops.fromInt32(indices, [indices.length]);
60
+ for (let l = 0; l < this.k.length; l++) {
61
+ const nk = ops.takeAxis(this.k[l]!, idx, 0);
62
+ const nv = ops.takeAxis(this.v[l]!, idx, 0);
63
+ this.k[l]!.dispose();
64
+ this.v[l]!.dispose();
65
+ this.k[l] = nk;
66
+ this.v[l] = nv;
67
+ }
68
+ idx.dispose();
69
+ }
70
+ dispose(): void {
71
+ for (const a of [...this.k, ...this.v, ...this.crossK, ...this.crossV]) a.dispose();
72
+ this.k = []; this.v = []; this.crossK = []; this.crossV = [];
73
+ }
74
+ }
75
+
76
+ const dispose = (old: MlxArray, next: MlxArray): MlxArray => {
77
+ old.dispose();
78
+ return next;
79
+ };
80
+
81
+ export class WhisperFastPath implements WhisperFastPathContract {
82
+ readonly model: WhisperFastModel;
83
+ readonly #H: number;
84
+ readonly #hd: number;
85
+ readonly #L: number;
86
+ /** Encoder per-layer fused [D, 3D] q/k/v weightᵀ and [3D] bias (k has none). */
87
+ readonly #encQkv = new Map<number, { wT: MlxArray; b: MlxArray }>();
88
+ #crossWkT: MlxArray | null = null;
89
+ #crossWvT: MlxArray | null = null;
90
+ #crossBv: MlxArray | null = null;
91
+ #encPos: MlxArray | null = null;
92
+ readonly #steps = new Map<string, CompiledFunction>();
93
+ #negInf: MlxArray | null = null;
94
+
95
+ constructor(model: WhisperFastModel) {
96
+ this.model = model;
97
+ this.#H = model.dims.nTextHead;
98
+ this.#hd = model.dims.nTextState / model.dims.nTextHead;
99
+ this.#L = model.dims.nTextLayer;
100
+ }
101
+
102
+ makeCache(): FastKvCache {
103
+ return new FastKvCache();
104
+ }
105
+
106
+ // --- helpers ------------------------------------------------------------------
107
+
108
+ #split(x: MlxArray, B: number, T: number, H: number, hd: number): MlxArray {
109
+ let h = ops.reshape(x, [B, T, H, hd]);
110
+ h = dispose(h, ops.transposeAxes(h, [0, 2, 1, 3]));
111
+ return h;
112
+ }
113
+
114
+ #merge(x: MlxArray, B: number, T: number, D: number): MlxArray {
115
+ let h = ops.transposeAxes(x, [0, 2, 1, 3]);
116
+ h = dispose(h, ops.reshape(h, [B, T, D]));
117
+ return h;
118
+ }
119
+
120
+ /** Attention with fused SDPA; k/v may carry batch 1 against B queries
121
+ * (broadcast materialized for the fused kernel only when needed). */
122
+ #attend(qh: MlxArray, kh: MlxArray, vh: MlxArray, causal: boolean): MlxArray {
123
+ const scale = this.#hd ** -0.5;
124
+ const B = qh.shape[0]!;
125
+ if (kh.shape[0] === B) return ops.sdpa(qh, kh, vh, scale, causal ? "causal" : "");
126
+ // beams over one encoder output: plain matmul attention broadcasts
127
+ // the batch for free; the fused kernel would need B copies.
128
+ const kT = ops.transposeAxes(kh, [0, 1, 3, 2]);
129
+ const s = ops.scalarLike(scale, qh);
130
+ let qk = ops.matmul(qh, kT);
131
+ kT.dispose();
132
+ qk = dispose(qk, ops.mul(qk, s));
133
+ s.dispose();
134
+ const w = ops.softmaxAxis(qk, -1, true);
135
+ qk.dispose();
136
+ const o = ops.matmul(w, vh);
137
+ w.dispose();
138
+ return o;
139
+ }
140
+
141
+ // --- encoder ------------------------------------------------------------------
142
+
143
+ /** One gemm for q/k/v instead of three (whisper.cpp fuses these too). */
144
+ #encoderQkv(layer: number): { wT: MlxArray; b: MlxArray } {
145
+ let e = this.#encQkv.get(layer);
146
+ if (e) return e;
147
+ const m = this.model;
148
+ const p = `encoder.blocks.${layer}.attn`;
149
+ const wT = ops.concatAxis([m.weightT(`${p}.query.weight`), m.weightT(`${p}.key.weight`), m.weightT(`${p}.value.weight`)], 1);
150
+ const zeroB = ops.zeros([m.dims.nAudioState], m.dtype);
151
+ const b = ops.concatAxis([m.weight(`${p}.query.bias`), zeroB, m.weight(`${p}.value.bias`)], 0);
152
+ zeroB.dispose();
153
+ ops.evalAll([wT, b]);
154
+ e = { wT, b };
155
+ this.#encQkv.set(layer, e);
156
+ return e;
157
+ }
158
+
159
+ #sinusoids(): MlxArray {
160
+ if (this.#encPos) return this.#encPos;
161
+ const { nAudioCtx: length, nAudioState: channels } = this.model.dims;
162
+ const half = channels / 2;
163
+ const inc = Math.log(10000) / (half - 1);
164
+ const ar = ops.arange(0, half, 1, Dtype.int32);
165
+ const negInc = MlxArray.fromFloat32(new Float32Array([-inc]), []);
166
+ let inv = ops.mul(ar, negInc);
167
+ ar.dispose(); negInc.dispose();
168
+ inv = dispose(inv, ops.exp(inv));
169
+ inv = dispose(inv, ops.reshape(inv, [1, half]));
170
+ let pos = ops.arange(0, length, 1, Dtype.int32);
171
+ pos = dispose(pos, ops.reshape(pos, [length, 1]));
172
+ const scaled = ops.mul(pos, inv);
173
+ pos.dispose(); inv.dispose();
174
+ const s = ops.sin(scaled);
175
+ const c = ops.cos(scaled);
176
+ scaled.dispose();
177
+ let pe = ops.concatAxis([s, c], 1);
178
+ s.dispose(); c.dispose();
179
+ pe = dispose(pe, pe.astype(this.model.dtype));
180
+ this.#encPos = pe;
181
+ return pe;
182
+ }
183
+
184
+ /** AudioEncoder with fused SDPA: mel [B, 3000, n_mels] → [B, 1500, D].
185
+ * `audioCtx` (Lab, whisper.cpp `-ac`): encode only the first
186
+ * 2·audioCtx mel frames → [B, audioCtx, D]; the positional table is
187
+ * truncated to match. Cheaper for short clips, NOT the trained context. */
188
+ encode(mel: MlxArray, audioCtx: number | null = null): MlxArray {
189
+ const m = this.model;
190
+ const d = m.dims;
191
+ const H = d.nAudioHead;
192
+ const hd = d.nAudioState / H;
193
+ let input = mel;
194
+ let ownInput = false;
195
+ if (audioCtx !== null && audioCtx < d.nAudioCtx) {
196
+ input = mel.slice([0, 0, 0], [mel.shape[0]!, 2 * audioCtx, mel.shape[2]!]);
197
+ ownInput = true;
198
+ }
199
+ let x = ops.conv1d(input, m.weight("encoder.conv1.weight"), 1, 1);
200
+ if (ownInput) input.dispose();
201
+ x = dispose(x, ops.add(x, m.weight("encoder.conv1.bias")));
202
+ x = dispose(x, ops.geluPrecise(x));
203
+ x = dispose(x, ops.conv1d(x, m.weight("encoder.conv2.weight"), 2, 1));
204
+ x = dispose(x, ops.add(x, m.weight("encoder.conv2.bias")));
205
+ x = dispose(x, ops.geluPrecise(x));
206
+ const pe = this.#sinusoids();
207
+ if (x.shape[1] !== d.nAudioCtx) {
208
+ const peT = pe.slice([0, 0], [x.shape[1]!, d.nAudioState]);
209
+ x = dispose(x, ops.add(x, peT));
210
+ peT.dispose();
211
+ } else x = dispose(x, ops.add(x, pe));
212
+ const [B, T, D] = x.shape as [number, number, number];
213
+ // Measured dead ends (M1 Max, 2026-09-15, interleaved A/B, median of 4):
214
+ // padding MLP rows to 1536 for gemm tile alignment (isolated gemm +25 %,
215
+ // whole encoder 0 %) and the unfused-vs-compiled GELU (0 %). The encoder
216
+ // is bound by its fp16 gemms at ~7–8 TFLOPS; ~290 ms per window here
217
+ // vs mlx-whisper's 298.
218
+ for (let i = 0; i < d.nAudioLayer; i++) {
219
+ const p = `encoder.blocks.${i}`;
220
+ const ln = m.layerNorm(x, `${p}.attn_ln`);
221
+ const { wT, b } = this.#encoderQkv(i);
222
+ const qkv = ops.addmm(b, ln, wT);
223
+ ln.dispose();
224
+ const q = qkv.slice([0, 0, 0], [B, T, D]);
225
+ const k = qkv.slice([0, 0, D], [B, T, 2 * D]);
226
+ const v = qkv.slice([0, 0, 2 * D], [B, T, 3 * D]);
227
+ qkv.dispose();
228
+ const qh = this.#split(q, B, T, H, hd); q.dispose();
229
+ const kh = this.#split(k, B, T, H, hd); k.dispose();
230
+ const vh = this.#split(v, B, T, H, hd); v.dispose();
231
+ let o = ops.sdpa(qh, kh, vh, hd ** -0.5, "");
232
+ qh.dispose(); kh.dispose(); vh.dispose();
233
+ o = dispose(o, this.#merge(o, B, T, D));
234
+ o = dispose(o, m.linear(o, `${p}.attn.out`));
235
+ x = dispose(x, ops.add(x, o));
236
+ o.dispose();
237
+ let h = m.layerNorm(x, `${p}.mlp_ln`);
238
+ h = dispose(h, m.linear(h, `${p}.mlp1`));
239
+ h = dispose(h, ops.geluPrecise(h));
240
+ h = dispose(h, m.linear(h, `${p}.mlp2`));
241
+ x = dispose(x, ops.add(x, h));
242
+ h.dispose();
243
+ }
244
+ x = dispose(x, m.layerNorm(x, "encoder.ln_post"));
245
+ return x;
246
+ }
247
+
248
+ // --- cross K/V (once per window) ---------------------------------------------
249
+
250
+ #crossWeights(): { wkT: MlxArray; wvT: MlxArray; bv: MlxArray } {
251
+ if (!this.#crossWkT) {
252
+ const m = this.model;
253
+ const ks: MlxArray[] = [];
254
+ const vs: MlxArray[] = [];
255
+ const bs: MlxArray[] = [];
256
+ for (let l = 0; l < this.#L; l++) {
257
+ ks.push(m.weightT(`decoder.blocks.${l}.cross_attn.key.weight`));
258
+ vs.push(m.weightT(`decoder.blocks.${l}.cross_attn.value.weight`));
259
+ bs.push(m.weight(`decoder.blocks.${l}.cross_attn.value.bias`));
260
+ }
261
+ this.#crossWkT = ops.concatAxis(ks, 1); // [D, L·D]
262
+ this.#crossWvT = ops.concatAxis(vs, 1);
263
+ this.#crossBv = ops.concatAxis(bs, 0); // [L·D]
264
+ ops.evalAll([this.#crossWkT, this.#crossWvT, this.#crossBv]);
265
+ }
266
+ return { wkT: this.#crossWkT!, wvT: this.#crossWvT!, bv: this.#crossBv! };
267
+ }
268
+
269
+ /** Project the encoder output into every decoder layer's cross K/V with
270
+ * one matmul pair; fills `cache.crossK/V` head-split. */
271
+ crossKv(features: MlxArray, cache: FastKvCache): void {
272
+ const { wkT, wvT, bv } = this.#crossWeights();
273
+ const [Bc, Tc] = features.shape as [number, number, number];
274
+ const D = this.model.dims.nTextState;
275
+ const kAll = ops.matmul(features, wkT);
276
+ const vAll = ops.addmm(bv, features, wvT);
277
+ for (let l = 0; l < this.#L; l++) {
278
+ const k = kAll.slice([0, 0, l * D], [Bc, Tc, (l + 1) * D]);
279
+ const v = vAll.slice([0, 0, l * D], [Bc, Tc, (l + 1) * D]);
280
+ const kh = this.#split(k, Bc, Tc, this.#H, this.#hd);
281
+ const vh = this.#split(v, Bc, Tc, this.#H, this.#hd);
282
+ k.dispose(); v.dispose();
283
+ // materialize once so every step reads contiguous tensors
284
+ cache.crossK.push(dispose(kh, ops.contiguous(kh)));
285
+ cache.crossV.push(dispose(vh, ops.contiguous(vh)));
286
+ }
287
+ kAll.dispose();
288
+ vAll.dispose();
289
+ ops.evalAll([...cache.crossK, ...cache.crossV]);
290
+ }
291
+
292
+ // --- decoder layers ---------------------------------------------------------------
293
+
294
+ /** One decoder layer. `selfK/selfV` are the cache prefix (or null);
295
+ * returns the new full K/V for the caller to store. */
296
+ #layer(
297
+ x: MlxArray, l: number, selfK: MlxArray | null, selfV: MlxArray | null,
298
+ crossK: MlxArray, crossV: MlxArray, causal: boolean,
299
+ ): { x: MlxArray; k: MlxArray; v: MlxArray } {
300
+ const m = this.model;
301
+ const p = `decoder.blocks.${l}`;
302
+ const [B, T, D] = x.shape as [number, number, number];
303
+ const ln = m.layerNorm(x, `${p}.attn_ln`);
304
+ const q = m.linear(ln, `${p}.attn.query`);
305
+ const k = m.linear(ln, `${p}.attn.key`, false);
306
+ const v = m.linear(ln, `${p}.attn.value`);
307
+ ln.dispose();
308
+ const qh = this.#split(q, B, T, this.#H, this.#hd); q.dispose();
309
+ let kh = this.#split(k, B, T, this.#H, this.#hd); k.dispose();
310
+ let vh = this.#split(v, B, T, this.#H, this.#hd); v.dispose();
311
+ if (selfK && selfV) {
312
+ kh = dispose(kh, ops.concatAxis([selfK, kh], 2));
313
+ vh = dispose(vh, ops.concatAxis([selfV, vh], 2));
314
+ }
315
+ let o = this.#attend(qh, kh, vh, causal);
316
+ qh.dispose();
317
+ o = dispose(o, this.#merge(o, B, T, D));
318
+ o = dispose(o, m.linear(o, `${p}.attn.out`));
319
+ let h = ops.add(x, o);
320
+ o.dispose();
321
+ const cln = m.layerNorm(h, `${p}.cross_attn_ln`);
322
+ const cq = m.linear(cln, `${p}.cross_attn.query`);
323
+ cln.dispose();
324
+ const cqh = this.#split(cq, B, T, this.#H, this.#hd); cq.dispose();
325
+ let co = this.#attend(cqh, crossK, crossV, false);
326
+ cqh.dispose();
327
+ co = dispose(co, this.#merge(co, B, T, D));
328
+ co = dispose(co, m.linear(co, `${p}.cross_attn.out`));
329
+ h = dispose(h, ops.add(h, co));
330
+ co.dispose();
331
+ let f = m.layerNorm(h, `${p}.mlp_ln`);
332
+ f = dispose(f, m.linear(f, `${p}.mlp1`));
333
+ f = dispose(f, ops.geluPrecise(f));
334
+ f = dispose(f, m.linear(f, `${p}.mlp2`));
335
+ h = dispose(h, ops.add(h, f));
336
+ f.dispose();
337
+ return { x: h, k: kh, v: vh };
338
+ }
339
+
340
+ #embed(tokens: MlxArray, offsetIdx: MlxArray): MlxArray {
341
+ const m = this.model;
342
+ const T = tokens.shape[1]!;
343
+ let x = ops.takeAxis(m.weight("decoder.token_embedding.weight"), tokens, 0);
344
+ // positional rows [offset, offset+T) gathered by an index array so the
345
+ // graph stays position-agnostic
346
+ let pos: MlxArray;
347
+ if (T === 1) pos = ops.takeAxis(m.weight("decoder.positional_embedding"), offsetIdx, 0); // [1, D]
348
+ else {
349
+ const ar = ops.arange(0, T, 1, Dtype.int32);
350
+ const idx = ops.add(ar, offsetIdx);
351
+ ar.dispose();
352
+ pos = ops.takeAxis(m.weight("decoder.positional_embedding"), idx, 0); // [T, D]
353
+ idx.dispose();
354
+ }
355
+ x = dispose(x, ops.add(x, pos));
356
+ pos.dispose();
357
+ return x;
358
+ }
359
+
360
+ #logits(x: MlxArray): MlxArray {
361
+ const m = this.model;
362
+ let h = m.layerNorm(x, "decoder.ln");
363
+ h = dispose(h, ops.matmul(h, m.weightT("decoder.token_embedding.weight")));
364
+ return h;
365
+ }
366
+
367
+ /** Eager multi-token prefill (initial tokens): fills the self-KV cache and
368
+ * returns logits [B, T, V] in the weight dtype. */
369
+ prefill(tokens: MlxArray, cache: FastKvCache): MlxArray {
370
+ const offsetIdx = ops.fromInt32([cache.offset], [1]);
371
+ let x = this.#embed(tokens, offsetIdx);
372
+ offsetIdx.dispose();
373
+ for (let l = 0; l < this.#L; l++) {
374
+ const r = this.#layer(x, l, cache.k[l] ?? null, cache.v[l] ?? null, cache.crossK[l]!, cache.crossV[l]!, true);
375
+ x.dispose();
376
+ x = r.x;
377
+ cache.k[l]?.dispose();
378
+ cache.v[l]?.dispose();
379
+ cache.k[l] = r.k;
380
+ cache.v[l] = r.v;
381
+ }
382
+ const logits = this.#logits(x);
383
+ x.dispose();
384
+ return logits;
385
+ }
386
+
387
+ // --- compiled single-token step -----------------------------------------------------
388
+
389
+ #negInfArr(): MlxArray {
390
+ return this.#negInf ??= MlxArray.fromFloat32(new Float32Array([-Infinity]), []);
391
+ }
392
+
393
+ /** Device-side logit filters (see header). `state` rows: lastTok,
394
+ * penultTok (-1 = none), lastTs (-1 = none) as int32 [B]; atBegin int32 [1]. */
395
+ #filters(logits: MlxArray, cfg: FastFilterConfig, state: { lastTok: MlxArray; penultTok: MlxArray; lastTs: MlxArray; atBegin: MlxArray }): MlxArray {
396
+ const V = cfg.nVocab;
397
+ const B = logits.shape[0]!;
398
+ const negInf = this.#negInfArr();
399
+ const zero = ops.scalarLike(0, logits);
400
+ const maskConst = new Float32Array(V);
401
+ for (const id of cfg.suppressIds) maskConst[id] = -Infinity;
402
+ if (cfg.useTimestampRules) maskConst[cfg.noTimestamps] = -Infinity;
403
+ const constMask = MlxArray.fromFloat32(maskConst, [1, V]);
404
+ let base = ops.add(logits, constMask);
405
+ constMask.dispose();
406
+ const atBegin = ops.greater(state.atBegin, ops.fromInt32([0], [1])); // [1] bool
407
+ if (cfg.blankIds) {
408
+ const blank = new Float32Array(V);
409
+ for (const id of cfg.blankIds) blank[id] = -Infinity;
410
+ const blankMask = MlxArray.fromFloat32(blank, [1, V]);
411
+ const bm = ops.where(atBegin, blankMask, zero);
412
+ blankMask.dispose();
413
+ base = dispose(base, ops.add(base, bm));
414
+ bm.dispose();
415
+ }
416
+ if (!cfg.useTimestampRules) {
417
+ atBegin.dispose(); zero.dispose();
418
+ return base;
419
+ }
420
+ const idx = ops.arange(0, V, 1, Dtype.int32);
421
+ const idx2 = ops.reshape(idx, [1, V]);
422
+ idx.dispose();
423
+ const c = (n: number) => ops.fromInt32([n], [1]);
424
+ const tsBegin = c(cfg.timestampBegin);
425
+ const eot = c(cfg.eot);
426
+ const minusOne = c(-1);
427
+ const one = c(1);
428
+ const col = (a: MlxArray) => ops.reshape(a, [B, 1]);
429
+ const lastTok = col(state.lastTok);
430
+ const penultTok = col(state.penultTok);
431
+ const lastTs = col(state.lastTs);
432
+ const lastWas = ops.greaterEqual(lastTok, tsBegin); // [B,1]
433
+ const penultNone = ops.less(penultTok, c(0));
434
+ const penultGe = ops.greaterEqual(penultTok, tsBegin);
435
+ const penultWas = ops.logicalOr(penultNone, penultGe);
436
+ const notPenult = ops.logicalNot(penultWas);
437
+ const idxGeTs = ops.greaterEqual(idx2, tsBegin); // [1,V]
438
+ const idxLtEot = ops.less(idx2, eot);
439
+ const idxLtTs = ops.less(idx2, tsBegin);
440
+ // m1: pairs rule
441
+ const a1 = ops.logicalAnd(lastWas, penultWas);
442
+ const a2 = ops.logicalAnd(lastWas, notPenult);
443
+ const m1a = ops.logicalAnd(a1, idxGeTs);
444
+ const m1b = ops.logicalAnd(a2, idxLtEot);
445
+ let m = ops.logicalOr(m1a, m1b);
446
+ // m2: monotonic timestamps
447
+ const hasTs = ops.greaterEqual(lastTs, c(0));
448
+ const lastTsPlus = ops.add(lastTs, one);
449
+ const tsLast = ops.where(a2, lastTs, lastTsPlus);
450
+ const idxLtLast = ops.less(idx2, tsLast);
451
+ const m2a = ops.logicalAnd(hasTs, idxGeTs);
452
+ const m2 = ops.logicalAnd(m2a, idxLtLast);
453
+ m = dispose(m, ops.logicalOr(m, m2));
454
+ // m3: at sample begin — timestamps only, capped by max_initial_timestamp
455
+ let m3 = idxLtTs;
456
+ let capMask: MlxArray | null = null;
457
+ if (cfg.maxInitialTimestampIndex !== null) {
458
+ const lastAllowed = c(cfg.timestampBegin + cfg.maxInitialTimestampIndex);
459
+ capMask = ops.greater(idx2, lastAllowed);
460
+ lastAllowed.dispose();
461
+ m3 = ops.logicalOr(idxLtTs, capMask);
462
+ }
463
+ const m3b = ops.logicalAnd(atBegin, m3);
464
+ m = dispose(m, ops.logicalOr(m, m3b));
465
+ const ruleMask = ops.where(m, negInf, zero);
466
+ // probability rule on the PRE-rule logits (the oracle's `logits`)
467
+ // (no Slice inside a shapeless compile — MLX cannot infer its output
468
+ // shape; mask the complementary halves to -inf instead)
469
+ const lse = ops.logsumexpAxis(base, -1, true);
470
+ const logprobs = ops.sub(base, lse);
471
+ const tsPart = ops.where(idxGeTs, logprobs, negInf);
472
+ const tsLp = ops.logsumexpAxis(tsPart, -1, true);
473
+ const textPart = ops.where(idxLtTs, logprobs, negInf);
474
+ const maxText = ops.maxAxis(textPart, -1, true);
475
+ const cond = ops.greater(tsLp, maxText); // [B,1]
476
+ const condMask = ops.logicalAnd(cond, idxLtTs);
477
+ const probMask = ops.where(condMask, negInf, zero);
478
+ let out = ops.add(base, ruleMask);
479
+ out = dispose(out, ops.add(out, probMask));
480
+ for (const a of [base, atBegin, zero, idx2, tsBegin, eot, minusOne, one, lastTok, penultTok, lastTs, lastWas, penultNone,
481
+ penultGe, penultWas, notPenult, idxGeTs, idxLtEot, idxLtTs, a1, a2, m1a, m1b, m, hasTs, lastTsPlus, tsLast, idxLtLast,
482
+ m2a, m2, m3b, ruleMask, lse, logprobs, tsPart, tsLp, textPart, maxText, cond, condMask, probMask]) a.dispose();
483
+ if (capMask) { capMask.dispose(); m3.dispose(); }
484
+ return out;
485
+ }
486
+
487
+ /** Greedy variant: the compiled graph also picks argmax(filtered) and its
488
+ * log-prob, so the next step can consume the token ARRAY without a host
489
+ * round trip (S5 pipelining: the host reads the previous step's token
490
+ * while the GPU runs the current one — the oracle's async_eval lag). */
491
+ #greedyStep(B: number, Bc: number, cfg: FastFilterConfig): CompiledFunction {
492
+ const key = `greedy:${B}:${Bc}:${cfg.suppressIds.join(",")}:${cfg.blankIds?.join(",") ?? "-"}:${cfg.useTimestampRules}:${cfg.maxInitialTimestampIndex}`;
493
+ let fn = this.#steps.get(key);
494
+ if (fn) return fn;
495
+ const L = this.#L;
496
+ fn = new CompiledFunction((inputs) => {
497
+ // inputs: [tok [B] int32, offsetIdx [1], lastTok [B], penultTok [B], lastTs [B], K.., V.., CK.., CV..]
498
+ const [tokFlat, offsetIdx, lastTok, penultTok, lastTs] = inputs as [MlxArray, MlxArray, MlxArray, MlxArray, MlxArray];
499
+ const K = inputs.slice(5, 5 + L);
500
+ const Vv = inputs.slice(5 + L, 5 + 2 * L);
501
+ const CK = inputs.slice(5 + 2 * L, 5 + 3 * L);
502
+ const CV = inputs.slice(5 + 3 * L, 5 + 4 * L);
503
+ const tok = ops.reshape(tokFlat, [B, 1]);
504
+ let x = this.#embed(tok, offsetIdx);
505
+ tok.dispose();
506
+ const newK: MlxArray[] = [];
507
+ const newV: MlxArray[] = [];
508
+ for (let l = 0; l < L; l++) {
509
+ const r = this.#layer(x, l, K[l]!, Vv[l]!, CK[l]!, CV[l]!, false);
510
+ x.dispose();
511
+ x = r.x;
512
+ newK.push(r.k);
513
+ newV.push(r.v);
514
+ }
515
+ const logits16 = this.#logits(x);
516
+ x.dispose();
517
+ const V = cfg.nVocab;
518
+ let last = ops.reshape(logits16, [B, V]);
519
+ logits16.dispose();
520
+ last = dispose(last, last.astype(Dtype.float32));
521
+ const atBegin = ops.fromInt32([0], [1]); // steps ≥ 1 are never at sample_begin
522
+ const filtered = this.#filters(last, cfg, { lastTok, penultTok, lastTs, atBegin });
523
+ atBegin.dispose();
524
+ let next = ops.argmaxAxis(filtered, -1);
525
+ next = dispose(next, next.astype(Dtype.int32));
526
+ const lse = ops.logsumexpAxis(filtered, -1, true);
527
+ const logprobs = ops.sub(filtered, lse);
528
+ lse.dispose();
529
+ const idx = ops.reshape(next, [B, 1]);
530
+ let lp = ops.takeAlongAxis(logprobs, idx, 1);
531
+ idx.dispose();
532
+ logprobs.dispose();
533
+ lp = dispose(lp, ops.reshape(lp, [B]));
534
+ // timestamp state for the NEXT step: last := next, penult := last,
535
+ // lastTs := next if it is a timestamp else unchanged
536
+ const tsBegin = ops.fromInt32([cfg.timestampBegin], [1]);
537
+ const isTs = ops.greaterEqual(next, tsBegin);
538
+ const newLastTs = ops.where(isTs, next, lastTs);
539
+ tsBegin.dispose();
540
+ isTs.dispose();
541
+ filtered.dispose();
542
+ return [last, next, lp, newLastTs, ...newK, ...newV];
543
+ }, true);
544
+ this.#steps.set(key, fn);
545
+ return fn;
546
+ }
547
+
548
+ /** Pipelined greedy step: consumes/produces token + timestamp-state ARRAYS.
549
+ * Returns owned arrays; the caller reads `next` lazily (one-step lag). */
550
+ greedyStep(
551
+ st: { tok: MlxArray; lastTok: MlxArray; penultTok: MlxArray; lastTs: MlxArray },
552
+ cache: FastKvCache, cfg: FastFilterConfig,
553
+ ): { pre: MlxArray; next: MlxArray; lp: MlxArray; lastTs: MlxArray } {
554
+ const B = st.tok.shape[0]!;
555
+ const Bc = cache.crossK[0]!.shape[0]!;
556
+ const fn = this.#greedyStep(B, Bc, cfg);
557
+ const offsetIdx = ops.fromInt32([cache.offset], [1]);
558
+ const outs = fn.apply([st.tok, offsetIdx, st.lastTok, st.penultTok, st.lastTs, ...cache.k, ...cache.v, ...cache.crossK, ...cache.crossV]);
559
+ offsetIdx.dispose();
560
+ const L = this.#L;
561
+ for (let l = 0; l < L; l++) {
562
+ cache.k[l]!.dispose();
563
+ cache.v[l]!.dispose();
564
+ cache.k[l] = outs[4 + l]!;
565
+ cache.v[l] = outs[4 + L + l]!;
566
+ }
567
+ return { pre: outs[0]!, next: outs[1]!, lp: outs[2]!, lastTs: outs[3]! };
568
+ }
569
+
570
+ /** Beam variant: top-(k) indices/values of the filtered log-probs come
571
+ * out of the graph (one host readback per step, no partition ops outside). */
572
+ #beamStep(B: number, Bc: number, k: number, cfg: FastFilterConfig): CompiledFunction {
573
+ const key = `beam${k}:${B}:${Bc}:${cfg.suppressIds.join(",")}:${cfg.blankIds?.join(",") ?? "-"}:${cfg.useTimestampRules}:${cfg.maxInitialTimestampIndex}`;
574
+ let fn = this.#steps.get(key);
575
+ if (fn) return fn;
576
+ const L = this.#L;
577
+ fn = new CompiledFunction((inputs) => {
578
+ const [tokFlat, offsetIdx, lastTok, penultTok, lastTs] = inputs as [MlxArray, MlxArray, MlxArray, MlxArray, MlxArray];
579
+ const K = inputs.slice(5, 5 + L);
580
+ const Vv = inputs.slice(5 + L, 5 + 2 * L);
581
+ const CK = inputs.slice(5 + 2 * L, 5 + 3 * L);
582
+ const CV = inputs.slice(5 + 3 * L, 5 + 4 * L);
583
+ const tok = ops.reshape(tokFlat, [B, 1]);
584
+ let x = this.#embed(tok, offsetIdx);
585
+ tok.dispose();
586
+ const newK: MlxArray[] = [];
587
+ const newV: MlxArray[] = [];
588
+ for (let l = 0; l < L; l++) {
589
+ const r = this.#layer(x, l, K[l]!, Vv[l]!, CK[l]!, CV[l]!, false);
590
+ x.dispose();
591
+ x = r.x;
592
+ newK.push(r.k);
593
+ newV.push(r.v);
594
+ }
595
+ const logits16 = this.#logits(x);
596
+ x.dispose();
597
+ const V = cfg.nVocab;
598
+ let last = ops.reshape(logits16, [B, V]);
599
+ logits16.dispose();
600
+ last = dispose(last, last.astype(Dtype.float32));
601
+ const atBegin = ops.fromInt32([0], [1]);
602
+ const filtered = this.#filters(last, cfg, { lastTok, penultTok, lastTs, atBegin });
603
+ atBegin.dispose();
604
+ const lse = ops.logsumexpAxis(filtered, -1, true);
605
+ const logprobs = ops.sub(filtered, lse);
606
+ lse.dispose();
607
+ filtered.dispose();
608
+ // top-k: sort descending is overkill; argpartition on the negation
609
+ // then gather. (Slice with constant bounds is fine here: its output
610
+ // shape does not depend on a varying dimension… but shapeless
611
+ // compile still refuses Slice, so use take with a constant index.)
612
+ const neg = ops.neg(logprobs);
613
+ const part = ops.argpartitionAxis(neg, k - 1, -1);
614
+ neg.dispose();
615
+ const firstK = ops.arange(0, k, 1, Dtype.int32);
616
+ let idx = ops.takeAxis(part, firstK, 1); // [B, k]
617
+ part.dispose();
618
+ firstK.dispose();
619
+ idx = dispose(idx, idx.astype(Dtype.int32));
620
+ const vals = ops.takeAlongAxis(logprobs, idx, 1);
621
+ logprobs.dispose();
622
+ return [last, idx, vals, ...newK, ...newV];
623
+ }, true);
624
+ this.#steps.set(key, fn);
625
+ return fn;
626
+ }
627
+
628
+ /** One compiled beam step: returns the pre-filter logits and the top-k
629
+ * (indices, log-probs) per row; updates the cache in place. */
630
+ beamStep(
631
+ tokens: number[], cache: FastKvCache, cfg: FastFilterConfig, k: number,
632
+ state: { lastTok: number[]; penultTok: number[]; lastTs: number[] },
633
+ ): { pre: MlxArray; idx: MlxArray; vals: MlxArray } {
634
+ const B = tokens.length;
635
+ const Bc = cache.crossK[0]!.shape[0]!;
636
+ const fn = this.#beamStep(B, Bc, k, cfg);
637
+ const tok = ops.fromInt32(tokens, [B]);
638
+ const offsetIdx = ops.fromInt32([cache.offset], [1]);
639
+ const lastTok = ops.fromInt32(state.lastTok, [B]);
640
+ const penultTok = ops.fromInt32(state.penultTok, [B]);
641
+ const lastTs = ops.fromInt32(state.lastTs, [B]);
642
+ const outs = fn.apply([tok, offsetIdx, lastTok, penultTok, lastTs, ...cache.k, ...cache.v, ...cache.crossK, ...cache.crossV]);
643
+ for (const a of [tok, offsetIdx, lastTok, penultTok, lastTs]) a.dispose();
644
+ const L = this.#L;
645
+ for (let l = 0; l < L; l++) {
646
+ cache.k[l]!.dispose();
647
+ cache.v[l]!.dispose();
648
+ cache.k[l] = outs[3 + l]!;
649
+ cache.v[l] = outs[3 + L + l]!;
650
+ }
651
+ // Compiled outputs may be strided views; host readback walks memory
652
+ // linearly, so materialize the small top-k arrays.
653
+ const idx = ops.contiguous(outs[1]!);
654
+ const vals = ops.contiguous(outs[2]!);
655
+ outs[1]!.dispose();
656
+ outs[2]!.dispose();
657
+ return { pre: outs[0]!, idx, vals };
658
+ }
659
+
660
+ /** Compiled step for a fixed (B, Bc, filter config) signature. Inputs:
661
+ * [tok [B,1], offsetIdx [1], lastTok [B], penultTok [B], lastTs [B],
662
+ * atBegin [1], K_0..K_{L-1}, V_0.., CK_0.., CV_0..]. Outputs:
663
+ * [preLogits [B,V] f32, filtered [B,V] f32, newK_0.., newV_0..]. */
664
+ #step(B: number, Bc: number, cfg: FastFilterConfig): CompiledFunction {
665
+ const key = `${B}:${Bc}:${cfg.suppressIds.join(",")}:${cfg.blankIds?.join(",") ?? "-"}:${cfg.useTimestampRules}:${cfg.maxInitialTimestampIndex}`;
666
+ let fn = this.#steps.get(key);
667
+ if (fn) return fn;
668
+ const L = this.#L;
669
+ fn = new CompiledFunction((inputs) => {
670
+ const [tok, offsetIdx, lastTok, penultTok, lastTs, atBegin] = inputs as [MlxArray, MlxArray, MlxArray, MlxArray, MlxArray, MlxArray];
671
+ const K = inputs.slice(6, 6 + L);
672
+ const Vv = inputs.slice(6 + L, 6 + 2 * L);
673
+ const CK = inputs.slice(6 + 2 * L, 6 + 3 * L);
674
+ const CV = inputs.slice(6 + 3 * L, 6 + 4 * L);
675
+ let x = this.#embed(tok, offsetIdx);
676
+ const newK: MlxArray[] = [];
677
+ const newV: MlxArray[] = [];
678
+ for (let l = 0; l < L; l++) {
679
+ const r = this.#layer(x, l, K[l]!, Vv[l]!, CK[l]!, CV[l]!, false);
680
+ x.dispose();
681
+ x = r.x;
682
+ newK.push(r.k);
683
+ newV.push(r.v);
684
+ }
685
+ const logits16 = this.#logits(x);
686
+ x.dispose();
687
+ const V = cfg.nVocab;
688
+ let last = ops.reshape(logits16, [B, V]);
689
+ logits16.dispose();
690
+ last = dispose(last, last.astype(Dtype.float32));
691
+ const filtered = this.#filters(last, cfg, { lastTok, penultTok, lastTs, atBegin });
692
+ return [last, filtered, ...newK, ...newV];
693
+ }, true);
694
+ this.#steps.set(key, fn);
695
+ return fn;
696
+ }
697
+
698
+ /** Run one compiled decode step; updates `cache` in place. Returns the
699
+ * pre-filter and filtered last-position logits [B, V] f32 (owned). */
700
+ step(
701
+ tokens: number[], cache: FastKvCache, cfg: FastFilterConfig,
702
+ state: { lastTok: number[]; penultTok: number[]; lastTs: number[]; atBegin: boolean },
703
+ ): { pre: MlxArray; filtered: MlxArray } {
704
+ const B = tokens.length;
705
+ const Bc = cache.crossK[0]!.shape[0]!;
706
+ const fn = this.#step(B, Bc, cfg);
707
+ const tok = ops.fromInt32(tokens, [B, 1]);
708
+ const offsetIdx = ops.fromInt32([cache.offset], [1]);
709
+ const lastTok = ops.fromInt32(state.lastTok, [B]);
710
+ const penultTok = ops.fromInt32(state.penultTok, [B]);
711
+ const lastTs = ops.fromInt32(state.lastTs, [B]);
712
+ const atBegin = ops.fromInt32([state.atBegin ? 1 : 0], [1]);
713
+ const outs = fn.apply([tok, offsetIdx, lastTok, penultTok, lastTs, atBegin, ...cache.k, ...cache.v, ...cache.crossK, ...cache.crossV]);
714
+ for (const a of [tok, offsetIdx, lastTok, penultTok, lastTs, atBegin]) a.dispose();
715
+ const L = this.#L;
716
+ for (let l = 0; l < L; l++) {
717
+ cache.k[l]!.dispose();
718
+ cache.v[l]!.dispose();
719
+ cache.k[l] = outs[2 + l]!;
720
+ cache.v[l] = outs[2 + L + l]!;
721
+ }
722
+ return { pre: outs[0]!, filtered: outs[1]! };
723
+ }
724
+
725
+ dispose(): void {
726
+ for (const e of this.#encQkv.values()) { e.wT.dispose(); e.b.dispose(); }
727
+ this.#encQkv.clear();
728
+ this.#crossWkT?.dispose(); this.#crossWvT?.dispose(); this.#crossBv?.dispose();
729
+ this.#encPos?.dispose();
730
+ this.#negInf?.dispose();
731
+ for (const f of this.#steps.values()) f.dispose();
732
+ this.#steps.clear();
733
+ }
734
+ }