mlx-bun 0.0.13 → 0.3.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 (427) hide show
  1. package/README.md +71 -578
  2. package/bin/mlx-bun.mjs +1 -1
  3. package/docs/README.md +91 -0
  4. package/docs/{design → archive}/hlg-sampling.md +3 -3
  5. package/docs/{design → archive/investigations}/audio-input-plan.md +4 -4
  6. package/docs/{design → archive/investigations}/colibri-glm52-port.md +1 -1
  7. package/docs/{design → archive/investigations}/diffusion-gemma-port.md +3 -3
  8. package/docs/{design → archive/investigations}/faithful-l1-consolidation.md +4 -4
  9. package/docs/{investigations → archive/investigations}/hlg-sampling-investigation.md +1 -1
  10. package/docs/{design → archive/investigations}/minicpm5-decode-megakernel.md +11 -11
  11. package/docs/{design → archive/investigations}/optimization_plan.md +1 -1
  12. package/docs/archive/investigations/pareto-specialized-runtime-findings.md +92 -0
  13. package/docs/{design → archive/investigations}/pre-colibri-stabilization.md +5 -5
  14. package/docs/{design → archive/investigations}/the-dreaming-handoff.md +2 -2
  15. package/docs/{planning → archive/planning}/chat-ui-vision.md +1 -1
  16. package/docs/{planning → archive/planning}/chunk-task-roadmap.md +1 -1
  17. package/docs/{planning → archive/planning}/curve-sampler-research-plan.md +4 -4
  18. package/docs/{planning → archive/planning}/memory-docs-and-dag-plan.md +1 -1
  19. package/docs/{planning → archive/planning}/release-notes-v0.0.9.md +1 -1
  20. package/docs/archive/planning/release-notes-v0.2.0.md +49 -0
  21. package/docs/{planning → archive/planning}/website-readme-pass-plan.md +1 -1
  22. package/docs/design/batching.md +569 -0
  23. package/docs/design/decode-speed-program.md +3178 -169
  24. package/docs/design/dreaming-nightly-pipeline.md +675 -282
  25. package/docs/design/generic-model-support.md +579 -334
  26. package/docs/design/kv-cache.md +680 -0
  27. package/docs/design/orpo-training.md +673 -1239
  28. package/docs/design/speculative-decoding.md +1627 -0
  29. package/docs/design/turboquant.md +2102 -0
  30. package/docs/design/unified-engine-frontier-plan.md +1831 -727
  31. package/docs/design/web-chat-redesign.md +666 -1083
  32. package/docs/planning/PRODUCT_ROADMAP.md +34 -1
  33. package/docs/planning/release-notes-v0.3.0.md +35 -0
  34. package/docs/planning/video-series.md +421 -0
  35. package/docs/reference/benchmarks.md +1162 -0
  36. package/docs/reference/cli.md +395 -149
  37. package/docs/reference/distribution.md +321 -181
  38. package/docs/reference/environment.md +348 -0
  39. package/docs/reference/glossary.md +39 -0
  40. package/docs/reference/library-api.md +580 -142
  41. package/docs/reference/memory.md +227 -64
  42. package/docs/reference/models.md +137 -6
  43. package/docs/reference/server-api.md +938 -422
  44. package/docs/reference/server-config.md +652 -320
  45. package/docs/reference/training.md +393 -254
  46. package/docs/reference/troubleshooting.md +166 -0
  47. package/package.json +10 -5
  48. package/src/admission-plan.ts +18 -0
  49. package/src/anthropic.ts +92 -3
  50. package/src/audio/decode.ts +2 -2
  51. package/src/audio/features.ts +2 -2
  52. package/src/backends/mlx/autoregressive.ts +108 -0
  53. package/src/backends/mlx/batch-group.ts +1646 -0
  54. package/src/backends/mlx/diffusion.ts +39 -0
  55. package/src/backends/mlx/draft-target.ts +47 -0
  56. package/src/backends/mlx/evaluation.ts +105 -0
  57. package/src/backends/mlx/fill-append.ts +45 -0
  58. package/src/backends/mlx/gateway-binding.ts +71 -0
  59. package/src/backends/mlx/graph.ts +38 -0
  60. package/src/backends/mlx/memory-client.ts +189 -0
  61. package/src/backends/mlx/memory-guard.ts +40 -0
  62. package/src/backends/mlx/methods.ts +90 -0
  63. package/src/backends/mlx/model-prompt.ts +164 -0
  64. package/src/backends/mlx/model-serving.ts +79 -0
  65. package/src/backends/mlx/prefill.ts +33 -0
  66. package/src/backends/mlx/request-policy.ts +20 -0
  67. package/src/backends/mlx/rollback.ts +51 -0
  68. package/src/backends/mlx/serial-executor.ts +273 -0
  69. package/src/backends/mlx/speculative.ts +114 -0
  70. package/src/backends/mlx/state-views.ts +26 -0
  71. package/src/backends/mlx/text-engine.ts +73 -0
  72. package/src/cli.ts +189 -61
  73. package/src/client.ts +66 -0
  74. package/src/config.ts +21 -4
  75. package/src/contracts/completion.ts +16 -0
  76. package/src/contracts/execution.ts +49 -0
  77. package/src/contracts/generation.ts +103 -0
  78. package/src/contracts/host.ts +7 -0
  79. package/src/contracts/jobs.ts +66 -0
  80. package/src/contracts/pi-provider.ts +30 -0
  81. package/src/contracts/pi.ts +259 -0
  82. package/src/contracts/resources.ts +18 -0
  83. package/src/contracts/scheduling.ts +27 -0
  84. package/src/contracts/tsconfig.json +14 -0
  85. package/src/diffusion/diffusion-generate.ts +218 -153
  86. package/src/embed.ts +1 -1
  87. package/src/engine/admission.ts +59 -0
  88. package/src/engine/cancellation.ts +38 -0
  89. package/src/engine/engine.ts +56 -0
  90. package/src/engine/execution-coordinator.ts +70 -0
  91. package/src/engine/execution-plan.ts +51 -0
  92. package/src/engine/resources.ts +42 -0
  93. package/src/engine/scheduler.ts +67 -0
  94. package/src/engine/session.ts +247 -0
  95. package/src/eval/README.md +44 -1
  96. package/src/eval/kl.ts +263 -23
  97. package/src/eval/reference-logits.ts +207 -0
  98. package/src/eval/runner.ts +10 -79
  99. package/src/eval/tasks/bfcl.ts +2 -1
  100. package/src/eval/tasks/gsm8k.ts +2 -1
  101. package/src/eval/tasks/hashhop.ts +2 -1
  102. package/src/eval/tasks/humaneval.ts +5 -4
  103. package/src/eval/tasks/ifeval.ts +2 -1
  104. package/src/eval/tasks/mmlu.ts +2 -1
  105. package/src/expert-io.ts +3 -1
  106. package/src/expert-offload.ts +3 -1
  107. package/src/fill/echo-index.ts +188 -0
  108. package/src/fill/fill-session.ts +452 -0
  109. package/src/fill/proposal.ts +68 -0
  110. package/src/fill/schema-rows.ts +506 -0
  111. package/src/fill/tool-boundary.ts +103 -0
  112. package/src/fit.ts +24 -43
  113. package/src/generate.ts +740 -337
  114. package/src/grammar.ts +14 -8
  115. package/src/harness-pi.ts +15 -34
  116. package/src/hub-rest.ts +4 -4
  117. package/src/index.ts +27 -0
  118. package/src/inference/denoising.ts +17 -0
  119. package/src/inference/graph.ts +33 -0
  120. package/src/inference/prefill.ts +39 -0
  121. package/src/inference/rollback.ts +7 -0
  122. package/src/jobs/db.ts +4 -0
  123. package/src/jobs/index.ts +2 -0
  124. package/src/jobs/job-entry.ts +15 -14
  125. package/src/jobs/runner.ts +98 -13
  126. package/src/jobs/task-client.ts +19 -0
  127. package/src/jobs/types.ts +2 -58
  128. package/src/kv-scheme.ts +199 -0
  129. package/src/kv-store.ts +459 -234
  130. package/src/lab/README.md +18 -0
  131. package/src/{assets → lab/curve}/curve-designer.html +2 -2
  132. package/src/{curve-sampler.ts → lab/curve/curve-sampler.ts} +2 -2
  133. package/src/{expert-trace.ts → lab/expert-trace/expert-trace.ts} +7 -5
  134. package/src/{model → lab/paged-kv}/paged-kv.ts +9 -6
  135. package/src/library.ts +40 -0
  136. package/src/lora.ts +18 -0
  137. package/src/media-fetch.ts +20 -4
  138. package/src/memory/article.ts +1 -1
  139. package/src/memory/chunk.ts +1 -1
  140. package/src/memory/model.ts +32 -170
  141. package/src/memory/prompts.ts +1 -1
  142. package/src/memory/rest.ts +1 -1
  143. package/src/memory/schedule.ts +1 -1
  144. package/src/memory/stages.ts +4 -1
  145. package/src/memory/vault.ts +3 -4
  146. package/src/mlx/array.ts +9 -10
  147. package/src/mlx/compile.ts +12 -2
  148. package/src/mlx/ffi.ts +66 -22
  149. package/src/mlx/materialize.ts +23 -0
  150. package/src/mlx/metal-kernel.ts +10 -11
  151. package/src/mlx/ops.ts +64 -19
  152. package/src/mlx/quantized-rows.ts +36 -0
  153. package/src/mlx/token-bitmask.ts +52 -0
  154. package/src/mlx/turboquant-kv-decode.ts +82 -0
  155. package/src/mlx/turboquant-kv-inverse.ts +122 -0
  156. package/src/mlx/turboquant-ops.ts +33 -3
  157. package/src/mlx/turboquant-tables.ts +2 -2
  158. package/src/model/batched-mask.ts +9 -3
  159. package/src/model/batched-quant.ts +2 -2
  160. package/src/model/batched-rotating-quant.ts +45 -99
  161. package/src/model/batched-rotating-state.ts +73 -0
  162. package/src/model/batched-rotating.ts +57 -106
  163. package/src/model/batched-row-storage.ts +127 -0
  164. package/src/model/compiled-decode.ts +4 -3
  165. package/src/model/diffusion-gemma.ts +8 -8
  166. package/src/model/factory.ts +87 -49
  167. package/src/model/fingerprint.ts +1 -1
  168. package/src/model/flash-attention.ts +3 -3
  169. package/src/model/gemma4-base.ts +187 -17
  170. package/src/model/gemma4.ts +6 -6
  171. package/src/model/generated/gemma4-12b.ts +52 -148
  172. package/src/model/generated/gemma4-26b.ts +34 -94
  173. package/src/model/generated/gemma4-e4b.ts +28 -76
  174. package/src/model/glm52-cache.ts +19 -9
  175. package/src/model/glm52-dsa.ts +13 -9
  176. package/src/model/glm52-memory.ts +28 -1
  177. package/src/model/implementation.ts +69 -0
  178. package/src/model/minicpm5.ts +7 -8
  179. package/src/model/profile.ts +443 -0
  180. package/src/model/qwen-append.ts +12 -0
  181. package/src/model/qwen-conv.ts +52 -0
  182. package/src/model/qwen3-delta.ts +98 -2
  183. package/src/model/qwen3-mrope.ts +252 -0
  184. package/src/model/qwen3_5-checkpoint.ts +164 -0
  185. package/src/model/qwen3_5.ts +336 -60
  186. package/src/model/support.ts +1 -1
  187. package/src/model/trellis-balanced-scatter.ts +51 -0
  188. package/src/model/trellis-linear.ts +672 -0
  189. package/src/model/trellis-shared-m.ts +151 -0
  190. package/src/model/trellis-shared-scatter.ts +144 -0
  191. package/src/model/trellis-splitk-prefill.ts +158 -0
  192. package/src/model/trellis-tiled-prefill.ts +136 -0
  193. package/src/model/trellis-vector-expand.ts +62 -0
  194. package/src/model/trellis-wide-prefill.ts +94 -0
  195. package/src/model/universal/dense.ts +1 -1
  196. package/src/model/universal/modules.ts +8 -1
  197. package/src/native/frame_extract.swift +140 -0
  198. package/src/native-pack.ts +15 -7
  199. package/src/pi-provider.ts +6 -64
  200. package/src/pi-web.ts +14 -320
  201. package/src/prompt-cache.ts +97 -54
  202. package/src/quantize/allocator.ts +1 -1
  203. package/src/quantize/atomic-output.ts +38 -0
  204. package/src/quantize/calibration.ts +2 -1
  205. package/src/quantize/config-writer.ts +16 -3
  206. package/src/quantize/index.ts +28 -1
  207. package/src/quantize/job.ts +11 -0
  208. package/src/quantize/quantizer.ts +265 -100
  209. package/src/quantize/rotate.ts +603 -0
  210. package/src/quantize/safetensors-writer.ts +79 -1
  211. package/src/quantize/sensitivity.ts +41 -59
  212. package/src/quantize/trellis.ts +586 -0
  213. package/src/quantize/weight-transform.ts +292 -0
  214. package/src/responses.ts +130 -3
  215. package/src/runtime-config.ts +81 -0
  216. package/src/sampler.ts +276 -4
  217. package/src/serve/admin-routes.ts +184 -0
  218. package/src/serve/aux-routes.ts +132 -0
  219. package/src/serve/batch-scheduler.ts +8 -1558
  220. package/src/serve/chat-request.ts +391 -0
  221. package/src/serve/chat-stage.ts +195 -0
  222. package/src/serve/checkpoint-identity.ts +64 -0
  223. package/src/serve/completion-executor.ts +354 -0
  224. package/src/serve/completion-sink.ts +157 -0
  225. package/src/serve/discovery-routes.ts +204 -0
  226. package/src/serve/generation-gateway.ts +211 -243
  227. package/src/serve/http.ts +159 -0
  228. package/src/serve/inference-request.ts +79 -0
  229. package/src/serve/isolate.ts +297 -105
  230. package/src/serve/kv-budget.ts +14 -0
  231. package/src/serve/lab-routes.ts +341 -0
  232. package/src/serve/lane-registry.ts +2 -1
  233. package/src/serve/model-admin-routes.ts +155 -0
  234. package/src/serve/model-binding.ts +43 -0
  235. package/src/serve/model-host.ts +512 -0
  236. package/src/serve/openai-wire.ts +103 -0
  237. package/src/serve/parent-application.ts +56 -0
  238. package/src/serve/pipeline.ts +33 -0
  239. package/src/serve/preparation.ts +33 -0
  240. package/src/serve/prompt-response-trace.ts +147 -0
  241. package/src/serve/request-plan.ts +162 -0
  242. package/src/serve/request-prep.ts +346 -0
  243. package/src/serve/response-lease.ts +24 -0
  244. package/src/serve/responses-client.ts +69 -0
  245. package/src/serve/session-completion-engine.ts +110 -0
  246. package/src/serve/session-search.ts +1 -1
  247. package/src/serve/static-routes.ts +101 -0
  248. package/src/serve/text-completion-stage.ts +104 -0
  249. package/src/serve/token-streams.ts +519 -0
  250. package/src/server.ts +523 -3551
  251. package/src/spec/assistant-source.ts +10 -44
  252. package/src/spec/deepspec-source.ts +6 -6
  253. package/src/spec/dflash-source.ts +9 -8
  254. package/src/spec/dspark/ab-stats.ts +3 -3
  255. package/src/spec/dspark/calibration.ts +2 -2
  256. package/src/spec/dspark/deepspec-module.ts +2 -2
  257. package/src/spec/dspark/generate-dflash.ts +2 -2
  258. package/src/spec/dspark/generate.ts +2 -2
  259. package/src/spec/dspark/loader.ts +1 -1
  260. package/src/spec/dspark/module-dflash.ts +5 -4
  261. package/src/spec/dspark/module.ts +3 -3
  262. package/src/spec/dspark/quantize-drafter.ts +1 -1
  263. package/src/spec/generate.ts +1 -1
  264. package/src/spec/glm52-mtp-source.ts +9 -1
  265. package/src/spec/ngram-source.ts +1 -1
  266. package/src/spec/prefix-state.ts +45 -0
  267. package/src/spec/qwen-mtp-source.ts +528 -0
  268. package/src/spec/serve-loop.ts +179 -213
  269. package/src/spec/source.ts +42 -10
  270. package/src/spec/two-model.ts +1 -1
  271. package/src/ssd-cache.ts +95 -24
  272. package/src/ssd-durability.ts +207 -0
  273. package/src/tool-call.ts +51 -14
  274. package/src/train/flash-cce.ts +14 -13
  275. package/src/train/forward.ts +38 -1
  276. package/src/train/loss.ts +3 -2
  277. package/src/train/prefix-shared.ts +5 -1
  278. package/src/train/segmented.ts +9 -6
  279. package/src/train/trainer.ts +9 -8
  280. package/src/vision/preprocess.ts +1 -1
  281. package/src/vision/prompt.ts +48 -2
  282. package/src/vision/qwen3vl-preprocess.ts +409 -0
  283. package/src/vision/qwen3vl-prompt.ts +196 -0
  284. package/src/vision/qwen3vl-tower.ts +555 -0
  285. package/src/vision/video-frames.ts +148 -0
  286. package/src/web/app.html +1 -1
  287. package/src/web/app.js +15 -6
  288. package/src/web/src/adapters-panel.ts +3 -3
  289. package/src/web/src/assistant.ts +2 -36
  290. package/src/web/src/chat.ts +16 -9
  291. package/src/web/src/composer.ts +4 -4
  292. package/src/web/src/hub.ts +1 -1
  293. package/src/web/src/markdown.ts +0 -0
  294. package/src/web/src/memory-panel.ts +1 -1
  295. package/src/web/src/model-picker.ts +1 -1
  296. package/src/web/src/palette.ts +1 -1
  297. package/src/web/src/protocol.ts +3 -39
  298. package/src/web/src/sessions.ts +1 -1
  299. package/src/web/src/ui-catalog.ts +4 -18
  300. package/src/web-assets.ts +47 -0
  301. package/src/weights.ts +134 -33
  302. package/docs/archive/mlx-bun-lab-report.html +0 -275
  303. package/docs/archive/wwdc-mlx-bun.html +0 -848
  304. package/docs/design/adapters-end-to-end.md +0 -205
  305. package/docs/design/batching-perf-path.md +0 -255
  306. package/docs/design/batching-v2-plan.md +0 -529
  307. package/docs/design/bucketing-stage.md +0 -197
  308. package/docs/design/compat-cli-surface-design.md +0 -230
  309. package/docs/design/docs-reorg-plan.md +0 -165
  310. package/docs/design/dspark-serving-program.md +0 -371
  311. package/docs/design/dspark-speculative-decoding.md +0 -268
  312. package/docs/design/grammar-spec-batching-integration.md +0 -301
  313. package/docs/design/memory-inference-path.md +0 -422
  314. package/docs/design/memory-synthesis.md +0 -287
  315. package/docs/design/memory-system.md +0 -237
  316. package/docs/design/mlx-lm-tool-parity-plan.md +0 -545
  317. package/docs/design/omlx-adoption-map.md +0 -98
  318. package/docs/design/orpo-dynamic-lambda.md +0 -220
  319. package/docs/design/orpo-future-enhancements.md +0 -340
  320. package/docs/design/paged-kv-cache.md +0 -214
  321. package/docs/design/parallel-slots.md +0 -567
  322. package/docs/design/parity-tier-dag.md +0 -161
  323. package/docs/design/repo-cleanup-plan.md +0 -189
  324. package/docs/design/runtime-isolation.md +0 -263
  325. package/docs/design/segmented-backward-training.md +0 -543
  326. package/docs/design/spec-decode-larger-targets.md +0 -229
  327. package/docs/design/ssd-kv-cold-tier.md +0 -288
  328. package/docs/design/structured-output.md +0 -484
  329. package/docs/design/tauri-desktop-app.md +0 -127
  330. package/docs/design/the-dreaming-master-plan.md +0 -445
  331. package/docs/design/turboquant-kv.md +0 -235
  332. package/docs/design/w4a16-compute-precision-spike.md +0 -232
  333. package/docs/design/web-chat-beat-matrix.md +0 -315
  334. package/docs/design/write-pipeline-entity.md +0 -468
  335. package/docs/investigations/curve-runs/bisector-route.json +0 -98
  336. package/docs/investigations/curve-runs/bisector-trace.json +0 -1393
  337. package/docs/investigations/curve-runs/curve-terrain.json +0 -1179
  338. package/docs/investigations/curve-runs/curve-terrain.svg +0 -42
  339. package/docs/investigations/hlg-ab.html +0 -34
  340. package/docs/investigations/hlg-explorer.html +0 -238
  341. package/docs/investigations/hlg-figs/cliff.svg +0 -1
  342. package/docs/investigations/hlg-figs/cross.svg +0 -1
  343. package/docs/investigations/hlg-figs/edges.svg +0 -1
  344. package/docs/investigations/hlg-figs/k20-grids.svg +0 -1
  345. package/docs/investigations/hlg-figs/k20-peaks.svg +0 -1
  346. package/docs/investigations/hlg-figs/lw-curve.svg +0 -1
  347. package/docs/investigations/hlg-figs/terrain.svg +0 -1
  348. package/docs/investigations/hlg-report.html +0 -262
  349. package/docs/investigations/hlg-runs/hlg-ab.json +0 -149
  350. package/docs/investigations/hlg-runs/hlg-blind-prompts.md +0 -87
  351. package/docs/investigations/hlg-runs/hlg-blind.json +0 -107
  352. package/docs/investigations/hlg-runs/hlg-cliff.json +0 -197
  353. package/docs/investigations/hlg-runs/hlg-cross.json +0 -505
  354. package/docs/investigations/hlg-runs/hlg-edge.json +0 -388
  355. package/docs/investigations/hlg-runs/hlg-eval1.json +0 -25
  356. package/docs/investigations/hlg-runs/hlg-frontier.json +0 -86
  357. package/docs/investigations/hlg-runs/hlg-grid.json +0 -242
  358. package/docs/investigations/hlg-runs/hlg-grid2.json +0 -343
  359. package/docs/investigations/hlg-runs/hlg-lw-coarse.json +0 -110
  360. package/docs/investigations/hlg-runs/hlg-map.json +0 -383
  361. package/docs/investigations/hlg-runs/run0-original.log +0 -159
  362. package/docs/investigations/hlg-runs/runA-recommended.log +0 -131
  363. package/docs/investigations/hlg-runs/runAA-cliff-cpm5.log +0 -26
  364. package/docs/investigations/hlg-runs/runAB-grid2-e4b.log +0 -18
  365. package/docs/investigations/hlg-runs/runAC-peak-e4b-partial.log +0 -25
  366. package/docs/investigations/hlg-runs/runAD-ab-samples.log +0 -8
  367. package/docs/investigations/hlg-runs/runAE-edge-e4b.log +0 -51
  368. package/docs/investigations/hlg-runs/runAF-logit-calibration.log +0 -10
  369. package/docs/investigations/hlg-runs/runAG-eval-config2.log +0 -16
  370. package/docs/investigations/hlg-runs/runB-gain-variants.log +0 -192
  371. package/docs/investigations/hlg-runs/runC-pivots.log +0 -162
  372. package/docs/investigations/hlg-runs/runD-oetf.log +0 -132
  373. package/docs/investigations/hlg-runs/runE-eotf.log +0 -162
  374. package/docs/investigations/hlg-runs/runF-shaper-working.log +0 -86
  375. package/docs/investigations/hlg-runs/runG-shaper-sweep.log +0 -101
  376. package/docs/investigations/hlg-runs/runH-wsweep.log +0 -176
  377. package/docs/investigations/hlg-runs/runI-asweep.log +0 -176
  378. package/docs/investigations/hlg-runs/runJ-knobmap.log +0 -49
  379. package/docs/investigations/hlg-runs/runK-frontier.log +0 -19
  380. package/docs/investigations/hlg-runs/runL-widemap.log +0 -65
  381. package/docs/investigations/hlg-runs/runM-frontier-emb.log +0 -15
  382. package/docs/investigations/hlg-runs/runN-verify.log +0 -116
  383. package/docs/investigations/hlg-runs/runO-frontier-seed5000.log +0 -15
  384. package/docs/investigations/hlg-runs/runP-grid-AxSm.log +0 -15
  385. package/docs/investigations/hlg-runs/runQ-grid-tgW.log +0 -15
  386. package/docs/investigations/hlg-runs/runR-blind-transcript.log +0 -79
  387. package/docs/investigations/hlg-runs/runS-cross-coherence.log +0 -33
  388. package/docs/investigations/hlg-runs/runT-cross-cpm5.log +0 -33
  389. package/docs/investigations/hlg-runs/runU-cross-12b.log +0 -33
  390. package/docs/investigations/hlg-runs/runV-lw-e4b.log +0 -22
  391. package/docs/investigations/hlg-runs/runW-lw-12b.log +0 -22
  392. package/docs/investigations/hlg-runs/runX-lw-cpm5.log +0 -22
  393. package/docs/investigations/hlg-runs/runY-cliff-e4b.log +0 -26
  394. package/docs/investigations/hlg-runs/runZ-cliff-12b.log +0 -26
  395. package/docs/planning/IDEAS.md +0 -69
  396. package/docs/reference/embedding.md +0 -197
  397. package/docs/reference/features-matrix.md +0 -100
  398. package/docs/reference/orpo-quickstart.md +0 -135
  399. package/src/flags.ts +0 -13
  400. /package/docs/{investigations → archive/investigations}/chunk-finetune-experiment.md +0 -0
  401. /package/docs/{investigations → archive/investigations}/colibri-oracle-pin.md +0 -0
  402. /package/docs/{investigations → archive/investigations}/curve-bisector-routing.md +0 -0
  403. /package/docs/{investigations → archive/investigations}/decode-roofline-lookagain.md +0 -0
  404. /package/docs/{investigations → archive/investigations}/dspark-handoff.md +0 -0
  405. /package/docs/{investigations → archive/investigations}/expert-offload-single-user-moe.md +0 -0
  406. /package/docs/{investigations → archive/investigations}/indexshare-performance-spike.md +0 -0
  407. /package/docs/{investigations → archive/investigations}/kernel-perf-review-2026-07.md +0 -0
  408. /package/docs/{investigations → archive/investigations}/lab-build-journal.md +0 -0
  409. /package/docs/{investigations → archive/investigations}/openwiki-evaluation.md +0 -0
  410. /package/docs/{investigations → archive/investigations}/orpo-base-uf-experiment-and-directions.md +0 -0
  411. /package/docs/{investigations → archive/investigations}/orpo-flash-cce-pin-leak.md +0 -0
  412. /package/docs/{investigations → archive/investigations}/orpo-uf-testing-handoff.md +0 -0
  413. /package/docs/{investigations → archive/investigations}/pi-builtin-investigation.md +0 -0
  414. /package/docs/{investigations → archive/investigations}/segmented-backward-handoff.md +0 -0
  415. /package/docs/{investigations → archive/investigations}/starter-model-port-handoff.md +0 -0
  416. /package/docs/{investigations → archive/investigations}/steel-flash-cce-handoff.md +0 -0
  417. /package/docs/{investigations → archive/investigations}/trainer-validation-experiment.md +0 -0
  418. /package/docs/{planning → archive/planning}/chat-ui-vision-memory-first.md +0 -0
  419. /package/docs/{planning → archive/planning}/journal.md +0 -0
  420. /package/docs/{planning → archive/planning}/pi-integration-review.md +0 -0
  421. /package/docs/{planning → archive/planning}/release-notes-v0.0.10.md +0 -0
  422. /package/docs/{planning → archive/planning}/release-notes-v0.0.11.md +0 -0
  423. /package/docs/{planning → archive/planning}/release-notes-v0.0.12.md +0 -0
  424. /package/docs/{planning → archive/planning}/release-notes-v0.0.13.md +0 -0
  425. /package/docs/{planning → archive/planning}/web-ui-pass-plan.md +0 -0
  426. /package/{docs/investigations → src/serve/assets}/curve-terrain.html +0 -0
  427. /package/{docs/dag → src/serve/assets}/training-inference-map.html +0 -0
@@ -0,0 +1,39 @@
1
+ import type { DenoisingGraph } from "../../inference/denoising";
2
+ import type { MlxArray } from "../../mlx/array";
3
+ import type { Cache } from "../../model/gemma4-base";
4
+ import type { DiffusionGemmaModel } from "../../model/diffusion-gemma";
5
+ import type { MlxModelMemory } from "./autoregressive";
6
+ import { disposeResources } from "../../engine/resources";
7
+ import { runtimeConfig, type RuntimeConfig } from "../../runtime-config";
8
+
9
+ export interface MlxDenoisingBinding<State = Cache[]> {
10
+ readonly runtime?: RuntimeConfig;
11
+ readonly graph: DenoisingGraph<MlxArray, State>;
12
+ readonly memory: MlxModelMemory;
13
+ readonly adapters?: { active: string[] };
14
+ }
15
+
16
+ export function bindLegacyDenoisingModel(model: DiffusionGemmaModel): MlxDenoisingBinding {
17
+ return {
18
+ runtime: runtimeConfig(),
19
+ memory: model, adapters: model.loraState,
20
+ graph: {
21
+ descriptor: Object.freeze({ id: "legacy-diffusion-gemma", backend: "mlx",
22
+ graphAbi: "mlx-denoising-v1", stateAbi: "legacy-cache-array-v1", artifact: "legacy-resident-model" }),
23
+ vocabSize: model.config.text.vocabSize, canvasLength: model.canvasLength, embedScale: model.embedScale,
24
+ prefill: (ids, vision) => vision ? model.prefillVision(ids, vision) : model.prefill(ids),
25
+ extendPrefill: model.extendPrefill.bind(model),
26
+ decoderLogits: model.decoderLogits.bind(model),
27
+ dequantEmbedWeight: model.dequantEmbedWeight.bind(model),
28
+ softEmbeddings: model.softEmbeddings.bind(model),
29
+ closeState: disposeResources,
30
+ },
31
+ };
32
+ }
33
+
34
+ export function assertMlxDenoisingGraph<State>(graph: DenoisingGraph<MlxArray, State>): void {
35
+ const descriptor = graph.descriptor;
36
+ if (descriptor.backend !== "mlx" || descriptor.graphAbi !== "mlx-denoising-v1" ||
37
+ !descriptor.stateAbi)
38
+ throw new Error(`denoising graph ${descriptor.id} has an incompatible backend, graph, or state ABI`);
39
+ }
@@ -0,0 +1,47 @@
1
+ import type { RuntimeModel } from "../../model/factory";
2
+ import { Gemma4Model, type Cache } from "../../model/gemma4";
3
+ import { Qwen35Model } from "../../model/qwen3_5";
4
+ import type { MlxArray } from "../../mlx/array";
5
+ import * as ops from "../../mlx/ops";
6
+ import type { TargetView, AssistantTarget } from "../../spec/source";
7
+
8
+ /** Class checks and live cache layout stay at the compatibility boundary.
9
+ * A replacement graph supplies the same ports without becoming these classes. */
10
+ export function bindLegacyDraftTarget(model: RuntimeModel, caches: Cache[]): TargetView {
11
+ if (model instanceof Gemma4Model) {
12
+ let sliding = -1, full = -1;
13
+ for (let i = 0; i < model.numDonors; i++) {
14
+ if (model.layers[i]!.layerType === "sliding_attention") sliding = i;
15
+ else full = i;
16
+ }
17
+ const view = (index: number): [MlxArray, MlxArray] => {
18
+ const cache = caches[index];
19
+ if (!cache || !("temporalView" in cache) || typeof cache.temporalView !== "function")
20
+ throw new Error("assistant target has no plain donor view");
21
+ return cache.temporalView();
22
+ };
23
+ const assistant: AssistantTarget | undefined = sliding >= 0 && full >= 0 ? {
24
+ position: () => caches[0]!.offset - 1,
25
+ embedScaled(token) {
26
+ const ids = ops.fromInt32([token], [1, 1]);
27
+ let embedded: MlxArray | undefined;
28
+ try { embedded = model.embed.encode(ids); return ops.mulScalar(embedded, model.embedScale); }
29
+ finally { ids.dispose(); embedded?.dispose(); }
30
+ },
31
+ readDonors() {
32
+ const slidingView = view(sliding);
33
+ try { return { sliding: slidingView, full: view(full) }; }
34
+ catch (error) { for (const array of slidingView) array.dispose(); throw error; }
35
+ },
36
+ } : undefined;
37
+ return Object.freeze({ identity: model, assistant, gemmaTaps: Object.freeze({
38
+ layerCount: model.layers.length,
39
+ projection: Object.freeze({ embed: model.embed, logitsFromHidden: model.logitsFromHidden.bind(model) }),
40
+ }) });
41
+ }
42
+ if (model instanceof Qwen35Model) return Object.freeze({ identity: model, qwenMtp: Object.freeze({
43
+ hiddenSize: model.config.text.hiddenSize, layerCount: model.config.text.numHiddenLayers,
44
+ embed: model.embed.encode.bind(model.embed), logitsFromHidden: model.logitsFromHidden.bind(model),
45
+ }) });
46
+ return Object.freeze({ identity: model });
47
+ }
@@ -0,0 +1,105 @@
1
+ import type { TaskModel, GenOpts } from "../../eval/runner";
2
+ import type { CompletionClient } from "../../contracts/completion";
3
+ import { generate } from "../../generate";
4
+ import { clearCache } from "../../mlx/ffi";
5
+ import * as ops from "../../mlx/ops";
6
+ import { runtimeValue } from "../../runtime-config";
7
+ import { DiffusionGemmaModel } from "../../model/diffusion-gemma";
8
+
9
+ /** Bit-exact greedy decode: raw `model.forward` + argmax loop, which matches mlx-lm
10
+ * token-for-token (gen8k: 8000/8000). The product `generate()` decode wrapper
11
+ * (forwardHidden/logitsFromHidden + pipelined sampler) diverges from the raw forward
12
+ * on near-ties past ~32 tokens, so the EVAL decodes bit-exactly itself rather than
13
+ * alter the serving path. Greedy + full-precision KV only. */
14
+ export function greedyDecodeBitExact(tm: TaskModel, ids: number[], maxTokens: number): string {
15
+ if (tm.activeAdapters) tm.model.loraState.active = tm.activeAdapters; // apply the mounted adapter
16
+ const cache = tm.model.makeCache();
17
+ try {
18
+ // Chunked, LM-head-free prefill. The old single-shot `forward(prompt)`
19
+ // materialized [1, L, vocab] logits just to discard them — at a 248k
20
+ // vocab that's ~1 GB per 1k prompt tokens, and with no allocator-cache
21
+ // clears the Metal buffer cache retained every giant freed tensor
22
+ // (the 27B capability-suite 73 GB swap-thrash). Prefill only needs the
23
+ // cache state, which forwardHidden advances identically (the LM head
24
+ // never touches the cache) — bit-exact by construction. Chunk size and
25
+ // clear cadence mirror mlx-lm generate_step / our generate().
26
+ const PREFILL_CHUNK = 2048;
27
+ const prompt = ids.slice(0, -1);
28
+ for (let s = 0; s < prompt.length; s += PREFILL_CHUNK) {
29
+ const chunk = prompt.slice(s, s + PREFILL_CHUNK);
30
+ const cIds = ops.fromInt32(chunk, [1, chunk.length]);
31
+ tm.model.forwardHidden(cIds, cache).dispose();
32
+ cIds.dispose();
33
+ clearCache();
34
+ }
35
+ let last = ids[ids.length - 1]!;
36
+ const eos = new Set(tm.config.eosTokenIds);
37
+ const out: number[] = [];
38
+ for (let i = 0; i < maxTokens; i++) {
39
+ const lg = tm.model.forward([last], cache);
40
+ const am = ops.argmaxAxis(lg, -1); // GPU argmax (mlx-lm greedy: lowest-index tie-break)
41
+ lg.dispose();
42
+ const best = ops.itemUint32(am);
43
+ am.dispose();
44
+ if (i % 256 === 0) clearCache(); // mlx-lm decode cadence
45
+ if (eos.has(best)) break; // mlx-lm halts on EOS and excludes it from the output
46
+ out.push(best);
47
+ last = best;
48
+ }
49
+ return tm.tokenizer.decode(out, true);
50
+ } finally {
51
+ for (const c of cache) c.dispose();
52
+ }
53
+ }
54
+
55
+ /** Complete `body` via the model's real quantized-KV path. Greedy by default;
56
+ * pass `opts.sampler` to drive temperature/HLG/etc. */
57
+ async function generateTextNative(tm: TaskModel, body: string, opts: GenOpts = {}): Promise<string> {
58
+ const maxTokens = opts.maxTokens ?? 256;
59
+ const enableThinking = opts.enableThinking ?? runtimeValue("MLX_BUN_EVAL_THINK") === "1";
60
+ const templated = opts.useChat !== false && tm.template !== null;
61
+ const text = templated
62
+ ? tm.template!.render([{ role: "user", content: body }], { addGenerationPrompt: true, enableThinking })
63
+ : body;
64
+ // The chat template ALREADY emits the BOS; encoding it with add_special_tokens on
65
+ // would prepend a SECOND BOS (<s><s>…), corrupting generation. Raw bodies (no
66
+ // template) get the BOS added as normal.
67
+ const ids = tm.tokenizer.encode(text, /* addSpecialTokens */ !templated);
68
+
69
+ // optiq's PUBLISHED eval generates with a FULL-PRECISION KV cache — its eval calls
70
+ // plain mlx_lm.generate (kv-quant lives in the serving runtime, not the eval). So
71
+ // for like-for-like parity we default the eval to UNQUANTIZED KV; MMLU's argmax
72
+ // path was already unquantized, which is why it matched while generation didn't.
73
+ // MLX_BUN_EVAL_KV_QUANT=1 generates through the model's quantized KV (serving
74
+ // config); an explicit opts.kvScheme (cli generate's resolved tier route) wins.
75
+ const envKv: GenOpts["kvScheme"] | undefined =
76
+ (runtimeValue("MLX_BUN_EVAL_KV_QUANT") === "1" && tm.config.kvQuant?.length)
77
+ ? { kvConfig: tm.config.kvQuant, quantizedKvStart: 0 } : undefined;
78
+ const kvScheme = opts.kvScheme ?? envKv;
79
+ const kvActive = !!(kvScheme && (kvScheme.kvBits || kvScheme.kvConfig?.length || kvScheme.turboQuant));
80
+
81
+ // Parity default — greedy + full-precision KV + no sampler arm: decode bit-exactly
82
+ // via the raw forward (matches mlx-lm token-for-token). Sampler arms / kv-quant fall
83
+ // through to the product generate(). DiffusionGemma is non-autoregressive (no AR
84
+ // forward) — always go through generate(), which routes it to the denoising engine.
85
+ const isDiffusion = tm.model instanceof DiffusionGemmaModel;
86
+ if (!isDiffusion && !kvActive && !opts.sampler && !tm.samplerOverride) {
87
+ return greedyDecodeBitExact(tm, ids, maxTokens);
88
+ }
89
+ const gen = generate(tm.model, ids, {
90
+ maxTokens,
91
+ temperature: 0, // greedy default — deterministic head-to-head arms
92
+ adapters: tm.activeAdapters ?? [], // per-request activation of the mounted eval-adapter
93
+ ...(kvActive ? kvScheme : {}),
94
+ ...(opts.sampler ?? {}), // overrides temperature when provided
95
+ ...(tm.samplerOverride ?? {}), // arm override wins over the task's own sampler
96
+ });
97
+
98
+ const out: number[] = [];
99
+ for await (const { token } of gen) out.push(token);
100
+ return tm.tokenizer.decode(out, true);
101
+ }
102
+
103
+ export function createEvalCompletionClient(tm: TaskModel): CompletionClient<{ body: string; options?: GenOpts }, string> {
104
+ return { complete: ({ body, options }) => generateTextNative(tm, body, options) };
105
+ }
@@ -0,0 +1,45 @@
1
+ import type { MlxArray } from "../../mlx/array";
2
+ import * as ops from "../../mlx/ops";
3
+ import type { Cache } from "../../model/gemma4";
4
+ import { withResource } from "../../engine/resources";
5
+ import { leaseCacheStates } from "./state-views";
6
+
7
+ /** Append an already accepted span without intermediate vocabulary heads.
8
+ * Chunking changes the graph shape, so callers must qualify its numerics for
9
+ * their model. A verify round must use chunkSize=0: recurrent rollback records
10
+ * one forward per round. The complete append precedes emission/cancellation
11
+ * boundaries, preserving the cache-covered prefix on a consumer break.
12
+ */
13
+ export async function appendFillHidden(
14
+ forward: (ids: MlxArray, cache: Cache[]) => MlxArray | Promise<MlxArray>,
15
+ cache: Cache[],
16
+ ids: number[],
17
+ chunkSize: (cache: readonly Cache[]) => number,
18
+ ): Promise<MlxArray> {
19
+ const input = ops.fromInt32(ids, [1, ids.length]);
20
+ const hidden: MlxArray[] = [];
21
+ try {
22
+ for (let start = 0; start < ids.length;) {
23
+ const limit = chunkSize(cache);
24
+ if (!Number.isSafeInteger(limit) || limit < 1)
25
+ throw new Error("Model append chunk limit must be a positive integer");
26
+ const end = Math.min(ids.length, start + limit);
27
+ if (start === 0 && end === ids.length) return await forward(input, cache);
28
+ const part = input.slice([0, start], [1, end]);
29
+ try {
30
+ const h = await forward(part, cache);
31
+ hidden.push(h);
32
+ // Bound transient graphs while retaining all positions for an optional
33
+ // diagnostic trace. Normal generation projects only the last position.
34
+ withResource(leaseCacheStates(cache), (state) => ops.evalAll([h, ...state]));
35
+ } finally {
36
+ part.dispose();
37
+ }
38
+ start = end;
39
+ }
40
+ return ops.concatAxis(hidden, 1);
41
+ } finally {
42
+ for (const h of hidden) h.dispose();
43
+ input.dispose();
44
+ }
45
+ }
@@ -0,0 +1,71 @@
1
+ import type { ModelConfig } from "../../config";
2
+ import type { KvScheme } from "../../kv-scheme";
3
+ import type { RuntimeModel } from "../../model/factory";
4
+ import { DiffusionGemmaModel } from "../../model/diffusion-gemma";
5
+ import { UniversalDenseModel } from "../../model/universal/dense";
6
+ import { KVCache, RotatingKVCache, isBatchableCache, isPlainKvCache, isRotatingPlainCache } from "../../model/gemma4-base";
7
+ import { SSMCache } from "../../model/qwen3-delta";
8
+ import { Qwen35Model } from "../../model/qwen3_5";
9
+ import { runtimeConfig, type RuntimeConfig } from "../../runtime-config";
10
+ import { disposeResources } from "../../engine/resources";
11
+ import { legacyCompiledDecodeAvailable } from "./autoregressive";
12
+ import { MlxBatchExecutionGroup, type MlxBatchExecutionGroupOptions } from "./batch-group";
13
+ import type { GenerateOptions } from "../../generate";
14
+ import type { ExecutionRequirements, ResolvedExecution } from "../../contracts/execution";
15
+ import { resolveExecution } from "../../engine/execution-plan";
16
+
17
+ export interface MlxBatchGroup extends Pick<MlxBatchExecutionGroup,
18
+ "activeRows" | "pendingRows" | "projectedKvBytes" | "kvBudgetBytes" | "submit" | "kick" | "close"> {}
19
+
20
+ /** A model implementation owns capability checks and the execution group.
21
+ * Scheduling never inspects concrete model/cache classes. */
22
+ export interface MlxGatewayBinding {
23
+ readonly config: ModelConfig;
24
+ readonly runtime: RuntimeConfig;
25
+ plan(request: ExecutionRequirements, options: GenerateOptions,
26
+ scheduling: { continuous: boolean; quantizedBatch: boolean; checkpoints: boolean }): ResolvedExecution;
27
+ cachesBatchable(): boolean;
28
+ kvBatchable(scheme: KvScheme): boolean;
29
+ createBatchGroup(options: MlxBatchExecutionGroupOptions): MlxBatchGroup;
30
+ }
31
+
32
+ export function bindMlxGateway(model: RuntimeModel): MlxGatewayBinding {
33
+ const runtime = runtimeConfig();
34
+ return {
35
+ config: model.config, runtime,
36
+ plan(request, options, scheduling) {
37
+ return resolveExecution(request, {
38
+ ...scheduling,
39
+ method: model instanceof DiffusionGemmaModel ? "denoising" : "autoregressive",
40
+ compiledDecode: legacyCompiledDecodeAvailable(model),
41
+ grammarBatch: runtime.value("MLX_BUN_GRAMMAR_BATCH") !== "0",
42
+ speculativeKvQuant: model instanceof Qwen35Model && options.kvBits === 4 &&
43
+ !options.kvConfig?.length && options.quantizedKvStart === 0 &&
44
+ runtime.flag("MLX_BUN_QWEN_SPEC_KV4", false),
45
+ }, {
46
+ pagedKv: !!options.pagedKv, fill: !!options.fill,
47
+ compiledDecode: runtime.flag("MLX_BUN_COMPILED_DECODE", true),
48
+ grammarJump: runtime.flag("MLX_BUN_GRAMMAR_JUMP", false),
49
+ });
50
+ },
51
+ cachesBatchable() {
52
+ if (model instanceof UniversalDenseModel)
53
+ return !model.args.maskArray && !model.args.layerTypes?.includes("sliding_attention");
54
+ const caches = model.makeCache();
55
+ try {
56
+ const ssm = runtime.value("MLX_BUN_BATCH_SSM") !== "0";
57
+ return caches.every((cache) => cache instanceof KVCache || cache instanceof RotatingKVCache ||
58
+ isBatchableCache(cache) || (ssm && cache instanceof SSMCache));
59
+ } finally { disposeResources(caches); }
60
+ },
61
+ kvBatchable(scheme) {
62
+ if (scheme.kind !== "affine-config") return false;
63
+ const caches = model.makeCache();
64
+ try {
65
+ return scheme.batchable(model.config,
66
+ (layer) => isPlainKvCache(caches[layer]) || isRotatingPlainCache(caches[layer]));
67
+ } finally { disposeResources(caches); }
68
+ },
69
+ createBatchGroup: (options) => new MlxBatchExecutionGroup(model, options),
70
+ };
71
+ }
@@ -0,0 +1,38 @@
1
+ import type { AutoregressiveGraph, GraphDescriptor, LogitSelection } from "../../inference/graph";
2
+ import type { MlxArray } from "../../mlx/array";
3
+
4
+ /** Structural input: generated and hand-written graphs need no union membership. */
5
+ export interface MlxGraphOperations<State> {
6
+ forwardHidden(ids: MlxArray, state: State): MlxArray;
7
+ forwardHiddenAsync?: (ids: MlxArray, state: State) => Promise<MlxArray>;
8
+ logitsFromHidden(hidden: MlxArray): MlxArray;
9
+ }
10
+
11
+ /** Bind once. No array wrapper, await, readback, or synchronization is added
12
+ * to synchronous forward calls. Existing quant-specific kernels stay inside
13
+ * the supplied operations. This adapter does not take ownership of weights. */
14
+ export function bindMlxGraph<State>(
15
+ operations: MlxGraphOperations<State>,
16
+ descriptor: Omit<GraphDescriptor, "backend" | "graphAbi">,
17
+ ): AutoregressiveGraph<MlxArray, State, MlxArray> {
18
+ const forward = operations.forwardHiddenAsync
19
+ ? operations.forwardHiddenAsync.bind(operations)
20
+ : operations.forwardHidden.bind(operations);
21
+ const project = operations.logitsFromHidden.bind(operations);
22
+ return {
23
+ descriptor: Object.freeze({ ...descriptor, backend: "mlx", graphAbi: "mlx-hidden-bsh-v1" }),
24
+ forwardHidden: forward,
25
+ projectLogits(hidden: MlxArray, selection: LogitSelection) {
26
+ if (selection.type === "all") return project(hidden);
27
+ const shape = hidden.shape;
28
+ if (shape.length !== 3) throw new Error("MLX hidden state must have shape [batch, positions, width]");
29
+ const [batch, positions, width] = shape as [number, number, number];
30
+ const start = selection.type === "last" ? positions - 1 : selection.start;
31
+ const end = selection.type === "last" ? positions : selection.end;
32
+ if (!Number.isInteger(start) || !Number.isInteger(end) || start < 0 || end <= start || end > positions)
33
+ throw new Error("invalid logits position selection");
34
+ const selected = hidden.slice([0, start, 0], [batch, end, width]);
35
+ try { return project(selected); } finally { selected.dispose(); }
36
+ },
37
+ };
38
+ }
@@ -0,0 +1,189 @@
1
+ import { existsSync } from "node:fs";
2
+ import { loadTaskModel, greedyDecodeBitExact, type TaskModel } from "../../eval/runner";
3
+ import type { ChatTemplate } from "../../chat-template";
4
+ import { MlxBatchExecutionGroup as BatchScheduler, type RowSampler } from "./batch-group";
5
+ import * as ops from "../../mlx/ops";
6
+ import type { MlxArray } from "../../mlx/array";
7
+ import { adapterDirFor, memoryBatchSize, memoryPromptIds, MODEL_ID,
8
+ type LocalInput, type MemoryCompletionClient } from "../../memory/model";
9
+
10
+ class AsyncMutex {
11
+ #tail: Promise<void> = Promise.resolve();
12
+ acquire(): Promise<() => void> {
13
+ let release!: () => void;
14
+ const gate = new Promise<void>((r) => { release = r; });
15
+ const wait = this.#tail;
16
+ this.#tail = this.#tail.then(() => gate);
17
+ return wait.then(() => release);
18
+ }
19
+ }
20
+
21
+ interface MemoryRuntime {
22
+ tm: TaskModel;
23
+ template: ChatTemplate;
24
+ scheduler: BatchScheduler;
25
+ /** True once the `memory-chunk` adapter is mounted on the shared model. */
26
+ hasChunkAdapter: boolean;
27
+ }
28
+
29
+
30
+ function requireModel(): void {
31
+ if (!existsSync(`${MODEL_ID}/config.json`))
32
+ throw new Error(`memory: Gemma-4-e4b is not downloaded (looked under ${MODEL_ID}). Fetch it first: HF_HUB_DISABLE_XET=1 hf download mlx-community/gemma-4-e4b-it-OptiQ-4bit`);
33
+ }
34
+
35
+ let runtimePromise: Promise<MemoryRuntime> | null = null;
36
+ const opLock = new AsyncMutex(); // serializes whole memory ops (loraState ownership)
37
+ const schedLock = new AsyncMutex(); // the scheduler's exclusive-GPU lock
38
+
39
+ /** Lazily load the ONE shared model + mount memory-chunk + build the scheduler.
40
+ * Never auto-downloads; throws (with the fetch hint) until the snapshot lands. */
41
+ async function getRuntime(): Promise<MemoryRuntime> {
42
+ if (!runtimePromise) {
43
+ runtimePromise = (async () => {
44
+ requireModel();
45
+ const tm = await loadTaskModel(MODEL_ID); // base e4b, no adapter
46
+ if (!tm.template) throw new Error(`memory: ${MODEL_ID} has no chat template`);
47
+ let hasChunkAdapter = false;
48
+ const chunkDir = adapterDirFor("chunk");
49
+ if (chunkDir) {
50
+ // Mount ONCE; activation is per-call via loraState.active (see decodeGreedy
51
+ // / batchDecode). Mounting alone runs base — it must be activated.
52
+ const { AdapterManager } = await import("../../lora");
53
+ await new AdapterManager(tm.model).mount("memory-chunk", chunkDir);
54
+ hasChunkAdapter = true;
55
+ }
56
+ const scheduler = new BatchScheduler(tm.model, {
57
+ maxBatch: memoryBatchSize(),
58
+ lock: { acquire: () => schedLock.acquire() },
59
+ });
60
+ return { tm, template: tm.template, scheduler, hasChunkAdapter };
61
+ })();
62
+ }
63
+ return runtimePromise;
64
+ }
65
+
66
+ /** The adapters to activate for a stage. Only `chunk` is adapter-bound (and only
67
+ * when `memory-chunk` is mounted); every base stage runs with `active = []`. */
68
+ function activeFor(stage: string, rt: MemoryRuntime): string[] {
69
+ return stage === "chunk" && rt.hasChunkAdapter ? ["memory-chunk"] : [];
70
+ }
71
+
72
+ /** One bit-exact greedy decode for a stage (raw-forward argmax, runner parity).
73
+ * Sets the stage's adapter for the call and resets `active` to [] after. */
74
+ function decodeGreedy(rt: MemoryRuntime, stage: string, input: LocalInput, maxTokens: number): string {
75
+ const active = activeFor(stage, rt);
76
+ rt.tm.model.loraState.active = active;
77
+ const ids = memoryPromptIds(stage, input, rt.tm.tokenizer, rt.template);
78
+ try {
79
+ return greedyDecodeBitExact(
80
+ { ...rt.tm, activeAdapters: active.length ? active : undefined },
81
+ ids,
82
+ maxTokens,
83
+ );
84
+ } finally {
85
+ rt.tm.model.loraState.active = [];
86
+ }
87
+ }
88
+
89
+ /** Batched decode for N independent inputs of ONE stage. Sets the stage adapter
90
+ * globally for the whole batch (the scheduler reads `loraState.active` per
91
+ * forward, so all rows get it uniformly — a batch is necessarily single-adapter),
92
+ * submits every row to the shared scheduler with a greedy per-row sampler, and
93
+ * resets `active` after all rows finish. Order-preserving: out[i] ↔ inputs[i].
94
+ *
95
+ * Decode-path caveat: this samples via forwardHidden/logitsFromHidden (the
96
+ * scheduler), which can diverge from the runner's raw-forward greedy on near-ties
97
+ * past ~32 tokens. For the JSON/binary memory stages (validated by
98
+ * well-formedness) this is acceptable; the single-call fallback stays bit-exact. */
99
+ async function batchDecode(
100
+ rt: MemoryRuntime,
101
+ stage: string,
102
+ inputs: LocalInput[],
103
+ maxTokens: number,
104
+ ): Promise<string[]> {
105
+ const active = activeFor(stage, rt);
106
+ rt.tm.model.loraState.active = active;
107
+ const eos = rt.tm.config.eosTokenIds;
108
+ const greedySample: RowSampler = (logits1V: MlxArray) => ops.argmaxAxis(logits1V, -1);
109
+ try {
110
+ return await Promise.all(
111
+ inputs.map((inp) => {
112
+ const ids = memoryPromptIds(stage, inp, rt.tm.tokenizer, rt.template);
113
+ const toks: number[] = [];
114
+ return rt.scheduler
115
+ .submit({
116
+ promptIds: ids,
117
+ maxTokens,
118
+ eosTokenIds: eos,
119
+ sample: greedySample,
120
+ onToken: (t) => { toks.push(t); },
121
+ })
122
+ .then(() => rt.tm.tokenizer.decode(toks, true));
123
+ }),
124
+ );
125
+ } finally {
126
+ rt.tm.model.loraState.active = [];
127
+ }
128
+ }
129
+
130
+ // ---------------------------------------------------------------------------
131
+ // Public seams
132
+ // ---------------------------------------------------------------------------
133
+
134
+ /** Call the local model for a pipeline `stage` with a {system?, user} input.
135
+ * Renders through the shared chat-template path, runs the MODEL precheck, and
136
+ * decodes ONE completion on the bit-exact greedy path (the safe fallback used by
137
+ * every single-call site). NEVER auto-downloads. */
138
+ export async function callLocal(
139
+ stage: string,
140
+ input: LocalInput,
141
+ opts?: { maxTokens?: number },
142
+ ): Promise<string> {
143
+ const rt = await getRuntime();
144
+ const release = await opLock.acquire();
145
+ try {
146
+ return decodeGreedy(rt, stage, input, opts?.maxTokens ?? 256);
147
+ } finally {
148
+ release();
149
+ }
150
+ }
151
+
152
+ /** N independent calls for ONE stage (homogeneous adapter by construction).
153
+ * Routed through the shared in-process BatchScheduler when batch>1 AND N>1, else
154
+ * looped on the bit-exact greedy fallback. Order-preserving: out[i] ↔ inputs[i]. */
155
+ export async function callLocalBatch(
156
+ stage: string,
157
+ inputs: LocalInput[],
158
+ opts?: { maxTokens?: number },
159
+ ): Promise<string[]> {
160
+ if (inputs.length === 0) return [];
161
+ const rt = await getRuntime();
162
+ const maxTokens = opts?.maxTokens ?? 256;
163
+ const release = await opLock.acquire();
164
+ try {
165
+ if (inputs.length === 1 || memoryBatchSize() <= 1) {
166
+ const out: string[] = [];
167
+ for (const inp of inputs) out.push(decodeGreedy(rt, stage, inp, maxTokens));
168
+ return out;
169
+ }
170
+ return await batchDecode(rt, stage, inputs, maxTokens);
171
+ } finally {
172
+ release();
173
+ }
174
+ }
175
+
176
+ export const memoryCompletionClient: MemoryCompletionClient = {
177
+ complete: ({ stage, input, maxTokens }) => callLocal(stage, input, { maxTokens }),
178
+ completeBatch(requests) {
179
+ if (!requests.length) return Promise.resolve([]);
180
+ const first = requests[0]!;
181
+ if (requests.some((request) => request.stage !== first.stage || request.maxTokens !== first.maxTokens))
182
+ return (async () => {
183
+ const results: string[] = [];
184
+ for (const request of requests) results.push(await this.complete(request));
185
+ return results;
186
+ })();
187
+ return callLocalBatch(first.stage, requests.map((request) => request.input), { maxTokens: first.maxTokens });
188
+ },
189
+ };
@@ -0,0 +1,40 @@
1
+ import { DEFAULT_CHUNK, TRANSIENT_PER_TOKEN } from "../../fit";
2
+ import { kvBytesAt, type KvSchemeOptions } from "../../kv-scheme";
3
+ import { activeMemory, clearCache, maxRecommendedWorkingSetSize } from "../../mlx/ffi";
4
+ import type { RuntimeModel } from "../../model/factory";
5
+ import { Qwen35Model } from "../../model/qwen3_5";
6
+ import type { PromptCache } from "../../prompt-cache";
7
+ import { runtimeValue } from "../../runtime-config";
8
+
9
+ /** Admission's ceiling and the cache that can release memory under the GPU lease. */
10
+ export interface MlxMemoryBudget {
11
+ readonly usableBytes: number;
12
+ readonly kvOptions: KvSchemeOptions;
13
+ readonly promptCache: Pick<PromptCache, "relievePressure" | "totalBytes">;
14
+ }
15
+
16
+ /** Both execution paths enter after acquiring the request's reusable prefix.
17
+ * The caller owns the GPU lease and must close the scope on every exit. */
18
+ export function enterMlxMemoryGuard(model: RuntimeModel, budget: MlxMemoryBudget,
19
+ promptTokens: number, prefillChunkSize?: number): { check(): void; close(): void } {
20
+ const reserve = (prefillChunkSize ?? DEFAULT_CHUNK) * TRANSIENT_PER_TOKEN +
21
+ kvBytesAt(model.config, promptTokens, budget.kvOptions);
22
+ const ceiling = Math.min(budget.usableBytes, maxRecommendedWorkingSetSize());
23
+ const check = () => {
24
+ if (activeMemory() + reserve <= ceiling) return;
25
+ clearCache();
26
+ if (runtimeValue("MLX_BUN_PREFILL_MEM_LOG") === "1")
27
+ console.error(`[cache-memory] active=${activeMemory()} prompt=${budget.promptCache.totalBytes} reserve=${reserve} ceiling=${ceiling}`);
28
+ const count = budget.promptCache.relievePressure(() => {
29
+ clearCache();
30
+ return activeMemory() + reserve > ceiling;
31
+ });
32
+ if (count) console.log(`[prompt-cache] demoted ${count} entries for GPU headroom`);
33
+ if (activeMemory() + reserve > ceiling)
34
+ throw new Error("insufficient GPU headroom after prompt-cache demotion; saved generation checkpoints are retained");
35
+ };
36
+ const qwen = model instanceof Qwen35Model ? model : undefined;
37
+ const previous = qwen?.prefillMemoryGuard;
38
+ if (qwen) qwen.prefillMemoryGuard = check;
39
+ return { check, close() { if (qwen) qwen.prefillMemoryGuard = previous ?? null; } };
40
+ }
@@ -0,0 +1,90 @@
1
+ import { runtimeConfig, withRuntimeConfig, type RuntimeConfig } from "../../runtime-config";
2
+ import { snapshotGenerationPolicy } from "./request-policy";
3
+ import type { GenerationOutput, InferenceMethod, MethodResult } from "../../contracts/generation";
4
+ import { throwIfCancelled } from "../../engine/cancellation";
5
+ import { generateAutoregressive, generateDenoising, type GenerateOptions, type GenerateStats } from "../../generate";
6
+ import { specRun } from "../../spec/serve-loop";
7
+ import type { MlxAutoregressiveBinding } from "./autoregressive";
8
+ import type { MlxSpeculativeBinding } from "./speculative";
9
+ import type { MlxDenoisingBinding } from "./diffusion";
10
+
11
+ /** The native algorithm owns cleanup in its finally. This host adapter owns
12
+ * cancellation subscriptions and waits for the algorithm before closing. */
13
+ function nativeMethod<Metrics>(
14
+ id: string,
15
+ execute: (output: GenerationOutput, signal: AbortSignal) => Promise<MethodResult<Metrics>>,
16
+ runtime: RuntimeConfig,
17
+ ): InferenceMethod<Metrics> {
18
+ return {
19
+ id,
20
+ async createRun(cancellation) {
21
+ throwIfCancelled(cancellation);
22
+ const abort = new AbortController();
23
+ const unsubscribe = cancellation.subscribe((reason) => abort.abort(
24
+ new DOMException(`generation cancelled: ${reason}`, "AbortError"),
25
+ ));
26
+ let work: Promise<MethodResult<Metrics>> | undefined;
27
+ let closing: Promise<void> | undefined;
28
+ return {
29
+ execute(output) {
30
+ if (work || closing) return Promise.reject(new Error("method run can execute only once"));
31
+ work = withRuntimeConfig(runtime, () => execute(output, abort.signal));
32
+ return work;
33
+ },
34
+ close() {
35
+ return closing ??= (async () => {
36
+ abort.abort(new DOMException("method closed", "AbortError"));
37
+ try { await work; } catch { /* execute owns the algorithm's error */ }
38
+ finally { unsubscribe(); }
39
+ })();
40
+ },
41
+ };
42
+ },
43
+ };
44
+ }
45
+
46
+ /** Each plan supplies its prepared per-run resources. Caches/media in options
47
+ * retain the same ownership contract as generateAutoregressive. */
48
+ export function createAutoregressiveMethod(
49
+ binding: MlxAutoregressiveBinding, promptIds: readonly number[], options: GenerateOptions,
50
+ ): InferenceMethod<GenerateStats> {
51
+ const prompt = [...promptIds];
52
+ options = snapshotGenerationPolicy(options);
53
+ return nativeMethod("mlx-autoregressive", async (output, signal) => {
54
+ const generation = generateAutoregressive(binding, prompt, { ...options, signal });
55
+ for await (const item of generation)
56
+ if (await output.commit([item.token], item.logprobs ? [item.logprobs] : undefined) === false) break;
57
+ const metrics = generation.stats;
58
+ if (!metrics) throw new Error("AR method did not settle its metrics");
59
+ return { finishReason: metrics.finishReason ?? (metrics.generatedTokens >= (options.maxTokens ?? 512) ? "length" : "stop"), metrics };
60
+ }, binding.runtime ?? runtimeConfig());
61
+ }
62
+
63
+ export function createSpeculativeMethod(
64
+ binding: MlxSpeculativeBinding, numDraftTokens: number,
65
+ promptIds: readonly number[], options: GenerateOptions,
66
+ ): InferenceMethod<GenerateStats> {
67
+ const prompt = [...promptIds];
68
+ options = snapshotGenerationPolicy(options);
69
+ return nativeMethod("mlx-speculative", async (output, signal) => {
70
+ const metrics = await specRun(binding, numDraftTokens, prompt, { ...options, signal },
71
+ (token) => output.commit([token]));
72
+ return { finishReason: metrics.finishReason ?? (metrics.generatedTokens >= (options.maxTokens ?? 512) ? "length" : "stop"), metrics };
73
+ }, binding.runtime ?? runtimeConfig());
74
+ }
75
+
76
+ /** The canvas stays private. This method publishes only the finished result. */
77
+ export function createDenoisingMethod<State>(
78
+ binding: MlxDenoisingBinding<State>, promptIds: readonly number[], options: GenerateOptions,
79
+ ): InferenceMethod<GenerateStats> {
80
+ const prompt = [...promptIds];
81
+ options = snapshotGenerationPolicy(options);
82
+ return nativeMethod("mlx-denoising", async (output, signal) => {
83
+ const generation = generateDenoising(binding, prompt, { ...options, signal });
84
+ for await (const item of generation)
85
+ if (await output.commit([item.token]) === false) break;
86
+ const metrics = generation.stats;
87
+ if (!metrics) throw new Error("denoising method did not settle its metrics");
88
+ return { finishReason: metrics.generatedTokens >= (options.maxTokens ?? 256) ? "length" : "stop", metrics };
89
+ }, binding.runtime ?? runtimeConfig());
90
+ }