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