@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
package/README.md CHANGED
@@ -1,3 +1,1476 @@
1
- # Temporary Holding Version
1
+ # @mlx-bun/inference
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Composable inference components built on [`@mlx-bun/mlx`](../mlx/README.md).
4
+ Callers choose artifacts and graphs, own state, and invoke inference directly.
5
+ The package includes the kernels, the layers that bind them, the entire graphs
6
+ selected by the loader for each composition, the cache legos, input processing,
7
+ sampling, generation, embeddings, transcription, and optional execution.
8
+
9
+ ## Contracts and composition
10
+
11
+ The root API composes the lower layers for loading and generation. Component
12
+ subpaths remain available for direct use and custom graphs. See the
13
+ [architecture](../../ARCHITECTURE.md) for dependency direction and ownership.
14
+
15
+ `contracts/portable` contains platform-free inference interfaces and can be consumed
16
+ without Bun or MLX types. `contracts/mlx` describes tensor and state interactions
17
+ using MLX types. `contracts` exports both. Use the portable entry when sharing
18
+ inference output or scheduling types with application and browser code.
19
+ Application protocols and job contracts migrate with their owning apps.
20
+
21
+ Paged attention accepts numerical storage; adapter mounting accepts named LoRA
22
+ targets; prompt preparation accepts encoder interfaces. Callers can supply their
23
+ own implementations without subclassing a concrete model or cache.
24
+
25
+ Each graph declares its construction facts (`GraphDeclaration`, portable; a model
26
+ exposes it as `graphCapabilities`): its method, media
27
+ input, compiled decode, sparse attention, hidden-layer taps for
28
+ drafts, a draft head its checkpoint carries (`nativeDraft`), whether long prompts prefill in workspace-bounded
29
+ chunks, and what its verifier qualifies (adapters, logprobs, external tokens,
30
+ grammar proposals). None names a cache format: the composition's caches serve
31
+ every method, and the planner never reads how they store keys and values.
32
+ Pooled embedding support is the presence of `embedPooled` itself; graphs supply no
33
+ separate `embeddings` flag. `graphCapabilitiesOf` derives that boolean in the
34
+ portable `GraphCapabilities` description consumed by the gateway. `isEmbeddingModel`
35
+ narrows the graph to its required pooling operation, and the existing
36
+ `embedOne`/`embedMany` entry points consume it directly. The architecture ratchet
37
+ rejects independent pooling flags and reads of the retired declaration field.
38
+ `MlxDeclaredGraph` names the operations behind the other promises (`embed`, `bindMediaInput`,
39
+ `mediaEncoders`, `pixelInput`, `draftTarget`, `nativeDraftHead`, `trainable`,
40
+ `compiledDecodeStep` and `releaseCompiledDecode`, `expertResidency`) and the facts its
41
+ composition declares (`verifyRoundCosts`, `knownSpans`, `mixed`), and
42
+ `declaredGraph` in `models/capabilities` checks the pairing once when composition
43
+ binds the graph: an undeclared graph, malformed pooling operation, or media
44
+ declaration without its operation is refused. The gateway plans from the
45
+ declarations, the request and dynamic state
46
+ (batch membership, cache contents, cancellation), and
47
+ `MlxGatewayBinding.capabilities` reports what it resolved. Supporting a new graph
48
+ means declaring and implementing; `execution/` and the app engine need no edit.
49
+ The batch group and the autoregressive binding take the structural `MlxTokenGraph`
50
+ (`models/graph`: config, caches, the named phases, and the declared ports), not
51
+ the registry's `RuntimeModel` union. A graph's compiled decode step lives with that
52
+ graph (`models/gemma4/compiled-decode`) and reaches them through `compiledDecodeStep`;
53
+ it drives caches through the `CompiledDecodeCache` protocol (`contracts/mlx/cache`,
54
+ implemented by the KV caches in `state/`) and names no cache class. Every cache
55
+ lego has that form except direct pages the kernel reads, which declare none
56
+ (`pagedDecodeCompiles` in `state/paged/cache`). The autoregressive binding resolves
57
+ replay eligibility from the paging and KV configuration once; requests with active
58
+ adapters use the ordinary declared operations.
59
+ The architecture gate rejects concrete model imports, model-class `instanceof`,
60
+ model-type and `architectures` checks, family predicates and model-scoped flags there,
61
+ in the app's engine, server and CLI, and in `@mlx-bun/training`. What those need to
62
+ know about a model is declared in its family record and read through its profile
63
+ (`models/profile`: `embeddingDeclarationFor`, `sentinelDeclarationFor`,
64
+ `mediaTokenDeclarationFor`, `generationDefaultsFor`, `chatTemplateFallbackFor`,
65
+ `trainingDefaultsFor`) or returned by its opened runtime (`models/runtime`,
66
+ `models/memory-plan`); token text resolves to
67
+ ids against the tokenizer in use (`input/special-tokens`). One record per model
68
+ family (`models/family`, listed in `models/families`) holds the family's `accepts`
69
+ rule, its graph id (an open string), tier, loader, loop, fidelity, engine
70
+ capabilities, roles and those declarations; support tiers, profiles, engine
71
+ capabilities, implementation loader/loop and fit's expert count derive from the list.
72
+ A new model is a record in `<family>/family.ts`, an entry in `MODEL_FAMILIES` and its
73
+ native implementation in `models/factory`; the records import no graph class, so
74
+ resolving a profile never loads MLX. The gate keeps model-type comparisons and family
75
+ predicates inside the registry, the family directories and the artifact readers
76
+ (`artifacts/config-dialects`, which spell how a `model_type`'s `config.json` is laid
77
+ out). `trainable`
78
+ (`TrainableGraph`) declares what the trainer consumes: the quantized
79
+ `lmHead` for fused linear-CE heads, `segmented` backward (how a range of layers
80
+ runs and which reused K/V crosses segment boundaries), `prefixShared` forwards,
81
+ `gradCheckpoint`, the `flashAttention` constraint, and the `denoising` objective.
82
+ The trainer names the declaration a graph lacks. The loader resolves module configuration once
83
+ (`contracts/portable/composition`, `models/composition`) from the caller's request
84
+ and the machine: KV scheme and paging, scheduler row cap and prompt chunk size,
85
+ draft method and depth, sampling and request policy. The file names its kernel
86
+ and compilation choices. Alternative layer forms remain importable for callers
87
+ writing their own graphs. Each request names its adapter file; its trained delta
88
+ adds to the outputs of the projections it targets, under that request's scheduler
89
+ context, independently of the file's projection kernel choices.
90
+
91
+ ## Numerical and execution policy
92
+
93
+ The inherited fidelity policy is L1 by default: bit-exact numerics against the
94
+ pinned mlx-lm oracle for matching artifacts, inputs, and settings. L2 covers
95
+ schemes with a different oracle, including mlx-optiq mixed-KV, and requires
96
+ bit-exact comparison to that scheme's reference. Lab paths without an oracle
97
+ need numerical/quality evaluation and a paired A/B before becoming defaults.
98
+ These are correctness obligations; the outstanding real-weight verification is
99
+ tracked in the [refactor plan](../../PLAN.md#verify-the-migrated-library).
100
+ They do not make application sampling or serving defaults identical to mlx-lm.
101
+
102
+ Execution policy treats default memory estimates as advisory. Attempt the
103
+ requested work without rejecting it or clamping output tokens solely because
104
+ of a predicted memory estimate. Explicit user limits, queue capacity, and actual
105
+ layout requirements still apply; release owned resources on failure. Application
106
+ composition must preserve this policy when binding the optional execution layer.
107
+
108
+ ### External parity evidence
109
+
110
+ On 2026-09-24, inference at `5527989` and main at `02d723a` independently matched
111
+ the external mlx-lm reference for MiniCPM5-1B-OptiQ-4bit, snapshot
112
+ `664aabaed233c653f82716d8dc822234d0091f78`. On an Apple M1 Max with 32 GiB RAM,
113
+ macOS 27.0 (26A428), and Bun 1.4.2, all 100 greedy tokens and all 13,056,000
114
+ float32 logits were byte-identical; all logits were finite. The prompt was
115
+ `The capital of France is` (six matching tokenizer IDs), batch one, default
116
+ unquantized KV, with no `MLX_BUN_*` overrides and no EOS early stopping.
117
+
118
+ The external environment used Python 3.13.5, MLX/MLX-Metal 0.32.2 and mlx-lm
119
+ 0.31.3. Reference generation used the unchanged Python body from
120
+ `02d723a:scripts/regen/minicpm5.ts`; model-file and per-step reference hashes
121
+ were checked. The reference manifest SHA-256 is
122
+ `e20d64193328d5dfe1c4b9681651730b35b2eeb2f5152b0ae3d8fb50b05dfd5f`.
123
+ The [MiniCPM5 parity test](tests/parity/minicpm5-parity.test.ts) is that harness:
124
+ public root, model, and scoring imports, forwarding the prompt once and each
125
+ selected token thereafter with the same live cache (plain KV from the model).
126
+ It requires byte-identical finite logits and the greedy token at all 100 steps.
127
+ Opt in with all of `MLX_BUN_TEST_MINICPM5_MODEL`, `MLX_BUN_TEST_MINICPM5_REFERENCE`
128
+ (the directory holding `minicpm5-parity.json` and the 100 step blobs) and
129
+ `MLX_BUN_TEST_MINICPM5_REFERENCE_SHA256`; absent all three it skips, any other
130
+ combination fails. Before native libraries load it verifies the manifest hash, the
131
+ pinned model files and shards by content (relocated copies pass) and every blob;
132
+ the runtime must match the reference's MLX version and GPU architecture.
133
+ This covers that path only, not other models, batching, quantized KV,
134
+ snapshot restore, long contexts, or speed. Raw outputs and the reference
135
+ remain external; Python is not a project dependency.
136
+
137
+ The [Gemma4 parity test](tests/parity/gemma4-parity.test.ts) restores main's
138
+ Gemma4 12B OptiQ-4bit oracle consumer on the same terms (batch one, plain KV
139
+ from the model's `makeCache`, the artifact's `kv_config.json` not applied). Its
140
+ reference is main's `scripts/regen/parity.ts` output: the prompt
141
+ `The capital of France is`, 100 greedy IDs and the first four full-vocabulary
142
+ float32 vectors. Through `createModel` it checks the 100-token greedy trajectory
143
+ and those four vectors byte for byte; a separate 12-step control does the same
144
+ through `new Gemma4Model`. Every step must be finite and select the reference
145
+ token. This is not 100-step full-vector parity. The loader's Gemma 4 table
146
+ builds the written 12B graph only for the `kv_config` composition it serves, so
147
+ for plain KV (the default composition) `createModel` builds `Gemma4Model` too,
148
+ and the test asserts that. Opt in with all of
149
+ `MLX_BUN_TEST_GEMMA4_MODEL`, `MLX_BUN_TEST_GEMMA4_REFERENCE` (the directory
150
+ holding `gemma4-parity.json`, the producer's unchanged `parity.json` and the four
151
+ step blobs) and `MLX_BUN_TEST_GEMMA4_REFERENCE_SHA256`. Before native libraries
152
+ load it verifies the manifest hash; the producer provenance (script hash, main
153
+ revision, installed mlx, mlx-lm and mlx-optiq source hashes and versions, and the
154
+ pinned `parity.json`, which must agree with the manifest); the tokenizer, config,
155
+ index and exactly its shards by content; and every blob. The runtime must match
156
+ the reference's MLX version and GPU architecture. Main's retained capture
157
+ (`02d723a:goldens/apple-m1-max/parity.json`) has no tokenizer, index, shard or
158
+ producer-source pins, so it cannot qualify. The reviewed run in
159
+ [#202](https://github.com/joshuarossi/mlx-bun/pull/202) used a fresh, fully pinned
160
+ capture from main `02d723a` and passed both consumer paths at `e7288425` on an
161
+ Apple M1 Max (MLX 0.32.2, mlx-lm 0.31.3), with input pins unchanged. This qualifies
162
+ the plain-KV scope above; the written graph's unrolled `kv_config` path and
163
+ performance remain separate checks.
164
+
165
+ The [written Gemma graph test](tests/parity/gemma4-written.test.ts) is that
166
+ unrolled-path check, ported from `02d723a:tests/parity/generated-parity.test.ts`
167
+ for each registered fingerprint (12B, e4b, 26B-A4B). With
168
+ `MLX_BUN_TEST_GEMMA4_GRAPH_MODEL` naming an artifact that ships `kv_config.json`, it
169
+ requires the `kv_config` composition (`--kv-quant config`) to select the written
170
+ graph and the default composition `Gemma4Model`; byte-identical vectors against
171
+ `Gemma4Model` over caches converted to `kv_config` before a prompt past the
172
+ sliding window; identical 24-token greedy trajectories under the `kv_config`
173
+ composition, each graph reading its own composed caches (stop tokens off, so
174
+ every trajectory is full length), uncompiled, where from the second forward on
175
+ every listed cache answers from its quantized storage, and with compiled decode;
176
+ and identical vectors over plain caches. This is identity between the written
177
+ graph and the family's generic graph within this tree, not an oracle claim. On 2026-09-28 it
178
+ passed on the M1 Max for mlx-community gemma-4-e4b-it-OptiQ-4bit (`98d7dc6a`; 24
179
+ unrolled forwards uncompiled, 2 compiled) and gemma-4-12B-it-OptiQ-4bit
180
+ (`5b110106`; 24 uncompiled, 1 compiled). The 26B-A4B cell was not run. A final
181
+ cell runs two different rows through one forward over `kv_config` caches and
182
+ requires bytes equal to the generic graph's; it caught the committed e4b graph slicing
183
+ its per-layer inputs for one row, and passes on e4b and 12B after regeneration
184
+ (2026-09-29, M1 Max).
185
+
186
+ The three Gemma files are written directly under `models/gemma4/`. Each names
187
+ its sliding/full attention, KV donors, per-layer inputs, activation and expert
188
+ operations in order. Changes to one file are checked by that file's numerical
189
+ gates. A shared block or kernel change reaches the files that import it.
190
+
191
+ The identity tests compare each specialized file with its family's generic graph
192
+ at maxDiff 0 where both serve the same operation. Real-weight parity suites and
193
+ app-path digests (`scripts/digest.ts`) hold the served numbers; recorded synthetic
194
+ digests (`scripts/synthetic-digests.ts`) cover cases without an artifact. There is
195
+ no source generator, parallel input description or generator drift test.
196
+
197
+ On 2026-09-27 UTC, the rotating live-window correction (source `5ec1f4ae`) was
198
+ checked on the same M1 Max (MLX 0.32.2, pinned native library) against selections
199
+ made independently of `temporalView`, main `02d723a`, and pinned optiq 0.2.7.
200
+ Gemma4 e2b and a custom graph over unchanged Llama-3.2-3B weights (alternating
201
+ sliding layers, window 8; not a published model) matched main's B1 logits byte for
202
+ byte in 13 cases. In 14 late-join and initial two-row cases, at both tail-split
203
+ settings, every projection, token and valid state equaled the same batching code
204
+ reading rows through the independent selection, and merged rows equaled their own
205
+ B1 state; before the correction, late joins without a tail split and initial
206
+ two-row batches merged the oldest window. Gemma4 e4b donor attention (plain and
207
+ affine) and the deterministic assistant-drafter chain
208
+ (`gemma-4-E4B-it-assistant-bf16` `844e008e`) equaled their independent references
209
+ byte for byte through prefill, a verify block, a rollback and a decode; below the
210
+ window the chain equaled main's. For DiffusionGemma 26B-A4B with a 1,500-token
211
+ prompt, the decoder-selected encoder K/V of all 30 layers and the full first-pass
212
+ logits equaled the pinned optiq reference byte for byte, and the opt-in real-weight
213
+ test passed with measured main references. This covers these paths only, not
214
+ stochastic speculative verification, other models, performance or full-candidate
215
+ qualification. Raw evidence remains external.
216
+
217
+ Three opt-in consumers repeat these checks. The
218
+ [rotating-join test](tests/execution/rotating-join.test.ts) takes
219
+ `MLX_BUN_TEST_ROTATING_JOIN_MODEL` (a Gemma4 artifact with sliding layers) and, for a
220
+ custom graph over a Llama-family artifact's unchanged weights,
221
+ `MLX_BUN_TEST_ROTATING_JOIN_WINDOW`; it runs late joins and two-row preparation with
222
+ the prompt tail split off and on, each against the same run reading rows through an
223
+ independent newest-window selection, and compares merged rows with their solo state.
224
+ The [Gemma4 assistant test](tests/parity/gemma4-assistant.test.ts) takes
225
+ `MLX_BUN_TEST_ASSISTANT_TARGET` and `MLX_BUN_TEST_ASSISTANT_DRAFT` and compares donor
226
+ attention and deterministic draft chains with independently selected donors past the
227
+ window. Its separate generation consumer uses the real assistant provider at B1,
228
+ depth 2, with plain KV below the sliding window, and compares greedy output with
229
+ ordinary generation. It checks complete forward blocks and retained K/V against independent direct-graph replay, including
230
+ acceptance, rejection and continuation; this is not main or external-oracle parity.
231
+ On 2026-09-27 UTC, this consumer passed at `9be1c241` on an M1 Max with 32 GiB
232
+ (macOS 27.0, Bun 1.4.2), using e4b `98d7dc6a` and assistant `844e008e`. Its 67
233
+ output tokens matched ordinary generation; 44 rounds included 24 accepted
234
+ proposals and 39 genuine target-disagreement rounds. All 45 forward blocks and
235
+ the retained state matched the direct replay byte for byte. This does not qualify
236
+ B>1, persistence or performance. Captures and their content pins remain external.
237
+ The [DiffusionGemma window test](tests/parity/diffusion-gemma-window.test.ts)
238
+ takes `MLX_BUN_TEST_DIFFUSION_WINDOW_MODEL`, `MLX_BUN_TEST_DIFFUSION_WINDOW_REFERENCE`
239
+ (a directory holding `manifest.json` and raw tensors, produced outside this
240
+ repository) and `MLX_BUN_TEST_DIFFUSION_WINDOW_REFERENCE_SHA256`; the manifest pins
241
+ the inputs, artifact files, producing runtime and every tensor. The artifact, inputs,
242
+ geometry and reference tensors are verified before native libraries load; the
243
+ producing runtime (MLX version, GPU architecture) is checked right after the native
244
+ import, and each produced tensor's shape and dtype before its bytes. Each of these
245
+ tests skips only when none of its settings is present.
246
+
247
+
248
+ ### Speech and embedding parity
249
+
250
+ Three opt-in consumers, ported from main `02d723a`, compare Whisper, Silero VAD and
251
+ Qwen3-Embedding with real weights against both the external oracle and main.
252
+ They skip only when none of their settings is present; any other combination
253
+ fails, and every reference and model pin is verified before native libraries load.
254
+ The oracle producers are main's unchanged scripts, run outside this repository in
255
+ their own environments: `scripts/oracle/gen-whisper-golden.py` and
256
+ `gen-silero-golden.py` (mlx-whisper 0.4.3 and silero-vad 6.2.1, MLX 0.32.2, in
257
+ `~/Code/mlx-whisper-oracle/.venv`) and `gen-qwen3-embed-golden.py` (mlx-lm 0.31.3,
258
+ MLX 0.32.2, `~/Code/mlx-lm/.venv-mlx-0.32.2-macos14`). The main side is
259
+ [`scripts/main-speech-reference.ts`](scripts/main-speech-reference.ts), run from a
260
+ main checkout (`whisper`, `vad` and `embed` commands; see its header): it imports
261
+ main's own modules by path, changes nothing there, and writes JSON outside this
262
+ repository. Nothing produced is committed.
263
+
264
+ - [Whisper](tests/parity/whisper-parity.test.ts): `MLX_BUN_TEST_WHISPER_MODEL`
265
+ (the mlx-community snapshot), `_AUDIO` (speech-fox.wav and jfk.wav), `_REFERENCE`
266
+ (the oracle's `whisper.json` and blobs plus `main-whisper.json`),
267
+ `_REFERENCE_SHA256` and `_MAIN_SHA256`. Against the oracle: mel and window-0
268
+ encoder output bit for bit, window-0 per-step logits bit for bit on the faithful
269
+ graph, identical transcripts, segment tokens, timestamps and seeks. Against main:
270
+ whole results and encoder hashes identical for the faithful and the fast path,
271
+ greedy and beam 5, word timestamps and a run fed one second at a time. The fast
272
+ path is held to the oracle within two tokens per clip, the bound main recorded.
273
+ The openai/whisper-large-v3-turbo tokenizer must be in the Hugging Face cache.
274
+ - [Silero VAD](tests/parity/silero-vad-parity.test.ts): `MLX_BUN_TEST_VAD_MODEL`
275
+ (`ggml-silero-v6.2.0.bin`), `_AUDIO` (adds chirp-1s6.wav), `_REFERENCE`
276
+ (`silero-vad.json` and `main-vad.json`), `_REFERENCE_SHA256`, `_MAIN_SHA256`.
277
+ Probabilities against the torch reference within 5e-3 (the ggml weights are fp16)
278
+ and identical segments under both parameter sets; against main, probabilities,
279
+ 100 ms-streamed probabilities and segments bit for bit. The oracle's noise clip
280
+ uses numpy's generator and is not compared.
281
+ - [Qwen3-Embedding](tests/parity/embedding-parity.test.ts): `MLX_BUN_TEST_EMBED_MODEL`,
282
+ `_REFERENCE` (`meta.json`, `hidden.bin`, `pooled.bin`, `main-embed.json`),
283
+ `_REFERENCE_SHA256`, `_MAIN_SHA256`. Hidden states and the pooled vector for the
284
+ oracle's ids bit for bit, the tokenizer's ids equal to the oracle's, and `embedMany`
285
+ vectors and token counts bit for bit with main for four texts, raw and with a
286
+ query instruction; it also checks the embed call counter.
287
+
288
+ The app repeats the served surfaces with the same references.
289
+ [`transcription-parity.test.ts`](../../apps/mlx-bun/tests/engine/transcription-parity.test.ts)
290
+ (`MLX_BUN_TEST_NATIVE=1`, `MLX_BUN_APP_TEST_WHISPER_MODEL`, `MLX_BUN_TEST_WHISPER_AUDIO`,
291
+ `_REFERENCE`, `_REFERENCE_SHA256`) requires the transcription-only server's
292
+ transcripts to equal the oracle's for the same audio across multipart and JSON
293
+ requests, beam search with vocabulary, SRT, translation, event streaming, VAD-gated
294
+ sessions and the error path. [`embeddings-parity.test.ts`](../../apps/mlx-bun/tests/engine/embeddings-parity.test.ts)
295
+ (`MLX_BUN_APP_TEST_EMBED_MODEL` and the embedding references) requires `/v1/embeddings`
296
+ to return the oracle's and main's vectors bit for bit. The
297
+ [voice test](../../apps/mlx-bun/tests/engine/voice.test.ts) covers the CLIs and the
298
+ chat-server companion on synthesized speech at word level.
299
+
300
+ On 2026-09-29 all of these passed on an Apple M1 Max with 32 GiB, macOS 27.0.1
301
+ (26A434), Bun 1.4.2, native MLX 0.32.2, with this tree on `e878baaa` plus the tests
302
+ above and main at `02d723a`. Whisper: 36 of 36 (7 oracle cases, 21 main runs,
303
+ 3 clips), including every mel, encoder output and per-step logit vector bit for bit
304
+ with the oracle; the fast path differed from the oracle by 0 tokens except 2 on
305
+ `long-noprev-en` (the recorded bound), and fast and faithful beam 5 agreed exactly.
306
+ VAD: 4 of 4 clips; the largest difference from torch was 2.8e-3 (jfk) and main matched
307
+ bit for bit. Embedding: 4 of 4; 35,840 hidden values and 2,560 pooled values identical
308
+ to mlx-lm, and main's vectors identical. App: 6 of 6 transcription and 3 of 3 embedding
309
+ route tests. Artifacts: whisper-large-v3-turbo `a4aaeec0` (weights SHA-256
310
+ `951ed3fc1203e6a62467abb2144a96ce7eafca8fa77e3704fdb8635ff3e7f8a6`), tokenizer from
311
+ openai/whisper-large-v3-turbo `41f01f3f`, Silero `ggml-silero-v6.2.0.bin` (SHA-256
312
+ `2aa269b785eeb53a82983a20501ddf7c1d9c48e33ab63a41391ac6c9f7fb6987`),
313
+ Qwen3-Embedding-4B-4bit-DWQ `b5d88f1f` (weights SHA-256
314
+ `cd373d0bc76a3464a69b036ac6beca217a0d668bdf901ff3b05a7d7997d2d607`). Reference
315
+ SHA-256s: whisper.json `348fb1ff2097f6b9b57b236293a67fd6da7c5eedaa886f793866eaba4a0aa965`,
316
+ main-whisper.json `e307ac92f636ac03a4ae95c39b8a1ae5d1cb09c6b42644a79979205281037a67`,
317
+ silero-vad.json `e961872b71f458894658f2f43c2eab6112c4517edaa9f49f5ff8fbc500460a51`,
318
+ main-vad.json `f9194f6d70c030f59f95e049b39e019c658e6486e75c274e79a96c830650a38d`,
319
+ meta.json `cb63915245d57da88f7df0ac088fd19ac249bcebd1829e4b25840163cf9059b3`,
320
+ main-embed.json `85e13387bda46cf9c2aff8181b8dc575cce926a5bf90ff5293211287412f08a1`.
321
+ Inputs were main's `speech-fox.wav` and `chirp-1s6.wav` fixtures and whisper.cpp's
322
+ `jfk.wav`; the mel comparison confirms the same samples. A deliberate change of the
323
+ attention scale made 13 Whisper tests fail, so the comparisons discriminate.
324
+ This covers these checkpoints and clips only: no other Whisper size, no non-WAV
325
+ input (AudioToolbox decoding), no oracle for word timestamps or the streaming run
326
+ (main only), no Whisper batching, and no performance. Raw references remain external.
327
+
328
+ ### Packed Qwen Trellis parity
329
+
330
+ [`tests/parity/qwen-trellis-parity.test.ts`](tests/parity/qwen-trellis-parity.test.ts)
331
+ compares the 11.99 GB `qwen38-trellis-global-exit5-h39-q4b-v2` and its folded
332
+ MTP companion with pre-refactor main. Stock mlx-lm cannot read this packed
333
+ artifact. Produce the reference with
334
+ [`scripts/main-qwen-trellis-reference.ts`](scripts/main-qwen-trellis-reference.ts),
335
+ passing `--main <main-checkout> --target <snapshot> --draft <companion> --out
336
+ <external-reference.json>` and main's native library through `MLX_BUN_LIBMLXC`.
337
+ The producer does not modify main. Its reference pins model metadata and every
338
+ weight shard by content, the main revision, MLX version and GPU architecture.
339
+ Set all of `MLX_BUN_TEST_QWEN_TRELLIS_TARGET`,
340
+ `MLX_BUN_TEST_QWEN_TRELLIS_DRAFT`, `MLX_BUN_TEST_QWEN_TRELLIS_REFERENCE` and
341
+ `MLX_BUN_TEST_QWEN_TRELLIS_REFERENCE_SHA256`, then run:
342
+
343
+ ```sh
344
+ bun --no-env-file test packages/inference/tests/parity/qwen-trellis-parity.test.ts
345
+ ```
346
+
347
+ The schema-2 reference independently records 96 complete last-position float32
348
+ vectors and greedy tokens across three prompts, plus three 64-token native MTP
349
+ runs. Each MTP run records emitted tokens, every target input, speculation
350
+ counters and the full logits at its actual native heads. Separate teacher-forced
351
+ controls record one-row and verify-window vectors, CPU first-maximum IDs, exact
352
+ maximum sets and cache positions before, after and after rollback.
353
+
354
+ The consumer requires equality with those main controls and literal equality
355
+ of native MTP tokens, target inputs, counters and head vectors. Every emitted
356
+ token must belong to the exact maximum set of its actual head. This preserves
357
+ native selection when an exact tie makes the CPU's first maximum a different
358
+ token. Window-versus-one-row KL checks remain separate checks of the existing
359
+ arithmetic; they do not relax equality against main. The producer records its
360
+ source revision and script content hashes, including whether its operational
361
+ checkout was dirty. Produce references from an isolated, pinned main checkout
362
+ on the same device as the candidate and retain the independent capture's hash.
363
+
364
+ The MTP scope is the shared gateway with batch capacity one, depth two, plain
365
+ KV, greedy sampling and stopping tokens disabled. These checks cover the named
366
+ artifact and geometry, not B>1, quantized KV, stochastic sampling, intelligence
367
+ or performance. Refactor run results and their source and artifact pins belong
368
+ in the PR and external run directory.
369
+
370
+ The existing runtime-report consumer also matches full logits, all live cache
371
+ planes and continuation in 18 cases (contexts 0/64/512 × append lengths
372
+ 1/3/4/8/16/128, fixed prefix chunk 256, plain KV), including serialized-state
373
+ restoration. Main's report SHA-256 is
374
+ `8bb68cc58d3aad035111b10e48102595a2c00f91c1b640cc7577da19dae71e54`. Main runs this
375
+ comparison with its compiled decode off, and this tree's runtime oracle runs no
376
+ compiled decode; the legacy reference's omitted configuration provenance is accepted
377
+ explicitly after checking its invocation. Captures and logs remain outside Git.
378
+
379
+ ### Repeatable runtime comparison
380
+
381
+ The source-checkout tool `bun packages/inference/scripts/runtime-oracle.ts --help`
382
+ explains the plan schema and `emit`/`compare` commands. It hashes complete logits,
383
+ live cache planes and one-token continuation; `compare` is CPU-only. Supply local
384
+ weights and external reference reports. No Python environment or reference data
385
+ is installed by this repository. The opt-in test uses `MLX_BUN_PARITY_PLAN` and
386
+ `MLX_BUN_PARITY_REFERENCE`; it skips only when none of its settings (those two,
387
+ `MLX_BUN_PARITY_TIMEOUT_MS`, `MLX_BUN_PARITY_ALLOW_UNRECORDED_CONFIG`,
388
+ `MLX_BUN_PARITY_REFERENCE_SHA256`) is set, and a partial or blank opt-in fails.
389
+ `MLX_BUN_PARITY_REFERENCE_SHA256` pins a published reference revision: the report's
390
+ bytes must match before the worker starts. Legacy reports require explicit
391
+ `--allow-unrecorded-config` (test: `MLX_BUN_PARITY_ALLOW_UNRECORDED_CONFIG=1`), after
392
+ verifying their environment separately. New reports record runtime overrides,
393
+ source/harness/native hashes, machine and plan; `--hash-weights` adds weight hashes.
394
+
395
+ The same consumer covers Qwen Trellis and Gemma. Packed Qwen Trellis has the
396
+ measured comparison below; Gemma still needs a recorded run with this consumer.
397
+ Produce references outside this repository with the unchanged producers at main
398
+ `02d723a`, sequentially on the comparison machine. Stock mlx-lm architectures
399
+ (Gemma4 e2b/e4b/26B-A4B, Gemma2, Llama, MiniCPM5) use
400
+ `MLX_BUN_TEST_RUNTIME_ORACLE=1 HF_HUB_OFFLINE=1 python scripts/oracle/check-runtime.py plan.json reference.json`
401
+ in the pinned oracle environment; it registers no OptiQ architectures, so the 12B
402
+ `gemma4_unified` artifact keeps its dedicated consumer above. Packed Qwen Trellis
403
+ has no external oracle (mlx-lm cannot load it), so main is the reference:
404
+ `MLX_BUN_TEST_RUNTIME_ORACLE=1 MLX_BUN_COMPILED_DECODE=0 bun --no-env-file tests/support/runtime-oracle-worker.ts plan.json reference.json`
405
+ in the main checkout (main's own switches; this tree reads neither, and its
406
+ runtime oracle runs no compiled decode). Neither producer records provenance or applies `kv`, so
407
+ compare plain-KV plans with the legacy acceptance and pin the report:
408
+ `MLX_BUN_PARITY_PLAN=plan.json MLX_BUN_PARITY_REFERENCE=reference.json MLX_BUN_PARITY_REFERENCE_SHA256=<sha256> MLX_BUN_PARITY_ALLOW_UNRECORDED_CONFIG=1 bun --no-env-file test packages/inference/tests/parity/runtime-oracle.test.ts`.
409
+ Useful plans keep main's IDs and vary geometry: for Trellis, contexts 0/64/512 ×
410
+ lengths 1/3/4/8/16/128 with prefix chunk 256 cover the M≤4, M5–15 and M≥16
411
+ dispatch profiles; for Gemma4 e2b/e4b, a context past the 512-token window (for
412
+ example 0/64/600 × 1/8/128, chunk 128) covers sliding-window wrap; for MiniCPM5
413
+ beyond the recorded cases, a longer multi-chunk context with `restore: true` adds
414
+ persisted-state continuation. Mixed KV (`kv: "artifact"`) has no reference producer
415
+ until the mixed-KV reference contract in [PLAN](../../PLAN.md#verify-the-migrated-library)
416
+ is confirmed.
417
+
418
+ On 2026-09-25 UTC, `6b0fd69` matched main `02d723a` and its unchanged external
419
+ `02d723a:scripts/oracle/check-runtime.py` for the same MiniCPM snapshot above. All nine
420
+ cases (contexts 0/64/320 × append lengths 1/8/128, prefix chunk 128, batch one)
421
+ matched bit-for-bit for full logits, prefix/append state and continuation logits/state.
422
+ The machine was the same M1 Max; Python was 3.14.5, MLX/MLX-Metal 0.32.2,
423
+ mlx-lm 0.31.3. Runtime overrides were only compiled decode off (main's switch,
424
+ which that tree also read) and `MLX_BUN_TEST_RUNTIME_ORACLE=1`; references ran
425
+ sequentially with HF offline.
426
+ The weight SHA-256 was
427
+ `88bb686ed4a28f7c2065e27aabef7669f84961ac47c83efe2d003436c179e2e4`;
428
+ the Python report SHA-256 was
429
+ `bc034fd8f6aec39ec9f90c1aeeed1caed713efc58453e85f4cfc32b6046c86d5`.
430
+ This covers plain KV and chunked prefix continuation, not mixed KV, saved-state
431
+ restoration, compiled decode, sliding-window wrap, other families or performance.
432
+ Raw reports remain outside Git; the broader PLAN verification item stays open.
433
+
434
+ The plan can additionally request `kv: "artifact"` for the checkpoint's existing
435
+ mixed-KV policy and `restore: true` for persisted state and continuation checks.
436
+ Conversion happens after each forward, never on empty state. Restore verifies
437
+ tensor integrity, the saved token prefix, live planes and continuation against
438
+ the uninterrupted path; it does not restore sampling or execution sessions.
439
+ Temporary checkpoints are removed after each case.
440
+
441
+ Oracle goldens and recorded comparisons are published outside this repository;
442
+ run the tool against a pinned golden revision or regenerate one. A mixed-KV
443
+ comparison must use the documented per-path composition (stock mlx-lm attention
444
+ for one query, OptiQ tiled attention for multi-query input), not every default of
445
+ `optiq serve`, whose fused install also changes single-query decode.
446
+
447
+ ### Compiled decode verification
448
+
449
+ The opt-in `tests/parity/compiled-decode.test.ts` accepts local checkpoint paths
450
+ through `MLX_BUN_COMPILED_GEMMA_E4B` and `MLX_BUN_COMPILED_GEMMA12B`. Run it with
451
+ `bun --no-env-file test packages/inference/tests/parity/compiled-decode.test.ts`
452
+ from the root. Each unset path skips that family; a supplied invalid path fails.
453
+ The checkpoints must include their existing mixed-KV configuration. No model or
454
+ reference data is downloaded.
455
+
456
+ The test compares native full-logit bytes for identical fixed tokens with plain
457
+ and artifact-configured KV, both before and after the sliding window fills.
458
+ Greedy trajectories and a dense Gemma mid-segment failure (which ends its generation:
459
+ the compiled step has no fallback) use main's original tokenizer-rendered prompts
460
+ (12B targets 600/1100 tokens; e4b targets 700).
461
+ Those checks require actual compiled-step activation and zero unexpected retraces.
462
+ The forced-token full-logit matrix uses deterministic IDs independently of EOS. These are
463
+ compiled-versus-ordinary checks, not an external-oracle or performance claim.
464
+ Runtime compilation overrides stay inside the test; this adds no application option.
465
+
466
+
467
+ ### Scheduler continuation and per-path checks
468
+
469
+ The opt-in [continuation test](tests/parity/ordinary-continuation.test.ts) takes
470
+ `MLX_BUN_TEST_CONTINUATION_MODEL=/cached/checkpoint`. It compares uninterrupted
471
+ and interrupted/restarted B1/B4 generation, including pending tokens, seeded
472
+ sampling history, byte-identical checkpoint planes, and actual restored-row
473
+ counts. `MLX_BUN_TEST_CONTINUATION_ADAPTER=/cached/adapter` adds adapter-context
474
+ and cache-namespace isolation without bundled fixtures; with it,
475
+ `MLX_BUN_TEST_CONTINUATION_IGNORED_DRAFT=1` binds a two-model draft whose rows
476
+ fail if opened and requires the adapter rows to match a draftless adapter
477
+ control, tokens and checkpoint planes. The existing KV matrix
478
+ uses `MLX_BUN_TEST_CONTINUATION_KV=bf16|4|8|per-layer|turbo`,
479
+ `MLX_BUN_TEST_CONTINUATION_KV_START=0` (or `prompt+N`), and
480
+ `MLX_BUN_TEST_CONTINUATION_INTERRUPT=6` (6–15).
481
+ The test uses the actual capability planner. With an adapter and positive affine
482
+ start it also checks grouped admission, an event-driven late join, cancellation
483
+ against a matched stop control, queued base-context isolation and reuse after
484
+ drain. Mounting must change full logits, and unmounting must restore the base.
485
+ Full-attention universal graphs accept ordinary adapter requests with uniform
486
+ or per-layer delayed affine KV and adapter-aware continuation. Paired B1
487
+ Qwen2.5-0.5B acceptance without a configured draft matched main for uniform
488
+ KV4/KV8 and partial mixed KV, greedy/seeded sampling, and fresh-process
489
+ continuation before and after conversion: full logits, every valid cache plane,
490
+ adapter identity and unmount restoration. Separate candidate B2/B4 tests without
491
+ a configured draft passed joins, cancellation, queued adapter/base isolation
492
+ and reuse after drain.
493
+
494
+ Gemma4 adapter rows take ordinary generation checkpoints. Paired B1 checks, recorded
495
+ while adapter rows still bypassed paging (so on plain KV),
496
+ on Gemma4 e2b OptiQ-4bit (snapshot `b0162532`, MacBookPro18,2, MLX 0.32.2)
497
+ matched main `02d723a` at capacity 1 against continuous capacity 8 for 16 tokens,
498
+ greedy and seeded sampling, and fresh-process restoration across both settings.
499
+ Full logits and all 15 donor KV states matched; all 35 layer outputs and the
500
+ final norm matched by hash. The nonzero adapter changed logits and unmounting
501
+ restored the base. This covers one active row within the sliding window, not grouped rows, paged numerics,
502
+ hard-kill durability, external-oracle parity or performance.
503
+
504
+ An adapter request whose draft cannot serve target adapters (the two-model
505
+ provider on any graph; any provider on a softcap graph, which never speculates
506
+ with adapters) decodes ordinarily, ignores the draft and, without fill, grammar
507
+ or logprobs, takes generation checkpoints like a draftless adapter request.
508
+ The continuation test's ignored-draft opt-in passed with a synthetic nonzero
509
+ q/v adapter on gemma-2-2b-it-4bit (with fresh-process restore) and on
510
+ Llama-3.2-3B-Instruct-4bit with a custom 4-token window (M1 Max, bf16 KV).
511
+
512
+ With `MLX_BUN_TEST_CONTINUATION_KV=turbo`, every saved and restored checkpoint
513
+ after conversion (immediately with a start of 0) must hold TurboQuant's exact
514
+ inventory: five encoded planes per full-attention cache (K indices, float16 K
515
+ scales and zeros per 32-wide group, packed uint8 V, float16 V scales) at the
516
+ scheme's bits, the fused decode when `MLX_BUN_TURBOQUANT_FUSED_DECODE` is unset,
517
+ a plain ring per sliding cache, every KV layer at the checkpoint's offset, and
518
+ no reuse floor with a start of 0. Before a positive start converts, checkpoints
519
+ are still plain. Paired B1 checks on Gemma4
520
+ e4b OptiQ-4bit (snapshot `98d7dc6a`, MacBookPro18,2, MLX 0.32.2) with
521
+ server-wide TurboQuant k8v3 from token 0 matched main `02d723a` through each
522
+ side's continuous gateway at capacity 8 for 16 tokens, greedy and seeded
523
+ sampling: full logits, all 42 layer outputs and the final norm by hash, and all
524
+ 24 donor states at every forward (20 plain sliding rings; the 4 full donors
525
+ plain on the prefill forward and TurboQuant, all five planes, from the next),
526
+ checkpoint saves at 4, 8 and 12, an interruption at 10 and a fresh-process
527
+ resume from the durable record at 8. Within this tree, the continuation test
528
+ passed on the same artifact at B1 and B4 with TurboQuant from token 0: seeded
529
+ sampling (temperature 0.7, seed 42 plus the row, repetition penalty 1.1) for
530
+ 16 tokens, interrupted at 6 with durable checkpoints at 4, then restored in the
531
+ same process and in a fresh one; every restored record held 12 tokens over 24
532
+ caches of 2 or 5 planes. The main-paired evidence is B1 only; a wrapped sliding
533
+ ring, a delayed TurboQuant start, hard-kill durability, external-oracle parity
534
+ and performance are not claimed.
535
+
536
+ MiniCPM5 and these universal graphs also ignore a configured draft for adapter
537
+ requests, as main did; requested logprobs remain available. Main also ignored
538
+ supplied fill for them, which the shared fill method now serves (the pairing
539
+ below predates that). Without fill/logprobs/grammar, ordinary checkpoints remain eligible. Ignoring the draft
540
+ does not invoke the provider during a request; loading an artifact-backed draft
541
+ still performs its existing compatibility probe. Paired B1 acceptance on cached
542
+ Qwen2.5-0.5B and MiniCPM5-1B compared six scenarios per model, each with a matched
543
+ draftless adapter control, over 16 generated tokens. The scenarios cover
544
+ TwoModel and Ngram, uniform KV4/KV8 and partial mixed KV, ignored strict/verify/echo
545
+ fill, requested logprobs, and greedy or seeded sampling. Full logits and all valid
546
+ KV planes matched main and controls; request-time draft/fill counters stayed zero,
547
+ nonzero adapters changed logits, and physical unmount restored base logits.
548
+ The no-fill scenarios retained plain or encoded checkpoints and matched
549
+ uninterrupted output after graceful cancellation and fresh-process restoration.
550
+ This does not qualify every combination or multi-row configured-draft requests.
551
+
552
+ Only the first cold decode/resume token may have signed versus unsigned int32
553
+ metadata; its raw bytes, value and computed state must match exactly. Main
554
+ ignored supplied fill on these graphs without adapters or a configured draft
555
+ too, having no affine append binding; the planner then chose ordinary
556
+ decoding, which the pairing below qualified, and now serves it through the
557
+ shared fill method. Paired B1 acceptance on cached MiniCPM5-1B and Qwen2.5-0.5B matched main and
558
+ no-fill controls for supplied strict/echo fill, unseeded greedy sampling and
559
+ KV4 starting at token 8. Each request generated 16 tokens across conversion;
560
+ all 17 forward states, 16 full-vocabulary projections and valid cache planes
561
+ matched, and every fill hook stayed unused. Main planned fill and disabled it
562
+ inside generation; the planner of that pairing reported `fill-incompatible-with-request`
563
+ and selected ordinary continuous decoding. This pair does not qualify B2 or
564
+ cancellation.
565
+ Requests that supply fill remain ineligible for generation checkpoints.
566
+ Speculation, grammar proposals and fill are planned over delayed affine KV as
567
+ over any other scheme. A graph whose layers softcap their attention scores
568
+ (Gemma2) composes the softcapped read (`state/softcap-attention`) at construction
569
+ in place of the cache's own read: it fetches each window as plain keys and values
570
+ from every storage (plain as stored, TurboQuant decoded, affine dequantized, paged
571
+ gathered) and attends with the manual composition. That attention runs unchanged over the dequantized keys and values (dequantize and
572
+ read, not a quantized kernel), so affine KV serves its ordinary, grammar,
573
+ speculative and fill rows, with checkpoints, before and after each row's
574
+ transition; main threw at the first forward past it. No artifact
575
+ holds those numbers, so the native Gemma2 suite (`MLX_BUN_GEMMA2_NATIVE=1`)
576
+ gates every phase at maxDiff 0 against the plain storage the graph composes
577
+ without a scheme, its fetched window requantized by mlx past the transition, and
578
+ holds the descriptor and mask cases to recorded synthetic digests
579
+ (`tests/models/synthetic-digest.ts`, fixtures under `tests/fixtures/synthetic-digests/`,
580
+ `scripts/synthetic-digests.ts record|check`, recorded per GPU architecture). Direct grammar jump commits its spans over any storage
581
+ through the shared span method, as main's jump did: one maintenance
582
+ call, then one unsplit forward of the pending token and the forced span, plain
583
+ across the start and over the converted layers after it. On 2026-09-28 UTC (M1 Max, MLX 0.32.2) the opt-in
584
+ `tests/parity/affine-grammar-spans.test.ts` (`MLX_BUN_TEST_AFFINE_SPANS_MODEL`)
585
+ passed on cached Gemma2-2B-4bit `2c715097` with KV4: below the transition, B1
586
+ spans matched direct jump-forward generation in tokens, matcher history and
587
+ every projection's bytes (a row crossing it was then refused, which this tree
588
+ no longer does); an interleaved peer matched its solo run and the
589
+ group served again.
590
+
591
+ On 2026-09-28 UTC, cached Gemma2-2B-4bit snapshot `2c715097` (26 full-attention
592
+ layers) passed bounded TurboQuant k8v3 acceptance on an M1 Max, MLX 0.32.2 and
593
+ Bun 1.4.2. [#210](https://github.com/joshuarossi/mlx-bun/pull/210) qualified
594
+ ordinary decoding, fresh-process continuation and HTTP cancellation/recovery
595
+ against main, with separate same-geometry candidate batching controls.
596
+ [#212](https://github.com/joshuarossi/mlx-bun/pull/212), tested at `e783c7b4`,
597
+ matched main `02d723a` byte for byte in full-vocabulary logits and all valid cache
598
+ planes for immediate and delayed conversion with real choice/JSON grammars.
599
+ The checks cover unsplit forced spans followed by sampling, callback stop within
600
+ a committed span, actual prefix reuse with conversion before the suffix,
601
+ cancellation with an active peer, and same-scheduler recovery. Graph calls remain
602
+ B1 and interleaved; this does not qualify stacked B2 grammar numerics, other
603
+ artifacts, an external TurboQuant oracle or performance. Captures and input pins
604
+ remain outside Git.
605
+
606
+ Universal graphs
607
+ with sliding layers take the same ordinary delayed path when their bound
608
+ attention reads encoded KV. Qwen3 and Qwen3-MoE attend affine KV as mlx-lm's
609
+ `scaled_dot_product_attention` does (quantized SDPA over a quantized cache) and
610
+ take the same encoded-read path: affine KV, immediate or delayed, batches,
611
+ speculates and takes generation checkpoints across its conversion. On 2026-09-28 UTC (M1 Max,
612
+ MLX 0.32.2), cached Qwen3-4B-Instruct-2507-4bit `50d42775` matched mlx-lm
613
+ 0.31.3's `generate_step` order bit for bit in six greedy steps' full logits
614
+ through direct generation with KV8 and KV4 at starts 0, 8 and 14 (a one-off
615
+ comparison); the opt-in spans test and the ordinary-continuation test (KV8 from
616
+ 0, KV4 from prompt+4, KV8 from prompt+2) passed on it; and a grouped n-gram
617
+ draft over immediate KV8 and KV4 reproduced ordinary greedy output. No
618
+ Qwen3-MoE artifact was run.
619
+ On encoded-read universal graphs, Qwen3 and MiniCPM5, whose verifiers qualify
620
+ grammar proposals, grammar jump is planned as verified proposals over every KV
621
+ scheme; bound directly, the span method commits spans over their delayed affine
622
+ rows with no dense-read requirement, before and after conversion.
623
+ The same opt-in real-weight spans test passed on cached MiniCPM5-1B-OptiQ-4bit
624
+ `664aabae` and Qwen2.5-0.5B-Instruct-4bit `a5339a41` with KV4: B1 spans matched
625
+ direct generation in tokens, matcher history and every projection's bytes, both
626
+ below the transition and for a row converting three tokens into decode and
627
+ continuing over converted layers; an interleaved peer matched its solo run. On 2026-09-27 UTC (M1 Max, MLX 0.32.2) at `a9b60646`,
628
+ a custom graph over unchanged Llama-3.2-3B-Instruct-4bit weights with window 8
629
+ (not a published model) matched main at B1 in full logits and all
630
+ valid cache planes for bf16, immediate affine, KV4 and KV8 converting in decode
631
+ after the ring wrapped, and KV4 converting in prefill at token 32. Gateway and
632
+ direct execution agreed at B1 and B2, with rows converting separately, a
633
+ prefill-converted joiner, a cancelled row's survivor and same-group reuse
634
+ matching a fresh group; the continuation test restored B1 and B4 rows in a
635
+ fresh process for KV4 and KV8. Gemma4's delayed rotating rows share the
636
+ single-row adoption: on e4b OptiQ-4bit, one-block and wrapped singletons matched
637
+ main through conversion (hidden states, logits, valid planes and
638
+ physical ring order), as did the speculative target layout and two rows in both
639
+ orders at the same batch width; main-produced wrapped checkpoints loaded into
640
+ both trees gave identical adoption and continuation, and RAM and disk restores
641
+ agreed. A restored wrapped ring's continuation can still differ from the live
642
+ one ([State and attention](#state-and-attention)). Captures and their content
643
+ pins remain external. These checks do not cover cross-version checkpoint
644
+ files, hard-kill durability, an external oracle or performance.
645
+
646
+ Grammar, grammar proposals and supplied fill on these graphs have a separate
647
+ opt-in consumer, the
648
+ [sliding-window grammar and fill test](tests/parity/sliding-grammar-fill.test.ts).
649
+ It takes `MLX_BUN_TEST_SLIDING_GRAMMAR_FILL_MODEL` (a Llama-family artifact) and
650
+ `MLX_BUN_TEST_SLIDING_GRAMMAR_FILL_WINDOW` (at least 4) and builds the same custom
651
+ graph as the rotating-join and continuation consumers. The reference is this
652
+ tree's direct token-by-token generation; the gateway runs its placement path
653
+ (plan, `methodRequest` or row sampling, the binding's group), with every case past
654
+ the window:
655
+ - Grammar (JSON schema, choice, EBNF): at B1, tokens, every forward's IDs and
656
+ valid K/V, and every projection's complete logits. At B2, prepared together
657
+ or joining late, each row's tokens.
658
+ - With the composition's `grammarJump` on: the speculative group verifies the matcher's
659
+ forced string instead of committing it. Tokens equal direct token-by-token
660
+ generation at B1 and B2, rejected proposals leave the ring, and proposals are
661
+ both accepted and rejected.
662
+ - Supplied strict and echo fill is applied through the shared fill binding and
663
+ by direct `generate`: strict rows restate the fill-off control, and echo and
664
+ scripted proposals (one accepted, one rejected after the ring wrapped) are
665
+ verified, so output equals the control while the session records injected and
666
+ accepted tokens. B2 pairs start together and late (a strict row joining beside
667
+ a wrapped echo row, whose first multi-position verify forward reads a ring the
668
+ joiner's one in-place write has rotated).
669
+ - For each of these shapes: a B2 row cancelled at its third token beside a
670
+ surviving peer, then reuse of the drained group.
671
+
672
+ Run it with
673
+ `MLX_BUN_TEST_SLIDING_GRAMMAR_FILL_MODEL=/Llama-3.2-3B-Instruct-4bit MLX_BUN_TEST_SLIDING_GRAMMAR_FILL_WINDOW=8 bun --no-env-file test packages/inference/tests/parity/sliding-grammar-fill.test.ts`.
674
+
675
+ The [padded-prefill test](tests/parity/padded-prefill-model.test.ts) takes
676
+ `MLX_BUN_TEST_PADDED_PREFILL_MODEL` and `MLX_BUN_TEST_PADDED_PREFILL_REFERENCE`.
677
+ The external JSON report is `{ runtime, configSha256, rows }`, where `rows` is
678
+ the output of the unchanged Python body in
679
+ [`02d723a:tests/parity/padded-prefill-model.test.ts` lines 9–56](https://github.com/joshuarossi/mlx-bun/blob/02d723a/tests/parity/padded-prefill-model.test.ts#L9-L56): prompts, padding side, chunk counts,
680
+ recurrent-state hashes, four per-row logit hashes, and row offsets. The test
681
+ requires all 18 cases (36 with `MLX_BUN_TEST_PADDED_PREFILL_WIDE=1`), then compares
682
+ the full-vocabulary float32 logit slice at the last prompt position for each row
683
+ and each of three continuation steps via SHA-256, plus recurrent state.
684
+ `runtime` is the MLX core version (for example, `0.32.2`), not the mlx-lm version. Generate the reference outside this repository on the same machine with
685
+ the pinned oracle and identical weights; this test never starts Python.
686
+ `MLX_BUN_TEST_PADDED_FULL_LAYOUT=affine|turbo`,
687
+ `MLX_BUN_TEST_SPECULATIVE_ROTATING_LAYOUT=1`, or
688
+ `MLX_BUN_TEST_PADDED_ROTATING_LAYOUT=1` exercise the corresponding pre-conversion
689
+ cache layouts against the same plain-KV reference, not quantized arithmetic.
690
+
691
+ The [Gemma2 batching test](tests/models/universal/gemma2-batching.test.ts) runs
692
+ synthetic mask checks with `MLX_BUN_GEMMA2_NATIVE=1`; add
693
+ `MLX_BUN_GEMMA2_MODEL=/cached/gemma2` for real B1/B2/B4 continuous lifecycle checks.
694
+ Supplying a model or reference without the native opt-in, a reference without a
695
+ model, or an invalid model config fails before native initialization.
696
+ `MLX_BUN_GEMMA2_REFERENCE=/external/report.json` adds full-logit and KV hashes
697
+ against same-shaped pinned Python runs. The reference uses upstream Gemma2
698
+ and `BatchKVCache`, adding only a GQA axis to the upstream row mask. B1 is
699
+ compared exactly with direct execution; B>1 uses the same-shaped reference,
700
+ since quantized matmul dispatch can change numerical results across batch sizes.
701
+ The app gateway's real Gemma2 placement test uses the same
702
+ `MLX_BUN_GEMMA2_NATIVE=1` opt-in: importing the graph loads MLX even though
703
+ that test only checks placement and allocates no tensors. Native-blocked CPU
704
+ runs skip this check; synthetic gateway tests remain CPU-only.
705
+
706
+ With the composition's `grammarJump` on, Gemma2 plain-KV ordinary requests preserve
707
+ main's direct forced spans through the shared scheduler: one graph append for
708
+ `[pending, ...forced]`, with no sampling of forced IDs. Graphs already using
709
+ verified grammar proposals retain that behavior. Paired greedy Gemma2-2B checks
710
+ against main passed a terminating choice and nonterminal JSON schema, including
711
+ full-vocabulary logits, all 26 valid KV layers, masks and publication order.
712
+ Separate real-gateway checks passed callback stop inside a committed span,
713
+ retention and restoration of the complete prefix, fixed-suffix continuation,
714
+ mid-span cancellation with an active peer, and same-scheduler recovery. Graph
715
+ calls remain B1; this is not stacked batching or an all-model qualification.
716
+ The shared gateway owns prefix transfer and, as on main's shared path, returns
717
+ an empty `stats.cacheTokens`; direct generation retains its exact cache history.
718
+ No full-stats, HTTP cancellation, abort-last/abort-plus-stop, oracle or performance
719
+ identity is claimed by these checks.
720
+
721
+ Shared Gemma2 qualifies plain KV, including grammar-constrained and adapter
722
+ requests; the opt-in [constrained Gemma2 test](../../apps/mlx-bun/tests/engine/gemma2-constrained.test.ts)
723
+ compares them exactly with direct generation. Plain-KV fill uses the shared fill
724
+ binding: with `MLX_BUN_GEMMA2_MODEL`, the batching test replays main's fill
725
+ (one-position assert appends, one verify forward per echo span, trim on rejection)
726
+ and requires B1 logits bit for bit, then B2/B3/B4 tokens through joins and
727
+ cancellation, observing each capacity as the batch's high water. Fill also runs
728
+ inside a request's adapter context; the constrained test's grouped case checks
729
+ four-row adapter groups, partition from base rows, an event-driven join and a
730
+ replacement, a cancellation against a same-count stop control, and no leak. Plain-KV
731
+ generation continuation uses the shared continuation binding once persistence is
732
+ configured; the opt-in [Gemma2 continuation test](../../apps/mlx-bun/tests/engine/gemma2-continuation.test.ts)
733
+ runs it through the app's serving composition: interval snapshots, a durable
734
+ record surviving cancellation, its restore after a fresh engine, and cleanup on
735
+ completion. Plain-KV two-model speculation uses the shared speculative group
736
+ with a second loaded instance as the draft; the batching test's opt-in two-model
737
+ case checks B1 determinism, main's gate that greedy grammar with speculation
738
+ equals greedy grammar alone, a ragged joined group (its measured target batch),
739
+ cancellation, and a follow-on request that reproduces the fresh B1 run. N-gram
740
+ lookup drafting uses the same group; its opt-in case observes rounds where a
741
+ proposing row verifies beside an empty, right-padded peer and rounds where no row
742
+ proposes, swaps the padded peer's content without changing the target's forwards,
743
+ proposals, tokens or logits (greedy and seeded), cancels a ragged third row against
744
+ a same-count stop control, and checks main's grammar gate. Draft providers whose
745
+ rows tap target hidden layers keep a typed refusal (this graph declares no taps);
746
+ KV schemes and paging are its composition's legos, read through the softcapped
747
+ read above.
748
+ Its full-attention policy remains the pinned mlx-lm policy documented in the
749
+ [architecture descriptor](src/models/universal/archs.ts).
750
+ A plain-KV adapter request with a configured TwoModel or Ngram draft uses
751
+ ordinary continuous decoding and ignores both draft and fill, as main did.
752
+ This is ordinary decoding, not adapter speculation support. Paired B1 greedy
753
+ acceptance against main passed with a synthetic nonzero adapter on Gemma2-2B:
754
+ full logits and valid KV match with either draft configured, with or without
755
+ fill and logprobs; draft and fill stay unused, and physical unmount restores
756
+ the base computation. This does not qualify multi-row adapter/draft requests.
757
+
758
+ A plain-KV request without adapters keeps two-model or n-gram speculation when
759
+ fill is supplied, but ignores the fill session (strict, verify or echo), as main
760
+ did. Logprobs instead select ordinary decoding and ignore both draft and fill.
761
+ Paired B1 greedy acceptance on Gemma2-2B matched main and each fill-off control:
762
+ full target/draft logits, valid KV, proposals, continuing commits, output and
763
+ logprobs; genuine drafting ran when selected, and every fill hook stayed unused.
764
+ Main omits its terminal speculative commit; the candidate's terminal state was
765
+ checked against its verified prefix, without a main-terminal parity claim.
766
+ This does not qualify B>1, external-oracle parity or performance, or enable
767
+ speculative fill.
768
+
769
+ The [Trellis variant test](tests/parity/trellis-shared-m.test.ts) takes
770
+ `MLX_BUN_TEST_TRELLIS_MODEL=/cached/packed-qwen`. It preserves main's variant
771
+ comparison: `MLX_BUN_TRELLIS_AB_VARIANT` (default 7) against
772
+ `MLX_BUN_TRELLIS_AB_BASELINE` (default 6). Use variant 13 against baseline 12
773
+ for the optimized expansion path. `TrellisLinear` passes only variant 13, so the
774
+ test runs the generic Qwen3.5 graph with MLPs it builds over the loaded weights,
775
+ each passing its variant explicitly. M=1–5 compares complete logits, recurrent/KV
776
+ state and continuation; variant 13 also checks M=16/128/512 with the last-position
777
+ head. Six alternating-order blocks also screen the last-position head at
778
+ M=1–5/8/9/16, M=32 for variants 11–13, and M=128/512 for variant 13.
779
+ These are exact within-artifact kernel-variant checks, not a Python oracle
780
+ or timing claim.
781
+
782
+ For variant 13, the `applegpu_g13s` single-row bf16 gate/up kernel at
783
+ 5120 → 17408, matching 2/3/4-bit widths, T=256 and L=12 shares a 16 KiB
784
+ threadgroup codebook across sixteen output rows. Its values retain the unrefined
785
+ float32 decoder contract, scale multiplication, accumulation order and activation
786
+ tail. At the same qualified single-row bf16 geometry, mixed-width gate/up and the
787
+ 2/3/4-bit down kernels construct the decoder's integer value through exact
788
+ float32 mantissa bits before the unchanged reciprocal multiply. This preserves
789
+ scale multiplication and reduction order. The qualified down kernels also
790
+ generate a 16 KiB float32 threadgroup codebook, using 128/512/256 threads for
791
+ 2/3/4-bit weights respectively. All 128 partial sums and their reduction order
792
+ remain unchanged. Other shapes and shared multi-row kernels keep their existing paths.
793
+
794
+ These representations are required kernel arguments: `fusedGateUpSwiglu`'s
795
+ `codebook`, `fusedGateUpSwigluMixed`'s `exactBits` and `trellisScatter`'s
796
+ `ScatterRepresentation` (`bits` for the balanced 3-bit down, `genericBits` for
797
+ 2/4-bit down, `floatCodebook`). `qualifiedRepresentations(architecture)` turns
798
+ them all on for `applegpu_g13s` and off elsewhere; `TrellisLinear` resolves it
799
+ once at construction and passes the stored result. Passing `false` selects the
800
+ original inline decoding and conversions for A/B runs; disable the float codebook
801
+ as well when comparing the down representations.
802
+ The [kernel test](tests/kernels/trellis-gateup-codebook.test.ts) checks all 4096
803
+ codebook values, all 1021 possible integer decoder values and fused outputs;
804
+ the [artifact test](tests/parity/trellis-gateup-codebook.test.ts)
805
+ uses `MLX_BUN_TEST_TRELLIS_MODEL` to compare complete finite logits, live cache state
806
+ and greedy continuation, running the generic Qwen3.5 graph with MLPs that pass each
807
+ representation explicitly. The [standard-request benchmark](../../apps/mlx-bun/scripts/bench-trellis-gateup.ts)
808
+ provides `--help`, requires local weights and an external output path, and runs
809
+ one fixed prompt with 256 greedy tokens and MTP off.
810
+
811
+ Run each file with `bun --no-env-file test <file>` and exclusive GPU access.
812
+ Unset required paths skip before native imports; supplied invalid model paths
813
+ or a partial padded-prefill opt-in fail. Open acceptance work lives in
814
+ [PLAN](../../PLAN.md#verify-the-migrated-library).
815
+ They extend rather than repeat [#61](https://github.com/joshuarossi/mlx-bun/pull/61)
816
+ (MiniCPM/Gemma plain and mixed state restore; Trellis against main),
817
+ [#62](https://github.com/joshuarossi/mlx-bun/pull/62) (Gemma e4b window-wrap state
818
+ restore), and [#64](https://github.com/joshuarossi/mlx-bun/pull/64) (compiled
819
+ Gemma e4b/12B). Those completed comparisons do not cover scheduler sampling
820
+ recovery, padded cohorts, or this real-model Trellis variant matrix.
821
+
822
+
823
+ ## Direct library use
824
+
825
+ See [Qwen3 loading and generation](examples/qwen3-generate.ts). Run it from the
826
+ repository root with `bun packages/inference/examples/qwen3-generate.ts <checkpoint-directory> "Hello"`.
827
+ It loads the local tokenizer, streams tokens from the supplied graph, and releases
828
+ weights after generation completes.
829
+
830
+ Use the graph matching your checkpoint, or compose your own operations and supply
831
+ an explicit binding. The root export contains common loading and generation
832
+ helpers; the subpaths below expose individual components.
833
+
834
+ ## Trellis weight expansion
835
+
836
+ See the runnable [Trellis expansion example](examples/trellis-expand.ts). Its
837
+ `expandWeights` function borrows codes and scales and returns an owned lazy tensor.
838
+ `bun packages/inference/examples/trellis-expand.ts` demonstrates the layout with
839
+ small generated inputs, without a model download.
840
+
841
+ The caller supplies uint32 packed codes, one floating-point scale per stored
842
+ row, and matching geometry. The eligibility helper checks the kernel's supported
843
+ geometry and output dtype; callers remain responsible for matching tensor
844
+ shapes and dtypes. This kernel supports the existing 1MAD codebook with L=12,
845
+ 256-symbol blocks, and 2-, 3-, or 4-bit codes. Output is the stored matrix in
846
+ bfloat16, shaped `[rows, cols]`. For axis 0, that is the transposed weight layout.
847
+
848
+ The existing interleaved 3-bit layout uses `blockInterleave: 2` and codes shaped
849
+ `[cols / 512, rows, 48]`. Row-major codes use `[rows, cols * k / 32]`.
850
+
851
+ Expansion is lazy. The caller decides when to evaluate the result and owns its
852
+ array handle. The kernel does not load checkpoints or choose a model.
853
+
854
+ ## Packed operations
855
+
856
+ All operations are imported from `@mlx-bun/inference/kernels/trellis`.
857
+ They borrow input arrays and return an owned, lazy output array.
858
+
859
+ | Operation | Input and purpose |
860
+ | --- | --- |
861
+ | `trellisReduce(x, codes, scales, geometry, variant)` | Axis-1 projection; `x` is `[M, inFeatures]`, M=1..4 |
862
+ | `trellisScatter(x, codes, scales, geometry, variant, useSharedScatterCodebook, representation)` | Axis-0 projection; same input shape and row budget |
863
+ | `fusedGateUpSwiglu(x, gate, up, variant, codebook)` | Matching axis-1 gate/up geometry and bit width; preserves leading input dimensions, with 1..4 total rows |
864
+ | `fusedGateUpSwigluMixed(x, gate, up, variant, tail, exactBits)` | Matching axis-1 geometry with independent bit widths; `tail` is `"fused"` or `"split"` |
865
+ | `gateUpReduceRow(x, gate, up, variant, codebook)` / `gateUpReduceRows(x, gate, up, variant)` | `fusedGateUpSwiglu`'s one-row / 2..4 shared-row kernel alone |
866
+ | `scatterRow` / `balancedScatterRow(x2, codes, scales, geometry, variant, representation)` | `trellisScatter`'s one-row kernel alone; balanced takes 3-bit codes at T 256, the other every other width |
867
+ | `scatterRows(x2, codes, scales, geometry, variant)` / `balancedScatterRows(..., sharedCodebook)` | `trellisScatter`'s 2..4 shared-row kernels alone, the same split |
868
+ | `expandTrellis(codes, scales, geometry, dtype, variant)` | General expansion, and variant 13's vector form |
869
+ | `tiledTrellisPrefill(x, codes, scales, geometry)` | Axis-1 packed prefill, M=5..32 |
870
+ | `splitKTrellisPrefill(x, codes, scales, geometry)` | Axis-0 packed prefill, M=5..8 |
871
+ | `wideTrellisPrefill(x, codes, scales, geometry)` | Axis-1 prefill following MLX's GemvWide arithmetic, M=5..15 |
872
+
873
+ `gate` and `up` are `TrellisWeights`: `{ codes, scales, geometry }`.
874
+ Callers supply compatible tensors and choose the variant explicitly. Existing
875
+ variant numbers are preserved: 6 uses the original packed f32 code/scale path;
876
+ 13 adds the shared-work and vector-expansion forms. Variant 4 is
877
+ the inherited timing-only path and does not compute decoded weights correctly.
878
+ No application environment flags are read by these operations.
879
+
880
+ For prefill, use the corresponding `*Eligible(geometry, rowCount, dtype)` helper
881
+ to preserve the existing dispatch profile; the wide and tiled helpers also take
882
+ `widePrefill` from `qualifiedRepresentations(architecture)`, which a caller
883
+ resolves once. Wide prefill additionally requires caller-proven aligned,
884
+ row-contiguous input. These profiles and numerical
885
+ contracts are unchanged; they are not universal dispatch rules for every shape.
886
+
887
+ Single-purpose kernels from the same entry point: each runs one algorithm for
888
+ one row form and one code layout, takes no options beyond the mixed-width
889
+ SwiGLU tail, and checks its inputs. The factored kernels decode each weight's
890
+ code value y and apply the row scale (with 1/147.8) once per output, or fold it
891
+ into each input row's activation; the matrix kernels run on the simdgroup matrix
892
+ unit. Both reassociate relative to variant 6 and agree with it to bf16
893
+ precision, not bit for bit. The codebook kernels look each code value up in a
894
+ threadgroup table and keep variant 13's one-row output bits.
895
+
896
+ | Operation | Input and purpose |
897
+ | --- | --- |
898
+ | `gateUpFactoredRow(x, gate, up)` / `gateUpFactoredRows(x, gate, up)` | Same-width axis-1 gate/up/SwiGLU; one row / 2..4 rows sharing each decoded weight |
899
+ | `mixedGateUpFactoredRows(x, gate, up)` | Different-width gate/up, 1..4 rows; MLX's compiled-swiglu tail |
900
+ | `downFactoredRow` / `downFactoredRows(x2, codes, scales, geometry)` | Axis-0 projection over row-major codes; one row / 2..4 rows |
901
+ | `downK3InterleavedFactoredRow` / `downK3InterleavedFactoredRows(...)` | The same over 3-bit block-interleaved codes (T 256, L 12) |
902
+ | `gateUpMma(x, gate, up)` | Gate/up/SwiGLU for 1..8 rows; widths may differ |
903
+ | `gateUpCodebookRow(x, gate, up, tail)` | One-row gate/up/SwiGLU through a threadgroup codebook; widths may differ; the reduce kernels' bits |
904
+ | `downCodebookRow` / `downK3InterleavedCodebookRow(x2, codes, scales, geometry)` | One-row axis-0 projection through a threadgroup codebook, row-major 2/4-bit / 3-bit interleaved codes; the scatter kernels' bits |
905
+ | `downMma` / `downK3InterleavedMma(x2, codes, scales, geometry)` | Axis-0 projection for 1..8 rows, row-major / 3-bit interleaved codes |
906
+ | `gateUpMmaWide(x, gate, up)` | Gate/up/SwiGLU for any number of rows, each decoded fragment feeding several 8-row blocks (threadgroup codebook, activations staged in threadgroup memory); each row the bits `gateUpMma` gives its block of 8 |
907
+ | `downMmaWide` / `downK3InterleavedMmaWide(x2, codes, scales, geometry)` | Axis-0 projection for any number of rows through a threadgroup codebook, each decoded fragment feeding up to three 8-row blocks in the pass the rows plan (`downMmaWidePass` / `downK3InterleavedMmaWidePass` take the pass, `DOWN_ONE_BLOCK` for 1..8 rows); each row the bits `downMma` gives its block of 8 |
908
+ | `gateUpCodebookMma(x, gate, up)` | Gate/up/SwiGLU for 1..8 rows, each code value read from a threadgroup codebook (the M1 Max form); `gateUpMma`'s bits |
909
+
910
+ Outside the Trellis family, `kernels/quantization/affine{3,4}-{rows,mma}` run
911
+ 3- and 4-bit group-64 affine projections for 2..8 rows (shared decode) and
912
+ 1..8 rows (matrix unit), and `affine-split-mma` any number of rows of either width,
913
+ in blocks of 8, on the matrix unit with output tiles sharing each activation
914
+ fragment and K split across the threadgroup (the M1 Max form); `affine-row` is one
915
+ row of either width bit for bit MLX's one-row quantized matmul (its qmv's
916
+ arithmetic, each threadgroup computing the activation chunks once for 64 rows);
917
+ `kernels/attention/kvq4-decode` is one-row attention
918
+ over a 4-bit group-64 KV cache with head dim 256, reading each packed row once
919
+ for all query heads of its KV head, the tokens per threadgroup the caller's (512 on
920
+ the M4 Pro, 128 on the M1 Max); `kernels/attention/multi-query` is causal
921
+ flash decoding for 2..8 query rows over a BF16 cache. `kernels/ane` holds the
922
+ Neural Engine programs (`linear`) and the GPU kernels that fill their buffers
923
+ (`fill`). The layers in `layers/trellis-gate-up`, `layers/trellis-down`,
924
+ `layers/trellis-codebook-gate-up`, `layers/trellis-codebook-down`, `layers/affine-verify-linear`, `layers/kv4-decode-attention` and
925
+ `layers/ane-prefill-split` bind loaded weights to these kernels, one kernel per
926
+ layer.
927
+
928
+ ## Artifact, input, and layer APIs
929
+
930
+ - `@mlx-bun/inference/artifacts`: model configuration, safetensors metadata,
931
+ and lazy native weight loading through `Weights.open(directory)`. The caller
932
+ supplies the local artifact directory and any graph-specific weight view.
933
+ - `@mlx-bun/inference/artifacts/auxiliary-files`: `copyAuxFiles(source, output)`
934
+ copies tokenizer, template, and other non-weight checkpoint files when present,
935
+ without loading MLX.
936
+ - `@mlx-bun/inference/input`: `loadTokenizer(directory)` consumes the existing
937
+ Hugging Face tokenizer files; `ChatTemplate.load(directory)` loads the template.
938
+ - `@mlx-bun/inference/layers`: quantized linear and embedding layers, RMSNorm,
939
+ and `TrellisLinear`, which composes the standalone Trellis kernels at decode
940
+ variant 13 and retains the existing row-count dispatch and dense expansion.
941
+ `ExpandedTrellisLinear` is the 8-bit affine carrier a caller constructs explicitly.
942
+ - `@mlx-bun/inference/layers/lora`: inference-time LoRA state and weights.
943
+ - `@mlx-bun/inference/runtime/config`: immutable execution settings and scoped
944
+ overrides for the `MLX_BUN_*` keys the library still reads (process settings,
945
+ diagnostics and test opt-ins; the website's configuration reference lists each
946
+ read site). A model-level option is a composition field instead (above).
947
+
948
+ These modules do not download models or start services. Tensor handles returned
949
+ by `Weights.tensor()` are borrowed from the weights owner; release them through
950
+ `Weights.release()`, `releaseShard()`, or `dispose()`. Layer outputs are owned
951
+ by the caller.
952
+
953
+ ## Concrete graphs
954
+
955
+ Import a graph directly, provide its weights and configuration, and own its state:
956
+
957
+ See [explicit forward passes and state ownership](examples/qwen3-forward.ts).
958
+ Run `bun packages/inference/examples/qwen3-forward.ts <checkpoint-directory> "[1,2,3]"`
959
+ with token IDs from your checkpoint’s tokenizer. The example forwards the prompt,
960
+ selects a token, continues with the same cache, and disposes cache before weights.
961
+
962
+ The direct graph imports currently include `models/gemma4`,
963
+ `models/gemma4/graphs`, `models/minicpm5`, `models/qwen3`, `models/qwen3-moe`,
964
+ `models/qwen3_5`, `models/qwen38-27b-trellis-m4pro`, `models/qwen38-27b-trellis-m1max`,
965
+ and `models/universal`.
966
+ `models/glm52`, `models/diffusion-gemma`, and `models/whisper` provide the other
967
+ graph families.
968
+ `@mlx-bun/inference/models` exposes the profile/implementation registry, the
969
+ composition resolver and loading helpers: `createModel` loads the graph
970
+ its family's selection table (`MLX_GRAPH_TABLES`) recommends for the composition,
971
+ else the family's generic graph. Direct graph
972
+ constructors remain available. Runtimes
973
+ that plan memory before opening weights (streamed experts) are opened with
974
+ `openPlannedRuntime` from generic `RuntimeOpenOptions` and return a generic
975
+ `MemoryPlan` and telemetry; `planRuntimeMemory` (`models/memory-plan`, native-free)
976
+ returns the plan from artifact headers, or null for models without one. A graph
977
+ that carries its own draft head declares it (`GraphCapabilities.nativeDraft`), and
978
+ `DraftProviderRegistry.native(graph)` builds the provider.
979
+
980
+ `models/qwen38-27b-trellis-m4pro` and `models/qwen38-27b-trellis-m1max` are
981
+ separate written files for `mlx-bun/Qwen3.8-27B-Trellis-3.2bpw`. Each names all 64
982
+ layers and the literal projection and MLP forms for its row bindings. M4 Pro
983
+ keeps its factored row and matrix-unit kernels. M1 Max names its threadgroup
984
+ codebook forms at one row, codebook matrix-unit forms at two to eight rows,
985
+ wide matrix-unit forms at nine to 256 rows, and expanded GPU forms above that.
986
+ Its prompt projection forms also retain the measured dequantized matmuls.
987
+ Its one-row binding fuses the attention residual addition with the MLP's
988
+ normalization, the recurrent normalization with its float32 SiLU gate, and
989
+ the convolution's SiLU with Q/K normalization and scaling. These retain MLX's
990
+ intermediate dtype rounding and reduction order. The
991
+ [normalization tests](tests/kernels/normalization-fusion.test.ts) compare raw
992
+ and compiled forms bit for bit, including changing shapes and layer weights;
993
+ the [DeltaNet heads tests](tests/kernels/delta-heads.test.ts) cover the fused
994
+ activation, Q/K scales, V output, and strided convolution inputs.
995
+
996
+ The files declare the Neural Engine program ranges directly: M4 Pro uses
997
+ 640..1024, 1025..1536 and 1537..2048 positions; M1 Max uses fifteen capacities
998
+ from 256 to 2048, beginning at 224 positions. The scheduler indexes those ranges
999
+ for one-sequence pieces, and installation compiles the listed programs. Each
1000
+ file also states its measured verification costs and draft-gate settings.
1001
+
1002
+ The loader recommends a file from its artifact, device and module-configuration
1003
+ keys. These are defaults: callers can explicitly choose a differently tuned file,
1004
+ a portable or device-tuned generic family graph, or supply an ordinary load function to `openModel`'s
1005
+ `graph` option. A custom function needs no family registration and returns the
1006
+ operations its caller needs, including a graph with prefill and scoring alone.
1007
+ The code-authoring rules do not restrict those choices.
1008
+
1009
+ For explicit state and tensor operations, compose the cache legos with
1010
+ `composeGraphCaches(graph, configuration)` or use the loader-bound `makeCache`.
1011
+ Use `MlxPhaseDispatch` to index prompt ranges and exact-row `decode`, `verify`
1012
+ and serving `logits` tables. Chunk-specific ranges preserve a file's distinct
1013
+ chunk and tail operations when it declares them. Known appends use whole-span ranges, preserving
1014
+ arbitrarily long accepted grammar spans without imposing the verifier's row cap.
1015
+ Scoring heads, media and denoising consumers index their own declared ranges. Each file declares verification costs and known-span operations
1016
+ where measured. `examples/qwen3-forward.ts` runs a prompt and a decode through
1017
+ these public components. Dispose each cache and returned array when finished.
1018
+
1019
+ Shared dense/quantized layers, activations, normalization, and RoPE live under
1020
+ `layers/`; architecture assembly lives under `models/<family>/`. DeltaNet
1021
+ kernels remain independently importable through `kernels/delta`.
1022
+
1023
+ ## Native expert I/O
1024
+
1025
+ GLM's expert I/O support is built from `native/expert-io.c` with
1026
+ `bun run --filter @mlx-bun/inference build:native`. The resulting dylib lives in
1027
+ `dist/native/` and is included in the package archive. `prepack` rejects missing
1028
+ artifacts. An existing build can be staged with `bun run stage:native <directory>`
1029
+ inside this package. Loading uses the bundled library, or the caller's explicit
1030
+ `libraryPath` / `MLX_BUN_EXPERT_IO_DYLIB` override.
1031
+
1032
+ `artifacts/experts` owns native I/O bindings, residency, and usage accounting;
1033
+ `artifacts/glm52` owns direct-container and quantized-weight loading. Numeric
1034
+ streamed expert kernels are independently available through `kernels/glm52`.
1035
+
1036
+ ## State and attention
1037
+
1038
+ `@mlx-bun/inference/state` exposes the cache legos (plain, affine, rotating,
1039
+ TurboQuant, recurrent, paged and GLM's latent caches, each owning its storage and
1040
+ its read), their row layouts, KV maintenance, the cache service, cloning, and
1041
+ persistence. The caller creates and owns the state.
1042
+ `@mlx-bun/inference/contracts` holds the shared cache and ownership interfaces.
1043
+ A rotating cache's `temporalView()` is its live window: the newest
1044
+ `min(offset, maxSize)` positions in chronological order, including right after a
1045
+ multi-token write leaves the ring oversized. A request's own rotating cache and
1046
+ its batched, speculative and aligned layouts select the same window.
1047
+ A single solo rotating row adopted into a shared layout keeps its physical
1048
+ columns, ring phase and offset, so it attends over its keys in the solo cache's
1049
+ order and later writes land where the solo cache's would; several rows are
1050
+ aligned in temporal order behind their left padding. `extractRow`, like mlx-lm's
1051
+ `BatchRotatingKVCache.extract`, and persistence store a row in temporal order,
1052
+ so a restored wrapped ring starts a new physical phase: its continuation can
1053
+ differ from the live row's, in main as well. RAM and disk restores agreed in the
1054
+ checks below.
1055
+
1056
+ - `state/`: storage layout, positions, row membership, precision transitions,
1057
+ snapshots, and persistence. `persistence.worker.js` performs CPU disk I/O.
1058
+ Every storage a graph's caches (or their precision maintenance, or paging) produce
1059
+ declares its three row layouts together in `state/layout`'s table, each implementing
1060
+ the `BatchableCache` port: `ownedCacheLayout` for ordinary continuous decoding,
1061
+ `prefillCacheLayout` for prefill cohorts and `targetCacheLayout` for verification
1062
+ rows, which hold the speculative transaction (`SpeculativeRowLayout`). The
1063
+ [cache contract test](tests/state/cache-contract.test.ts) runs every lego through
1064
+ every read, these layouts, the transaction, snapshots and SSD. The
1065
+ batch group only calls `mergeRows`, `filterRows` and `extractRow` on it and names no
1066
+ storage family; the gate rejects `execution/` importing a concrete cache module.
1067
+ A request's own cache is a row source for its layout, not a batch: an adopted lone row is the
1068
+ first source of the layout's first `mergeRows`.
1069
+ - `layers/quantized-attention.ts`: the affine attention kernels (mlx-lm's unfused
1070
+ port, OptiQ's tiled port, the folded and grouped-heads forms). An affine lego
1071
+ takes its kernel set at construction (`state/affine-attention`: unfused or
1072
+ tiled-causal as the composition names, or the opt-in always-tiled set).
1073
+ - `layers/folded-attention.ts`: bf16 causal windows of up to eight queries that MLX's
1074
+ fused SDPA hands to its unfused fallback (more than 32 query vectors per KV head),
1075
+ with GQA folded into rows: that fallback bit for bit, each KV head's keys and values
1076
+ read once instead of once per query head. A bf16 storage reads its windows with the
1077
+ read its composition names (`KVCache.windowRead`: `sdpaRead`, or `foldedWindowRead`),
1078
+ and its row layouts and clones with the same.
1079
+ - `kernels/turboquant/`: packing, rotation, codebooks, and packed decode kernels.
1080
+ - `kernels/delta/gated.ts`: DeltaNet kernels; recurrent storage and its
1081
+ `recurDecode`/`recurWindow` live in `state/ssm.ts`. The training cache
1082
+ (`TrainingSSMCache`, the training forward) implements the same two calls with
1083
+ a backward attached that differentiates an ops recomputation of the recurrence.
1084
+ - `state/paged/`: the paged lego (`--paged-kv`) and its persistence codec.
1085
+ The numerical attention implementation lives in `kernels/attention/paged.ts`.
1086
+
1087
+ Public kernel imports are `@mlx-bun/inference/kernels/turboquant`,
1088
+ `@mlx-bun/inference/kernels/delta`, and
1089
+ `@mlx-bun/inference/kernels/attention/paged`. Paged state is available through
1090
+ `@mlx-bun/inference/state/paged`.
1091
+
1092
+ The opt-in [Gemma4 paged test](tests/parity/gemma4-paged.test.ts) takes
1093
+ `MLX_BUN_TEST_PAGED_MODEL` (any Gemma4 artifact) and places requests through the
1094
+ gateway binding's plan, state policy and execution group. Gathered bf16 pages must
1095
+ equal plain KV at B1 and B3 with blocks 16 and 256 (tokens, every sampled vector,
1096
+ each retired row's valid K/V), the bit-exact contract. Over bf16, KV4 and KV8
1097
+ pages it also requires the direct reader to serve decode, each call within 2^-4
1098
+ of the gathered reader's output scale over the same query and pages (Lab
1099
+ numerics; greedy tokens are reported, not compared, since that difference can
1100
+ flip a low-margin step), abort-versus-stop survivor identity with recovery on
1101
+ the drained group, and RAM then fresh-SSD-store reuse of pages of the same
1102
+ encoding and reader. It passes on Gemma4 E4B and 12B. Run it from the root with
1103
+ `MLX_BUN_TEST_PAGED_MODEL=/gemma4/snapshot bun --no-env-file test packages/inference/tests/parity/gemma4-paged.test.ts`.
1104
+
1105
+ ## Source ownership
1106
+
1107
+ All kernel files below live under `src/kernels/trellis/`.
1108
+
1109
+ | File | Responsibility |
1110
+ | --- | --- |
1111
+ | `codebook.ts` | Shared 1MAD Metal helpers, host LUT, and decoder variant mapping |
1112
+ | `geometry.ts` | Trellis geometry and borrowed weight types |
1113
+ | `reduce.ts` | Axis-1 packed matvec and its shared-row variant |
1114
+ | `scatter.ts` | Axis-0 packed matvec, balanced/shared variants, and partial reduction |
1115
+ | `gate-up.ts` | Same-width fused gate/up SwiGLU |
1116
+ | `mixed-gate-up.ts` | Independent-width gate/up and its two activation tails |
1117
+ | `expand.ts` | General stored-matrix expansion and vector dispatch |
1118
+ | `vector-expand.ts` | Four-weight vector expansion |
1119
+ | `tiled-prefill.ts` | Axis-1 tiled prefill |
1120
+ | `splitk-prefill.ts` | Axis-0 split-K prefill and ordered reduction |
1121
+ | `wide-prefill.ts` | GemvWide-compatible prefill and hardware eligibility |
1122
+ | `index.ts` | Public Trellis imports |
1123
+
1124
+ Tests in `tests/kernels/` cover the host decoding reference, variant equivalence,
1125
+ activation tails, packing, strides, and existing prefill comparisons against MLX.
1126
+ The wide-prefill native comparison runs only on supported hardware.
1127
+
1128
+ The MLX runtime libraries belong to `@mlx-bun/mlx`; this package bundles its
1129
+ expert I/O and video helpers. Set up
1130
+ that package's native artifacts, then run `bun run typecheck` and `bun run test`
1131
+ from the repository root.
1132
+
1133
+
1134
+ ## Sampling, embeddings, and adapters
1135
+
1136
+ `@mlx-bun/inference/sampling` exposes `makeSampler`, `makeLogitsProcessors`,
1137
+ `makeStepSampler`, and the individual top-p/top-k/min-p/XTC and tone-curve
1138
+ operations. `sampling/types.ts` owns options; `filters.ts`, `processors.ts`,
1139
+ `hlg.ts`, and `curve.ts` own transformations; `step.ts` owns token history and
1140
+ per-step sampling; `extras.ts` owns captured log-probability readback and disposal.
1141
+ The caller supplies scores and chooses options. Returned arrays belong to the caller.
1142
+
1143
+ `sampling/grammar` compiles constraints against the caller's loaded tokenizer
1144
+ using the existing xgrammar dependency. Await `ready()`, apply the mask to logits,
1145
+ accept the sampled token, and dispose the controller when finished. The normalized
1146
+ argmax and token-bitmask kernels are exposed through `kernels/sampling`.
1147
+
1148
+ `embeddings` provides the pooled-embedding helpers: `embedOne`, `embedMany`,
1149
+ `embeddingTerminatorId` and `withInstruction`. Any graph declaring `embeddings` and
1150
+ providing `embedPooled` works; the pooling token comes from the graph's profile
1151
+ (`embeddingDeclarationFor`, resolved with the tokenizer). Supply the graph and tokenizer explicitly. `adapters`
1152
+ provides `AdapterManager` for loading and applying existing mlx-lm and PEFT LoRA
1153
+ artifacts to a caller-owned graph; adapter weight/state types are also exported.
1154
+
1155
+ ## Generation
1156
+
1157
+ The [generation example](examples/qwen3-generate.ts) iterates over emitted tokens
1158
+ and returns the final generation statistics. It uses the same public generation
1159
+ API available through the root and `@mlx-bun/inference/generation`.
1160
+
1161
+ `generate` uses the graph supplied by the caller. `generateAutoregressive` accepts
1162
+ an explicit `MlxAutoregressiveBinding` from `generation/bindings/autoregressive`, including
1163
+ caller-defined graph operations, state construction, and optional compiled decode.
1164
+ `generateDenoising` accepts a denoising binding; `generation/diffusion` also exposes
1165
+ `denoiseSync` and `denoiseAsync` directly. No service or model selection is involved.
1166
+
1167
+ `generation/autoregressive.ts` owns prefill and token iteration;
1168
+ `generation/diffusion.ts` owns canvas denoising; `generation/result.ts` owns the
1169
+ async iterator and final stats. `generation/scopes.ts` owns adapter,
1170
+ wired-memory, and expert-usage lifetimes. Existing cancellation and early-return
1171
+ cleanup behavior is preserved. `generation/fill` exposes the existing optional
1172
+ fill session and proposal interfaces.
1173
+
1174
+ ## Speech and vision
1175
+
1176
+ - `input/audio`: WAV parsing, AudioToolbox decoding, transcoding, mel features,
1177
+ and the existing Whisper tokenizer. `loadWhisperTokenizer` reads local HF
1178
+ artifacts; it does not download them.
1179
+ - `transcription`: Whisper decoding, long-form and streaming transcription,
1180
+ word timing, and text/SRT/VTT formatting. Callers supply the Whisper graph,
1181
+ tokenizer, and audio samples. `transcription/format` exposes the
1182
+ text/SRT/VTT/verbose-JSON formatters alone, without loading native MLX, for
1183
+ HTTP layers that only shape responses.
1184
+ - `models/audio/conformer`: the audio encoder graph. `transcription/silero-vad`:
1185
+ the voice-activity gate transcription runs before Whisper.
1186
+ - `input/vision`: image decoding/preprocessing, multimodal prompt assembly,
1187
+ and video frames. Qwen preprocessing and prompt assembly have separate
1188
+ `input/vision/qwen3vl` and `input/vision/qwen3vl-prompt` imports.
1189
+ - `models/vision/siglip`, `models/vision/qwen3vl`, and `models/vision/unified`:
1190
+ concrete vision encoders.
1191
+
1192
+ The AVFoundation frame extractor is built by `build:native` and bundled beside
1193
+ expert I/O in `dist/native`. It needs no runtime download or compilation.
1194
+ `MLX_BUN_FRAME_EXTRACT` remains an explicit override. AudioToolbox and `afconvert`
1195
+ use macOS system facilities. Optional encoder caching lives in `state/encoder-cache`;
1196
+ media fetching keeps the existing destination, size, and timeout controls.
1197
+ `MediaFetchPolicy.allowPrivate` permits LAN destinations; callers may supply
1198
+ `privateDestinationHint` when their UI needs a specific configuration instruction.
1199
+
1200
+ ## Speculative generation
1201
+
1202
+ `bun --no-env-file scripts/bench-mtp.ts --help` describes the paired MTP
1203
+ performance runner. It consumes this package's production batch-one grouped
1204
+ method with local target and companion artifacts, pins their content, warms
1205
+ each proposal depth, and reverses variant order on alternating repetitions.
1206
+ Reports stay outside the checkout. It records accepted draft proposals and
1207
+ delivered tokens separately: depth two permits at most two accepted drafts,
1208
+ while a fully accepted round can deliver three tokens including its bonus.
1209
+ The optimization score is total delivered tokens divided by total step time;
1210
+ accepted drafts per step time, request/first-token/decode time, per-position
1211
+ acceptance, peak MLX memory and output token identity are also recorded.
1212
+
1213
+ Separate diagnostic runs report draft, verification, sampling and commit wall
1214
+ time. They disable device-first overlap and evaluate retained state through
1215
+ the target and Qwen MTP materialization hooks, including commit work that would
1216
+ otherwise execute in the next round. Their throughput is not a production
1217
+ performance estimate. These checks cover plain KV, greedy sampling and B1;
1218
+ kernel candidates still need their numerical/state gates and serving checks.
1219
+
1220
+ The standalone `scripts/bench/build-q4-native.sh` probe builds an offline Metal
1221
+ library and a native MLX `Primitive` with `output_shapes`, bypassing
1222
+ `fast.metal_kernel`'s shapeless limitation. It reads packed affine Q4 directly
1223
+ and shares decoded weights across verification rows. This is a lab experiment,
1224
+ not a runtime dispatch choice or a Trellis decoder. Its exact Q4-to-half and
1225
+ BF16-to-FP32 conversions do not establish reduction-order parity; the executable
1226
+ reports equality and error against MLX separately from projection timings.
1227
+ Build inputs must match the installed MLX version. The script's usage lists
1228
+ the required MLX source, Metal C++ headers, native libraries and external output
1229
+ directory. Offline compilation needs Xcode's Metal Toolchain component;
1230
+ `--runtime-compile` instead uses macOS's compiler through the native Metal
1231
+ backend, still without `fast.metal_kernel`. The probe checks all 16 Q4 codes
1232
+ and all 65,280 finite BF16 bit patterns before measuring projections. Append
1233
+ `mma` to the executable's arguments to measure its register-only matrix-product
1234
+ candidate instead of its shared-weight scalar reduction. Both are experiments;
1235
+ the current runtime dispatch is unchanged.
1236
+
1237
+ `generation/speculative` exposes the draft contract and the assistant, two-model,
1238
+ Qwen/GLM MTP, DFlash, DFlash 2, DeepSpec, and n-gram proposal providers. A provider
1239
+ drafts only through its grouped rows (`DraftProvider.grouped`), in the gateway's
1240
+ speculative group; one request is a group of one row, and there is no per-request
1241
+ draft source. DFlash 2 (`incoai/Qwen3.8-27B-DFlash2`) drafts a whole block in one pass from
1242
+ five tapped target layers, with
1243
+ its projections quantized at load to the width the registry passes (4 bits). `bindMlxGateway(model, { provider,
1244
+ numDraftTokens, adaptiveDepth })` drafts `numDraftTokens` every round, or with
1245
+ `adaptiveDepth` chooses each round's count (up to that, none included) from the decayed
1246
+ acceptance at each draft position and the graph's declared `verifyRoundCosts`, weighing a
1247
+ round of B sequences by the rows it verifies, B(k + 1), against a step without drafts over
1248
+ the same B rows (past the widest measured forward, at that forward's cost per row). The app
1249
+ passes `numDraftTokens` as the drafter's own width, or, for an adaptive drafter of a kind
1250
+ the graph lists in `adaptiveDraftWidths` (the M1 Max graph: an MTP head, 4), the width
1251
+ measured there; the composition's `draftDepth` records the drafter's own. A round
1252
+ without drafts is the graph's decode step, pipelined as plain decode is: built from the
1253
+ previous step's samples on the device before they are read, its input dropped again
1254
+ (`bindRowStepRollback`) if that sample ends its row. A run of such rounds probes at
1255
+ intervals that double while drafting keeps not paying: a drafter whose rows draft without
1256
+ committing (`preview`: the DFlash 2 block, or the Qwen MTP head's first draft under a round of
1257
+ its own cache that it rolls back) proposes from a step's input and the steps that follow compare
1258
+ its proposals with their samples; any other drafter verifies one draft. A graph whose composition measured them
1259
+ declares the gate's constants per drafter kind (`adaptiveDraftGates`: the prior's acceptance and weight, the decay per
1260
+ round and per step without drafts, the margin, the probe intervals and depth; the M1 Max graph, for DFlash 2 and the
1261
+ MTP head, counts that keep 95% per observing round and 93% per step without drafts, DFlash 2 probing one draft deep);
1262
+ other graphs and kinds keep the gate's defaults. A target graph whose residual stream
1263
+ carries a TurboQuant R1 fold declares it on its draft target (`residualBasis`: the R1 seed and the
1264
+ final-norm gain the fold moved into the head); the drafter loads unrotated and applies it when a draft group binds.
1265
+ A graph whose composition measured them declares the kernels of a drafter's own 4-bit projections per width class
1266
+ (`draftProjections`; the M1 Max graph: the affine row kernel at two to three rows, the split matrix-unit kernel from
1267
+ four, MLX's quantized matmul at one); the DFlash 2 drafter builds them when its first group binds and
1268
+ otherwise keeps its own rule (the simdgroup matrix unit at five to eight rows, MLX's quantized matmul otherwise).
1269
+ A graph whose composition measured it declares the target's frequency-ranked draft vocabulary on its draft head
1270
+ (`DraftProjection.vocabulary`: the M1 Max graph, the artifact's `mtp/draft_vocab.json`, those rows of its head on the
1271
+ head's own layers, gathered when a drafter first reads them); DFlash 2 then projects onto those rows and selects among
1272
+ them, the target still verifying over its full head.
1273
+ A library caller speculates as the app does: `binding.plan` with `hasDraft`, then
1274
+ `binding.methodRequest` and `submit` on `binding.createBatchGroup({ maxBatch: 1 })`
1275
+ (`@mlx-bun/training/dspark`'s calibration runs this way).
1276
+ `generation/speculative/binding` exports the target operations the groups bind
1277
+ (`bindSpeculativeTargetModel`); they do not require a concrete model class.
1278
+ Each request's draft selection is resolved when the binding binds the request and
1279
+ keys its group: argmax under the composition's `greedyDraft`, else the draft vocabulary
1280
+ the provider declares for the target (`draftVocabulary`, the Qwen MTP companion's
1281
+ `draft_vocab.json`) when the request's draft sampler reproduces on it, else each
1282
+ row's sampler over the full head. A drafter's rows bind one selection operation
1283
+ when they open and their draft loop calls it; the assistant and DeepSpec drafters
1284
+ bind their head's selection at construction the same way.
1285
+ `generation/speculative/loader` (a file of `models/speculative`) loads a `dspark.json`
1286
+ checkpoint (`loadDsparkDrafter`) and also exports the drafter class and its config types,
1287
+ which `@mlx-bun/training/dspark` uses to produce those checkpoints.
1288
+
1289
+ `DraftProviderRegistry` (`generation/speculative/draft-registry`) is how a server
1290
+ selects and loads a draft: each provider kind registers `detect(artifact)` (files
1291
+ only, never MLX) and `load(request)`, and `defaultDraftProviders()` registers the
1292
+ built-in kinds (`dspark`, `deepspec`, `assistant`, `mtp`, `two-model` as the
1293
+ catch-all, `ngram` by name only). An application uses the default, extends a copy
1294
+ with `register`, or hands `loadContext` a ready provider (`draftProvider`); it never
1295
+ names a provider class, and the architecture gate rejects one there. A graph whose
1296
+ checkpoint carries its own draft head declares it as `GraphCapabilities.nativeDraft`
1297
+ (GLM-5.2's MTP row); `registry.native(graph)` binds the registered provider.
1298
+ Providers read the target through capability-named ports on `TargetView`
1299
+ (`assistantRows`, `hiddenLayerTaps`, `recurrentMtp`) and refuse a target lacking
1300
+ one with `target graph does not provide <port>`.
1301
+
1302
+ Draft graphs live in `models/gemma4/assistant`, `models/qwen/mtp`, `models/glm52/mtp`,
1303
+ and `models/speculative/*`. Drafters are models: `models/drafters` holds their artifact
1304
+ conventions and `models/drafter-loaders` their lazy loaders (`loadAssistantDrafter`, `loadDeepspecDrafter`,
1305
+ `loadDsparkDrafter`, `loadQwenMtpModule`), and hands the sources the ports of
1306
+ `contracts/mlx/drafter`; a graph that declares `nativeDraft` also provides
1307
+ `nativeDraftHead()`. Sources and the packages above (quantization included) never name
1308
+ a drafter class. Proposal sources live in `generation/speculative/sources`;
1309
+ verification and acceptance belong to `generation/speculative`; batched draft work
1310
+ belongs to `generation/speculative/bindings`; draft checkpoints belong to `state/speculative`.
1311
+ Existing sampling, rejection, rollback, and kernel behavior is preserved.
1312
+ One intentional correction: grouped speculation maintains a reused target prefix
1313
+ before its first forward. A round without drafts maintains only before its append,
1314
+ so under a delayed KV scheme a stored prefix can still owe the conversion its last
1315
+ append reached. Main read that prefix unconverted in its first batched
1316
+ suffix chunk.
1317
+
1318
+ Grouped speculation binds any provider whose operations the target meets: batchable
1319
+ caches, row layouts for verification and rollback, and a forward that captures the
1320
+ hidden layers the provider taps. A provider whose rows tap target layers declares
1321
+ them with `targetTapLayers(target)` on its grouped provider; the binding resolves
1322
+ the list once for the bound target, rows opened for it must tap exactly that list,
1323
+ and placement refuses the provider when the target forward cannot capture those
1324
+ layers. The post-final-norm sentinel (index = layer count) is the forward output.
1325
+
1326
+ The [Qwen MTP group-prefix test](tests/parity/qwen-mtp-group-prefix.test.ts) takes
1327
+ `MLX_BUN_TEST_MTP_TARGET` and `MLX_BUN_TEST_MTP_DRAFT` (both or neither) and runs
1328
+ the Trellis target with its folded MTP draft through the gateway binding at B2,
1329
+ depth 2, plain KV and EOS disabled: two unequal prompts, the first retiring by a
1330
+ stop, an abort or a consumer failure, greedy and seeded. It observes every verify
1331
+ round and each row's committed target state, attributed to its request through
1332
+ joins and filters, and checks within this tree the exact committed inventory,
1333
+ accepted and rejected proposals while both rows are active, retirement outcomes,
1334
+ and that a stopped or finished request publishes one generated checkpoint equal
1335
+ to its terminal committed state while a cancelled or failed one publishes none.
1336
+ Both requests then continue from those checkpoints, twice from RAM and once in a
1337
+ fresh process from the flushed SSD store, with identical records. Main parity is
1338
+ external evidence; B>2, KV quantization, HTTP and performance are not covered.
1339
+
1340
+ The [grouped speculation test](tests/parity/speculative-group.test.ts) covers
1341
+ each built-in draft provider over its target with plain, affine, TurboQuant or
1342
+ per-layer KV. It takes `MLX_BUN_TEST_SPEC_TARGET`, `MLX_BUN_TEST_SPEC_KIND`
1343
+ (`ngram|two-model|assistant|mtp|dspark|deepspec|glm-mtp`) and
1344
+ `MLX_BUN_TEST_SPEC_DRAFT` (none for n-gram or `glm-mtp`), and optionally
1345
+ `MLX_BUN_TEST_SPEC_KV` (`bf16|4|8|turbo|config`),
1346
+ `MLX_BUN_TEST_SPEC_KV_START`, `MLX_BUN_TEST_SPEC_DEPTH`, `MLX_BUN_TEST_SPEC_WINDOW`
1347
+ (the custom sliding-window graph over a Llama-family target) and
1348
+ `MLX_BUN_TEST_SPEC_ADAPTER`. Providers load through `defaultDraftProviders()`, as
1349
+ the app's model host loads them. Requests go through placement, `methodRequest` and
1350
+ the binding's group: B1 repeating exactly, B4 cohorts that prefill together and repeat,
1351
+ stop/failure/abort retirement at exact counts, late and prefill joins, prompt
1352
+ snapshots with their draft attachment, generated prefixes restored from RAM and
1353
+ a fresh SSD store after a provider reload, and, with an adapter, a live adapter
1354
+ whose B2/B4 rows with the draft configured equal B1 controls, context
1355
+ partitioning, failure cleanup and adapter-isolated prefix reuse. Adapter rows
1356
+ speculate only through providers that support target adapters. It has run for
1357
+ n-gram, two-model and assistant providers; MTP, DSpark, DeepSpec and GLM
1358
+ native MTP have not.
1359
+ For example:
1360
+ `MLX_BUN_TEST_SPEC_TARGET=/gemma4-e4b MLX_BUN_TEST_SPEC_KIND=assistant MLX_BUN_TEST_SPEC_DRAFT=/e4b-assistant MLX_BUN_TEST_SPEC_KV=4 bun --no-env-file test packages/inference/tests/parity/speculative-group.test.ts`.
1361
+
1362
+ `glm-mtp` selects GLM-5.2's checkpoint-native MTP (`--mtp on`), mounted as the
1363
+ app's model host mounts it: the Colibri runtime opened with the MTP tier planned
1364
+ for one drafting lane, and the provider the graph declares
1365
+ (`GraphCapabilities.nativeDraft`) at the plan's draft depth unless `MLX_BUN_TEST_SPEC_DEPTH` overrides it. The same checks run as for the
1366
+ other providers, except the KV matrix: this test takes plain KV only and no custom
1367
+ window for this kind (the latent cache's affine, TurboQuant and paged legos are
1368
+ gated over synthetic weights by `tests/models/glm52/` and the cache contract test,
1369
+ not here):
1370
+ `MLX_BUN_TEST_SPEC_TARGET=/GLM-5.2 MLX_BUN_TEST_SPEC_KIND=glm-mtp bun --no-env-file test packages/inference/tests/parity/speculative-group.test.ts`.
1371
+
1372
+ `state` also exposes the byte-limited `PromptCache`, retention policies, row state,
1373
+ and checkpoint attachments. The caller owns cache lifetime and reuse namespaces.
1374
+
1375
+ ## Optional execution and persistence
1376
+
1377
+ `execution` exposes `createInferenceEngine`, method adapters, cancellation,
1378
+ admission, and continuous batching. Supply an execution planner and its graph
1379
+ bindings; the engine manages in-process session lifetimes and bounded output.
1380
+ It opens no network listener. `createAutoregressiveMethod` and
1381
+ `createDenoisingMethod` adapt the direct methods when a consumer needs sessions;
1382
+ speculation runs only in the batch group. `MlxBatchExecutionGroup` owns batched rows;
1383
+ `ExecutionCoordinator` and `driveExecutionGroup` coordinate its work.
1384
+
1385
+ DiffusionGemma requests share that group through `execution/denoising-group`.
1386
+ This is interleaved execution, not stacked canvases: each row keeps its own
1387
+ encoder state, canvas, feedback and MLX key sequence, and the group advances one
1388
+ row by one bounded unit per iteration in round-robin order. The first unit is the
1389
+ whole prompt prefill and first canvas draw; each later unit is one denoising step.
1390
+ A row publishes only its finished result, and its decode time ends when that result
1391
+ is computed. Rows borrow one dequantized embedding table, which the group releases
1392
+ after the last row's run closes. Each unit runs with only its row's adapters
1393
+ active. An image request passes its pixels as the row's prefill input: only the
1394
+ first unit reads them (the vision encoder runs there), and the request owner
1395
+ releases them after the row settles. Grammar, draft, logprobs, logits processors,
1396
+ fill requests are refused with typed plan reasons; its caches are the legos the composition's KV scheme and paging name. `DenoisingKeys` reproduces MLX's
1397
+ global key sequence per request, so denoising never reads or reseeds the process
1398
+ key. The [denoising tests](tests/generation/denoising-binding.test.ts) take
1399
+ `MLX_BUN_DIFFUSION_MODEL=/cached/diffusiongemma` for the real-weight check and
1400
+ optionally `MLX_BUN_DIFFUSION_REFERENCE`, comma-separated trajectories in main's
1401
+ `goldens/diffusion/gen*.json` or `goldens/diffusion/vision.json` format. An image
1402
+ reference must have nonempty output and require more than one denoising step, so
1403
+ its cancellation check interrupts unfinished image work. With the measured main
1404
+ image reference, the real-weight check passed image cancellation beside a live
1405
+ text survivor and image/text reuse on the same group, preserving survivor and
1406
+ recovery tokens and checking state/table disposal calls and caller-owned pixels.
1407
+ The disposal checks do not establish allocator leak freedom. Separate paired B1
1408
+ HTTP acceptance against main passed image SSE disconnect after the first decoder
1409
+ step, server cancellation, prepared-pixel release, drain and same-server image/text
1410
+ recovery. Both clients received headers and the initial role frame, with no canvas
1411
+ tokens published before cancellation. Recovery tokens, messages, finish, usage and
1412
+ prepared inputs matched controls and main. Concurrent HTTP rows, full logits/KV
1413
+ planes, external-oracle parity and performance remain unqualified.
1414
+
1415
+ `execution/fit` estimates whether a model fits a machine at a context length:
1416
+ resident weights (bytes the caller supplies, such as a registry's), KV bytes from
1417
+ `state/kv-scheme`, and the prefill transient, against RAM × `WIRED_FRACTION` or an
1418
+ explicit budget. `fit` also solves the maximum safe context and predicts decode
1419
+ tokens per second from memory bandwidth; `skuMatrix` repeats it across Apple Silicon
1420
+ configurations. Estimates are advisory, and the entry imports without MLX. See the
1421
+ runnable [fit example](examples/fit-model.ts):
1422
+ `bun packages/inference/examples/fit-model.ts <checkpoint-directory> [context-tokens]`.
1423
+
1424
+ `state/cache-service` composes the RAM prompt cache, the KV budget, the saved-state
1425
+ store and paging into one service per loaded model,
1426
+ `createKvCacheService(graph, options)`, constructed with its budget (the RAM cap,
1427
+ `kvBudgetBytes`, a saved-state root with its shared byte budget, and the graph's
1428
+ paging): the batch group takes it as `cache` and only acquires a request's state
1429
+ (the prompt tokens it already covers, 0 for a miss), publishes state back,
1430
+ offers continuation checkpoints and releases the request; it never sees what is
1431
+ resident, evicted or saved. The service resumes a replayable generation under the
1432
+ key its continuation request names, with the request's sampler, pending token,
1433
+ adapter and cache identities. `report()` is what it holds. `SsdCacheStore`,
1434
+ `TieredPromptCache` and `SsdDurabilityCoordinator` remain for callers composing
1435
+ their own parts. Applications choose their own storage paths,
1436
+ capacity, scheduler settings, and shutdown lifecycle. A store's directory is
1437
+ `<dir>/<configFingerprint>/`; the app's fingerprint joins architecture, KV scheme,
1438
+ binding compatibility and the hub's resolved model-directory identity. HF snapshot
1439
+ revisions have separate directories; startup does not hash weights. If weights are
1440
+ replaced in place, clear saved KV for a fresh run. `scan()` skips, and
1441
+ never deletes, files whose header names another identity; only its own corrupt
1442
+ files and `.tmp` orphans are removed. Saved KV is disposable; changes to the
1443
+ store identity rebuild the cache.
1444
+
1445
+ ## Scoring
1446
+
1447
+ `scoreDecisionAnswers` takes a structural `DecisionScoringGraph`, prompt tokens,
1448
+ and complete candidate token sequences. It teacher-forces each candidate, applies
1449
+ the head only to answer-predicting rows, and normalizes average response token
1450
+ log likelihoods across candidates. It returns `meanLogLikelihoods` and their
1451
+ softmax `probabilities`. No sampling or generation loop runs. Include the
1452
+ answer terminator in each candidate; it counts in the average. An optional `execute` callback schedules
1453
+ each candidate separately; direct consumers can run under their own graph lease.
1454
+ Prepared media uses the graph's `MlxPromptInput` to feed the supplied tokens at
1455
+ their positions. Every candidate owns fresh caches, released before it settles.
1456
+
1457
+ `sequenceLogProbabilities` and `logitsFromHiddenPadM` are the existing DPO
1458
+ response-mask reduction and DPO/ORPO head geometry, now owned here and consumed by
1459
+ training. The extraction preserves operation order and differentiability. API
1460
+ schemas, answer text encoding, confidence, and wire responses belong to the app.
1461
+
1462
+ `scoring` exposes `forwardSequence` / `forwardSequenceHidden` for full-sequence
1463
+ logits and hidden states, including the existing padded-batch masks. The original
1464
+ `trainForward` names remain aliases for compatibility. `evalPpl` computes
1465
+ perplexity over caller-provided token rows; `klPerToken` compares supplied logits.
1466
+ Neither requires an evaluation dataset registry or runner; the app's
1467
+ `mlx-bun perplexity` drives `evalPpl` over a local text/JSONL file. Tool-call
1468
+ parsing is available from `input`; template/schema fill compilation lives in
1469
+ `generation/fill`.
1470
+
1471
+ Standalone app bundles keep the expert-I/O library, the Neural Engine bridge
1472
+ and the frame extractor beside the executable. Explicit
1473
+ `MLX_BUN_EXPERT_IO_DYLIB` / `MLX_BUN_ANE_DYLIB` / `MLX_BUN_FRAME_EXTRACT`
1474
+ overrides take precedence; ordinary source/package execution resolves the
1475
+ package's `dist/native` directory. The app bundle verification covers relocation
1476
+ without loading native numerical code.