mlx-bun 0.4.0 → 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 (601) hide show
  1. package/README.md +1453 -88
  2. package/THIRD_PARTY_NOTICES.md +882 -0
  3. package/bin/mlx-bun.convert.mjs +4 -0
  4. package/bin/mlx-bun.fuse.mjs +4 -0
  5. package/bin/mlx-bun.generate.mjs +4 -0
  6. package/bin/mlx-bun.lora.mjs +4 -0
  7. package/bin/mlx-bun.mjs +13 -21
  8. package/bin/mlx-bun.server.mjs +4 -0
  9. package/bin/mlx-bun.upload.mjs +4 -0
  10. package/dist/web/app.js +9584 -0
  11. package/package.json +50 -53
  12. package/src/cli/ane-install.ts +76 -0
  13. package/src/cli/args.ts +179 -0
  14. package/src/cli/engine-entry.ts +68 -0
  15. package/src/cli/inference.ts +214 -0
  16. package/src/cli/job-entry.ts +97 -0
  17. package/src/cli/library-host.ts +117 -0
  18. package/src/cli/main.ts +57 -0
  19. package/src/cli/memory-engine.ts +163 -0
  20. package/src/cli/memory.ts +36 -0
  21. package/src/cli/mlx-lm-aliases.ts +309 -0
  22. package/src/cli/model-choice.ts +33 -0
  23. package/src/cli/model-selection.ts +84 -0
  24. package/src/cli/module-host.ts +11 -0
  25. package/src/cli/module-verbs.ts +50 -0
  26. package/src/cli/numerical-policy.ts +12 -0
  27. package/src/cli/perplexity.ts +89 -0
  28. package/src/cli/serve-host.ts +423 -0
  29. package/src/cli/serve-isolated.ts +217 -0
  30. package/src/cli/serve-options.ts +106 -0
  31. package/src/cli/serve-state.ts +205 -0
  32. package/src/cli/serve.ts +500 -0
  33. package/src/cli/served-model-host.ts +102 -0
  34. package/src/cli/server-entry.ts +148 -0
  35. package/src/cli/serving-unit.ts +177 -0
  36. package/src/{tui.ts → cli/terminal.ts} +13 -8
  37. package/src/cli/worker-adapters.ts +34 -0
  38. package/src/cli/worker-entry.ts +272 -0
  39. package/src/cli/worker-unit.ts +179 -0
  40. package/src/engine/adapter-operation.ts +26 -0
  41. package/src/engine/cache-services.ts +114 -0
  42. package/src/engine/completion.ts +39 -0
  43. package/src/engine/generation-gateway.ts +358 -0
  44. package/src/engine/index.ts +59 -0
  45. package/src/engine/kv-budget.ts +120 -0
  46. package/src/engine/media-preparation.ts +232 -0
  47. package/src/engine/model-binding.ts +24 -0
  48. package/src/engine/model-host.ts +513 -0
  49. package/src/engine/model-serving.ts +89 -0
  50. package/src/{serve → engine}/preparation.ts +10 -8
  51. package/src/{serve → engine}/session-completion-engine.ts +6 -6
  52. package/src/engine/telemetry.ts +109 -0
  53. package/src/hub/downloads.ts +116 -0
  54. package/src/jobs/db.ts +16 -7
  55. package/src/jobs/events.ts +29 -0
  56. package/src/jobs/executable.ts +5 -0
  57. package/src/jobs/host.ts +51 -0
  58. package/src/jobs/in-process.ts +55 -0
  59. package/src/{contracts/jobs.ts → jobs/protocol.ts} +7 -9
  60. package/src/jobs/runner.ts +150 -231
  61. package/src/jobs/service.ts +75 -0
  62. package/src/jobs/sse.ts +59 -23
  63. package/src/jobs/worker-memory.ts +46 -0
  64. package/src/jobs/worker-process.ts +163 -0
  65. package/src/jobs/worker-supervisor.ts +258 -0
  66. package/src/modules.ts +58 -0
  67. package/src/publishing/catalog-hub.ts +18 -0
  68. package/src/publishing/credentials.ts +31 -0
  69. package/src/publishing/upload.ts +37 -0
  70. package/src/residency/leased-adapters.ts +21 -0
  71. package/src/residency/local-records.ts +16 -0
  72. package/src/residency/model-residency.ts +405 -0
  73. package/src/residency/record-index.ts +28 -0
  74. package/src/residency/resident-estimate.ts +50 -0
  75. package/src/{anthropic.ts → server/anthropic.ts} +3 -81
  76. package/src/server/cache-routes.ts +36 -0
  77. package/src/{serve → server}/chat-request.ts +12 -10
  78. package/src/{serve → server}/chat-stage.ts +27 -22
  79. package/src/{client.ts → server/client.ts} +28 -2
  80. package/src/{serve → server}/completion-executor.ts +42 -42
  81. package/src/server/decision-body.ts +55 -0
  82. package/src/server/decision-routes.ts +134 -0
  83. package/src/server/decision-wire.ts +228 -0
  84. package/src/{serve → server}/discovery-routes.ts +42 -83
  85. package/src/server/generated-token-history.ts +57 -0
  86. package/src/{serve → server}/http.ts +25 -13
  87. package/src/{serve → server}/inference-request.ts +5 -5
  88. package/src/server/job-routes.ts +20 -0
  89. package/src/server/media-prompt.ts +63 -0
  90. package/src/server/memory-completion-client.ts +46 -0
  91. package/src/server/model-routes.ts +192 -0
  92. package/src/{serve → server}/openai-wire.ts +3 -2
  93. package/src/{serve → server}/pipeline.ts +1 -1
  94. package/src/server/prompt-builder.ts +13 -0
  95. package/src/server/prompt-contracts.ts +17 -0
  96. package/src/server/proxy-routes.ts +187 -0
  97. package/src/server/publishing-routes.ts +41 -0
  98. package/src/{serve → server}/request-plan.ts +8 -8
  99. package/src/{serve → server}/request-prep.ts +82 -68
  100. package/src/{serve → server}/responses-client.ts +30 -13
  101. package/src/{responses.ts → server/responses.ts} +12 -93
  102. package/src/server/routes.ts +205 -0
  103. package/src/server/start.ts +72 -0
  104. package/src/server/status-routes.ts +108 -0
  105. package/src/{serve → server}/text-completion-stage.ts +25 -4
  106. package/src/server/text-prompt.ts +12 -0
  107. package/src/{serve → server}/token-streams.ts +36 -38
  108. package/src/server/worker-routes.ts +311 -0
  109. package/src/storage/paths.ts +61 -0
  110. package/src/web/assets.ts +37 -0
  111. package/src/web/{src → browser}/api.ts +5 -23
  112. package/src/web/browser/chat-host.ts +17 -0
  113. package/src/web/browser/installed-panels.ts +6 -0
  114. package/src/web/browser/main.ts +55 -0
  115. package/src/web/browser/markdown.ts +279 -0
  116. package/src/web/browser/memory-panel.ts +9 -0
  117. package/src/web/browser/palette.ts +64 -0
  118. package/src/web/browser/protocol.ts +13 -0
  119. package/src/web/browser/shell.ts +467 -0
  120. package/src/web/{src → browser}/status.ts +32 -36
  121. package/src/web/build.ts +51 -0
  122. package/src/web/public/app.html +1054 -0
  123. package/src/web/{sw.js → public/sw.js} +4 -3
  124. package/src/web/{vendor → public/vendor}/hljs-theme.css +2 -2
  125. package/src/web/{vendor → public/vendor}/hljs.js +2 -2
  126. package/THIRD_PARTY_LICENSES.md +0 -51
  127. package/docs/README.md +0 -94
  128. package/docs/archive/hlg-sampling.md +0 -350
  129. package/docs/archive/investigations/audio-input-plan.md +0 -356
  130. package/docs/archive/investigations/chunk-finetune-experiment.md +0 -84
  131. package/docs/archive/investigations/colibri-glm52-port.md +0 -1486
  132. package/docs/archive/investigations/colibri-oracle-pin.md +0 -1284
  133. package/docs/archive/investigations/curve-bisector-routing.md +0 -123
  134. package/docs/archive/investigations/decode-roofline-lookagain.md +0 -348
  135. package/docs/archive/investigations/diffusion-gemma-port.md +0 -602
  136. package/docs/archive/investigations/dspark-handoff.md +0 -224
  137. package/docs/archive/investigations/expert-offload-single-user-moe.md +0 -292
  138. package/docs/archive/investigations/faithful-l1-consolidation.md +0 -310
  139. package/docs/archive/investigations/hlg-sampling-investigation.md +0 -970
  140. package/docs/archive/investigations/indexshare-performance-spike.md +0 -357
  141. package/docs/archive/investigations/kernel-perf-review-2026-07.md +0 -171
  142. package/docs/archive/investigations/lab-build-journal.md +0 -157
  143. package/docs/archive/investigations/minicpm5-decode-megakernel.md +0 -489
  144. package/docs/archive/investigations/openjarvis-fit.md +0 -198
  145. package/docs/archive/investigations/openwiki-evaluation.md +0 -249
  146. package/docs/archive/investigations/optimization_plan.md +0 -346
  147. package/docs/archive/investigations/orpo-base-uf-experiment-and-directions.md +0 -120
  148. package/docs/archive/investigations/orpo-flash-cce-pin-leak.md +0 -167
  149. package/docs/archive/investigations/orpo-uf-testing-handoff.md +0 -126
  150. package/docs/archive/investigations/pareto-specialized-runtime-findings.md +0 -92
  151. package/docs/archive/investigations/pi-builtin-investigation.md +0 -308
  152. package/docs/archive/investigations/pre-colibri-stabilization.md +0 -329
  153. package/docs/archive/investigations/qwen38-27b-campaign-log.md +0 -1092
  154. package/docs/archive/investigations/segmented-backward-handoff.md +0 -148
  155. package/docs/archive/investigations/starter-model-port-handoff.md +0 -342
  156. package/docs/archive/investigations/steel-flash-cce-handoff.md +0 -489
  157. package/docs/archive/investigations/the-dreaming-handoff.md +0 -141
  158. package/docs/archive/investigations/trainer-validation-experiment.md +0 -173
  159. package/docs/archive/planning/chat-ui-vision-memory-first.md +0 -199
  160. package/docs/archive/planning/chat-ui-vision.md +0 -50
  161. package/docs/archive/planning/chunk-task-roadmap.md +0 -77
  162. package/docs/archive/planning/curve-sampler-research-plan.md +0 -331
  163. package/docs/archive/planning/journal.md +0 -316
  164. package/docs/archive/planning/memory-docs-and-dag-plan.md +0 -194
  165. package/docs/archive/planning/pi-integration-review.md +0 -178
  166. package/docs/archive/planning/release-notes-v0.0.10.md +0 -57
  167. package/docs/archive/planning/release-notes-v0.0.11.md +0 -239
  168. package/docs/archive/planning/release-notes-v0.0.12.md +0 -80
  169. package/docs/archive/planning/release-notes-v0.0.13.md +0 -77
  170. package/docs/archive/planning/release-notes-v0.0.9.md +0 -122
  171. package/docs/archive/planning/release-notes-v0.2.0.md +0 -49
  172. package/docs/archive/planning/release-notes-v0.3.0.md +0 -35
  173. package/docs/archive/planning/web-ui-pass-plan.md +0 -213
  174. package/docs/archive/planning/website-readme-pass-plan.md +0 -224
  175. package/docs/design/batching.md +0 -1841
  176. package/docs/design/decode-speed-program.md +0 -2349
  177. package/docs/design/dreaming-nightly-pipeline.md +0 -684
  178. package/docs/design/generic-model-support.md +0 -705
  179. package/docs/design/kv-cache.md +0 -824
  180. package/docs/design/orpo-training.md +0 -680
  181. package/docs/design/speculative-decoding.md +0 -1990
  182. package/docs/design/turboquant.md +0 -2212
  183. package/docs/design/unified-engine-frontier-plan.md +0 -1880
  184. package/docs/design/web-chat-redesign.md +0 -695
  185. package/docs/planning/PRODUCT_ROADMAP.md +0 -594
  186. package/docs/planning/ResearchTopics.md +0 -119
  187. package/docs/planning/release-notes-v0.4.0.md +0 -45
  188. package/docs/planning/video-series.md +0 -421
  189. package/docs/reference/benchmarks.md +0 -3613
  190. package/docs/reference/cli.md +0 -585
  191. package/docs/reference/distribution.md +0 -355
  192. package/docs/reference/environment.md +0 -398
  193. package/docs/reference/glossary.md +0 -39
  194. package/docs/reference/library-api.md +0 -674
  195. package/docs/reference/memory.md +0 -260
  196. package/docs/reference/models.md +0 -334
  197. package/docs/reference/server-api.md +0 -1775
  198. package/docs/reference/server-config.md +0 -846
  199. package/docs/reference/training.md +0 -462
  200. package/docs/reference/troubleshooting.md +0 -158
  201. package/src/admission-plan.ts +0 -18
  202. package/src/audio/conformer.ts +0 -647
  203. package/src/audio/decode.ts +0 -133
  204. package/src/audio/features.ts +0 -202
  205. package/src/audio/transcode.ts +0 -67
  206. package/src/backends/mlx/assistant-target.ts +0 -46
  207. package/src/backends/mlx/autoregressive-draft-rows.ts +0 -120
  208. package/src/backends/mlx/autoregressive.ts +0 -108
  209. package/src/backends/mlx/batch-group.ts +0 -1790
  210. package/src/backends/mlx/cache-attachment.ts +0 -67
  211. package/src/backends/mlx/cache-layout.ts +0 -31
  212. package/src/backends/mlx/checkpoint-state.ts +0 -53
  213. package/src/backends/mlx/continuation-persistence.ts +0 -106
  214. package/src/backends/mlx/continuation-request.ts +0 -43
  215. package/src/backends/mlx/continuation.ts +0 -77
  216. package/src/backends/mlx/diffusion.ts +0 -39
  217. package/src/backends/mlx/draft-target.ts +0 -32
  218. package/src/backends/mlx/evaluation.ts +0 -105
  219. package/src/backends/mlx/fill-append.ts +0 -45
  220. package/src/backends/mlx/full-kv-row-append.ts +0 -32
  221. package/src/backends/mlx/full-kv-row-donor.ts +0 -54
  222. package/src/backends/mlx/full-transitioning-kv-rows.ts +0 -65
  223. package/src/backends/mlx/gateway-binding.ts +0 -130
  224. package/src/backends/mlx/graph.ts +0 -38
  225. package/src/backends/mlx/kv-codec.ts +0 -9
  226. package/src/backends/mlx/kv-maintenance.ts +0 -133
  227. package/src/backends/mlx/kv-tensor-rows.ts +0 -212
  228. package/src/backends/mlx/memory-client.ts +0 -189
  229. package/src/backends/mlx/methods.ts +0 -90
  230. package/src/backends/mlx/model-prompt.ts +0 -164
  231. package/src/backends/mlx/model-serving.ts +0 -79
  232. package/src/backends/mlx/prefill-cohort.ts +0 -89
  233. package/src/backends/mlx/prefill-rows.ts +0 -200
  234. package/src/backends/mlx/prefill.ts +0 -33
  235. package/src/backends/mlx/projected-context-rows.ts +0 -72
  236. package/src/backends/mlx/request-policy.ts +0 -20
  237. package/src/backends/mlx/request-state-policy.ts +0 -41
  238. package/src/backends/mlx/rollback.ts +0 -89
  239. package/src/backends/mlx/row-sampling.ts +0 -49
  240. package/src/backends/mlx/serial-executor.ts +0 -242
  241. package/src/backends/mlx/speculative-group.ts +0 -359
  242. package/src/backends/mlx/speculative-round.ts +0 -161
  243. package/src/backends/mlx/speculative.ts +0 -114
  244. package/src/backends/mlx/state-rows.ts +0 -104
  245. package/src/backends/mlx/state-views.ts +0 -46
  246. package/src/backends/mlx/target-layout-capability.ts +0 -35
  247. package/src/backends/mlx/text-engine.ts +0 -73
  248. package/src/backends/mlx/transitioning-kv-rows.ts +0 -165
  249. package/src/chat-template.ts +0 -382
  250. package/src/cli.ts +0 -3168
  251. package/src/config.ts +0 -399
  252. package/src/contracts/completion.ts +0 -16
  253. package/src/contracts/execution.ts +0 -60
  254. package/src/contracts/generation.ts +0 -103
  255. package/src/contracts/host.ts +0 -7
  256. package/src/contracts/pi-provider.ts +0 -30
  257. package/src/contracts/pi.ts +0 -259
  258. package/src/contracts/prefix-cache.ts +0 -23
  259. package/src/contracts/resources.ts +0 -25
  260. package/src/contracts/sampling.ts +0 -23
  261. package/src/contracts/scheduling.ts +0 -45
  262. package/src/contracts/tsconfig.json +0 -14
  263. package/src/dataset/generators.ts +0 -1087
  264. package/src/dataset/index.ts +0 -12
  265. package/src/dataset/job.ts +0 -81
  266. package/src/dataset/llm.ts +0 -71
  267. package/src/dataset/registry.ts +0 -528
  268. package/src/diffusion/diffusion-generate.ts +0 -507
  269. package/src/download.ts +0 -505
  270. package/src/embed.ts +0 -77
  271. package/src/engine/admission.ts +0 -59
  272. package/src/engine/cancellation.ts +0 -38
  273. package/src/engine/engine.ts +0 -56
  274. package/src/engine/execution-coordinator.ts +0 -70
  275. package/src/engine/execution-plan.ts +0 -53
  276. package/src/engine/resources.ts +0 -53
  277. package/src/engine/scheduler.ts +0 -72
  278. package/src/engine/session.ts +0 -247
  279. package/src/eval/README.md +0 -130
  280. package/src/eval/ifeval.ts +0 -81
  281. package/src/eval/kl-prompts.ts +0 -29
  282. package/src/eval/kl.ts +0 -590
  283. package/src/eval/perplexity.ts +0 -164
  284. package/src/eval/quality-db.ts +0 -92
  285. package/src/eval/reference-logits.ts +0 -207
  286. package/src/eval/runner.ts +0 -124
  287. package/src/eval/score.ts +0 -49
  288. package/src/eval/tasks/bfcl.ts +0 -535
  289. package/src/eval/tasks/gsm8k.ts +0 -98
  290. package/src/eval/tasks/hashhop.ts +0 -139
  291. package/src/eval/tasks/humaneval.ts +0 -275
  292. package/src/eval/tasks/ifeval.ts +0 -525
  293. package/src/eval/tasks/mmlu.ts +0 -157
  294. package/src/eval/ultrafeedback.ts +0 -60
  295. package/src/evaldb.ts +0 -199
  296. package/src/expert-io.ts +0 -393
  297. package/src/expert-offload-build.ts +0 -77
  298. package/src/expert-offload.ts +0 -76
  299. package/src/expert-residency.ts +0 -1134
  300. package/src/expert-usage.ts +0 -415
  301. package/src/fill/echo-index.ts +0 -188
  302. package/src/fill/fill-session.ts +0 -452
  303. package/src/fill/proposal.ts +0 -68
  304. package/src/fill/schema-rows.ts +0 -506
  305. package/src/fill/tool-boundary.ts +0 -103
  306. package/src/fit.ts +0 -249
  307. package/src/generate.ts +0 -1346
  308. package/src/grammar.ts +0 -591
  309. package/src/harness-pi.ts +0 -184
  310. package/src/hf-push.ts +0 -628
  311. package/src/hub-rest.ts +0 -242
  312. package/src/index.ts +0 -63
  313. package/src/inference/denoising.ts +0 -17
  314. package/src/inference/draft-acceptance.ts +0 -41
  315. package/src/inference/draft-output.ts +0 -49
  316. package/src/inference/graph.ts +0 -33
  317. package/src/inference/prefill.ts +0 -39
  318. package/src/inference/rollback.ts +0 -7
  319. package/src/jobs/index.ts +0 -31
  320. package/src/jobs/job-entry.ts +0 -107
  321. package/src/jobs/task-client.ts +0 -19
  322. package/src/jobs/types.ts +0 -2
  323. package/src/kv-scheme.ts +0 -257
  324. package/src/kv-store.ts +0 -1099
  325. package/src/lab/README.md +0 -18
  326. package/src/lab/curve/curve-designer.html +0 -303
  327. package/src/lab/curve/curve-sampler.ts +0 -122
  328. package/src/lab/expert-trace/expert-trace.ts +0 -69
  329. package/src/lab/paged-kv/paged-kv-rows.ts +0 -83
  330. package/src/lab/paged-kv/paged-kv.ts +0 -270
  331. package/src/library.ts +0 -40
  332. package/src/lora.ts +0 -408
  333. package/src/md-modules.d.ts +0 -49
  334. package/src/media-fetch.ts +0 -442
  335. package/src/memory/article.ts +0 -558
  336. package/src/memory/chunk-validation.ts +0 -150
  337. package/src/memory/chunk.ts +0 -318
  338. package/src/memory/cluster.ts +0 -351
  339. package/src/memory/coverage.ts +0 -134
  340. package/src/memory/crosslink.ts +0 -0
  341. package/src/memory/db.ts +0 -253
  342. package/src/memory/entity.ts +0 -203
  343. package/src/memory/gate.ts +0 -187
  344. package/src/memory/history.ts +0 -152
  345. package/src/memory/ingest.ts +0 -335
  346. package/src/memory/model.ts +0 -183
  347. package/src/memory/normalize.ts +0 -671
  348. package/src/memory/parse.ts +0 -22
  349. package/src/memory/pipeline.ts +0 -275
  350. package/src/memory/prompts.ts +0 -65
  351. package/src/memory/query.ts +0 -334
  352. package/src/memory/reconcile.ts +0 -734
  353. package/src/memory/redirect.ts +0 -108
  354. package/src/memory/reindex.ts +0 -325
  355. package/src/memory/resolve.ts +0 -307
  356. package/src/memory/rest.ts +0 -245
  357. package/src/memory/route.ts +0 -272
  358. package/src/memory/schedule.ts +0 -165
  359. package/src/memory/stages.ts +0 -777
  360. package/src/memory/synthesize.ts +0 -1638
  361. package/src/memory/tools.ts +0 -749
  362. package/src/memory/vault.ts +0 -1194
  363. package/src/memory/wikify.ts +0 -1064
  364. package/src/mlx/array.ts +0 -388
  365. package/src/mlx/autograd.ts +0 -304
  366. package/src/mlx/checkpoint.ts +0 -149
  367. package/src/mlx/compile.ts +0 -168
  368. package/src/mlx/custom-vjp.ts +0 -188
  369. package/src/mlx/ffi.ts +0 -512
  370. package/src/mlx/materialize.ts +0 -23
  371. package/src/mlx/metal-kernel.ts +0 -159
  372. package/src/mlx/ops.ts +0 -1021
  373. package/src/mlx/quantized-rows.ts +0 -36
  374. package/src/mlx/token-bitmask.ts +0 -52
  375. package/src/mlx/turboquant-kv-decode.ts +0 -82
  376. package/src/mlx/turboquant-kv-inverse.ts +0 -122
  377. package/src/mlx/turboquant-ops.ts +0 -861
  378. package/src/mlx/turboquant-tables.ts +0 -237
  379. package/src/mmap.ts +0 -90
  380. package/src/model/artifact-identity.ts +0 -73
  381. package/src/model/batched-kv.ts +0 -235
  382. package/src/model/batched-mask.ts +0 -279
  383. package/src/model/batched-quant.ts +0 -212
  384. package/src/model/batched-quantized-kv.ts +0 -144
  385. package/src/model/batched-rotating-quant.ts +0 -353
  386. package/src/model/batched-rotating-state.ts +0 -149
  387. package/src/model/batched-rotating.ts +0 -338
  388. package/src/model/batched-row-storage.ts +0 -179
  389. package/src/model/batched-ssm.ts +0 -52
  390. package/src/model/batched-turboquant-kv.ts +0 -93
  391. package/src/model/compiled-decode.ts +0 -712
  392. package/src/model/decoded-kv-donor.ts +0 -12
  393. package/src/model/delayed-quantized-kv.ts +0 -72
  394. package/src/model/delayed-rotating-quantized-kv.ts +0 -93
  395. package/src/model/delayed-turboquant-kv.ts +0 -55
  396. package/src/model/diffusion-gemma.ts +0 -1026
  397. package/src/model/factory.ts +0 -189
  398. package/src/model/fingerprint.ts +0 -33
  399. package/src/model/flash-attention.ts +0 -694
  400. package/src/model/full-prefill-padding.ts +0 -56
  401. package/src/model/full-prefill-row.ts +0 -105
  402. package/src/model/gemma4-base.ts +0 -2351
  403. package/src/model/gemma4.ts +0 -1315
  404. package/src/model/generated/gemma4-12b.ts +0 -429
  405. package/src/model/generated/gemma4-26b.ts +0 -448
  406. package/src/model/generated/gemma4-e4b.ts +0 -717
  407. package/src/model/generated/index.ts +0 -18
  408. package/src/model/glm52-atlas.ts +0 -511
  409. package/src/model/glm52-cache.ts +0 -792
  410. package/src/model/glm52-config.ts +0 -262
  411. package/src/model/glm52-container.ts +0 -305
  412. package/src/model/glm52-coupling.ts +0 -388
  413. package/src/model/glm52-dsa.ts +0 -606
  414. package/src/model/glm52-expert-layout.ts +0 -253
  415. package/src/model/glm52-layout.ts +0 -152
  416. package/src/model/glm52-memory.ts +0 -500
  417. package/src/model/glm52-mla.ts +0 -792
  418. package/src/model/glm52-moe.ts +0 -223
  419. package/src/model/glm52-pilot.ts +0 -537
  420. package/src/model/glm52-reference.ts +0 -445
  421. package/src/model/glm52-residency.ts +0 -557
  422. package/src/model/glm52-resident-weights.ts +0 -135
  423. package/src/model/glm52-streamed-experts.ts +0 -453
  424. package/src/model/glm52-streamed-metal.ts +0 -610
  425. package/src/model/glm52-weights.ts +0 -503
  426. package/src/model/glm52.ts +0 -1132
  427. package/src/model/implementation.ts +0 -69
  428. package/src/model/kv-attention-view.ts +0 -67
  429. package/src/model/minicpm5.ts +0 -376
  430. package/src/model/profile.ts +0 -443
  431. package/src/model/qwen-append.ts +0 -12
  432. package/src/model/qwen-conv.ts +0 -52
  433. package/src/model/qwen3-delta-state.ts +0 -74
  434. package/src/model/qwen3-delta.ts +0 -451
  435. package/src/model/qwen3-moe.ts +0 -404
  436. package/src/model/qwen3-mrope.ts +0 -252
  437. package/src/model/qwen3.ts +0 -277
  438. package/src/model/qwen3_5-checkpoint.ts +0 -164
  439. package/src/model/qwen3_5.ts +0 -819
  440. package/src/model/rotating-kv-donor.ts +0 -35
  441. package/src/model/rotating-kv-layout.ts +0 -262
  442. package/src/model/rotating-row-transaction.ts +0 -35
  443. package/src/model/speculative-rotating-kv.ts +0 -101
  444. package/src/model/ssm-prefill-padding.ts +0 -39
  445. package/src/model/support.ts +0 -102
  446. package/src/model/trellis-balanced-scatter.ts +0 -51
  447. package/src/model/trellis-linear.ts +0 -672
  448. package/src/model/trellis-shared-m.ts +0 -151
  449. package/src/model/trellis-shared-scatter.ts +0 -144
  450. package/src/model/trellis-splitk-prefill.ts +0 -158
  451. package/src/model/trellis-tiled-prefill.ts +0 -136
  452. package/src/model/trellis-vector-expand.ts +0 -62
  453. package/src/model/trellis-wide-prefill.ts +0 -94
  454. package/src/model/turboquant-codec.ts +0 -83
  455. package/src/model/universal/archs.ts +0 -332
  456. package/src/model/universal/dense.ts +0 -612
  457. package/src/model/universal/modules.ts +0 -170
  458. package/src/model/universal/rope.ts +0 -381
  459. package/src/native/expert_io.c +0 -523
  460. package/src/native/frame_extract.swift +0 -140
  461. package/src/native-pack.ts +0 -162
  462. package/src/pi-provider.ts +0 -53
  463. package/src/pi-session.ts +0 -81
  464. package/src/pi-terminal.ts +0 -274
  465. package/src/pi-web.ts +0 -1932
  466. package/src/preflight.ts +0 -158
  467. package/src/prompt-cache.ts +0 -460
  468. package/src/quantize/allocator.ts +0 -627
  469. package/src/quantize/atomic-output.ts +0 -38
  470. package/src/quantize/calibration.ts +0 -181
  471. package/src/quantize/config-writer.ts +0 -145
  472. package/src/quantize/index.ts +0 -84
  473. package/src/quantize/job.ts +0 -162
  474. package/src/quantize/quantizer.ts +0 -666
  475. package/src/quantize/rotate.ts +0 -603
  476. package/src/quantize/safetensors-writer.ts +0 -249
  477. package/src/quantize/sensitivity.ts +0 -216
  478. package/src/quantize/trellis.ts +0 -586
  479. package/src/quantize/weight-transform.ts +0 -292
  480. package/src/registry.ts +0 -602
  481. package/src/runtime-config.ts +0 -81
  482. package/src/safetensors.ts +0 -158
  483. package/src/sampler.ts +0 -1011
  484. package/src/serve/admin-routes.ts +0 -184
  485. package/src/serve/assets/curve-terrain.html +0 -105
  486. package/src/serve/assets/training-inference-map.html +0 -575
  487. package/src/serve/aux-routes.ts +0 -132
  488. package/src/serve/batch-scheduler.ts +0 -8
  489. package/src/serve/checkpoint-identity.ts +0 -64
  490. package/src/serve/generation-gateway.ts +0 -496
  491. package/src/serve/isolate.ts +0 -609
  492. package/src/serve/kv-budget.ts +0 -14
  493. package/src/serve/lab-routes.ts +0 -341
  494. package/src/serve/lane-registry.ts +0 -45
  495. package/src/serve/model-admin-routes.ts +0 -155
  496. package/src/serve/model-binding.ts +0 -43
  497. package/src/serve/model-host.ts +0 -511
  498. package/src/serve/parent-application.ts +0 -56
  499. package/src/serve/prompt-response-trace.ts +0 -147
  500. package/src/serve/response-lease.ts +0 -24
  501. package/src/serve/session-search.ts +0 -326
  502. package/src/serve/static-routes.ts +0 -101
  503. package/src/server.ts +0 -1314
  504. package/src/spec/assistant-rows.ts +0 -92
  505. package/src/spec/assistant-source.ts +0 -126
  506. package/src/spec/cache-identity.ts +0 -22
  507. package/src/spec/deepspec-rows.ts +0 -29
  508. package/src/spec/deepspec-source.ts +0 -145
  509. package/src/spec/dflash-source.ts +0 -149
  510. package/src/spec/drafter.ts +0 -304
  511. package/src/spec/dspark/ab-stats.ts +0 -136
  512. package/src/spec/dspark/calibration.ts +0 -112
  513. package/src/spec/dspark/data-dflash.ts +0 -156
  514. package/src/spec/dspark/data.ts +0 -201
  515. package/src/spec/dspark/deepspec-module.ts +0 -776
  516. package/src/spec/dspark/generate-dflash.ts +0 -245
  517. package/src/spec/dspark/generate.ts +0 -290
  518. package/src/spec/dspark/loader.ts +0 -23
  519. package/src/spec/dspark/loss.ts +0 -213
  520. package/src/spec/dspark/module-dflash.ts +0 -560
  521. package/src/spec/dspark/module.ts +0 -540
  522. package/src/spec/dspark/quantize-drafter.ts +0 -91
  523. package/src/spec/dspark/sample.ts +0 -112
  524. package/src/spec/generate.ts +0 -276
  525. package/src/spec/glm52-mtp-graph.ts +0 -29
  526. package/src/spec/glm52-mtp-rows.ts +0 -132
  527. package/src/spec/glm52-mtp-source.ts +0 -317
  528. package/src/spec/glm52-mtp-state.ts +0 -33
  529. package/src/spec/ngram-source.ts +0 -204
  530. package/src/spec/projected-draft-rows.ts +0 -73
  531. package/src/spec/qwen-mtp-module.ts +0 -171
  532. package/src/spec/qwen-mtp-rows.ts +0 -219
  533. package/src/spec/qwen-mtp-source.ts +0 -383
  534. package/src/spec/qwen-mtp-state.ts +0 -30
  535. package/src/spec/serve-loop.ts +0 -585
  536. package/src/spec/source.ts +0 -215
  537. package/src/spec/two-model-rows.ts +0 -61
  538. package/src/spec/two-model.ts +0 -113
  539. package/src/ssd-cache.ts +0 -372
  540. package/src/ssd-durability.ts +0 -211
  541. package/src/tokenizer.ts +0 -335
  542. package/src/tool-approvals.ts +0 -131
  543. package/src/tool-call.ts +0 -695
  544. package/src/train/dataset.ts +0 -484
  545. package/src/train/diffusion-lora.ts +0 -216
  546. package/src/train/export.ts +0 -32
  547. package/src/train/flash-cce.ts +0 -1234
  548. package/src/train/forward.ts +0 -266
  549. package/src/train/fuse.ts +0 -243
  550. package/src/train/index.ts +0 -46
  551. package/src/train/job.ts +0 -205
  552. package/src/train/lora-params.ts +0 -254
  553. package/src/train/loss.ts +0 -1591
  554. package/src/train/merge.ts +0 -195
  555. package/src/train/optimizer.ts +0 -183
  556. package/src/train/prefix-shared.ts +0 -490
  557. package/src/train/rank.ts +0 -137
  558. package/src/train/segmented.ts +0 -1828
  559. package/src/train/steel-qmm-header.ts +0 -1461
  560. package/src/train/trainer.ts +0 -1221
  561. package/src/train/watch.ts +0 -542
  562. package/src/vision/diffusion-vision.ts +0 -620
  563. package/src/vision/embedder.ts +0 -168
  564. package/src/vision/preprocess.ts +0 -247
  565. package/src/vision/prompt.ts +0 -373
  566. package/src/vision/qwen3vl-preprocess.ts +0 -409
  567. package/src/vision/qwen3vl-prompt.ts +0 -196
  568. package/src/vision/qwen3vl-tower.ts +0 -555
  569. package/src/vision/siglip.ts +0 -599
  570. package/src/vision/video-frames.ts +0 -148
  571. package/src/web/app.html +0 -2143
  572. package/src/web/app.js +0 -5703
  573. package/src/web/skills/memory/SKILL.md +0 -120
  574. package/src/web/skills/web-research/SKILL.md +0 -46
  575. package/src/web/skills.ts +0 -97
  576. package/src/web/src/adapters-panel.ts +0 -288
  577. package/src/web/src/assistant.ts +0 -451
  578. package/src/web/src/chat.ts +0 -1057
  579. package/src/web/src/composer.ts +0 -959
  580. package/src/web/src/dataset.ts +0 -132
  581. package/src/web/src/finetune.ts +0 -267
  582. package/src/web/src/hub.ts +0 -428
  583. package/src/web/src/main.ts +0 -98
  584. package/src/web/src/markdown.ts +0 -0
  585. package/src/web/src/memory-panel.ts +0 -572
  586. package/src/web/src/model-picker.ts +0 -159
  587. package/src/web/src/palette.ts +0 -346
  588. package/src/web/src/protocol.ts +0 -26
  589. package/src/web/src/quantize.ts +0 -167
  590. package/src/web/src/rag.ts +0 -274
  591. package/src/web/src/sessions.ts +0 -469
  592. package/src/web/src/shell.ts +0 -804
  593. package/src/web/src/ui-catalog.ts +0 -78
  594. package/src/web/vendor/README.md +0 -61
  595. package/src/web-assets.ts +0 -47
  596. package/src/web-tools.ts +0 -478
  597. package/src/weights.ts +0 -255
  598. /package/src/{serve → server}/completion-sink.ts +0 -0
  599. /package/src/web/{icon.svg → public/icon.svg} +0 -0
  600. /package/src/web/{manifest.webmanifest → public/manifest.webmanifest} +0 -0
  601. /package/src/web/{vendor → public/vendor}/hljs-LICENSE +0 -0
package/src/generate.ts DELETED
@@ -1,1346 +0,0 @@
1
- // Streaming generation — port of mlx-lm's generate_step:
2
- // - prefill in chunks; cache state evaluated per chunk (bounded transient
3
- // memory), logits never computed for non-final prefill positions
4
- // - decode pipelining via mx.async_eval: step n+1's graph is built and
5
- // dispatched before step n's token is read back, so the GPU never idles
6
- // on the JS round-trip
7
- // - opt-in early token-zero yield reduces latency before that pipeline starts
8
- // - sampling stays on-device; only the chosen token id crosses to JS
9
-
10
- import { appendFileSync } from "node:fs";
11
- import { MlxArray, gpuStream } from "./mlx/array";
12
- import {
13
- Dtype,
14
- activeMemory,
15
- clearCache,
16
- maxRecommendedWorkingSetSize,
17
- peakMemory,
18
- setWiredLimit,
19
- synchronize,
20
- } from "./mlx/ffi";
21
- import * as ops from "./mlx/ops";
22
- import { KVCache, RotatingKVCache, type Cache } from "./model/gemma4";
23
- import { maybePageKv } from "./backends/mlx/request-state-policy";
24
- export { maybePageKv } from "./backends/mlx/request-state-policy";
25
- import { DiffusionGemmaModel } from "./model/diffusion-gemma";
26
- import { denoiseAsync } from "./diffusion/diffusion-generate";
27
- import { bindLegacyDenoisingModel, type MlxDenoisingBinding } from "./backends/mlx/diffusion";
28
- import type { RuntimeModel } from "./model/factory";
29
- import {
30
- assertMlxAutoregressiveBinding, bindLegacyAutoregressiveModel,
31
- type MlxAutoregressiveBinding, type MlxModelMemory, type MlxDecodeStep, type MlxTokenAppend,
32
- } from "./backends/mlx/autoregressive";
33
- import { createKvMaintenance } from "./backends/mlx/kv-maintenance";
34
- import { nextPrefillStep } from "./inference/prefill";
35
- import { evalCacheState, executeMlxPrefillStep } from "./backends/mlx/prefill";
36
- import { appendFillHidden } from "./backends/mlx/fill-append";
37
- export { evalCacheState } from "./backends/mlx/prefill";
38
- import type { TokenLogprobs } from "./contracts/generation";
39
- export type { TokenLogprobs } from "./contracts/generation";
40
- import type { PromptResponseTrace } from "./serve/prompt-response-trace";
41
- import type { KvQuantSpec, TurboQuantScheme } from "./config";
42
- import { runtimeConfig, runtimeValue, withRuntimeConfig, type RuntimeConfig } from "./runtime-config";
43
- import {
44
- disposeStepExtras,
45
- makeStepSampler,
46
- readStepExtras,
47
- stepExtrasArrays,
48
- type LogitsProcessorOptions,
49
- type SamplerOptions,
50
- type StepExtras,
51
- } from "./sampler";
52
- import type { GrammarController } from "./grammar";
53
- import {
54
- fillTraceEnabled,
55
- fillTracePath,
56
- type FillTraceRecord,
57
- resolveFillMode,
58
- type FillSession,
59
- type FillStats,
60
- type Proposal,
61
- } from "./fill/fill-session";
62
-
63
- export interface GenerateOptions extends SamplerOptions, LogitsProcessorOptions {
64
- /** Resolved host policy. Direct compatibility calls resolve from their
65
- * captured binding when this is absent. */
66
- decodePolicy?: Readonly<Pick<import("./contracts/execution").ResolvedExecution, "compiledDecode" | "grammarJump">>;
67
- /** Cooperatively cancel at prefill/decode boundaries; pending native work
68
- * finishes before owned resources are released. */
69
- signal?: AbortSignal;
70
- maxTokens?: number;
71
- eosTokenIds?: number[];
72
- prefillChunkSize?: number;
73
- /** Whether the request supplied its seed explicitly. Durable generation
74
- * checkpoints use this to distinguish a reproducible user policy from the
75
- * server-generated seed that must be recovered from checkpoint metadata. */
76
- seedWasExplicit?: boolean;
77
- /** Fired once during prefill, at `snapshotAt` tokens (default: the full
78
- * prompt) and BEFORE any further KV is written — the one moment the
79
- * caches hold exactly that prefix (post-wrap rings can't be rewound
80
- * later). The server's prompt-boundary cache snapshot hangs here. */
81
- onPrefillDone?: () => void;
82
- /** Token count at which onPrefillDone fires (the STABLE cache boundary —
83
- * chat prompts end in a generation primer, e.g. 12B's thought-channel
84
- * tokens, that the NEXT turn's re-render does not contain; snapshotting
85
- * there would make the entry untrimmable-and-divergent). Prefill is
86
- * split at this index when it falls mid-prompt. */
87
- snapshotAt?: number;
88
- /** Resume an interrupted autoregressive generation from a durable KV
89
- * checkpoint. `cache` must cover every token in `promptTokens` exactly;
90
- * `initialPendingToken` is the already-sampled next token, so no cache
91
- * rewind is required (important for recurrent/untrimmable layers). */
92
- initialPendingToken?: number;
93
- /** Number of completion tokens already emitted before this resumed run. */
94
- initialGeneratedTokens?: number;
95
- /** Original request prompt length used for protocol usage accounting when
96
- * `promptTokens` also contains replayed completion tokens. */
97
- originalPromptTokens?: number;
98
- /** Periodic, safe decode-boundary checkpoint hook. The cache covers
99
- * `cacheTokens`; `pendingToken` has been sampled but not emitted. */
100
- checkpointEveryTokens?: number;
101
- onDecodeCheckpoint?: (state: {
102
- cacheTokens: number[];
103
- caches: Cache[];
104
- generatedTokens: number;
105
- pendingToken: number;
106
- }) => void | Promise<void>;
107
- /** Pre-warmed KV caches (e.g. from the prompt cache). cache[0].offset
108
- * prompt tokens are treated as already prefilled; only the suffix is
109
- * forwarded. Caller keeps ownership — generate() will not dispose. */
110
- cache?: Cache[];
111
- /** Vision path: pre-merged (unscaled) input embeddings [1, L, hidden]
112
- * covering the whole prompt; prefilled in one shot (no chunking).
113
- * Caller keeps ownership. */
114
- promptEmbeddings?: MlxArray;
115
- /** bool [L] marking image tokens (bidirectional attention among them).
116
- * MUST be unset when the prompt contains any audio — audio prompts run
117
- * fully causal (docs/design/generic-model-support.md §3.3 Q1). */
118
- imageMask?: MlxArray;
119
- /** bool [L] marking ALL multimodal soft tokens (image | audio) for
120
- * per-layer-input id zeroing (e2b/e4b), decoupled from imageMask so
121
- * audio-only prompts (no bidirectional mask) still zero their positions.
122
- * When unset, zeroing falls back to imageMask (the legacy vision shape).
123
- * Caller keeps ownership. */
124
- multimodalMask?: MlxArray;
125
- /** Quantize full-attention KV caches to this many bits (4 or 8).
126
- * Full-attention and rotating caches both convert when populated. */
127
- kvBits?: number;
128
- kvGroupSize?: number;
129
- /** Per-layer mixed-precision KV from kv_config.json (config.kvQuant).
130
- * Overrides kvBits, like optiq serve's --kv-config. layerIdx indexes
131
- * the cache list (== layer index for the donor prefix), including
132
- * rotating/sliding caches. */
133
- kvConfig?: KvQuantSpec[];
134
- /** Convert once a cache's offset reaches this (uniform-kvBits default
135
- * 5000 = mlx-lm; kvConfig default 0 = optiq serve). */
136
- quantizedKvStart?: number;
137
- /** TurboQuant scheme (docs/design/turboquant.md): rotation-based KV
138
- * quantization, a CLI-only runtime lever in the same class as uniform
139
- * kvBits (mutually exclusive with kvBits/kvConfig — maybeQuantizeKv
140
- * checks turboQuant first, so set at most one). Full-attention
141
- * KVCache layers convert via TurboQuantKVCache.fromKVCache;
142
- * RotatingKVCache (sliding-window) layers stay bf16 in v1 — a one-time
143
- * warning names the limitation, never a throw. */
144
- turboQuant?: TurboQuantScheme;
145
- /** OPTIONAL paged KV storage (docs/design/kv-cache.md): fresh
146
- * full-attention KVCache layers are replaced with PagedKVCache (block
147
- * pool + gather-to-contiguous) before prefill. Scope: B1/B>1
148
- * Gemma4-family, bf16 — mutually exclusive with kvBits/kvConfig/
149
- * turboQuant/draft/compiled decode (callers refuse the combos; the
150
- * cache swap itself only ever touches plain empty KVCache entries).
151
- * Values are gated bit-exact vs the plain path (tests/paged-kv*). */
152
- pagedKv?: { blockSize?: number };
153
- /** Mounted LoRA adapter ids to apply (resolved/validated by
154
- * AdapterManager.resolveSpec). Residuals sum in order. Set on the
155
- * model's LoraState for exactly the duration of this generation —
156
- * a plain field, safe because the generation queue is serialized. */
157
- adapters?: string[];
158
- /** DiffusionGemma image-text-to-text: channel-first pixel_values [1,3,H,W].
159
- * When set, `promptTokens` are the spliced (<|image|>-expanded) ids and the
160
- * denoising engine prefills the merged vision features. Caller keeps ownership. */
161
- visionPixels?: MlxArray;
162
- /** Capture each emitted token's log-probability (mlx_lm.server `logprobs`).
163
- * The distribution matches mlx-lm generate_step exactly: full-vocab
164
- * log-softmax of the logits AFTER logits processors, BEFORE the sampler's
165
- * temperature/top-p/top-k/min-p/XTC (generate.py L409-422). Off by default —
166
- * the hot path pays nothing when unset. */
167
- logprobs?: boolean;
168
- /** Capture the top-k (token id, logprob) pairs per emitted token from the
169
- * same distribution (mlx_lm.server `top_logprobs`). 0/unset = off. */
170
- topLogprobs?: number;
171
- /** Grammar-constrained decoding (src/grammar.ts): a compiled GrammarController
172
- * that masks invalid tokens to -inf each step. L2-class (oMLX oracle). When
173
- * set, the decode loop takes a slightly different shape: it eager-reads the
174
- * token id (acceptToken needs a JS number, which the pipelined loop defers),
175
- * advances the matcher, and awaits the async mask precompute (which overlaps
176
- * the GPU forward). Non-grammar requests keep the fast pipelined loop. */
177
- grammar?: GrammarController;
178
- /** Token fast-forwarding (src/fill/, docs/design/speculative-decoding.md
179
- * "Token fast-forwarding"): a per-request table of DETERMINED token spans
180
- * (compiled from the request's `tools` + the chat template). When the
181
- * stream enters one, the engine appends the whole span itself with ONE
182
- * multi-token forward and resumes sampling after it.
183
- *
184
- * This is context extension, NOT speculation: nothing is drafted, verified,
185
- * or rolled back, and no comparison is made against what the model would
186
- * have produced. Injection bypasses the sampler, which is a documented
187
- * behavior-policy deviation at temperature > 0 — hence opt-in
188
- * (MLX_BUN_FILL=strict) and the composition refusals in shouldUseFill.
189
- * Serial lane only: the batch scheduler and the spec loop never read it. */
190
- fill?: FillSession;
191
- }
192
-
193
- export function shouldUseGrammarJump(
194
- options: Pick<GenerateOptions, "grammar" | "logprobs" | "topLogprobs">,
195
- runtime: RuntimeConfig = runtimeConfig(),
196
- ): boolean {
197
- return options.grammar !== undefined &&
198
- runtime.flag("MLX_BUN_GRAMMAR_JUMP", false) &&
199
- !options.logprobs &&
200
- !(options.topLogprobs && options.topLogprobs > 0);
201
- }
202
-
203
- /** Composition gate for token fast-forwarding, enforced inside the engine
204
- * (the serve layer refuses more shapes it alone can see — a user-fixed seed,
205
- * a mounted draft model, continuous placement: src/serve/request-prep.ts,
206
- * src/serve/chat-stage.ts). Throws when MLX_BUN_FILL names the unbuilt echo
207
- * tier — an operator who asked for K3c must not get K3b silently.
208
- * - grammar: forced-token production is the grammar's job; two producers in
209
- * one iteration is forbidden (asserted in the loop).
210
- * - logprobs/top_logprobs: injected tokens are never sampled, so they have
211
- * no distribution row (same rule as shouldUseGrammarJump).
212
- * - promptEmbeddings: vision/audio prompts (and mRoPE) — untested shape.
213
- * - kvBits/kvConfig/turboQuant: post-conversion multi-token append is
214
- * L-generic but unvalidated; refuse in v1 rather than pay it silently. */
215
- export function shouldUseFill(
216
- options: Pick<
217
- GenerateOptions,
218
- "fill" | "grammar" | "logprobs" | "topLogprobs" | "promptEmbeddings"
219
- | "kvBits" | "kvConfig" | "turboQuant"
220
- >,
221
- runtime: RuntimeConfig = runtimeConfig(),
222
- ): boolean {
223
- if (!options.fill) return false;
224
- if (resolveFillMode(runtime.value("MLX_BUN_FILL") ?? "off") === "off") return false;
225
- return options.grammar === undefined &&
226
- !options.logprobs &&
227
- !(options.topLogprobs && options.topLogprobs > 0) &&
228
- options.promptEmbeddings === undefined &&
229
- !options.kvBits &&
230
- !options.kvConfig?.length &&
231
- options.turboQuant === undefined;
232
- }
233
-
234
- /** Compatibility entry point for callers that perform a single conversion.
235
- * Execution sessions compose createKvMaintenance once and reuse it. */
236
- export function maybeQuantizeKv(cache: Cache[], options: GenerateOptions): void {
237
- createKvMaintenance(options)(cache);
238
- }
239
-
240
- /** Emitted once per process: token fast-forwarding skips models with
241
- * sliding-window layers in v1 (see the gate in generateInner). */
242
- let warnedFillRotating = false;
243
-
244
- /** The rewind surface a verify-policy fill needs. `Cache` declares the
245
- * spec-round trio as optional members, but the cache list is a UNION with
246
- * GLM's MLACache (which declares none of them), so the union has no such
247
- * property to read. One narrowing helper keeps the call sites honest — every
248
- * use is still optional-chained. */
249
- type RewindableCache = {
250
- isTrimmable(): boolean;
251
- trim(n: number, bypass?: boolean): void;
252
- specRoundBegin?(): void;
253
- specRoundCommit?(): void;
254
- specRoundRollback?(keep: number): void;
255
- };
256
- const rewindable = (c: unknown): RewindableCache => c as RewindableCache;
257
-
258
- export interface GenerateStats {
259
- promptTokens: number;
260
- /** An observed terminal cause takes precedence over the token-count limit. */
261
- finishReason?: "stop" | "length";
262
- /** Prompt tokens skipped via a pre-warmed cache. */
263
- cachedTokens: number;
264
- generatedTokens: number;
265
- prefillTps: number;
266
- decodeTps: number;
267
- prefillMs: number;
268
- decodeMs: number;
269
- /** Exact token sequence whose KV now lives in the cache (prompt + every
270
- * decoded token that was forwarded, including a trailing EOS the
271
- * pipeline forwarded before reading it). For PromptCache.put(). */
272
- cacheTokens: number[];
273
- /** Speculative-decoding telemetry (serve --draft-model path only).
274
- * draftedByPos/acceptedByPos: per-draft-position round counts (index =
275
- * position within a round's block) — the Phase-1c per-position
276
- * acceptance signal. */
277
- spec?: {
278
- drafted: number; accepted: number; targetCalls: number;
279
- draftedByPos?: number[]; acceptedByPos?: number[];
280
- rejected?: number; rounds?: number; acceptanceLengths?: number[];
281
- tokensPerForward?: number; forwardsSaved?: number;
282
- };
283
- /** Token fast-forwarding telemetry (serial lane, MLX_BUN_FILL=strict).
284
- * Present only when a fill table was actually armed for this generation. */
285
- fill?: FillStats;
286
- }
287
-
288
- export interface GenerateDiagnostics {
289
- trace?: PromptResponseTrace;
290
- mechanism?: "serial" | "continuous";
291
- }
292
-
293
- export interface GeneratedToken {
294
- token: number;
295
- index: number;
296
- /** Present only when GenerateOptions.logprobs / topLogprobs requested it. */
297
- logprobs?: TokenLogprobs;
298
- }
299
-
300
- export class Generation implements AsyncIterable<GeneratedToken> {
301
- stats: GenerateStats | null = null;
302
- readonly #next: () => Promise<IteratorResult<GeneratedToken, GenerateStats>>;
303
- readonly #return: () => Promise<IteratorResult<GeneratedToken, GenerateStats>>;
304
-
305
- constructor(iter: AsyncGenerator<GeneratedToken, GenerateStats>, runtime = runtimeConfig()) {
306
- // Async generators resume in the caller's context on every next/return.
307
- // Bind both operations so consumer awaits and early close cannot change
308
- // the runtime snapshot seen by kernels or their cleanup.
309
- const next = iter.next.bind(iter);
310
- const close = () => iter.return(undefined as unknown as GenerateStats);
311
- this.#next = () => withRuntimeConfig(runtime, next);
312
- this.#return = () => withRuntimeConfig(runtime, close);
313
- }
314
-
315
- async *[Symbol.asyncIterator](): AsyncGenerator<GeneratedToken> {
316
- try {
317
- while (true) {
318
- const r = await this.#next();
319
- if (r.done) {
320
- this.stats = r.value;
321
- return;
322
- }
323
- yield r.value;
324
- }
325
- } finally {
326
- // Consumer broke early (e.g. a decoded-text stop sequence fired):
327
- // drive the inner generator's shutdown so its finallys run (array
328
- // disposal, wired/adapter scopes) and capture the stats its
329
- // early-return path still reports.
330
- if (this.stats === null) {
331
- const r = await this.#return();
332
- if (r.done && r.value) this.stats = r.value;
333
- }
334
- }
335
- }
336
- }
337
-
338
- // Scoped wired limit, raised only for near-ceiling models. mlx-lm's
339
- // wired_limit context wires unconditionally per generation; we deviate
340
- // with a measured justification (PLAN Phase 6 verification findings):
341
- // - 26B-A4B (16.4 GB = 92% of the 17.8 GiB working set) NEEDS wiring —
342
- // 8.6 tok/s without, 32.3 with (Metal evicts weight buffers per token).
343
- // - 12B/e4b (≤47%) hit reference parity WITHOUT wiring, and wiring in a
344
- // multi-model process (the test suite) pins memory the OTHER resident
345
- // models need — async GPU exec OOM, which is uncatchable (the mlx
346
- // completion-handler throw terminates the process).
347
- // Scope semantics match the reference: set → generate → synchronize →
348
- // restore; nothing stays pinned between generations. Re-entrant: only
349
- // the outermost wiring scope touches the limit.
350
- // macOS 26.6 reports a 26.8e9 B (24.96 GiB, 0.78 × RAM) recommended set on
351
- // a 32 GB M1 Max (2026-09-08; the earlier "24 GB machine" attribution was
352
- // wrong — 24.96 GiB exceeds a 24 GB box's RAM). At 0.75 × that the old
353
- // fraction stopped wiring the 13-16 GiB Qwen/GLM models even though they
354
- // page heavily without an explicit wired limit.
355
- // Keep smaller 8-9 GiB models unwired while covering the large-model class.
356
- const WIRE_THRESHOLD = 0.5;
357
- let wiredScopeDepth = 0;
358
- let wiredOldLimit = 0;
359
-
360
- type WiredModelMemory = MlxModelMemory;
361
-
362
- /** Bytes whose MLX graph must stay resident while a model executes. */
363
- export function wiredWorkingSetBytes(model: WiredModelMemory): number {
364
- const planned = model.expertRuntime?.plan.plannedBytes;
365
- return typeof planned === "number" && Number.isFinite(planned) && planned > 0
366
- ? Math.max(model.weightsBytes, planned)
367
- : model.weightsBytes;
368
- }
369
-
370
- export function modelNeedsWiredLimit(
371
- model: WiredModelMemory,
372
- recommendedBytes = maxRecommendedWorkingSetSize(),
373
- force = runtimeValue("MLX_BUN_FORCE_WIRE") === "1",
374
- ): boolean {
375
- return force ||
376
- wiredWorkingSetBytes(model) > WIRE_THRESHOLD * recommendedBytes;
377
- }
378
-
379
- function enterWiredScope(): void {
380
- if (wiredScopeDepth++ === 0)
381
- wiredOldLimit = setWiredLimit(maxRecommendedWorkingSetSize());
382
- }
383
- function exitWiredScope(): void {
384
- if (--wiredScopeDepth === 0) {
385
- synchronize(gpuStream);
386
- setWiredLimit(wiredOldLimit);
387
- }
388
- }
389
-
390
- /** Hold the process wired limit while one model owns GPU execution. */
391
- export function acquireModelWiredLimit(model: WiredModelMemory): () => void {
392
- if (!modelNeedsWiredLimit(model)) return () => {};
393
- enterWiredScope();
394
- let released = false;
395
- return () => {
396
- if (released) return;
397
- released = true;
398
- exitWiredScope();
399
- };
400
- }
401
-
402
- export function generate(
403
- model: RuntimeModel,
404
- promptTokens: number[],
405
- options: GenerateOptions = {},
406
- diagnostics: GenerateDiagnostics = {},
407
- ): Generation {
408
- return bindGeneration(model)(promptTokens, options, diagnostics);
409
- }
410
-
411
- /** Resolve the compatibility model once at host construction. Every request
412
- * borrows the same binding and immutable runtime snapshot. */
413
- export function bindGeneration(model: RuntimeModel): (
414
- promptTokens: number[], options?: GenerateOptions, diagnostics?: GenerateDiagnostics,
415
- ) => Generation {
416
- // DiffusionGemma is non-autoregressive: route to the denoising engine instead
417
- // of the AR decode loop. Same Generation/GenerateStats contract so the CLI and
418
- // server stream it through the existing token machinery.
419
- if (!(model instanceof DiffusionGemmaModel)) {
420
- const binding = bindLegacyAutoregressiveModel(model);
421
- return (prompt, options, diagnostics) => generateAutoregressive(binding, prompt, options, diagnostics);
422
- }
423
- const binding = bindLegacyDenoisingModel(model);
424
- return (prompt, options) => generateDenoising(binding, prompt, options);
425
- }
426
-
427
- /** Execute a supplied backend binding through the same AR loop and resource
428
- * scopes as generate(). Callers serialize shared model/adapter access. Replacing
429
- * this binding replaces graph, cache construction, media, and optional decode
430
- * together; it never borrows another model's compiled execution implicitly. */
431
- export function generateAutoregressive(
432
- binding: MlxAutoregressiveBinding,
433
- promptTokens: number[],
434
- options: GenerateOptions = {},
435
- diagnostics: GenerateDiagnostics = {},
436
- ): Generation {
437
- const runtime = binding.runtime ?? runtimeConfig();
438
- let inner = generateInner(binding, promptTokens, options, diagnostics, runtime);
439
- if (options.adapters?.length && binding.adapters) {
440
- inner = adapterScoped({ loraState: binding.adapters }, options.adapters, inner);
441
- }
442
- if (binding.memory.expertRuntime?.finishUsage || binding.memory.expertRuntime?.flushUsage)
443
- inner = usageScoped(binding.memory, inner);
444
- return new Generation(modelNeedsWiredLimit(binding.memory, undefined,
445
- runtime.value("MLX_BUN_FORCE_WIRE") === "1") ? wiredScoped(inner) : inner, runtime);
446
- }
447
-
448
- /** Denoising bindings use their own state and feedback graph. Callers hold
449
- * the native runtime's exclusive generation lease, including its global RNG. */
450
- export function generateDenoising<State>(
451
- binding: MlxDenoisingBinding<State>, promptTokens: number[], options: GenerateOptions = {},
452
- ): Generation {
453
- let inner = generateDiffusionInner(binding, promptTokens, options);
454
- if (options.adapters?.length && binding.adapters)
455
- inner = adapterScoped({ loraState: binding.adapters }, options.adapters, inner);
456
- if (binding.memory.expertRuntime?.finishUsage || binding.memory.expertRuntime?.flushUsage)
457
- inner = usageScoped(binding.memory, inner);
458
- const runtime = binding.runtime ?? runtimeConfig();
459
- return new Generation(modelNeedsWiredLimit(binding.memory, undefined,
460
- runtime.value("MLX_BUN_FORCE_WIRE") === "1") ? wiredScoped(inner) : inner, runtime);
461
- }
462
-
463
- /** Non-autoregressive diffusion generation, adapted to the AR Generation
464
- * contract. Runs the denoising engine (its own prefill + canvas loop) and
465
- * streams the emitted tokens. v1: greedy (temperature 0, confidence-threshold
466
- * sampler — the OptiQ default); per-block intra-stream + temperature>0
467
- * (categorical) are follow-ups. */
468
- async function* generateDiffusionInner<State>(
469
- binding: MlxDenoisingBinding<State>,
470
- promptTokens: number[],
471
- options: GenerateOptions,
472
- ): AsyncGenerator<GeneratedToken, GenerateStats> {
473
- options.signal?.throwIfAborted();
474
- const t0 = performance.now();
475
- const maxTokens = options.maxTokens ?? 256;
476
- // A fresh random canvas seed per request unless the caller pins one.
477
- const seed =
478
- options.seed !== undefined
479
- ? BigInt(options.seed)
480
- : BigInt(Math.floor(Math.random() * 0x7fffffff));
481
- // The shipped checkpoint's tokenizer stops on {1, 106}; union any caller eos.
482
- const eos = [...new Set([1, 106, ...(options.eosTokenIds ?? [])])];
483
- const result = await denoiseAsync(binding.graph, promptTokens, {
484
- maxTokens,
485
- sampler: "confidence-threshold",
486
- temperature: 0,
487
- eosTokenIds: eos,
488
- seed,
489
- visionPixels: options.visionPixels,
490
- }, options.signal);
491
- const decodeMs = performance.now() - t0;
492
- let index = 0;
493
- let failure: { error: unknown } | undefined;
494
- try {
495
- for (const token of result.tokens) {
496
- options.signal?.throwIfAborted();
497
- yield { token, index: index++ };
498
- }
499
- } catch (error) { failure = { error }; }
500
- finally {
501
- // A stop parser may close delivery before the finished canvas is exhausted.
502
- // Settle the emitted count on return(), without suppressing cancellation/errors.
503
- if (failure) throw failure.error;
504
- return {
505
- promptTokens: promptTokens.length,
506
- cachedTokens: 0,
507
- generatedTokens: index,
508
- prefillTps: 0,
509
- decodeTps: index / Math.max(decodeMs / 1000, 1e-9),
510
- prefillMs: 0,
511
- decodeMs,
512
- cacheTokens: [],
513
- };
514
- }
515
- }
516
-
517
- /** Hold the model's active-adapter list for exactly this generation. */
518
- async function* adapterScoped(
519
- model: { loraState: { active: string[] } },
520
- adapters: string[],
521
- inner: AsyncGenerator<GeneratedToken, GenerateStats>,
522
- ): AsyncGenerator<GeneratedToken, GenerateStats> {
523
- const previous = model.loraState.active;
524
- model.loraState.active = adapters;
525
- try {
526
- return yield* inner;
527
- } finally {
528
- model.loraState.active = previous;
529
- }
530
- }
531
-
532
- /** Wrap the generator so the wired limit is held exactly while it runs
533
- * (incl. early break/return/throw — finally fires on .return()). */
534
- async function* wiredScoped(
535
- inner: AsyncGenerator<GeneratedToken, GenerateStats>,
536
- ): AsyncGenerator<GeneratedToken, GenerateStats> {
537
- enterWiredScope();
538
- try {
539
- return yield* inner;
540
- } finally {
541
- exitWiredScope();
542
- }
543
- }
544
-
545
- /** Publish the streamed model's route ledger on every generator exit path. */
546
- async function* usageScoped(
547
- model: WiredModelMemory,
548
- inner: AsyncGenerator<GeneratedToken, GenerateStats>,
549
- ): AsyncGenerator<GeneratedToken, GenerateStats> {
550
- try {
551
- return yield* inner;
552
- } finally {
553
- if (model.expertRuntime?.finishUsage)
554
- await model.expertRuntime.finishUsage();
555
- else
556
- model.expertRuntime?.flushUsage?.();
557
- }
558
- }
559
-
560
- /** Apply generate()'s scoped wiring policy to non-generator execution paths. */
561
- export async function withModelWiredLimit<T>(
562
- model: WiredModelMemory,
563
- run: () => Promise<T>,
564
- ): Promise<T> {
565
- const release = acquireModelWiredLimit(model);
566
- try {
567
- return await run();
568
- } finally {
569
- release();
570
- }
571
- }
572
-
573
- /** Apply generate()'s usage safe-point to direct/non-generator paths. */
574
- export async function withModelUsageFlush<T>(
575
- model: WiredModelMemory,
576
- run: () => Promise<T>,
577
- ): Promise<T> {
578
- try {
579
- return await run();
580
- } finally {
581
- if (model.expertRuntime?.finishUsage)
582
- await model.expertRuntime.finishUsage();
583
- else
584
- model.expertRuntime?.flushUsage?.();
585
- }
586
- }
587
-
588
- async function* generateInner(
589
- binding: MlxAutoregressiveBinding,
590
- promptTokens: number[],
591
- options: GenerateOptions,
592
- diagnostics: GenerateDiagnostics,
593
- runtime: RuntimeConfig,
594
- ): AsyncGenerator<GeneratedToken, GenerateStats> {
595
- if (options.signal?.aborted) {
596
- options.grammar?.dispose();
597
- options.signal.throwIfAborted();
598
- }
599
- const {
600
- maxTokens = 512,
601
- eosTokenIds = binding.eosTokenIds,
602
- prefillChunkSize = 2048,
603
- } = options;
604
-
605
- try {
606
- assertMlxAutoregressiveBinding(binding);
607
- if (options.promptEmbeddings && !binding.forwardEmbeddings)
608
- throw new Error("AR binding does not support prompt embeddings");
609
- if (options.adapters?.length && !binding.adapters)
610
- throw new Error("AR binding does not support adapters");
611
- } catch (error) {
612
- options.grammar?.dispose();
613
- throw error;
614
- }
615
- const graph = binding.graph;
616
- const maintainKv = createKvMaintenance(options);
617
-
618
- // logprobs capture (mlx_lm.server semantics — see GenerateOptions.logprobs).
619
- // Everything below is gated: when neither flag is set, no extra ops, evals,
620
- // or readbacks happen on the hot path.
621
- const captureSelLp = options.logprobs === true;
622
- const captureTopK =
623
- options.topLogprobs && options.topLogprobs > 0 ? options.topLogprobs : 0;
624
- const stepSampler = makeStepSampler(options, {
625
- tokenRepresentation: "device",
626
- grammarWait: "external",
627
- historyUpdate: "manual",
628
- captureSelectedLogprob: captureSelLp,
629
- captureTopLogprobs: captureTopK,
630
- });
631
- const needsTokenHistory = stepSampler.needsHistory;
632
-
633
- const closePrefill = diagnostics.trace?.begin("prefill.total", {
634
- mechanism: diagnostics.mechanism ?? "serial",
635
- promptTokens: promptTokens.length,
636
- cachedTokens: options.cache?.[0]?.offset ?? 0,
637
- });
638
- const closeBatchSetup = diagnostics.trace?.begin("prefill.batch_setup", {
639
- mechanism: diagnostics.mechanism ?? "serial",
640
- });
641
- const ownsCache = !options.cache;
642
- const resuming = options.initialPendingToken !== undefined;
643
- let cache: Cache[] = options.cache ?? [];
644
- try {
645
- if (ownsCache) cache = binding.makeCache();
646
- if (!cache.length) throw new Error("AR binding returned an empty cache");
647
- const cached = cache[0]!.offset;
648
- if ((!resuming && cached >= promptTokens.length) ||
649
- (resuming && cached !== promptTokens.length))
650
- throw new Error(
651
- resuming
652
- ? `resumed cache (${cached} tokens) must exactly cover the resume prefix (${promptTokens.length})`
653
- : `pre-warmed cache (${cached} tokens) must be a strict prefix of the prompt (${promptTokens.length})`,
654
- );
655
- // Replace fresh full-attention caches before any forward. The deepest
656
- // write is prompt + maxTokens - 1, so capacity covers every decode step.
657
- maybePageKv(cache, options, promptTokens.length + maxTokens);
658
- } catch (error) {
659
- if (ownsCache) for (const state of cache) state.dispose();
660
- stepSampler.dispose();
661
- options.grammar?.dispose();
662
- closeBatchSetup?.();
663
- closePrefill?.();
664
- throw error;
665
- }
666
- const cachedTokens = cache[0]!.offset;
667
- // Token fast-forwarding gate (see shouldUseFill). RotatingKVCache (sliding
668
- // window) layers append multi-token writes through #updateConcat — O(window)
669
- // per append rather than the O(L) a plain ring pays — so v1 warns and skips
670
- // the whole feature for those models rather than paying it silently.
671
- let fillOn = shouldUseFill(options, runtime);
672
- if (fillOn && cache.some((c) => c instanceof RotatingKVCache)) {
673
- fillOn = false;
674
- if (!warnedFillRotating) {
675
- warnedFillRotating = true;
676
- console.warn(
677
- "[fill] sliding-window (RotatingKVCache) layers skip token " +
678
- "fast-forwarding in v1 (multi-token append is O(window) there) — " +
679
- "docs/design/speculative-decoding.md.",
680
- );
681
- }
682
- }
683
- const fillTrace = fillOn && fillTraceEnabled();
684
- const fillTraceFile = fillOn ? fillTracePath() : null;
685
- // Verify-policy proposals (echo tier) need the rejected tail rewound. That
686
- // is the SAME contract the spec lane's rounds use: trimmable caches drop the
687
- // tail; recurrent caches (SSMCache — gated-DeltaNet state, untrimmable)
688
- // restore a pre-round snapshot and bit-exactly replay the accepted prefix.
689
- // A model whose caches can do neither still gets assert-policy fills; verify
690
- // proposals are dropped and counted (stats.verifyUnsupported).
691
- const verifyCapable = fillOn && cache.every(
692
- (c) => c.isTrimmable() || typeof rewindable(c).specRoundRollback === "function",
693
- );
694
- closeBatchSetup?.();
695
-
696
- // logits [1,1,V] → sampled token array [1] (+ optional logprob capture,
697
- // all on-device)
698
- const sampleStep = (
699
- logits3d: MlxArray,
700
- step: number,
701
- ): { tok: MlxArray; extras: StepExtras | null } => {
702
- const result = stepSampler.sample(logits3d, step);
703
- return { tok: result.token, extras: result.extras };
704
- };
705
-
706
- const pushHistory = (tok: MlxArray): void => stepSampler.commitDevice(tok);
707
-
708
- // Decode-loop state lives at function scope so the finally can still
709
- // report stats and dispose in-flight arrays when the consumer
710
- // terminates the generator early (break on a stop sequence — the
711
- // forced .return() resumes at the yield and runs the finally).
712
- let prefillMs = 0;
713
- let tDecode = 0;
714
- let decodeMs = 0;
715
- let generated = options.initialGeneratedTokens ?? 0;
716
- let finishReason: GenerateStats["finishReason"];
717
- const forwarded: number[] = [];
718
- let pending: MlxArray | null = null;
719
- let nextPending: MlxArray | null = null;
720
- let pendingExtras: StepExtras | null = null;
721
- let nextExtras: StepExtras | null = null;
722
- let decoder: MlxDecodeStep | null = null;
723
- let appender: MlxTokenAppend | null = null;
724
- let finished = false;
725
- let threw = false;
726
- let executionError: unknown;
727
- let earlyUnforwardedToken: number | null = null;
728
- let closeTokenZero: (() => void) | undefined;
729
- const makeStats = (): GenerateStats => ({
730
- promptTokens: options.originalPromptTokens ?? promptTokens.length,
731
- cachedTokens: Math.min(cachedTokens, options.originalPromptTokens ?? promptTokens.length),
732
- generatedTokens: generated,
733
- ...(finishReason ? { finishReason } : {}),
734
- prefillMs,
735
- decodeMs,
736
- prefillTps: prefillMs > 0
737
- ? ((promptTokens.length - cachedTokens) / prefillMs) * 1000
738
- : 0,
739
- decodeTps: (generated / decodeMs) * 1000,
740
- cacheTokens: [...promptTokens, ...forwarded],
741
- ...(fillOn ? { fill: options.fill!.stats } : {}),
742
- });
743
-
744
- /** The one invariant that makes fill safe: the caches hold exactly
745
- * prompt + everything `forwarded` records, so a fill append must forward
746
- * ONLY the injected ids — the normal step already consumed `cur` and wrote
747
- * its KV. Forwarding [token, ...ids] would duplicate a position and
748
- * silently corrupt both the KV and PromptCache.put's key. Checked under
749
- * MLX_BUN_FILL_TRACE=1 on both sides of the append. */
750
- const assertFillAlignment = (where: string): void => {
751
- const offset = cache[0]!.offset;
752
- const want = promptTokens.length + forwarded.length;
753
- if (offset !== want)
754
- throw new Error(
755
- `[fill] cache misalignment ${where}: cache offset ${offset} != ` +
756
- `prompt ${promptTokens.length} + forwarded ${forwarded.length}`,
757
- );
758
- };
759
-
760
- /** THE apply primitive for token fast-forwarding — ONE chunked forward
761
- * carries a proposal into the KV (and the recurrent state), and BOTH
762
- * policies ride it (src/fill/proposal.ts):
763
- *
764
- * - assert: the span is determined (a template scaffold). Append and move
765
- * on. No readback, no checkpoint, no rewind. The in-flight sample for
766
- * the position after `token` is dropped UNEXAMINED — a discarded
767
- * pipeline dispatch, not a rejected draft.
768
- * - verify: the span is likely (a copy from earlier in the session).
769
- * Position 0 is checked BEFORE the forward against the in-flight sample
770
- * (free, and a mismatch costs nothing — nothing has been written).
771
- * Positions 1..m-1 are checked against the argmax already sitting in
772
- * THIS forward's logits, so verification adds no pass over the weights.
773
- * The rejected tail is rewound through the same cache contract the spec
774
- * lane's rounds use, and decode resumes at the first disagreement.
775
- *
776
- * Returns the ids the engine must now emit (empty = no fill happened).
777
- * Mutates nextPending/nextExtras/forwarded, exactly like the normal step. */
778
- /** One trace record: the proposal next to the model's own token at every
779
- * span position (`actual[0]` = in-flight sample, `actual[j]` = argmax after
780
- * ids[j-1]). Appended as JSONL to MLX_BUN_FILL_TRACE=<file>. */
781
- const traceProposal = (
782
- proposal: Proposal, generatedNow: number, accepted: number, actual: number[],
783
- ): void => {
784
- const ids = proposal.ids;
785
- let firstMismatch = -1;
786
- for (let j = 0; j < ids.length && j < actual.length; j++)
787
- if (actual[j] !== ids[j]) { firstMismatch = j; break; }
788
- const dec = options.fill!.decode;
789
- const rec: FillTraceRecord = {
790
- ts: new Date().toISOString(), origin: proposal.origin, policy: proposal.policy,
791
- generated: generatedNow, proposedLen: ids.length, accepted, firstMismatch,
792
- proposed: ids, actual,
793
- ...(dec ? { proposedText: dec(ids), actualText: dec(actual) } : {}),
794
- };
795
- try { appendFileSync(fillTraceFile!, JSON.stringify(rec) + "\n"); } catch { /* trace is best-effort */ }
796
- };
797
-
798
- const applyProposal = async (
799
- proposal: Proposal, generatedNow: number,
800
- ): Promise<number[]> => {
801
- const fill = options.fill!;
802
- const ids = proposal.ids;
803
- const verify = proposal.policy === "verify";
804
- if (verify && !verifyCapable) {
805
- fill.noteVerifyUnsupported();
806
- fill.commit(proposal, 0);
807
- return [];
808
- }
809
- // The in-flight sample IS the model's own choice for position 0 (read for
810
- // the trace under both policies; the served assert path never checks it).
811
- const inFlight = verify || fillTraceFile ? ops.itemUint32(nextPending!) : -1;
812
- if (verify) {
813
- if (inFlight !== ids[0]) {
814
- if (fillTraceFile) traceProposal(proposal, generatedNow, 0, [inFlight]);
815
- fill.commit(proposal, 0);
816
- return []; // nextPending survives; this becomes an ordinary step
817
- }
818
- } else {
819
- fill.noteWastedSample();
820
- }
821
- if (fillTrace) assertFillAlignment("at fill entry");
822
- nextPending!.dispose();
823
- nextPending = null;
824
- disposeStepExtras(nextExtras);
825
- nextExtras = null;
826
- // No-op under the fill exclusions (kv quant refuses fill); kept so the
827
- // boundary stays correct if those exclusions ever loosen.
828
- maintainKv(cache);
829
-
830
- let checkpointMs = 0;
831
- let roundOpen = false;
832
- if (verify) {
833
- const t0 = performance.now();
834
- for (const c of cache) rewindable(c).specRoundBegin?.();
835
- roundOpen = true;
836
- checkpointMs += performance.now() - t0;
837
- }
838
- let accepted = ids.length;
839
- let hf: MlxArray | null = null;
840
- let logitsAll: MlxArray | null = null;
841
- try {
842
- const chunkSize = (state: readonly Cache[]) => {
843
- const maxChunk = appender?.maxChunkSize(state) ?? 1;
844
- return fill.appendChunkSize > 0 ? Math.min(fill.appendChunkSize, maxChunk) : maxChunk;
845
- };
846
- hf = await appendFillHidden(
847
- !verify && appender ? appender.forwardHidden.bind(appender) : graph.forwardHidden.bind(graph),
848
- cache, ids, verify ? () => ids.length : chunkSize,
849
- );
850
- const [, Lf, Hf] = hf.shape as [number, number, number];
851
- let pred: number[] | null = null;
852
- if (verify || fillTraceFile) {
853
- // Free logits: this forward already computed every span position's
854
- // hidden state. argmax at j is the model's continuation after ids[j],
855
- // so it verifies ids[j+1]. (Under assert this readback is trace-only.)
856
- logitsAll = graph.projectLogits(hf, { type: "all" });
857
- const argmax = ops.argmaxAxis(logitsAll, -1);
858
- pred = argmax.toIntTokens();
859
- argmax.dispose();
860
- }
861
- if (verify && pred) {
862
- for (let j = 0; j + 1 < ids.length; j++) {
863
- if (pred[j] !== ids[j + 1]) { accepted = j + 1; break; }
864
- }
865
- }
866
- if (fillTraceFile && pred)
867
- traceProposal(proposal, generatedNow, accepted, [inFlight, ...pred.slice(0, ids.length - 1)]);
868
- const t1 = performance.now();
869
- if (accepted < ids.length) {
870
- // Trimmable caches drop the rejected tail; recurrent caches restore
871
- // their pre-round snapshot and bit-exactly replay the accepted prefix
872
- // (Cache.specRoundRollback — the same primitive src/spec/serve-loop.ts
873
- // uses after its accept walk).
874
- for (const c of cache) {
875
- const rc = rewindable(c);
876
- if (rc.specRoundRollback) rc.specRoundRollback(accepted);
877
- else rc.trim(ids.length - accepted);
878
- }
879
- roundOpen = false;
880
- } else if (roundOpen) {
881
- for (const c of cache) rewindable(c).specRoundCommit?.();
882
- roundOpen = false;
883
- }
884
- if (verify) checkpointMs += performance.now() - t1;
885
- forwarded.push(...ids.slice(0, accepted));
886
- if (fillTrace) assertFillAlignment("after fill append");
887
- // The sampler's token history gets the ACCEPTED ids only, before the
888
- // resume sample — so logits processors (repetition/presence/frequency)
889
- // see exactly the sequence that was emitted.
890
- stepSampler.commitNumbers(ids.slice(0, accepted));
891
- // Resume from the LAST ACCEPTED position's logits: the bonus token on a
892
- // full accept, the correction on a partial one. Same sampler and step
893
- // index as an ordinary step — the correction's KV is written by the next
894
- // iteration's forward, like any sampled token.
895
- const willGen = generatedNow + accepted;
896
- if (willGen < maxTokens) {
897
- let logits: MlxArray;
898
- if (logitsAll) {
899
- const V = logitsAll.shape[2]!;
900
- logits = logitsAll.slice([0, accepted - 1, 0], [1, accepted, V]);
901
- } else {
902
- const hLast = hf.slice([0, Lf - 1, 0], [1, Lf, Hf]);
903
- logits = graph.projectLogits(hLast, { type: "all" });
904
- hLast.dispose();
905
- }
906
- const sn = sampleStep(logits, willGen);
907
- nextPending = sn.tok;
908
- nextExtras = sn.extras;
909
- logits.dispose();
910
- ops.asyncEvalAll([nextPending, ...stepExtrasArrays(nextExtras)]);
911
- }
912
- } finally {
913
- // A throw mid-round would otherwise leave the recurrent caches armed
914
- // (their next forward raises "spec round already recorded").
915
- if (roundOpen) for (const c of cache) rewindable(c).specRoundCommit?.();
916
- logitsAll?.dispose();
917
- hf?.dispose();
918
- }
919
- if (verify) fill.noteVerifyEvent(checkpointMs);
920
- fill.commit(proposal, accepted);
921
- if (fillTrace)
922
- console.error(
923
- `[fill] ${proposal.origin}/${proposal.policy} ${accepted}/${ids.length} ` +
924
- `after ${generatedNow} generated (events ${fill.stats.events}, ` +
925
- `injected ${fill.stats.injected}, checkpoint ${checkpointMs.toFixed(2)}ms)`,
926
- );
927
- return ids.slice(0, accepted);
928
- };
929
-
930
- let nextCheckpoint = options.checkpointEveryTokens && options.checkpointEveryTokens > 0
931
- ? (Math.floor(generated / options.checkpointEveryTokens) + 1) * options.checkpointEveryTokens
932
- : Number.POSITIVE_INFINITY;
933
- let tPrefill = performance.now();
934
-
935
- try {
936
- if (resuming) {
937
- // The checkpoint cache already covers the complete replay prefix. Its
938
- // pending token was sampled from that exact state before the snapshot,
939
- // so resume starts directly at the decode loop without another forward.
940
- if (needsTokenHistory) stepSampler.seedHistory(promptTokens);
941
- // Device samplers return uint32. Preserve that signature on restore so
942
- // compiled decode does not trace a different signed-token graph first.
943
- using restoredToken = ops.fromInt32([options.initialPendingToken!], [1]);
944
- pending = restoredToken.astype(Dtype.uint32);
945
- ops.asyncEvalAll([pending]);
946
- prefillMs = 0;
947
- closePrefill?.();
948
- tDecode = performance.now();
949
- } else {
950
- // ---- prefill ----
951
- const closeInitialKv = diagnostics.trace?.begin("prefill.kv_maintenance", {
952
- mechanism: diagnostics.mechanism ?? "serial",
953
- boundary: "initial",
954
- });
955
- maintainKv(cache);
956
- closeInitialKv?.();
957
- tPrefill = performance.now();
958
- let h0: MlxArray;
959
- if (options.promptEmbeddings) {
960
- if (cachedTokens !== 0)
961
- throw new Error("promptEmbeddings cannot be combined with a pre-warmed cache");
962
- if (needsTokenHistory)
963
- stepSampler.seedHistory(promptTokens);
964
- // e2b/e4b need the spliced token ids to build per-layer inputs
965
- // (multimodal soft-token positions zeroed inside forwardEmbeddings).
966
- const embedIds = ops.fromInt32(promptTokens, [1, promptTokens.length]);
967
- h0 = binding.forwardEmbeddings!(
968
- options.promptEmbeddings, cache, options.imageMask ?? null, embedIds,
969
- options.multimodalMask ?? null,
970
- );
971
- embedIds.dispose();
972
- } else {
973
- let pos = cachedTokens;
974
- const snapshotAt = options.onPrefillDone && options.snapshotAt !== undefined
975
- ? Math.min(Math.max(options.snapshotAt, cachedTokens), promptTokens.length)
976
- : undefined;
977
- const tailSplit = runtime.flag("MLX_BUN_PREFILL_TAIL_SPLIT", true);
978
- const forward = graph.forwardHidden.bind(graph);
979
- const maintain = () => maintainKv(cache);
980
- while (true) {
981
- options.signal?.throwIfAborted();
982
- const step = nextPrefillStep({ length: promptTokens.length, position: pos,
983
- chunkSize: prefillChunkSize, tailSplit, snapshotAt });
984
- if (step.kind === "final" && needsTokenHistory) stepSampler.seedHistory(promptTokens);
985
- const closeChunk = diagnostics.trace?.begin("prefill.chunk", {
986
- mechanism: diagnostics.mechanism ?? "serial", startToken: step.start,
987
- tokens: step.end - step.start, ...(step.kind === "final" ? { final: true } : {}),
988
- });
989
- let hidden: MlxArray | null;
990
- try { hidden = await executeMlxPrefillStep(forward, cache, promptTokens, step, maintain); }
991
- finally { closeChunk?.(); }
992
- pos = step.end;
993
- if (hidden) { h0 = hidden; break; }
994
- if (runtime.value("MLX_BUN_PREFILL_MEM_LOG") === "1")
995
- console.error(`[prefill-mem] ${pos} active ${(activeMemory() / 2 ** 30).toFixed(2)} peak ${(peakMemory() / 2 ** 30).toFixed(2)}`);
996
- await new Promise<void>((resolve) => setImmediate(resolve));
997
- if (step.snapshot) options.onPrefillDone?.();
998
- }
999
- }
1000
- if (options.snapshotAt === undefined || options.snapshotAt >= promptTokens.length)
1001
- options.onPrefillDone?.();
1002
- // The trace's prefill span is the uncached model-forward portion only.
1003
- // Token-0 lm-head, sampling, and readback are a separate additive stage.
1004
- if (diagnostics.trace && runtime.value("MLX_BUN_P2R_SYNC") === "1")
1005
- synchronize(gpuStream);
1006
- closePrefill?.();
1007
- closeTokenZero = diagnostics.trace?.begin("token_zero.total", {
1008
- mechanism: diagnostics.mechanism ?? "serial",
1009
- });
1010
- const [, L0, H] = h0.shape as [number, number, number];
1011
- const hLast = h0.slice([0, L0 - 1, 0], [1, L0, H]);
1012
- h0.dispose();
1013
- const logits0 = graph.projectLogits(hLast, { type: "all" });
1014
- hLast.dispose();
1015
- const s0 = sampleStep(logits0, 0); // token array [1] (+ optional extras)
1016
- pending = s0.tok;
1017
- pendingExtras = s0.extras;
1018
- logits0.dispose();
1019
- // mirror mlx-lm generate_step: async-dispatch the first token's
1020
- // compute; the prefill clock keeps running until the token ARRIVES
1021
- // (first itemUint32 below). mlx-lm stops its prompt clock at the
1022
- // first yielded token, which bills the prefill→decode boundary
1023
- // (allocator reclaim of prefill transients + first-step dispatch)
1024
- // to prompt_time, not decode — replicated so cross-stack decode
1025
- // tok/s measure the same quantity. The boundary cost is real and
1026
- // scales with prompt length; it belongs to "having prefilled".
1027
- ops.asyncEvalAll([pending, ...stepExtrasArrays(pendingExtras)]);
1028
- }
1029
-
1030
- // ---- decode (pipelined) ----
1031
- // Selection and transactional fallback belong to the graph's binding.
1032
- decoder = options.decodePolicy?.compiledDecode === false ? null : binding.createDecode?.({
1033
- hasAdapters: !!options.adapters?.length, pagedKv: !!options.pagedKv,
1034
- }) ?? null;
1035
- appender = fillOn ? binding.createAppend?.({
1036
- hasAdapters: !!options.adapters?.length, pagedKv: !!options.pagedKv,
1037
- }) ?? null : null;
1038
- let stop = false;
1039
- /** Token id read eagerly at the top of the loop for grammar (reused for
1040
- * the yield, avoiding a second readback). -1 when grammar is off — the
1041
- * pipelined path keeps its deferred itemUint32 below. */
1042
- let grammarTok = -1;
1043
- // Jump-forward decoding (opt-in, MLX_BUN_GRAMMAR_JUMP=1; serial lane
1044
- // only — the batch lane's #stepGrammar doesn't jump yet): when the
1045
- // grammar forces a unique continuation, emit its retokenized ids without
1046
- // per-token forwards — ONE multi-token forward carries them into the KV
1047
- // (see GrammarController.jumpForward for the contract + the fidelity
1048
- // note on why this is opt-in). Excluded when logprobs are requested
1049
- // (jumped tokens are never sampled, so they'd have no logprobs rows).
1050
- const grammarJump = options.decodePolicy?.grammarJump ?? shouldUseGrammarJump(options, runtime);
1051
- // Yield token zero before its own decode forward. The captured runtime
1052
- // keeps this scheduling choice stable across consumer awaits.
1053
- const earlyFirstToken = runtime.value("MLX_BUN_EARLY_FIRST_TOKEN") === "1" &&
1054
- maxTokens > 1 && !resuming && !fillOn && !options.grammar;
1055
- while (!stop) {
1056
- options.signal?.throwIfAborted();
1057
- const cur = pending!;
1058
- const stepIndex = generated;
1059
- let curExtras = pendingExtras;
1060
- let yieldedEarly = false;
1061
- if (earlyFirstToken && stepIndex === 0) {
1062
- const firstToken = ops.itemUint32(cur);
1063
- options.signal?.throwIfAborted();
1064
- if (!eosTokenIds.includes(firstToken)) {
1065
- prefillMs = performance.now() - tPrefill;
1066
- closeTokenZero?.();
1067
- closeTokenZero = undefined;
1068
- tDecode = performance.now();
1069
- const logprobs = readStepExtras(curExtras);
1070
- curExtras = null;
1071
- pendingExtras = null;
1072
- // Count before yielding so an early return reports this token. Its
1073
- // KV is still absent; forwarded describes the prompt-only cache.
1074
- generated++;
1075
- yieldedEarly = true;
1076
- earlyUnforwardedToken = firstToken;
1077
- yield { token: firstToken, index: stepIndex, ...(logprobs ? { logprobs } : {}) };
1078
- earlyUnforwardedToken = null;
1079
- options.signal?.throwIfAborted();
1080
- }
1081
- }
1082
- // Keep current extras owned by the run until readback succeeds. A
1083
- // forward/grammar failure must leave them reachable by finally.
1084
- // Grammar advance (src/grammar.ts): acceptToken needs the token id as a
1085
- // JS number, which the pipelined loop defers (it operates on device
1086
- // arrays). So grammar requests eager-read cur here, advance the matcher,
1087
- // and await the async mask precompute — which overlaps the GPU forward
1088
- // dispatched just below. This trades the readback/forward overlap for
1089
- // correctness (the mask for step n+1 must reflect token n). Non-grammar
1090
- // requests keep the fast pipelined loop untouched.
1091
- //
1092
- // F1 fix (batched-lane plan): always eager-read `grammarTok` when
1093
- // grammar is on, even at the max_tokens boundary — the emitted token
1094
- // reuses it unconditionally. The OLD code gated the READ on
1095
- // generated+1 < maxTokens but emitted grammarTok regardless, so the LAST
1096
- // iteration skipped the refresh and emitted a stale/garbage token (the
1097
- // previous step's token, or -1 when max_tokens=1) → truncated JSON ended
1098
- // on a corrupted token + cacheTokens recorded the wrong id. Now only
1099
- // accept()/ready() (which prepare the NEXT step's mask) are gated.
1100
- /** Forced ids to emit after cur this iteration (jump-forward), else null. */
1101
- let jumpEmit: number[] | null = null;
1102
- if (options.grammar) {
1103
- grammarTok = ops.itemUint32(cur);
1104
- if (stepIndex + 1 < maxTokens) {
1105
- options.grammar.accept(grammarTok);
1106
- await options.grammar.ready();
1107
- if (grammarJump && !options.grammar.isTerminated) {
1108
- jumpEmit = options.grammar.jumpForward(maxTokens - (stepIndex + 1));
1109
- // jumpForward advanced the matcher and fired the post-jump mask
1110
- // fill; it must be ready before this iteration's sampleStep.
1111
- if (jumpEmit) await options.grammar.ready();
1112
- }
1113
- }
1114
- }
1115
- // build step n+1's graph from the *unread* pending token
1116
- nextPending = null;
1117
- nextExtras = null;
1118
- // When the grammar has terminated (a complete valid JSON/schema
1119
- // accepted), there are no valid tokens left — skip building the next
1120
- // step so the sampler never sees an all--inf distribution.
1121
- if (jumpEmit) {
1122
- // JUMP iteration: one [1, 1+m] forward carries cur AND the forced ids
1123
- // into the KV (they are all committed content — jumpForward's
1124
- // contract); the next sampled token, if the budget and grammar allow
1125
- // one, comes from its last position. Compiled decode resumes on the
1126
- // following iteration (supports() re-checks the grown caches).
1127
- maintainKv(cache);
1128
- pushHistory(cur);
1129
- stepSampler.commitNumbers(jumpEmit);
1130
- const chunk = [grammarTok, ...jumpEmit];
1131
- const ids = ops.fromInt32(chunk, [1, chunk.length]);
1132
- const h = await graph.forwardHidden(ids, cache);
1133
- ids.dispose();
1134
- // Every chunk token's KV is in the cache regardless of what follows.
1135
- forwarded.push(...chunk);
1136
- const willGen = stepIndex + 1 + jumpEmit.length;
1137
- if (willGen < maxTokens && !options.grammar!.isTerminated) {
1138
- const [, Lj, Hj] = h.shape as [number, number, number];
1139
- const hLast = h.slice([0, Lj - 1, 0], [1, Lj, Hj]);
1140
- h.dispose();
1141
- const logits = graph.projectLogits(hLast, { type: "all" });
1142
- hLast.dispose();
1143
- const sn = sampleStep(logits, willGen);
1144
- nextPending = sn.tok;
1145
- nextExtras = sn.extras;
1146
- logits.dispose();
1147
- ops.asyncEvalAll([nextPending, ...stepExtrasArrays(nextExtras)]);
1148
- } else {
1149
- h.dispose(); // burst ends the generation (max_tokens or grammar done)
1150
- }
1151
- } else if (stepIndex + 1 < maxTokens && !options.grammar?.isTerminated) {
1152
- maintainKv(cache);
1153
- pushHistory(cur);
1154
- let logits: MlxArray | null = null;
1155
- let evalWith: MlxArray[] = [];
1156
- const decoded = decoder?.tryStep(cur, cache);
1157
- if (decoded) {
1158
- logits = decoded.logits;
1159
- evalWith = decoded.evalWith;
1160
- }
1161
- if (!logits) {
1162
- const ids = ops.reshape(cur, [1, 1]);
1163
- const h = await graph.forwardHidden(ids, cache);
1164
- ids.dispose();
1165
- logits = graph.projectLogits(h, { type: "all" });
1166
- h.dispose();
1167
- }
1168
- const sn = sampleStep(logits, stepIndex + 1);
1169
- nextPending = sn.tok;
1170
- nextExtras = sn.extras;
1171
- logits.dispose();
1172
- ops.asyncEvalAll([nextPending, ...stepExtrasArrays(nextExtras), ...evalWith]);
1173
- }
1174
-
1175
- // sync-read step n's token while n+1 computes
1176
- // sync-read step n's token while n+1 computes (grammar already read it
1177
- // eagerly above — reuse to avoid a second GPU sync)
1178
- const token = options.grammar ? grammarTok : ops.itemUint32(cur);
1179
- options.signal?.throwIfAborted();
1180
- if (generated === 0) {
1181
- // first token arrived: prompt clock stops, decode clock starts
1182
- // (mlx-lm stream_generate's n==0 clock swap; the first token is
1183
- // "free" on the decode clock there too)
1184
- prefillMs = performance.now() - tPrefill;
1185
- closeTokenZero?.();
1186
- closeTokenZero = undefined;
1187
- tDecode = performance.now();
1188
- }
1189
- cur.dispose();
1190
- pending = null;
1191
- if (!yieldedEarly) generated++;
1192
- // if a next-step graph was built, this token's KV entered the cache
1193
- // (jump iterations pushed the whole chunk already)
1194
- if (nextPending !== null && !jumpEmit) forwarded.push(token);
1195
-
1196
- if (eosTokenIds.includes(token)) {
1197
- finishReason = "stop";
1198
- disposeStepExtras(curExtras);
1199
- pendingExtras = null;
1200
- nextPending?.dispose();
1201
- nextPending = null;
1202
- disposeStepExtras(nextExtras);
1203
- nextExtras = null;
1204
- stop = true;
1205
- } else {
1206
- // ---- token fast-forwarding (K3): APPEND, then yield ----------------
1207
- // The engine already knows the next m tokens, so it writes them into
1208
- // the KV itself with an append and resumes sampling after them. No
1209
- // draft, no verify, no rollback: an injected token is indistinguishable
1210
- // to the model from one it sampled. The append happens BEFORE any of
1211
- // the burst's yields, which is what makes a consumer break mid-burst
1212
- // safe — `forwarded` already describes the cache exactly.
1213
- let fillEmit: number[] | null = null;
1214
- if (fillOn) {
1215
- if (jumpEmit)
1216
- throw new Error("fill and grammar jump-forward cannot share an iteration");
1217
- // A fill needs an in-flight step to ride; with nextPending null this
1218
- // token's KV was never written and the generation ends here. push()
1219
- // still runs so the session's history (and echo index) stay exact.
1220
- const proposal = options.fill!.push(
1221
- token, nextPending !== null ? maxTokens - generated : 0,
1222
- );
1223
- if (proposal && nextPending !== null) {
1224
- const emitted = await applyProposal(proposal, generated);
1225
- if (emitted.length) fillEmit = emitted;
1226
- } else if (proposal) {
1227
- options.fill!.commit(proposal, 0); // unreachable at budget 0; belt
1228
- }
1229
- }
1230
- // readExtras before the yield: if the consumer breaks at this yield,
1231
- // the extras are already read and disposed.
1232
- const logprobs = readStepExtras(curExtras);
1233
- pendingExtras = null;
1234
- if (!yieldedEarly) yield { token, index: generated - 1, ...(logprobs ? { logprobs } : {}) };
1235
- options.signal?.throwIfAborted();
1236
- // mlx-lm generate_step: clear_cache after token 0 (drops the
1237
- // remaining prefill transients) and every 256 tokens after
1238
- if ((generated - 1) % 256 === 0) clearCache();
1239
- // Jump-forward burst: the forced ids follow cur, one yield each (the
1240
- // consumer's stop-sequence matcher and detokenizer see the same
1241
- // one-at-a-time stream shape as always). Their KV is already in the
1242
- // cache (the chunk forward above); a consumer break mid-burst is
1243
- // safe — `forwarded` already reflects the cache exactly.
1244
- if (jumpEmit) {
1245
- for (const jt of jumpEmit) {
1246
- options.signal?.throwIfAborted();
1247
- generated++;
1248
- yield { token: jt, index: generated - 1 };
1249
- if ((generated - 1) % 256 === 0) clearCache();
1250
- }
1251
- }
1252
- // Fill burst: same one-yield-per-token shape as the jump burst, so
1253
- // CompletionSink / StopMatcher / StreamDecoder see an ordinary stream
1254
- // (a stop sequence inside an injected span fires exactly where it
1255
- // would have in an unfilled run).
1256
- if (fillEmit) {
1257
- for (const ft of fillEmit) {
1258
- options.signal?.throwIfAborted();
1259
- generated++;
1260
- yield { token: ft, index: generated - 1 };
1261
- if ((generated - 1) % 256 === 0) clearCache();
1262
- }
1263
- }
1264
- if (nextPending === null) {
1265
- stop = true;
1266
- } else {
1267
- pending = nextPending;
1268
- nextPending = null;
1269
- pendingExtras = nextExtras;
1270
- nextExtras = null;
1271
- }
1272
- if (
1273
- pending !== null &&
1274
- options.onDecodeCheckpoint &&
1275
- generated >= nextCheckpoint
1276
- ) {
1277
- const pendingToken = ops.itemUint32(pending);
1278
- await options.onDecodeCheckpoint({
1279
- cacheTokens: [...promptTokens, ...forwarded],
1280
- caches: cache,
1281
- generatedTokens: generated,
1282
- pendingToken,
1283
- });
1284
- const every = options.checkpointEveryTokens!;
1285
- while (nextCheckpoint <= generated) nextCheckpoint += every;
1286
- }
1287
- }
1288
- }
1289
- decodeMs = performance.now() - tDecode;
1290
- finished = true;
1291
- return makeStats();
1292
- } catch (e) {
1293
- threw = true;
1294
- executionError = e;
1295
- throw e;
1296
- } finally {
1297
- try {
1298
- // An early consumer return leaves token zero outside the retained KV.
1299
- // Finish that one M=1 forward before handing caller-owned caches back,
1300
- // matching the ordinary pipeline's boundary. Otherwise a later prefix
1301
- // hit folds the token into an M>1 prefill and changes native reductions.
1302
- // Aborted requests and caches owned by this run need no retained state.
1303
- if (!ownsCache && !threw && !options.signal?.aborted && earlyUnforwardedToken !== null) {
1304
- maintainKv(cache);
1305
- const ids = ops.reshape(pending!, [1, 1]);
1306
- try {
1307
- const hidden = await graph.forwardHidden(ids, cache);
1308
- try { evalCacheState(cache); }
1309
- finally { hidden.dispose(); }
1310
- } finally { ids.dispose(); }
1311
- forwarded.push(earlyUnforwardedToken);
1312
- }
1313
- } catch (error) {
1314
- threw = true;
1315
- executionError = error;
1316
- throw error;
1317
- } finally {
1318
- if (!finished) {
1319
- pending?.dispose();
1320
- nextPending?.dispose();
1321
- disposeStepExtras(pendingExtras);
1322
- disposeStepExtras(nextExtras);
1323
- }
1324
- try {
1325
- const closing = decoder?.close();
1326
- if (closing) await closing;
1327
- } catch (error) {
1328
- if (threw) throw new AggregateError([executionError, error],
1329
- "AR execution and decoder cleanup failed", { cause: executionError });
1330
- throw error;
1331
- } finally {
1332
- if (ownsCache) for (const c of cache) c.dispose();
1333
- // TokenizerInfo is process-cached; this request owns the matcher.
1334
- options.grammar?.dispose();
1335
- stepSampler.dispose();
1336
- }
1337
- if (!finished && !threw) {
1338
- // forced early return (consumer break at a yield): still report
1339
- // stats — `forwarded` only lists tokens whose KV actually entered
1340
- // the cache, so cacheTokens stays exact for PromptCache.put().
1341
- decodeMs = performance.now() - tDecode;
1342
- return makeStats();
1343
- }
1344
- }
1345
- }
1346
- }