@mlx-bun/inference 0.0.0-stage → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (467) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1475 -2
  3. package/THIRD_PARTY_NOTICES.md +3404 -0
  4. package/dist/native/libmlx_bun_ane.dylib +0 -0
  5. package/dist/native/libmlx_bun_expert_io.dylib +0 -0
  6. package/dist/native/mlx-bun-frame-extract +0 -0
  7. package/examples/fit-model.ts +25 -0
  8. package/examples/qwen3-forward.ts +42 -0
  9. package/examples/qwen3-generate.ts +28 -0
  10. package/examples/trellis-expand.ts +23 -0
  11. package/package.json +129 -4
  12. package/src/adapters/available.ts +110 -0
  13. package/src/adapters/index.ts +3 -0
  14. package/src/adapters/manager.ts +316 -0
  15. package/src/artifacts/auxiliary-files.ts +45 -0
  16. package/src/artifacts/config-dialects.ts +94 -0
  17. package/src/artifacts/config.ts +341 -0
  18. package/src/artifacts/draft-vocabulary.ts +30 -0
  19. package/src/artifacts/expert-offload-build.ts +75 -0
  20. package/src/artifacts/expert-offload.ts +89 -0
  21. package/src/artifacts/experts/index.ts +3 -0
  22. package/src/artifacts/experts/io.ts +386 -0
  23. package/src/artifacts/experts/residency.ts +1134 -0
  24. package/src/artifacts/experts/usage.ts +415 -0
  25. package/src/artifacts/fingerprint.ts +44 -0
  26. package/src/artifacts/glm52/container.ts +305 -0
  27. package/src/artifacts/glm52/expert-layout.ts +249 -0
  28. package/src/artifacts/glm52/index.ts +7 -0
  29. package/src/artifacts/glm52/kernel-layout.ts +124 -0
  30. package/src/artifacts/glm52/layout.ts +152 -0
  31. package/src/artifacts/glm52/memory.ts +500 -0
  32. package/src/artifacts/glm52/resident-weights.ts +135 -0
  33. package/src/artifacts/glm52/weights.ts +503 -0
  34. package/src/artifacts/glm52-config.ts +262 -0
  35. package/src/artifacts/identity.ts +94 -0
  36. package/src/artifacts/index.ts +15 -0
  37. package/src/artifacts/mmap.ts +90 -0
  38. package/src/artifacts/safetensors-writer.ts +249 -0
  39. package/src/artifacts/safetensors.ts +164 -0
  40. package/src/artifacts/turboquant-fold.ts +42 -0
  41. package/src/artifacts/weight-audit.ts +43 -0
  42. package/src/artifacts/weights.ts +255 -0
  43. package/src/contracts/index.ts +2 -0
  44. package/src/contracts/mlx/attention.ts +8 -0
  45. package/src/contracts/mlx/cache.ts +510 -0
  46. package/src/contracts/mlx/checkpoint.ts +10 -0
  47. package/src/contracts/mlx/draft-checkpoint.ts +14 -0
  48. package/src/contracts/mlx/draft-projection.ts +27 -0
  49. package/src/contracts/mlx/draft-target.ts +84 -0
  50. package/src/contracts/mlx/drafter.ts +176 -0
  51. package/src/contracts/mlx/forward-work.ts +23 -0
  52. package/src/contracts/mlx/glm52-weights.ts +25 -0
  53. package/src/contracts/mlx/graph.ts +309 -0
  54. package/src/contracts/mlx/head.ts +6 -0
  55. package/src/contracts/mlx/index.ts +15 -0
  56. package/src/contracts/mlx/kv-codec.ts +9 -0
  57. package/src/contracts/mlx/media.ts +113 -0
  58. package/src/contracts/mlx/positions.ts +16 -0
  59. package/src/contracts/mlx/row-table.ts +2 -0
  60. package/src/contracts/mlx/trainable.ts +103 -0
  61. package/src/contracts/mlx/whisper.ts +96 -0
  62. package/src/contracts/portable/composition.ts +106 -0
  63. package/src/contracts/portable/denoising.ts +17 -0
  64. package/src/contracts/portable/execution.ts +79 -0
  65. package/src/contracts/portable/generation.ts +103 -0
  66. package/src/contracts/portable/graph.ts +80 -0
  67. package/src/contracts/portable/index.ts +15 -0
  68. package/src/contracts/portable/memory-plan.ts +26 -0
  69. package/src/contracts/portable/object-cache.ts +8 -0
  70. package/src/contracts/portable/prefill.ts +63 -0
  71. package/src/contracts/portable/prefix-cache.ts +33 -0
  72. package/src/contracts/portable/prompt.ts +2 -0
  73. package/src/contracts/portable/resources.ts +25 -0
  74. package/src/contracts/portable/rollback.ts +7 -0
  75. package/src/contracts/portable/sampling.ts +44 -0
  76. package/src/contracts/portable/scheduling.ts +54 -0
  77. package/src/contracts/portable/token-delivery.ts +4 -0
  78. package/src/contracts/portable/tsconfig.json +14 -0
  79. package/src/embeddings/index.ts +1 -0
  80. package/src/embeddings/text.ts +85 -0
  81. package/src/execution/admission.ts +59 -0
  82. package/src/execution/batch-group.ts +1240 -0
  83. package/src/execution/batch-types.ts +225 -0
  84. package/src/execution/cancellation.ts +38 -0
  85. package/src/execution/continuation-request.ts +45 -0
  86. package/src/execution/continuation-types.ts +10 -0
  87. package/src/execution/coordinator.ts +70 -0
  88. package/src/execution/denoising-group.ts +180 -0
  89. package/src/execution/engine.ts +56 -0
  90. package/src/execution/fill-group.ts +505 -0
  91. package/src/execution/fit.ts +184 -0
  92. package/src/execution/gateway-binding.ts +178 -0
  93. package/src/execution/grammar-group.ts +224 -0
  94. package/src/execution/index.ts +18 -0
  95. package/src/execution/methods.ts +75 -0
  96. package/src/execution/mixed-iteration.ts +96 -0
  97. package/src/execution/plan.ts +65 -0
  98. package/src/execution/prefill-cohort.ts +100 -0
  99. package/src/execution/prefill-rows.ts +263 -0
  100. package/src/execution/request-policy.ts +19 -0
  101. package/src/execution/row-sampling.ts +49 -0
  102. package/src/execution/scheduler.ts +92 -0
  103. package/src/execution/session.ts +247 -0
  104. package/src/execution/speculative-group.ts +797 -0
  105. package/src/execution/tasks.ts +44 -0
  106. package/src/generation/autoregressive.ts +840 -0
  107. package/src/generation/bindings/autoregressive.ts +104 -0
  108. package/src/generation/bindings/denoising.ts +60 -0
  109. package/src/generation/bindings/prefill-policy.ts +36 -0
  110. package/src/generation/checkpoint-identity.ts +60 -0
  111. package/src/generation/denoising.ts +85 -0
  112. package/src/generation/diffusion.ts +583 -0
  113. package/src/generation/fill/echo-index.ts +188 -0
  114. package/src/generation/fill/index.ts +3 -0
  115. package/src/generation/fill/proposal.ts +69 -0
  116. package/src/generation/fill/schema-rows.ts +506 -0
  117. package/src/generation/fill/session.ts +464 -0
  118. package/src/generation/fill/tool-boundary.ts +103 -0
  119. package/src/generation/fill-append.ts +58 -0
  120. package/src/generation/generate.ts +44 -0
  121. package/src/generation/grammar-step.ts +39 -0
  122. package/src/generation/index.ts +14 -0
  123. package/src/generation/phase-dispatch.ts +115 -0
  124. package/src/generation/prefill.ts +33 -0
  125. package/src/generation/result.ts +49 -0
  126. package/src/generation/scopes.ts +157 -0
  127. package/src/generation/speculative/acceptance.ts +41 -0
  128. package/src/generation/speculative/adaptive-draft-gate.ts +166 -0
  129. package/src/generation/speculative/bindings/assistant-rows.ts +102 -0
  130. package/src/generation/speculative/bindings/autoregressive-rows.ts +124 -0
  131. package/src/generation/speculative/bindings/binding.ts +106 -0
  132. package/src/generation/speculative/bindings/deepspec-rows.ts +33 -0
  133. package/src/generation/speculative/bindings/draft-target.ts +10 -0
  134. package/src/generation/speculative/bindings/glm52-mtp-rows.ts +128 -0
  135. package/src/generation/speculative/bindings/projected-context-rows.ts +78 -0
  136. package/src/generation/speculative/bindings/projected-draft-rows.ts +95 -0
  137. package/src/generation/speculative/bindings/qwen-mtp-rows.ts +336 -0
  138. package/src/generation/speculative/bindings/two-model-rows.ts +63 -0
  139. package/src/generation/speculative/cache-identity.ts +16 -0
  140. package/src/generation/speculative/draft-kind.ts +149 -0
  141. package/src/generation/speculative/draft-policy.ts +41 -0
  142. package/src/generation/speculative/draft-registry.ts +118 -0
  143. package/src/generation/speculative/draft-vocab.ts +110 -0
  144. package/src/generation/speculative/index.ts +17 -0
  145. package/src/generation/speculative/output.ts +49 -0
  146. package/src/generation/speculative/round.ts +307 -0
  147. package/src/generation/speculative/source.ts +144 -0
  148. package/src/generation/speculative/sources/assistant-source.ts +64 -0
  149. package/src/generation/speculative/sources/deepspec-source.ts +70 -0
  150. package/src/generation/speculative/sources/dflash-source.ts +84 -0
  151. package/src/generation/speculative/sources/dflash2-source.ts +86 -0
  152. package/src/generation/speculative/sources/glm52-mtp-source.ts +66 -0
  153. package/src/generation/speculative/sources/ngram-source.ts +165 -0
  154. package/src/generation/speculative/sources/qwen-mtp-source.ts +154 -0
  155. package/src/generation/speculative/sources/two-model.ts +75 -0
  156. package/src/generation/types.ts +194 -0
  157. package/src/index.ts +20 -0
  158. package/src/input/audio/audiotoolbox.ts +112 -0
  159. package/src/input/audio/decode.ts +133 -0
  160. package/src/input/audio/features.ts +202 -0
  161. package/src/input/audio/index.ts +6 -0
  162. package/src/input/audio/transcode.ts +67 -0
  163. package/src/input/audio/whisper-mel.ts +185 -0
  164. package/src/input/audio/whisper-tokenizer.ts +238 -0
  165. package/src/input/chat-template.ts +389 -0
  166. package/src/input/index.ts +6 -0
  167. package/src/input/media-fetch.ts +439 -0
  168. package/src/input/packed-mixed.ts +43 -0
  169. package/src/input/special-tokens.ts +75 -0
  170. package/src/input/token-groups.ts +49 -0
  171. package/src/input/tokenizer.ts +336 -0
  172. package/src/input/tool-call.ts +689 -0
  173. package/src/input/vision/index.ts +3 -0
  174. package/src/input/vision/preprocess.ts +247 -0
  175. package/src/input/vision/prompt.ts +398 -0
  176. package/src/input/vision/qwen3vl-preprocess.ts +409 -0
  177. package/src/input/vision/qwen3vl-prompt.ts +237 -0
  178. package/src/input/vision/video-frames.ts +100 -0
  179. package/src/kernels/ane/fill.ts +101 -0
  180. package/src/kernels/ane/linear.ts +416 -0
  181. package/src/kernels/attention/flash.ts +694 -0
  182. package/src/kernels/attention/kvq4-decode.ts +155 -0
  183. package/src/kernels/attention/masks.ts +40 -0
  184. package/src/kernels/attention/multi-query.ts +193 -0
  185. package/src/kernels/attention/paged.ts +130 -0
  186. package/src/kernels/delta/gated.ts +306 -0
  187. package/src/kernels/delta/index.ts +3 -0
  188. package/src/kernels/delta/silu-rms-heads.ts +69 -0
  189. package/src/kernels/delta/state.ts +74 -0
  190. package/src/kernels/gated-rms-norm.ts +64 -0
  191. package/src/kernels/glm52/layout.ts +37 -0
  192. package/src/kernels/glm52/streamed-experts.ts +360 -0
  193. package/src/kernels/logits.ts +38 -0
  194. package/src/kernels/quantization/affine-row.ts +154 -0
  195. package/src/kernels/quantization/affine-rows.ts +36 -0
  196. package/src/kernels/quantization/affine-split-mma.ts +144 -0
  197. package/src/kernels/quantization/affine3-mma.ts +117 -0
  198. package/src/kernels/quantization/affine3-rows.ts +103 -0
  199. package/src/kernels/quantization/affine4-mma.ts +113 -0
  200. package/src/kernels/quantization/affine4-rows.ts +102 -0
  201. package/src/kernels/residual-rms-norm.ts +65 -0
  202. package/src/kernels/sampling/index.ts +2 -0
  203. package/src/kernels/sampling/normalized-argmax.ts +116 -0
  204. package/src/kernels/sampling/token-bitmask.ts +52 -0
  205. package/src/kernels/softcap.ts +35 -0
  206. package/src/kernels/trellis/codebook.ts +123 -0
  207. package/src/kernels/trellis/down-codebook.ts +184 -0
  208. package/src/kernels/trellis/down-factored.ts +180 -0
  209. package/src/kernels/trellis/down-k3-interleaved-factored.ts +146 -0
  210. package/src/kernels/trellis/down-mma-wide.ts +195 -0
  211. package/src/kernels/trellis/down-mma.ts +145 -0
  212. package/src/kernels/trellis/expand.ts +42 -0
  213. package/src/kernels/trellis/gate-up-codebook-mma.ts +141 -0
  214. package/src/kernels/trellis/gate-up-codebook.ts +137 -0
  215. package/src/kernels/trellis/gate-up-factored.ts +197 -0
  216. package/src/kernels/trellis/gate-up-mma-wide.ts +147 -0
  217. package/src/kernels/trellis/gate-up-mma.ts +148 -0
  218. package/src/kernels/trellis/gate-up.ts +248 -0
  219. package/src/kernels/trellis/geometry.ts +22 -0
  220. package/src/kernels/trellis/index.ts +103 -0
  221. package/src/kernels/trellis/launch.ts +12 -0
  222. package/src/kernels/trellis/mixed-gate-up-factored.ts +119 -0
  223. package/src/kernels/trellis/mixed-gate-up.ts +146 -0
  224. package/src/kernels/trellis/qualified.ts +34 -0
  225. package/src/kernels/trellis/reduce.ts +150 -0
  226. package/src/kernels/trellis/scatter.ts +407 -0
  227. package/src/kernels/trellis/splitk-prefill.ts +152 -0
  228. package/src/kernels/trellis/tiled-prefill.ts +131 -0
  229. package/src/kernels/trellis/vector-expand.ts +58 -0
  230. package/src/kernels/trellis/wide-prefill.ts +89 -0
  231. package/src/kernels/turboquant/decode.ts +82 -0
  232. package/src/kernels/turboquant/index.ts +5 -0
  233. package/src/kernels/turboquant/inverse.ts +122 -0
  234. package/src/kernels/turboquant/ops.ts +860 -0
  235. package/src/kernels/turboquant/signs.ts +130 -0
  236. package/src/kernels/turboquant/tables.ts +124 -0
  237. package/src/layers/affine-verify-linear.ts +75 -0
  238. package/src/layers/ane-prefill-split.ts +283 -0
  239. package/src/layers/compiled-normalization.ts +41 -0
  240. package/src/layers/delta-heads.ts +23 -0
  241. package/src/layers/dense-embedding.ts +29 -0
  242. package/src/layers/dense-linear.ts +32 -0
  243. package/src/layers/dequantized-linear.ts +72 -0
  244. package/src/layers/folded-attention.ts +42 -0
  245. package/src/layers/geglu.ts +130 -0
  246. package/src/layers/helpers.ts +7 -0
  247. package/src/layers/index.ts +36 -0
  248. package/src/layers/kv4-decode-attention.ts +15 -0
  249. package/src/layers/layer-norm.ts +24 -0
  250. package/src/layers/loading.ts +92 -0
  251. package/src/layers/lora.ts +150 -0
  252. package/src/layers/normalization.ts +9 -0
  253. package/src/layers/prefix-layout.ts +33 -0
  254. package/src/layers/quantized-append-attention.ts +32 -0
  255. package/src/layers/quantized-attention.ts +353 -0
  256. package/src/layers/quantized-embedding.ts +39 -0
  257. package/src/layers/quantized-linear.ts +137 -0
  258. package/src/layers/quantized-switch-linear.ts +37 -0
  259. package/src/layers/qwen-append.ts +12 -0
  260. package/src/layers/qwen-mrope.ts +238 -0
  261. package/src/layers/rope-2d.ts +110 -0
  262. package/src/layers/rope.ts +389 -0
  263. package/src/layers/swiglu.ts +23 -0
  264. package/src/layers/trellis-codebook-down.ts +52 -0
  265. package/src/layers/trellis-codebook-gate-up.ts +42 -0
  266. package/src/layers/trellis-down.ts +117 -0
  267. package/src/layers/trellis-gate-up.ts +71 -0
  268. package/src/layers/trellis-linear.ts +211 -0
  269. package/src/layers/trellis-prompt.ts +102 -0
  270. package/src/layers/trellis-reduce-gate-up.ts +73 -0
  271. package/src/layers/trellis-scatter-down.ts +98 -0
  272. package/src/models/ane-install.ts +74 -0
  273. package/src/models/audio/conformer.ts +656 -0
  274. package/src/models/cache-composition.ts +44 -0
  275. package/src/models/capabilities.ts +89 -0
  276. package/src/models/chat-template.ts +10 -0
  277. package/src/models/composition.ts +107 -0
  278. package/src/models/diffusion-gemma/experts.ts +59 -0
  279. package/src/models/diffusion-gemma/family.ts +21 -0
  280. package/src/models/diffusion-gemma/model.ts +900 -0
  281. package/src/models/diffusion-gemma/vision.ts +549 -0
  282. package/src/models/drafter-loaders.ts +49 -0
  283. package/src/models/drafters.ts +39 -0
  284. package/src/models/factory.ts +351 -0
  285. package/src/models/families.ts +70 -0
  286. package/src/models/family.ts +125 -0
  287. package/src/models/gemma4/assistant-target.ts +46 -0
  288. package/src/models/gemma4/assistant.ts +327 -0
  289. package/src/models/gemma4/blocks.ts +755 -0
  290. package/src/models/gemma4/compiled-decode.ts +452 -0
  291. package/src/models/gemma4/family.ts +39 -0
  292. package/src/models/gemma4/gemma4-12b.ts +677 -0
  293. package/src/models/gemma4/gemma4-26b.ts +544 -0
  294. package/src/models/gemma4/gemma4-e4b.ts +599 -0
  295. package/src/models/gemma4/graphs.ts +147 -0
  296. package/src/models/gemma4/media.ts +88 -0
  297. package/src/models/gemma4/model.ts +860 -0
  298. package/src/models/gemma4/serving-blocks.ts +72 -0
  299. package/src/models/gemma4/trainable.ts +186 -0
  300. package/src/models/gemma4/written-blocks.ts +232 -0
  301. package/src/models/gemma4/written-forward.ts +159 -0
  302. package/src/models/gemma4/written-input.ts +29 -0
  303. package/src/models/glm52/atlas.ts +507 -0
  304. package/src/models/glm52/coupling.ts +388 -0
  305. package/src/models/glm52/dsa.ts +601 -0
  306. package/src/models/glm52/family.ts +20 -0
  307. package/src/models/glm52/mla.ts +812 -0
  308. package/src/models/glm52/model.ts +1127 -0
  309. package/src/models/glm52/moe.ts +223 -0
  310. package/src/models/glm52/mtp.ts +60 -0
  311. package/src/models/glm52/pilot.ts +537 -0
  312. package/src/models/glm52/reference.ts +445 -0
  313. package/src/models/glm52/residency.ts +557 -0
  314. package/src/models/glm52/streamed-experts.ts +447 -0
  315. package/src/models/graph.ts +59 -0
  316. package/src/models/implementation.ts +230 -0
  317. package/src/models/index.ts +15 -0
  318. package/src/models/media-input.ts +15 -0
  319. package/src/models/memory-plan.ts +39 -0
  320. package/src/models/minicpm5/family.ts +29 -0
  321. package/src/models/minicpm5/model.ts +394 -0
  322. package/src/models/minicpm5/trainable.ts +90 -0
  323. package/src/models/profile.ts +337 -0
  324. package/src/models/qwen/blocks.ts +658 -0
  325. package/src/models/qwen/checkpoint.ts +190 -0
  326. package/src/models/qwen/families.ts +57 -0
  327. package/src/models/qwen/media-input.ts +161 -0
  328. package/src/models/qwen/mtp.ts +101 -0
  329. package/src/models/qwen/qwen3-moe.ts +355 -0
  330. package/src/models/qwen/qwen3.ts +323 -0
  331. package/src/models/qwen/qwen38-27b-trellis-m1max.ts +1532 -0
  332. package/src/models/qwen/qwen38-27b-trellis-m4pro.ts +1092 -0
  333. package/src/models/qwen/qwen3_5.ts +433 -0
  334. package/src/models/qwen/written-layers.ts +285 -0
  335. package/src/models/row-table.ts +1 -0
  336. package/src/models/runtime.ts +73 -0
  337. package/src/models/speculative/deepspec-artifact.ts +10 -0
  338. package/src/models/speculative/deepspec.ts +744 -0
  339. package/src/models/speculative/dflash.ts +553 -0
  340. package/src/models/speculative/dflash2-artifact.ts +9 -0
  341. package/src/models/speculative/dflash2-operations.ts +1 -0
  342. package/src/models/speculative/dflash2.ts +576 -0
  343. package/src/models/speculative/loader.ts +29 -0
  344. package/src/models/support.ts +79 -0
  345. package/src/models/universal/archs.ts +365 -0
  346. package/src/models/universal/blocks.ts +479 -0
  347. package/src/models/universal/dense.ts +255 -0
  348. package/src/models/universal/family.ts +17 -0
  349. package/src/models/vision/qwen3vl.ts +566 -0
  350. package/src/models/vision/siglip.ts +520 -0
  351. package/src/models/vision/unified.ts +176 -0
  352. package/src/models/whisper/family.ts +19 -0
  353. package/src/models/whisper/fast.ts +734 -0
  354. package/src/models/whisper/model.ts +430 -0
  355. package/src/runtime/compiled-trace.ts +16 -0
  356. package/src/runtime/config.ts +77 -0
  357. package/src/runtime/expert-trace.ts +71 -0
  358. package/src/runtime/graph-forward.ts +65 -0
  359. package/src/runtime/integers.ts +6 -0
  360. package/src/runtime/native.ts +16 -0
  361. package/src/runtime/resources.ts +53 -0
  362. package/src/runtime/row-operations.ts +42 -0
  363. package/src/runtime/trace.ts +161 -0
  364. package/src/sampling/curve.ts +122 -0
  365. package/src/sampling/draft.ts +55 -0
  366. package/src/sampling/extras.ts +45 -0
  367. package/src/sampling/filters.ts +134 -0
  368. package/src/sampling/grammar.ts +630 -0
  369. package/src/sampling/hlg.ts +290 -0
  370. package/src/sampling/index.ts +9 -0
  371. package/src/sampling/processors.ts +142 -0
  372. package/src/sampling/sampler.ts +120 -0
  373. package/src/sampling/step.ts +343 -0
  374. package/src/sampling/types.ts +161 -0
  375. package/src/sampling/window-filters.ts +46 -0
  376. package/src/scoring/decisions.ts +96 -0
  377. package/src/scoring/full-sequence.ts +103 -0
  378. package/src/scoring/index.ts +9 -0
  379. package/src/scoring/kl.ts +29 -0
  380. package/src/scoring/perplexity.ts +165 -0
  381. package/src/scoring/response-logps.ts +65 -0
  382. package/src/state/affine-attention.ts +180 -0
  383. package/src/state/attachment.ts +68 -0
  384. package/src/state/attention-read.ts +215 -0
  385. package/src/state/batched-kv.ts +294 -0
  386. package/src/state/batched-mask.ts +445 -0
  387. package/src/state/batched-quant.ts +389 -0
  388. package/src/state/batched-quantized-kv.ts +209 -0
  389. package/src/state/batched-rotating-quant.ts +457 -0
  390. package/src/state/batched-rotating-state.ts +149 -0
  391. package/src/state/batched-rotating.ts +430 -0
  392. package/src/state/batched-row-storage.ts +179 -0
  393. package/src/state/batched-ssm.ts +54 -0
  394. package/src/state/batched-turboquant-kv.ts +144 -0
  395. package/src/state/bf16-first-kv.ts +663 -0
  396. package/src/state/cache-service.ts +592 -0
  397. package/src/state/capabilities.ts +65 -0
  398. package/src/state/checkpoint.ts +46 -0
  399. package/src/state/continuation-persistence.ts +126 -0
  400. package/src/state/decode-step-inputs.ts +31 -0
  401. package/src/state/decoded-kv-donor.ts +12 -0
  402. package/src/state/delayed-quantized-kv.ts +141 -0
  403. package/src/state/delayed-rotating-quantized-kv.ts +178 -0
  404. package/src/state/delayed-turboquant-kv.ts +123 -0
  405. package/src/state/durability.ts +214 -0
  406. package/src/state/encoder-cache.ts +26 -0
  407. package/src/state/full-kv-row-append.ts +33 -0
  408. package/src/state/full-kv-row-donor.ts +54 -0
  409. package/src/state/full-prefill-padding.ts +56 -0
  410. package/src/state/full-prefill-row.ts +112 -0
  411. package/src/state/full-transitioning-kv-rows.ts +70 -0
  412. package/src/state/glm52-cache.ts +783 -0
  413. package/src/state/grouped-heads-kv4-cache.ts +26 -0
  414. package/src/state/index.ts +38 -0
  415. package/src/state/kv-attention-view.ts +77 -0
  416. package/src/state/kv-budget.ts +14 -0
  417. package/src/state/kv-scheme.ts +315 -0
  418. package/src/state/kv-storage.ts +57 -0
  419. package/src/state/kv-tensor-rows.ts +212 -0
  420. package/src/state/kv.ts +324 -0
  421. package/src/state/kv4-head256-cache.ts +46 -0
  422. package/src/state/latent-legos.ts +895 -0
  423. package/src/state/layout.ts +277 -0
  424. package/src/state/leases.ts +26 -0
  425. package/src/state/paged/cache.ts +689 -0
  426. package/src/state/paged/codec.ts +36 -0
  427. package/src/state/paged/rows.ts +223 -0
  428. package/src/state/persistence-types.ts +173 -0
  429. package/src/state/persistence-worker.ts +83 -0
  430. package/src/state/persistence.ts +1036 -0
  431. package/src/state/persistence.worker.d.ts +3 -0
  432. package/src/state/persistence.worker.js +264 -0
  433. package/src/state/prefix-cache.ts +685 -0
  434. package/src/state/quantized-kv.ts +369 -0
  435. package/src/state/quantized-tensor.ts +26 -0
  436. package/src/state/request-policy.ts +48 -0
  437. package/src/state/retention-policy.ts +37 -0
  438. package/src/state/rollback.ts +131 -0
  439. package/src/state/rotating-kv-donor.ts +35 -0
  440. package/src/state/rotating-kv-layout.ts +341 -0
  441. package/src/state/rotating-kv.ts +529 -0
  442. package/src/state/rotating-quantized-kv.ts +616 -0
  443. package/src/state/rotating-row-transaction.ts +35 -0
  444. package/src/state/rows.ts +104 -0
  445. package/src/state/softcap-attention.ts +118 -0
  446. package/src/state/speculative/glm52-mtp-state.ts +38 -0
  447. package/src/state/speculative/qwen-mtp-state.ts +34 -0
  448. package/src/state/speculative-rotating-kv.ts +113 -0
  449. package/src/state/ssd-cache.ts +434 -0
  450. package/src/state/ssm-prefill-padding.ts +39 -0
  451. package/src/state/ssm.ts +422 -0
  452. package/src/state/target-layout.ts +7 -0
  453. package/src/state/tiered-prefix-cache.ts +45 -0
  454. package/src/state/training-cache.ts +300 -0
  455. package/src/state/transitioning-kv-rows.ts +267 -0
  456. package/src/state/turboquant-attention.ts +97 -0
  457. package/src/state/turboquant-codec.ts +90 -0
  458. package/src/state/turboquant-kv.ts +442 -0
  459. package/src/state/views.ts +26 -0
  460. package/src/transcription/index.ts +3 -0
  461. package/src/transcription/silero-vad.ts +379 -0
  462. package/src/transcription/whisper/decode.ts +773 -0
  463. package/src/transcription/whisper/format.ts +74 -0
  464. package/src/transcription/whisper/index.ts +4 -0
  465. package/src/transcription/whisper/timing.ts +274 -0
  466. package/src/transcription/whisper/transcribe.ts +394 -0
  467. package/src/transcription/whisper/types.ts +22 -0
@@ -0,0 +1,112 @@
1
+ // In-process audio decoding through macOS AudioToolbox (ExtAudioFile) over
2
+ // bun:ffi — every container/codec CoreAudio reads (mp3, m4a/aac, flac, ogg,
3
+ // aiff, caf, wav, mov/mp4 audio tracks…) straight to 16 kHz mono float32,
4
+ // with CoreAudio's own resampler. Replaces the `afconvert` subprocess of
5
+ // transcode.ts (no process spawn, no WAV re-parse); the bytes still touch a
6
+ // temp file because ExtAudioFile opens URLs.
7
+
8
+ import { dlopen, FFIType, ptr, read } from "bun:ffi";
9
+ import { randomUUID } from "node:crypto";
10
+ import { unlinkSync, writeFileSync } from "node:fs";
11
+ import { tmpdir } from "node:os";
12
+ import { join } from "node:path";
13
+
14
+ const { ptr: P, i32, u32, u64, i64 } = FFIType;
15
+
16
+ function openLibs() {
17
+ const at = dlopen("/System/Library/Frameworks/AudioToolbox.framework/AudioToolbox", {
18
+ ExtAudioFileOpenURL: { args: [u64, P], returns: i32 },
19
+ ExtAudioFileSetProperty: { args: [u64, u32, u32, P], returns: i32 },
20
+ ExtAudioFileGetProperty: { args: [u64, u32, P, P], returns: i32 },
21
+ ExtAudioFileRead: { args: [u64, P, P], returns: i32 },
22
+ ExtAudioFileDispose: { args: [u64], returns: i32 },
23
+ }).symbols;
24
+ const cf = dlopen("/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation", {
25
+ CFURLCreateFromFileSystemRepresentation: { args: [u64, P, i64, FFIType.bool], returns: u64 },
26
+ CFRelease: { args: [u64], returns: i32 },
27
+ }).symbols;
28
+ return { at, cf };
29
+ }
30
+
31
+ let libs: ReturnType<typeof openLibs> | null = null;
32
+ const load = () => (libs ??= openLibs());
33
+
34
+ const fourcc = (s: string) => (s.charCodeAt(0) << 24 | s.charCodeAt(1) << 16 | s.charCodeAt(2) << 8 | s.charCodeAt(3)) >>> 0;
35
+ const kExtAudioFileProperty_ClientDataFormat = fourcc("cfmt");
36
+ const kExtAudioFileProperty_FileLengthFrames = fourcc("#frm");
37
+ const kAudioFormatLinearPCM = fourcc("lpcm");
38
+ const kAudioFormatFlagIsFloat = 1;
39
+ const kAudioFormatFlagIsPacked = 8;
40
+
41
+ export const AUDIOTOOLBOX_SAMPLE_RATE = 16_000;
42
+
43
+ /** Decode a file to 16 kHz mono float32 with CoreAudio. Throws on undecodable input. */
44
+ export function decodeFileWithAudioToolbox(path: string, sampleRate = AUDIOTOOLBOX_SAMPLE_RATE): Float32Array {
45
+ const { at, cf } = load();
46
+ const pathBuf = Buffer.from(path, "utf8");
47
+ const url = cf.CFURLCreateFromFileSystemRepresentation(0n, ptr(pathBuf), BigInt(pathBuf.length), false) as bigint;
48
+ if (!url) throw new Error("AudioToolbox: cannot build file URL");
49
+ const refSlot = new BigUint64Array(1);
50
+ try {
51
+ let st = at.ExtAudioFileOpenURL(url, ptr(refSlot)) as number;
52
+ if (st !== 0) throw new Error(`AudioToolbox: unrecognized or undecodable audio (ExtAudioFileOpenURL ${st})`);
53
+ const ref = read.u64(ptr(refSlot), 0);
54
+ try {
55
+ // AudioStreamBasicDescription: 40 bytes
56
+ const asbd = Buffer.alloc(40);
57
+ asbd.writeDoubleLE(sampleRate, 0);
58
+ asbd.writeUInt32LE(kAudioFormatLinearPCM, 8);
59
+ asbd.writeUInt32LE(kAudioFormatFlagIsFloat | kAudioFormatFlagIsPacked, 12);
60
+ asbd.writeUInt32LE(4, 16); // bytes per packet
61
+ asbd.writeUInt32LE(1, 20); // frames per packet
62
+ asbd.writeUInt32LE(4, 24); // bytes per frame
63
+ asbd.writeUInt32LE(1, 28); // channels
64
+ asbd.writeUInt32LE(32, 32); // bits per channel
65
+ st = at.ExtAudioFileSetProperty(ref, kExtAudioFileProperty_ClientDataFormat, 40, ptr(asbd)) as number;
66
+ if (st !== 0) throw new Error(`AudioToolbox: cannot set client format (${st})`);
67
+ // frame count hint (source rate), used only to size the first buffer
68
+ const lenSlot = new BigInt64Array(1);
69
+ const lenSize = new Uint32Array([8]);
70
+ at.ExtAudioFileGetProperty(ref, kExtAudioFileProperty_FileLengthFrames, ptr(lenSize), ptr(lenSlot));
71
+ const chunkFrames = 1 << 16;
72
+ const chunk = new Float32Array(chunkFrames);
73
+ // AudioBufferList { UInt32 mNumberBuffers; [pad 4]; AudioBuffer { UInt32 mNumberChannels; UInt32 mDataByteSize; void* mData } }
74
+ const abl = Buffer.alloc(24);
75
+ const frames = new Uint32Array(1);
76
+ const parts: Float32Array[] = [];
77
+ let total = 0;
78
+ for (;;) {
79
+ abl.writeUInt32LE(1, 0);
80
+ abl.writeUInt32LE(1, 8);
81
+ abl.writeUInt32LE(chunkFrames * 4, 12);
82
+ abl.writeBigUInt64LE(BigInt(ptr(chunk)), 16);
83
+ frames[0] = chunkFrames;
84
+ st = at.ExtAudioFileRead(ref, ptr(frames), ptr(abl)) as number;
85
+ if (st !== 0) throw new Error(`AudioToolbox: decode failed (ExtAudioFileRead ${st})`);
86
+ const n = frames[0]!;
87
+ if (n === 0) break;
88
+ parts.push(chunk.slice(0, n));
89
+ total += n;
90
+ }
91
+ const out = new Float32Array(total);
92
+ let o = 0;
93
+ for (const p of parts) { out.set(p, o); o += p.length; }
94
+ return out;
95
+ } finally {
96
+ at.ExtAudioFileDispose(ref);
97
+ }
98
+ } finally {
99
+ cf.CFRelease(url);
100
+ }
101
+ }
102
+
103
+ /** Decode in-memory bytes (any CoreAudio-readable container) via a temp file. */
104
+ export function decodeBytesWithAudioToolbox(bytes: Uint8Array, sampleRate = AUDIOTOOLBOX_SAMPLE_RATE): Float32Array {
105
+ const path = join(tmpdir(), `mlx-bun-audio-${randomUUID()}`);
106
+ writeFileSync(path, bytes);
107
+ try {
108
+ return decodeFileWithAudioToolbox(path, sampleRate);
109
+ } finally {
110
+ try { unlinkSync(path); } catch { /* already gone */ }
111
+ }
112
+ }
@@ -0,0 +1,133 @@
1
+ // WAV decode for audio input (`02d723a:docs/design/generic-model-support.md` §6.6).
2
+ //
3
+ // Scope: RIFF/WAVE containers — PCM 16/24/32-bit and IEEE float32, any
4
+ // channel count (mean mixdown), any sample rate (linear resample to 16 kHz).
5
+ // PCM16 scales by 1/32768 to match the oracle golden generator
6
+ // (`02d723a:scripts/oracle/gen-e4b-audio-golden.py`) exactly. Non-WAV containers (mp3/m4a/
7
+ // flac/ogg) are transcoded to WAV by the serve layer via macOS `afconvert`
8
+ // before reaching this parser.
9
+ //
10
+ // Resampling note: optiq's serve frontend never exposes audio, so there is
11
+ // no oracle behavior for non-16 kHz input — linear interpolation is our
12
+ // documented choice (the parity fixtures are native 16 kHz and skip it).
13
+
14
+ export const AUDIO_SAMPLE_RATE = 16_000;
15
+ export const AUDIO_MS_PER_TOKEN = 40;
16
+ export const AUDIO_SEQ_LENGTH = 750; // 30 s cap, matches the oracle processor
17
+ /** Raw-speech truncation bound — the oracle feature extractor's default
18
+ * (audio_feature_extractor.py __call__: truncation=True,
19
+ * max_length=480_000 → `w[:max_length]`, i.e. 30 s at 16 kHz) applied
20
+ * BEFORE feature extraction. Mirrored in decodeAudio so a >30 s clip
21
+ * yields exactly the capped 750 soft tokens AND the matching mel frame
22
+ * count — the prompt builder hard-checks that the tower's output length
23
+ * equals the splice's soft-token count. */
24
+ export const AUDIO_MAX_SAMPLES = 30 * AUDIO_SAMPLE_RATE; // 480_000
25
+
26
+ export interface DecodedWav {
27
+ samples: Float32Array; // mono
28
+ sampleRate: number;
29
+ }
30
+
31
+ /** Number of audio soft tokens for a clip — processing_gemma4.py
32
+ * `_compute_audio_num_tokens`: ceil(duration_ms / 40) capped at 750.
33
+ * MUST be computed from decoded samples, never from container metadata. */
34
+ export function audioSoftTokenCount(
35
+ numSamples: number,
36
+ sampleRate: number = AUDIO_SAMPLE_RATE,
37
+ ): number {
38
+ const durationMs = (numSamples / sampleRate) * 1000;
39
+ return Math.min(Math.ceil(durationMs / AUDIO_MS_PER_TOKEN), AUDIO_SEQ_LENGTH);
40
+ }
41
+
42
+ const WAVE_FORMAT_PCM = 1;
43
+ const WAVE_FORMAT_IEEE_FLOAT = 3;
44
+ const WAVE_FORMAT_EXTENSIBLE = 0xfffe;
45
+
46
+ /** Parse a RIFF/WAVE file into mono float samples at its native rate. */
47
+ export function decodeWav(bytes: Uint8Array): DecodedWav {
48
+ const v = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
49
+ const tag = (o: number) => String.fromCharCode(bytes[o]!, bytes[o + 1]!, bytes[o + 2]!, bytes[o + 3]!);
50
+ if (bytes.length < 44 || tag(0) !== "RIFF" || tag(8) !== "WAVE")
51
+ throw new Error("not a RIFF/WAVE file");
52
+
53
+ let fmt: { format: number; channels: number; sampleRate: number; bits: number } | null = null;
54
+ let dataOff = -1;
55
+ let dataLen = 0;
56
+ for (let o = 12; o + 8 <= bytes.length; ) {
57
+ const id = tag(o);
58
+ const size = v.getUint32(o + 4, true);
59
+ const body = o + 8;
60
+ if (id === "fmt ") {
61
+ // the reads below reach body+16 (base fields) / body+26 (extensible)
62
+ if (size < 16 || body + 16 > bytes.length)
63
+ throw new Error("WAV: truncated fmt chunk");
64
+ let format = v.getUint16(body, true);
65
+ const channels = v.getUint16(body + 2, true);
66
+ const sampleRate = v.getUint32(body + 4, true);
67
+ const bits = v.getUint16(body + 14, true);
68
+ if (format === WAVE_FORMAT_EXTENSIBLE) {
69
+ if (size < 40 || body + 26 > bytes.length)
70
+ throw new Error("WAV: truncated WAVE_FORMAT_EXTENSIBLE fmt chunk");
71
+ format = v.getUint16(body + 24, true); // first 2 bytes of SubFormat GUID
72
+ }
73
+ fmt = { format, channels, sampleRate, bits };
74
+ } else if (id === "data") {
75
+ dataOff = body;
76
+ dataLen = Math.min(size, bytes.length - body);
77
+ }
78
+ o = body + size + (size & 1); // chunks are word-aligned
79
+ }
80
+ if (!fmt) throw new Error("WAV: missing fmt chunk");
81
+ if (dataOff < 0) throw new Error("WAV: missing data chunk");
82
+ const { format, channels, sampleRate, bits } = fmt;
83
+ if (channels < 1) throw new Error("WAV: zero channels");
84
+
85
+ const bytesPer = bits / 8;
86
+ const frames = Math.floor(dataLen / (bytesPer * channels));
87
+ const out = new Float32Array(frames);
88
+
89
+ const readSample = (off: number): number => {
90
+ if (format === WAVE_FORMAT_IEEE_FLOAT && bits === 32) return v.getFloat32(off, true);
91
+ if (format === WAVE_FORMAT_PCM && bits === 16) return v.getInt16(off, true) / 32768;
92
+ if (format === WAVE_FORMAT_PCM && bits === 24) {
93
+ const raw = bytes[off]! | (bytes[off + 1]! << 8) | (bytes[off + 2]! << 16);
94
+ return ((raw << 8) >> 8) / 8388608;
95
+ }
96
+ if (format === WAVE_FORMAT_PCM && bits === 32) return v.getInt32(off, true) / 2147483648;
97
+ throw new Error(`WAV: unsupported format ${format} / ${bits}-bit`);
98
+ };
99
+
100
+ for (let f = 0; f < frames; f++) {
101
+ const base = dataOff + f * bytesPer * channels;
102
+ let acc = 0;
103
+ for (let c = 0; c < channels; c++) acc += readSample(base + c * bytesPer);
104
+ out[f] = Math.fround(acc / channels);
105
+ }
106
+ return { samples: out, sampleRate };
107
+ }
108
+
109
+ /** Linear resample to the model rate (no-op passthrough at 16 kHz). */
110
+ export function resampleTo16k(samples: Float32Array, sampleRate: number): Float32Array {
111
+ if (sampleRate === AUDIO_SAMPLE_RATE) return samples;
112
+ if (sampleRate <= 0) throw new Error(`bad sample rate ${sampleRate}`);
113
+ const outLen = Math.max(1, Math.round((samples.length * AUDIO_SAMPLE_RATE) / sampleRate));
114
+ const out = new Float32Array(outLen);
115
+ const step = samples.length / outLen;
116
+ for (let i = 0; i < outLen; i++) {
117
+ const pos = i * step;
118
+ const j = Math.floor(pos);
119
+ const fracPart = pos - j;
120
+ const a = samples[Math.min(j, samples.length - 1)]!;
121
+ const b = samples[Math.min(j + 1, samples.length - 1)]!;
122
+ out[i] = Math.fround(a + (b - a) * fracPart);
123
+ }
124
+ return out;
125
+ }
126
+
127
+ /** WAV bytes → mono 16 kHz float samples (the feature-extractor input),
128
+ * truncated at 30 s exactly like the oracle (AUDIO_MAX_SAMPLES). */
129
+ export function decodeAudio(bytes: Uint8Array): Float32Array {
130
+ const { samples, sampleRate } = decodeWav(bytes);
131
+ const s = resampleTo16k(samples, sampleRate);
132
+ return s.length > AUDIO_MAX_SAMPLES ? s.subarray(0, AUDIO_MAX_SAMPLES) : s;
133
+ }
@@ -0,0 +1,202 @@
1
+ // USM log-mel feature extraction for gemma-4 audio — a verbatim-semantics
2
+ // port of the oracle's Gemma4AudioFeatureExtractor (optiq/vlm/_mlxvlm/
3
+ // models/gemma4/audio_feature_extractor.py, constructor DEFAULTS —
4
+ // `02d723a:docs/design/generic-model-support.md` §6.6: no per-bin normalization, params are
5
+ // fixed for all gemma-4 models). Gated bit-close against the T0 mel goldens
6
+ // (untracked goldens/e4b-audio-*-mel.bin) in `02d723a:tests/parity/audio-features.test.ts`.
7
+ //
8
+ // Pipeline (single waveform, 16 kHz mono float32):
9
+ // pad to a multiple of 128 samples (validity mask over the original part)
10
+ // → semicausal left-pad of frame_length/2 = 160 zeros
11
+ // → unfold into 321-sample frames at hop 160, drop the 321st sample
12
+ // (preemphasis = 0 branch)
13
+ // → × periodic Hann window (numpy computes it in FLOAT32 — emulated with
14
+ // Math.fround so the products match the oracle's f32 rounding)
15
+ // → rfft n=512 (numpy promotes to float64; we FFT in f64)
16
+ // → |·| → @ mel filter bank (128 HTK bins over 0–8 kHz, float64, the
17
+ // transformers mel_filter_bank construction: linspace fft freqs,
18
+ // unguarded slope division, no norm)
19
+ // → log(x + 1e-3) → zero out invalid frames → float32 [T, 128].
20
+ //
21
+ // Frame validity: frame f is valid iff original-sample index
22
+ // f·hop + frame_length lands inside the un-padded waveform.
23
+
24
+ const SR = 16_000;
25
+ const FRAME = 320; // 20 ms
26
+ const HOP = 160; // 10 ms
27
+ const FFT_N = 512; // 2^ceil(log2(320)), fft_overdrive=false
28
+ const FREQ_BINS = FFT_N / 2 + 1; // 257
29
+ const MEL_BINS = 128;
30
+ const MEL_FMIN = 0.0;
31
+ const MEL_FMAX = 8000.0;
32
+ const MEL_FLOOR = 1e-3;
33
+ const PAD_MULTIPLE = 128;
34
+
35
+ export interface MelFeatures {
36
+ /** [frames, 128] row-major log-mel, float32 (invalid frames zeroed). */
37
+ features: Float32Array;
38
+ frames: number;
39
+ /** Per-frame validity (1 = real audio, 0 = padding). */
40
+ mask: Uint8Array;
41
+ }
42
+
43
+ // --- mel filter bank (float64, transformers semantics) -----------------------
44
+ const hzToMel = (f: number): number => 2595.0 * Math.log10(1.0 + f / 700.0);
45
+ const melToHz = (m: number): number => 700.0 * (10 ** (m / 2595.0) - 1.0);
46
+
47
+ function buildMelFilters(): Float64Array {
48
+ // filter_freqs: 130 points linspace in mel space, mapped back to Hz
49
+ const melMin = hzToMel(MEL_FMIN);
50
+ const melMax = hzToMel(MEL_FMAX);
51
+ const nPts = MEL_BINS + 2;
52
+ const filterFreqs = new Float64Array(nPts);
53
+ for (let i = 0; i < nPts; i++)
54
+ filterFreqs[i] = melToHz(melMin + ((melMax - melMin) * i) / (nPts - 1));
55
+ // fft_freqs: linspace(0, sr//2, 257)
56
+ const fftFreqs = new Float64Array(FREQ_BINS);
57
+ for (let k = 0; k < FREQ_BINS; k++) fftFreqs[k] = (8000.0 * k) / (FREQ_BINS - 1);
58
+ // triangular filters: max(0, min(down, up)) — unguarded diffs like the oracle
59
+ const filters = new Float64Array(FREQ_BINS * MEL_BINS);
60
+ for (let m = 0; m < MEL_BINS; m++) {
61
+ const lower = filterFreqs[m]!;
62
+ const center = filterFreqs[m + 1]!;
63
+ const upper = filterFreqs[m + 2]!;
64
+ for (let k = 0; k < FREQ_BINS; k++) {
65
+ const down = (fftFreqs[k]! - lower) / (center - lower);
66
+ const up = (upper - fftFreqs[k]!) / (upper - center);
67
+ filters[k * MEL_BINS + m] = Math.max(0, Math.min(down, up));
68
+ }
69
+ }
70
+ return filters;
71
+ }
72
+
73
+ // --- periodic Hann window: the ORACLE'S EXACT float32 values -----------------
74
+ // numpy builds this in float32 end-to-end (arange f32 → ×2π/320 → cos, all in
75
+ // numpy's vectorized f32 routines) and those cos values differ from
76
+ // fround(Math.cos(x)) by 1 ulp on some inputs. That 1 ulp is the ONLY
77
+ // divergence in the whole pipeline: with these exact values our extractor
78
+ // reproduces the oracle mel golden BIT-EXACTLY; with a recomputed window the
79
+ // log-amplified cancellation error reaches ~4.5e-4 (verified 2026-07-07).
80
+ // So the constant IS the spec. Regen (oracle venv):
81
+ // python -c "from optiq.vlm._mlxvlm.models.gemma4.audio_feature_extractor \
82
+ // import Gemma4AudioFeatureExtractor as F; import base64; \
83
+ // print(base64.b64encode(F().window.astype('<f4').tobytes()).decode())"
84
+ const WINDOW_B64 =
85
+ "AAAAAAAgyjgAHMo5AFRjOgAHyjqAyR07ACJjOwCDmjvAtsk7wCf/O0BoHTyAVT48gFhiPNC2hDyQ" +
86
+ "yJk8UF+wPPB4yDwQE+I8ECv9PDDfDD0Q5Rs9qKUrPXAfPD3IUE09+DdfPUjTcT1okII9XI+MPYDl" +
87
+ "lj3QkaE9PJOsPbDotz0MkcM9LIvPPeDV2z30b+g9JFj1PZZGAT7iBgg+SOwOPhr2FT6mIx0+PHQk" +
88
+ "PhrnKz6OezM+zjA7Ph4GQz64+ko+0A1TPp4+Wz5OjGM+EvZrPhR7dD54Gn0+uemCPopShz5Ix4s+" +
89
+ "fEeQPrbSlD6GaJk+dQiePhCyoj7gZKc+biCsPkTksD7or7U+4oK6Prdcvz7uPMQ+CiPJPpEOzj4F" +
90
+ "/9I+6fPXPsTs3D4S6eE+W+jmPhvq6z7Y7fA+EvP1Pkr5+j4AAAA/XIMCP3cGBT8ViQc/8woKP9OL" +
91
+ "DD93Cw8/oIkRPwwGFD9+gBY/uPgYP3xuGz+K4R0/pVEgP5C+Ij8OKCU/340nP8rvKT+RTSw/+KYu" +
92
+ "P8b7MD++SzM/pZY1P0PcNz9dHDo/u1Y8PySLPj9iuUA/PeFCP3wCRT/sHEc/WjBJP448Sz9SQU0/" +
93
+ "eD5PP84zUT8dIVM/OgZVP/LiVj8Yt1g/eoJaP+5EXD9I/l0/Wq5fP/xUYT8C8mI/RIVkP5oOZj/e" +
94
+ "jWc/6gJpP5ltaj/GzWs/UCNtPxVubj/0rW8/zOJwP4EMcj/0KnM/Cj50P6ZFdT+wQXY/DTJ3P6gW" +
95
+ "eD9o73g/OLx5PwZ9ej+8MXs/Stp7P552fD+qBn0/YIp9P7ABfj+SbH4/+sp+P94cfz82Yn8//Zp/" +
96
+ "PyvHfz+85n8/r/l/PwAAgD+v+X8/vOZ/PyvHfz/9mn8/NmJ/P94cfz/6yn4/kmx+P7ABfj9gin0/" +
97
+ "qgZ9P552fD9J2ns/uzF7PwV9ej84vHk/aO94P6gWeD8NMnc/r0F2P6RFdT8IPnQ/8ipzP4AMcj/M" +
98
+ "4nA/861vPxRubj9QI20/xc1rP5dtaj/rAmk/3o1nP5oOZj9EhWQ/AfJiP/tUYT9Yrl8/SP5dP+9E" +
99
+ "XD95glo/FbdYP/DiVj84BlU/HCFTP80zUT93Pk8/UkFNP4o8Sz9ZMEk/6xxHP3gCRT894UI/YrlA" +
100
+ "PyKLPj+6Vjw/Whw6P0LcNz+mljU/vEszP8b7MD/4pi4/jk0sP8jvKT/cjSc/DCglP5C+Ij+kUSA/" +
101
+ "iuEdP3puGz+1+Bg/fYAWPwwGFD+diRE/dwsPP9KLDD/zCgo/EokHP3MGBT9dgwI/AAAAP0X5+j4R" +
102
+ "8/U+0+3wPhrq6z5U6OY+EenhPsXs3D7o89c+/v7SPo4Ozj4EI8k+6zzEPrhcvz7fgro+6a+1PkHk" +
103
+ "sD5oIKw+3WSnPgqyoj5zCJ4+iGiZPrTSlD58R5A+RMeLPoRShz616YI+ehp9Pgx7dD4Q9ms+Roxj" +
104
+ "Ppw+Wz7IDVM+qvpKPiQGQz7MMDs+hHszPhrnKz4ydCQ+piMdPhL2FT5E7A4+5AYIPpRGAT4UWPU9" +
105
+ "7G/oPdDV2z0oi889EJHDPajotz08k6w9yJGhPXDllj1Uj4w9WJCCPTjTcT34N189uFBNPXAfPD2Y" +
106
+ "pSs98OQbPTjfDD0QK/088BLiPPB4yDxAX7A8gMiZPMC2hDxgWGI8gFU+PCBoHTyAJ/87wLbJO8CC" +
107
+ "mjsAImM7gMkdOwAGyjoAVGM6ABzKOQAgyjg=";
108
+
109
+ function buildWindow(): Float32Array {
110
+ const raw = Buffer.from(WINDOW_B64, "base64");
111
+ return new Float32Array(raw.buffer, raw.byteOffset, FRAME);
112
+ }
113
+
114
+ // --- iterative radix-2 complex FFT (float64), N = 512 ------------------------
115
+ const FFT_COS = new Float64Array(FFT_N / 2);
116
+ const FFT_SIN = new Float64Array(FFT_N / 2);
117
+ for (let i = 0; i < FFT_N / 2; i++) {
118
+ FFT_COS[i] = Math.cos((-2 * Math.PI * i) / FFT_N);
119
+ FFT_SIN[i] = Math.sin((-2 * Math.PI * i) / FFT_N);
120
+ }
121
+
122
+ function fft512(re: Float64Array, im: Float64Array): void {
123
+ // bit-reversal permutation
124
+ for (let i = 1, j = 0; i < FFT_N; i++) {
125
+ let bit = FFT_N >> 1;
126
+ for (; j & bit; bit >>= 1) j ^= bit;
127
+ j ^= bit;
128
+ if (i < j) {
129
+ const tr = re[i]!; re[i] = re[j]!; re[j] = tr;
130
+ const ti = im[i]!; im[i] = im[j]!; im[j] = ti;
131
+ }
132
+ }
133
+ for (let len = 2; len <= FFT_N; len <<= 1) {
134
+ const half = len >> 1;
135
+ const step = FFT_N / len;
136
+ for (let i = 0; i < FFT_N; i += len) {
137
+ for (let k = 0; k < half; k++) {
138
+ const c = FFT_COS[k * step]!;
139
+ const s = FFT_SIN[k * step]!;
140
+ const xr = re[i + k + half]! * c - im[i + k + half]! * s;
141
+ const xi = re[i + k + half]! * s + im[i + k + half]! * c;
142
+ re[i + k + half] = re[i + k]! - xr;
143
+ im[i + k + half] = im[i + k]! - xi;
144
+ re[i + k] = re[i + k]! + xr;
145
+ im[i + k] = im[i + k]! + xi;
146
+ }
147
+ }
148
+ }
149
+ }
150
+
151
+ let melFilters: Float64Array | null = null;
152
+ let hannWindow: Float32Array | null = null;
153
+
154
+ /** 16 kHz mono float32 waveform → USM log-mel features [T, 128]. */
155
+ export function extractMelFeatures(waveform: Float32Array): MelFeatures {
156
+ melFilters ??= buildMelFilters();
157
+ hannWindow ??= buildWindow();
158
+
159
+ const nReal = waveform.length;
160
+ const nPadded = Math.ceil(nReal / PAD_MULTIPLE) * PAD_MULTIPLE;
161
+ const PAD_LEFT = FRAME >> 1; // 160
162
+ const total = PAD_LEFT + nPadded;
163
+ const frames = Math.floor((total - (FRAME + 1)) / HOP) + 1;
164
+ if (frames <= 0) return { features: new Float32Array(0), frames: 0, mask: new Uint8Array(0) };
165
+
166
+ const features = new Float32Array(frames * MEL_BINS);
167
+ const mask = new Uint8Array(frames);
168
+ const re = new Float64Array(FFT_N);
169
+ const im = new Float64Array(FFT_N);
170
+ const mel = new Float64Array(MEL_BINS);
171
+
172
+ for (let f = 0; f < frames; f++) {
173
+ // frame f covers padded indices [f*HOP, f*HOP+FRAME) after the left pad;
174
+ // validity = the frame-END sample (index f*HOP+FRAME in the left-padded
175
+ // stream, i.e. f*HOP+FRAME-PAD_LEFT in the original) is real audio.
176
+ const endIdx = f * HOP + FRAME - PAD_LEFT;
177
+ const valid = endIdx >= 0 && endIdx < nReal;
178
+ mask[f] = valid ? 1 : 0;
179
+ if (!valid) continue; // oracle zeroes invalid frames; ours start zeroed
180
+
181
+ re.fill(0);
182
+ im.fill(0);
183
+ for (let n = 0; n < FRAME; n++) {
184
+ const srcIdx = f * HOP + n - PAD_LEFT; // <0 → semicausal zero pad
185
+ const sample = srcIdx >= 0 && srcIdx < nReal ? waveform[srcIdx]! : 0;
186
+ re[n] = Math.fround(sample * hannWindow[n]!); // numpy f32 product
187
+ }
188
+ fft512(re, im);
189
+
190
+ mel.fill(0);
191
+ for (let k = 0; k < FREQ_BINS; k++) {
192
+ const mag = Math.hypot(re[k]!, im[k]!);
193
+ if (mag === 0) continue;
194
+ const base = k * MEL_BINS;
195
+ for (let m = 0; m < MEL_BINS; m++) mel[m] = mel[m]! + mag * melFilters![base + m]!;
196
+ }
197
+ const out = f * MEL_BINS;
198
+ for (let m = 0; m < MEL_BINS; m++)
199
+ features[out + m] = Math.fround(Math.log(mel[m]! + MEL_FLOOR));
200
+ }
201
+ return { features, frames, mask };
202
+ }
@@ -0,0 +1,6 @@
1
+ export * from "./decode";
2
+ export * from "./audiotoolbox";
3
+ export * from "./transcode";
4
+ export * from "./features";
5
+ export * from "./whisper-mel";
6
+ export * from "./whisper-tokenizer";
@@ -0,0 +1,67 @@
1
+ // Non-WAV audio containers → 16 kHz WAV via macOS `afconvert` (CoreAudio —
2
+ // built into darwin, our only platform; zero new dependencies). The serve
3
+ // layer runs every clip through ensureWav(): RIFF/WAVE bytes pass through
4
+ // untouched (decode.ts owns the parse), anything else (mp3/m4a/flac/ogg/
5
+ // aiff/…) round-trips through a temp file under os.tmpdir().
6
+ //
7
+ // Two deliberate choices:
8
+ // - The WAV/non-WAV decision is CONTENT-based (RIFF magic), not
9
+ // metadata-based — a mislabelled `format` field must not break a valid
10
+ // WAV, and an mp3 labelled "wav" still transcodes.
11
+ // - Channel count is preserved (decodeWav does the mono mixdown); the
12
+ // sample rate converts here (`@16000`) because CoreAudio's resampler
13
+ // beats decode.ts's documented linear interpolation for real
14
+ // compressed sources.
15
+
16
+ import { randomUUID } from "node:crypto";
17
+ import { unlink } from "node:fs/promises";
18
+ import { tmpdir } from "node:os";
19
+ import { join } from "node:path";
20
+
21
+ /** RIFF/WAVE magic sniff ("RIFF" at 0, "WAVE" at 8). */
22
+ export function isRiffWave(bytes: Uint8Array): boolean {
23
+ return (
24
+ bytes.length >= 12 &&
25
+ bytes[0] === 0x52 && bytes[1] === 0x49 && bytes[2] === 0x46 && bytes[3] === 0x46 &&
26
+ bytes[8] === 0x57 && bytes[9] === 0x41 && bytes[10] === 0x56 && bytes[11] === 0x45
27
+ );
28
+ }
29
+
30
+ /** Transcode any CoreAudio-readable container to 16 kHz PCM16 WAV. Throws
31
+ * with afconvert's diagnostic on failure — the serve layer surfaces it as
32
+ * a 400 ("prompt build failed: audio transcode failed …"). Temp files are
33
+ * cleaned up on every path. */
34
+ export async function transcodeToWav(bytes: Uint8Array): Promise<Uint8Array> {
35
+ const base = join(tmpdir(), `mlx-bun-audio-${randomUUID()}`);
36
+ const inPath = `${base}.in`;
37
+ const outPath = `${base}.wav`;
38
+ await Bun.write(inPath, bytes);
39
+ try {
40
+ const proc = Bun.spawn(
41
+ ["afconvert", "-f", "WAVE", "-d", "LEI16@16000", inPath, outPath],
42
+ { stdout: "ignore", stderr: "pipe" },
43
+ );
44
+ // ≤30 s clips convert in well under a second — a hung afconvert must
45
+ // not pin the request forever. kill() → nonzero exit → the 400 below.
46
+ const killer = setTimeout(() => proc.kill(), 30_000);
47
+ const exit = await proc.exited;
48
+ clearTimeout(killer);
49
+ if (exit !== 0) {
50
+ const err = (await new Response(proc.stderr).text()).trim();
51
+ throw new Error(
52
+ `audio transcode failed (afconvert exit ${exit}): ` +
53
+ `${err.split("\n").at(-1) || "unknown error"} — ` +
54
+ `supported containers: WAV (native), mp3/m4a/flac/ogg/aiff via CoreAudio`,
55
+ );
56
+ }
57
+ return new Uint8Array(await Bun.file(outPath).arrayBuffer());
58
+ } finally {
59
+ await Promise.allSettled([unlink(inPath), unlink(outPath)]);
60
+ }
61
+ }
62
+
63
+ /** WAV passthrough or afconvert transcode — every serve-side clip goes
64
+ * through here before decodeAudio. */
65
+ export async function ensureWav(bytes: Uint8Array): Promise<Uint8Array> {
66
+ return isRiffWave(bytes) ? bytes : transcodeToWav(bytes);
67
+ }