@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,185 @@
1
+ // Whisper log-mel spectrogram — an op-for-op port of mlx-whisper's
2
+ // audio.py (log_mel_spectrogram / stft / mel_filters), gated bit-exact
3
+ // against the oracle in `02d723a:tests/parity/whisper.test.ts`.
4
+ //
5
+ // Pipeline (single 16 kHz mono float32 waveform):
6
+ // [optional zero pad of `padding` samples on the right]
7
+ // → reflect pad N_FFT/2 = 200 samples each side (done host-side; the
8
+ // values are a copy so the device graph is identical)
9
+ // → frames [T, 400] via as_strided (stride HOP = 160)
10
+ // → × periodic Hann window (np.hanning(401)[:-1], float32)
11
+ // → rfft n=400 → drop the LAST frame → |·|² (float32)
12
+ // → @ melᵀ [201, 128] (librosa slaney filters, float32 — built below)
13
+ // → log10(max(·, 1e-10)) → max(·, global_max − 8) → (· + 4) / 4
14
+ // Output: [T, n_mels] float32 where T = (len + 400 − 400 + 160) // 160 − 1
15
+ // (the oracle's `freqs[:-1]`), i.e. exactly len/160 frames for a multiple
16
+ // of the hop.
17
+ //
18
+ // The mel filter bank reproduces librosa.filters.mel(sr=16000, n_fft=400,
19
+ // n_mels, htk=False, norm="slaney") in float64 and rounds to float32 — the
20
+ // same values mlx-whisper ships pre-computed in assets/mel_filters.npz
21
+ // (checked bit-exact by the parity test against the oracle dump).
22
+
23
+ import { MlxArray } from "@mlx-bun/mlx/array";
24
+ import { Dtype } from "@mlx-bun/mlx/ffi";
25
+ import * as ops from "@mlx-bun/mlx/ops";
26
+
27
+ export const WHISPER_SAMPLE_RATE = 16_000;
28
+ export const WHISPER_N_FFT = 400;
29
+ export const WHISPER_HOP = 160;
30
+ export const WHISPER_CHUNK_SECONDS = 30;
31
+ /** 480_000 samples per 30 s window. */
32
+ export const WHISPER_N_SAMPLES = WHISPER_CHUNK_SECONDS * WHISPER_SAMPLE_RATE;
33
+ /** 3000 mel frames per window. */
34
+ export const WHISPER_N_FRAMES = WHISPER_N_SAMPLES / WHISPER_HOP;
35
+ /** 100 mel frames per second. */
36
+ export const WHISPER_FRAMES_PER_SECOND = WHISPER_SAMPLE_RATE / WHISPER_HOP;
37
+ /** 50 encoder positions per second (conv2 stride 2). */
38
+ export const WHISPER_TOKENS_PER_SECOND = WHISPER_SAMPLE_RATE / (WHISPER_HOP * 2);
39
+
40
+ const dispose = (old: MlxArray, next: MlxArray): MlxArray => {
41
+ old.dispose();
42
+ return next;
43
+ };
44
+
45
+ // --- librosa slaney mel filter bank ----------------------------------------
46
+ const F_SP = 200.0 / 3;
47
+ const MIN_LOG_HZ = 1000.0;
48
+ const MIN_LOG_MEL = MIN_LOG_HZ / F_SP; // 15
49
+ const LOGSTEP = Math.log(6.4) / 27.0;
50
+
51
+ function hzToMel(f: number): number {
52
+ const m = f / F_SP;
53
+ return f >= MIN_LOG_HZ ? MIN_LOG_MEL + Math.log(f / MIN_LOG_HZ) / LOGSTEP : m;
54
+ }
55
+ function melToHz(m: number): number {
56
+ return m >= MIN_LOG_MEL ? MIN_LOG_HZ * Math.exp(LOGSTEP * (m - MIN_LOG_MEL)) : F_SP * m;
57
+ }
58
+
59
+ /** Row-major [nMels, 201] float32 — librosa.filters.mel semantics. */
60
+ export function whisperMelFilters(nMels: number): Float32Array {
61
+ const nBins = WHISPER_N_FFT / 2 + 1; // 201
62
+ const fmax = WHISPER_SAMPLE_RATE / 2;
63
+ // np.fft.rfftfreq(400, 1/16000) = arange(201) * 16000/400
64
+ // np.fft.rfftfreq: val = 1/(n·d) with d = 1/sr, then k·val — keep numpy's
65
+ // float64 rounding (1/16000 is inexact in binary) for bit-exact filters.
66
+ const val = 1.0 / (WHISPER_N_FFT * (1.0 / WHISPER_SAMPLE_RATE));
67
+ const fftFreqs = new Float64Array(nBins);
68
+ for (let k = 0; k < nBins; k++) fftFreqs[k] = k * val;
69
+ // mel_frequencies(n_mels + 2): np.linspace in mel space, mapped to Hz
70
+ const nPts = nMels + 2;
71
+ const melMin = hzToMel(0);
72
+ const melMax = hzToMel(fmax);
73
+ const melF = new Float64Array(nPts);
74
+ const step = (melMax - melMin) / (nPts - 1);
75
+ for (let i = 0; i < nPts; i++) melF[i] = melToHz(melMin + step * i);
76
+ const out = new Float32Array(nMels * nBins);
77
+ for (let i = 0; i < nMels; i++) {
78
+ const fd0 = melF[i + 1]! - melF[i]!;
79
+ const fd1 = melF[i + 2]! - melF[i + 1]!;
80
+ const enorm = 2.0 / (melF[i + 2]! - melF[i]!);
81
+ for (let k = 0; k < nBins; k++) {
82
+ const lower = -(melF[i]! - fftFreqs[k]!) / fd0;
83
+ const upper = (melF[i + 2]! - fftFreqs[k]!) / fd1;
84
+ // librosa stores the un-normalized weight into a float32 array, then
85
+ // `weights *= enorm[:, None]` (float64 multiply, float32 store).
86
+ const w = Math.fround(Math.max(0, Math.min(lower, upper)));
87
+ out[i * nBins + k] = Math.fround(w * enorm);
88
+ }
89
+ }
90
+ return out;
91
+ }
92
+
93
+ /** np.hanning(N+1)[:-1] as float32 (periodic Hann). */
94
+ export function whisperHannWindow(): Float32Array {
95
+ const n = WHISPER_N_FFT;
96
+ const w = new Float32Array(n);
97
+ for (let i = 0; i < n; i++) w[i] = Math.fround(0.5 - 0.5 * Math.cos((2 * Math.PI * i) / n));
98
+ return w;
99
+ }
100
+
101
+ /** Device-resident constants for one mel configuration (built once per model). */
102
+ export class WhisperMelExtractor {
103
+ readonly nMels: number;
104
+ /** [201, nMels] float32 — filters transposed for `magnitudes @ filtersᵀ`. */
105
+ readonly #filtersT: MlxArray;
106
+ /** [400] float32. */
107
+ readonly #window: MlxArray;
108
+
109
+ constructor(nMels: number) {
110
+ this.nMels = nMels;
111
+ const f = whisperMelFilters(nMels);
112
+ const nBins = WHISPER_N_FFT / 2 + 1;
113
+ const t = new Float32Array(nBins * nMels);
114
+ for (let i = 0; i < nMels; i++)
115
+ for (let k = 0; k < nBins; k++) t[k * nMels + i] = f[i * nBins + k]!;
116
+ this.#filtersT = MlxArray.fromFloat32(t, [nBins, nMels]);
117
+ this.#window = MlxArray.fromFloat32(whisperHannWindow(), [WHISPER_N_FFT]);
118
+ }
119
+
120
+ /** Number of mel frames the oracle produces for `numSamples` (+ padding). */
121
+ static frameCount(numSamples: number, padding = 0): number {
122
+ const len = numSamples + padding + WHISPER_N_FFT; // reflect-padded length
123
+ return Math.floor((len - WHISPER_N_FFT + WHISPER_HOP) / WHISPER_HOP) - 1;
124
+ }
125
+
126
+ /** log_mel_spectrogram(audio, n_mels, padding) → [T, nMels] float32 (lazy). */
127
+ logMel(samples: Float32Array, padding = 0): MlxArray {
128
+ const half = WHISPER_N_FFT / 2;
129
+ const n = samples.length + padding;
130
+ if (samples.length <= half) throw new Error(`whisper mel: clip too short (${samples.length} samples)`);
131
+ // host-side: zero pad right, then reflect pad `half` on both sides
132
+ const padded = new Float32Array(n + 2 * half);
133
+ // prefix = x[1:half+1][::-1]
134
+ for (let i = 0; i < half; i++) padded[i] = samples[half - i] ?? 0;
135
+ padded.set(samples, half);
136
+ // (zero padding region already 0)
137
+ // suffix = x[-(half+1):-1][::-1] over the ZERO-PADDED signal
138
+ const src = (idx: number): number => (idx < samples.length ? samples[idx]! : 0);
139
+ for (let i = 0; i < half; i++) padded[half + n + i] = src(n - 2 - i);
140
+ const x = MlxArray.fromFloat32(padded, [padded.length]);
141
+ const t = Math.floor((padded.length - WHISPER_N_FFT + WHISPER_HOP) / WHISPER_HOP);
142
+ let frames = ops.asStrided(x, [t, WHISPER_N_FFT], [WHISPER_HOP, 1]);
143
+ frames = dispose(frames, ops.mul(frames, this.#window));
144
+ let spec = ops.rfft(frames, WHISPER_N_FFT, -1);
145
+ frames.dispose();
146
+ x.dispose();
147
+ spec = dispose(spec, spec.slice([0, 0], [t - 1, WHISPER_N_FFT / 2 + 1]));
148
+ spec = dispose(spec, ops.abs(spec));
149
+ spec = dispose(spec, ops.square(spec));
150
+ let mel = ops.matmul(spec, this.#filtersT);
151
+ spec.dispose();
152
+ const floor = ops.scalarLike(1e-10, mel);
153
+ mel = dispose(mel, ops.maximum(mel, floor));
154
+ floor.dispose();
155
+ mel = dispose(mel, ops.log10(mel));
156
+ const mx = ops.maxAll(mel);
157
+ const eight = ops.scalarLike(8.0, mel);
158
+ const lo = ops.sub(mx, eight);
159
+ mel = dispose(mel, ops.maximum(mel, lo));
160
+ mx.dispose();
161
+ eight.dispose();
162
+ lo.dispose();
163
+ const four = ops.scalarLike(4.0, mel);
164
+ mel = dispose(mel, ops.add(mel, four));
165
+ mel = dispose(mel, ops.div(mel, four));
166
+ four.dispose();
167
+ return mel;
168
+ }
169
+
170
+ dispose(): void {
171
+ this.#filtersT.dispose();
172
+ this.#window.dispose();
173
+ }
174
+ }
175
+
176
+ /** pad_or_trim along the frame axis to `length` frames (zeros on the right). */
177
+ export function padOrTrimFrames(mel: MlxArray, length: number, dtype: Dtype): MlxArray {
178
+ const [t, m] = mel.shape as [number, number];
179
+ let out: MlxArray;
180
+ if (t > length) out = mel.slice([0, 0], [length, m]);
181
+ else if (t < length) out = ops.pad(mel, [[0, length - t], [0, 0]]);
182
+ else out = ops.copyOf(mel);
183
+ if (out.dtype !== dtype) out = dispose(out, out.astype(dtype));
184
+ return out;
185
+ }
@@ -0,0 +1,238 @@
1
+ // Whisper tokenizer — mlx-whisper's tokenizer.py semantics over the stock
2
+ // HF tokenizer.json (openai/whisper-large-v3-turbo ships the multilingual
3
+ // tiktoken vocab in HF form; ids are identical).
4
+ //
5
+ // Special-token layout (multilingual v3, n_vocab 51866): 50256 BPE ranks +
6
+ // "<|endoftext|>"(50257) "<|startoftranscript|>"(50258) + 100 language
7
+ // tokens + "<|translate|>" "<|transcribe|>" "<|startoflm|>" "<|startofprev|>"
8
+ // "<|nospeech|>" "<|notimestamps|>" + 1501 timestamp tokens "<|0.00|>" …
9
+ // "<|30.00|>". The mlx-community/whisper-* repos ship no tokenizer, so the
10
+ // loader resolves the openai/whisper-* tokenizer files (see
11
+ // resolveWhisperTokenizerDir).
12
+
13
+ import { existsSync, readdirSync } from "node:fs";
14
+ import { loadTokenizer, type LoadedTokenizer } from "../tokenizer";
15
+
16
+ export const WHISPER_LANGUAGES: Record<string, string> = {
17
+ en: "english", zh: "chinese", de: "german", es: "spanish", ru: "russian", ko: "korean",
18
+ fr: "french", ja: "japanese", pt: "portuguese", tr: "turkish", pl: "polish", ca: "catalan",
19
+ nl: "dutch", ar: "arabic", sv: "swedish", it: "italian", id: "indonesian", hi: "hindi",
20
+ fi: "finnish", vi: "vietnamese", he: "hebrew", uk: "ukrainian", el: "greek", ms: "malay",
21
+ cs: "czech", ro: "romanian", da: "danish", hu: "hungarian", ta: "tamil", no: "norwegian",
22
+ th: "thai", ur: "urdu", hr: "croatian", bg: "bulgarian", lt: "lithuanian", la: "latin",
23
+ mi: "maori", ml: "malayalam", cy: "welsh", sk: "slovak", te: "telugu", fa: "persian",
24
+ lv: "latvian", bn: "bengali", sr: "serbian", az: "azerbaijani", sl: "slovenian", kn: "kannada",
25
+ et: "estonian", mk: "macedonian", br: "breton", eu: "basque", is: "icelandic", hy: "armenian",
26
+ ne: "nepali", mn: "mongolian", bs: "bosnian", kk: "kazakh", sq: "albanian", sw: "swahili",
27
+ gl: "galician", mr: "marathi", pa: "punjabi", si: "sinhala", km: "khmer", sn: "shona",
28
+ yo: "yoruba", so: "somali", af: "afrikaans", oc: "occitan", ka: "georgian", be: "belarusian",
29
+ tg: "tajik", sd: "sindhi", gu: "gujarati", am: "amharic", yi: "yiddish", lo: "lao",
30
+ uz: "uzbek", fo: "faroese", ht: "haitian creole", ps: "pashto", tk: "turkmen", nn: "nynorsk",
31
+ mt: "maltese", sa: "sanskrit", lb: "luxembourgish", my: "myanmar", bo: "tibetan", tl: "tagalog",
32
+ mg: "malagasy", as: "assamese", tt: "tatar", haw: "hawaiian", ln: "lingala", ha: "hausa",
33
+ ba: "bashkir", jw: "javanese", su: "sundanese", yue: "cantonese",
34
+ };
35
+
36
+ const TO_LANGUAGE_CODE: Record<string, string> = {
37
+ ...Object.fromEntries(Object.entries(WHISPER_LANGUAGES).map(([c, n]) => [n, c])),
38
+ burmese: "my", valencian: "ca", flemish: "nl", haitian: "ht", letzeburgesch: "lb",
39
+ pushto: "ps", panjabi: "pa", moldavian: "ro", moldovan: "ro", sinhalese: "si", castilian: "es",
40
+ mandarin: "zh",
41
+ };
42
+
43
+ /** "English" / "en" → "en"; throws on unknown. */
44
+ export function normalizeWhisperLanguage(language: string): string {
45
+ const l = language.toLowerCase();
46
+ if (l in WHISPER_LANGUAGES) return l;
47
+ const code = TO_LANGUAGE_CODE[l];
48
+ if (code) return code;
49
+ throw new Error(`Unsupported language: ${language}`);
50
+ }
51
+
52
+ export type WhisperTask = "transcribe" | "translate";
53
+
54
+ const ASCII_PUNCTUATION = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~";
55
+
56
+ export class WhisperTokenizer {
57
+ readonly eot: number;
58
+ readonly sot: number;
59
+ readonly translate: number;
60
+ readonly transcribe: number;
61
+ readonly sotLm: number;
62
+ readonly sotPrev: number;
63
+ readonly noSpeech: number;
64
+ readonly noTimestamps: number;
65
+ readonly timestampBegin: number;
66
+ readonly numLanguages: number;
67
+ /** Language token ids in id order (sot+1 …). */
68
+ readonly allLanguageTokens: number[];
69
+ readonly allLanguageCodes: string[];
70
+ readonly #tok: LoadedTokenizer;
71
+ readonly #encodeCache = new Map<string, number[]>();
72
+
73
+ constructor(tok: LoadedTokenizer, numLanguages: number) {
74
+ this.#tok = tok;
75
+ this.numLanguages = numLanguages;
76
+ const one = (text: string): number => {
77
+ const ids = tok.encode(text, false);
78
+ if (ids.length !== 1) throw new Error(`whisper tokenizer: ${text} is not a single token`);
79
+ return ids[0]!;
80
+ };
81
+ this.eot = one("<|endoftext|>");
82
+ this.sot = one("<|startoftranscript|>");
83
+ this.translate = one("<|translate|>");
84
+ this.transcribe = one("<|transcribe|>");
85
+ this.sotLm = one("<|startoflm|>");
86
+ this.sotPrev = one("<|startofprev|>");
87
+ this.noSpeech = one("<|nospeech|>");
88
+ this.noTimestamps = one("<|notimestamps|>");
89
+ this.timestampBegin = one("<|0.00|>");
90
+ // language tokens: the oracle's order is by (insertion) special-token
91
+ // order == ascending id, capped at num_languages
92
+ const codes = Object.keys(WHISPER_LANGUAGES).slice(0, numLanguages);
93
+ this.allLanguageTokens = codes.map((c) => one(`<|${c}|>`));
94
+ this.allLanguageCodes = codes;
95
+ }
96
+
97
+ encode(text: string): number[] {
98
+ let ids = this.#encodeCache.get(text);
99
+ if (!ids) {
100
+ ids = this.#tok.encode(text, false);
101
+ if (this.#encodeCache.size < 4096) this.#encodeCache.set(text, ids);
102
+ }
103
+ return ids;
104
+ }
105
+
106
+ /** decode() drops timestamp tokens (ids ≥ timestampBegin) like the oracle. */
107
+ decode(ids: number[]): string {
108
+ return this.#tok.decode(ids.filter((t) => t < this.timestampBegin), false);
109
+ }
110
+
111
+ /** Text tokens only (id < eot). */
112
+ decodeText(ids: number[]): string {
113
+ return this.#tok.decode(ids.filter((t) => t < this.eot), false);
114
+ }
115
+
116
+ decodeWithTimestamps(ids: number[]): string {
117
+ return this.#tok.decode(ids, false);
118
+ }
119
+
120
+ languageToken(language: string): number {
121
+ const i = this.allLanguageCodes.indexOf(language);
122
+ if (i < 0) throw new Error(`Language ${language} not found in tokenizer.`);
123
+ return this.allLanguageTokens[i]!;
124
+ }
125
+
126
+ /** [sot, <|lang|>, <|task|>] */
127
+ sotSequence(language: string | null, task: WhisperTask): number[] {
128
+ const seq = [this.sot];
129
+ if (language !== null) seq.push(this.languageToken(language));
130
+ seq.push(task === "transcribe" ? this.transcribe : this.translate);
131
+ return seq;
132
+ }
133
+
134
+ /** tokenizer.non_speech_tokens — punctuation/symbol tokens suppressed at
135
+ * every step (mlx-whisper tokenizer.py). */
136
+ get nonSpeechTokens(): number[] {
137
+ const symbols = "\"#()*+/:;<=>@[\\]^_`{|}~「」『』".split("");
138
+ symbols.push(...["<<", ">>", "<<<", ">>>", "--", "---", "-(", "-[", "('", "(\"", "((", "))", "(((", ")))", "[[", "]]", "{{", "}}", "♪♪", "♪♪♪"]);
139
+ const miscellaneous = new Set("♩♪♫♬♭♮♯".split(""));
140
+ const result = new Set<number>([this.encode(" -")[0]!, this.encode(" '")[0]!]);
141
+ for (const symbol of [...symbols, ...miscellaneous]) {
142
+ for (const tokens of [this.encode(symbol), this.encode(" " + symbol)]) {
143
+ if (tokens.length === 1 || miscellaneous.has(symbol)) result.add(tokens[0]!);
144
+ }
145
+ }
146
+ return [...result].sort((a, b) => a - b);
147
+ }
148
+
149
+ /** Split text tokens into words (split_to_word_tokens): unicode-boundary
150
+ * split for CJK/Thai/Lao/Myanmar, space split otherwise. */
151
+ splitToWordTokens(tokens: number[], language: string): { words: string[]; wordTokens: number[][] } {
152
+ if (["zh", "ja", "th", "lo", "my", "yue"].includes(language)) return this.#splitOnUnicode(tokens);
153
+ return this.#splitOnSpaces(tokens);
154
+ }
155
+
156
+ #splitOnUnicode(tokens: number[]): { words: string[]; wordTokens: number[][] } {
157
+ const decodedFull = this.decodeWithTimestamps(tokens);
158
+ const replacement = "�";
159
+ const words: string[] = [];
160
+ const wordTokens: number[][] = [];
161
+ let current: number[] = [];
162
+ let unicodeOffset = 0;
163
+ for (const token of tokens) {
164
+ current.push(token);
165
+ const decoded = this.decodeWithTimestamps(current);
166
+ const idx = decoded.indexOf(replacement);
167
+ if (idx < 0 || decodedFull[unicodeOffset + idx] === replacement) {
168
+ words.push(decoded);
169
+ wordTokens.push(current);
170
+ current = [];
171
+ unicodeOffset += decoded.length;
172
+ }
173
+ }
174
+ return { words, wordTokens };
175
+ }
176
+
177
+ #splitOnSpaces(tokens: number[]): { words: string[]; wordTokens: number[][] } {
178
+ const sub = this.#splitOnUnicode(tokens);
179
+ const words: string[] = [];
180
+ const wordTokens: number[][] = [];
181
+ for (let i = 0; i < sub.words.length; i++) {
182
+ const subword = sub.words[i]!;
183
+ const subwordTokens = sub.wordTokens[i]!;
184
+ const special = subwordTokens[0]! >= this.eot;
185
+ const withSpace = subword.startsWith(" ");
186
+ // python: `subword.strip() in string.punctuation` — a SUBSTRING test.
187
+ const punctuation = ASCII_PUNCTUATION.includes(subword.trim());
188
+ if (special || withSpace || punctuation || words.length === 0) {
189
+ words.push(subword);
190
+ wordTokens.push(subwordTokens);
191
+ } else {
192
+ words[words.length - 1] += subword;
193
+ wordTokens[wordTokens.length - 1]!.push(...subwordTokens);
194
+ }
195
+ }
196
+ return { words, wordTokens };
197
+ }
198
+ }
199
+
200
+ /** Tokenizer directory for a Whisper checkpoint: the model dir itself when
201
+ * it ships tokenizer.json, else the matching openai/whisper-* HF snapshot
202
+ * (multilingual v3 vocab → large-v3-turbo; v2 → large-v2; english-only →
203
+ * base.en). Returns null when nothing is on disk. */
204
+ export function resolveWhisperTokenizerDir(modelDir: string, nVocab: number): string | null {
205
+ if (existsSync(`${modelDir}/tokenizer.json`) && existsSync(`${modelDir}/tokenizer_config.json`))
206
+ return modelDir;
207
+ const home = process.env.HOME ?? "";
208
+ const hub = `${home}/.cache/huggingface/hub`;
209
+ for (const repo of whisperTokenizerRepos(nVocab)) {
210
+ const base = `${hub}/models--${repo.replace("/", "--")}/snapshots`;
211
+ try {
212
+ for (const snap of readdirSync(base)) {
213
+ const dir = `${base}/${snap}`;
214
+ if (existsSync(`${dir}/tokenizer.json`) && existsSync(`${dir}/tokenizer_config.json`)) return dir;
215
+ }
216
+ } catch { /* not downloaded */ }
217
+ }
218
+ return null;
219
+ }
220
+
221
+ /** Candidate HF repos whose tokenizer matches an n_vocab. */
222
+ export function whisperTokenizerRepos(nVocab: number): string[] {
223
+ if (nVocab === 51866) return ["openai/whisper-large-v3-turbo", "openai/whisper-large-v3"];
224
+ if (nVocab === 51865) return ["openai/whisper-large-v2", "openai/whisper-small", "openai/whisper-tiny"];
225
+ return ["openai/whisper-tiny.en", "openai/whisper-base.en", "openai/whisper-small.en", "openai/whisper-medium.en"];
226
+ }
227
+
228
+ export async function loadWhisperTokenizer(modelDir: string, nVocab: number): Promise<WhisperTokenizer> {
229
+ const dir = resolveWhisperTokenizerDir(modelDir, nVocab);
230
+ if (!dir)
231
+ throw new Error(
232
+ `no Whisper tokenizer for ${modelDir}: place tokenizer.json + tokenizer_config.json ` +
233
+ `beside the weights or download ${whisperTokenizerRepos(nVocab)[0]} (tokenizer files only)`,
234
+ );
235
+ const isMultilingual = nVocab >= 51865;
236
+ const numLanguages = nVocab - 51765 - (isMultilingual ? 1 : 0);
237
+ return new WhisperTokenizer(await loadTokenizer(dir), numLanguages);
238
+ }