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
@@ -1,567 +0,0 @@
1
- # Parallel slots — configurable batched serving
2
-
3
- Status: **S0 + S1b + S2 SHIPPED** (S2's continuous inject/evict shipped
4
- with the scheduler; its byte-budget admission half landed 2026-07-03 as
5
- `--kv-budget`; S3+ deferred)
6
- Owner: serving layer
7
- Default: **off** (`--batch 1` = today's serialized path, untouched)
8
-
9
- > **Flag rename note:** This document was drafted with `--slots N` as the design name.
10
- > The shipped flag is `--batch N` (`--decode-concurrency` accepted as an mlx_lm.server alias).
11
- > Occurrences of `--slots` in pre-ship text below refer to today's `--batch N`.
12
-
13
- ## Goal
14
-
15
- Let the server process more than one generation request concurrently,
16
- gated entirely behind a `--batch N` flag (originally designed as `--slots N`). `N=1` runs exactly what we
17
- have today (the serialized promise chain). `N>1` loads a batch
18
- scheduler that runs multiple sequences through one forward pass.
19
-
20
- This is a server-path feature — serving speed is the user metric.
21
- Direct/embedding mode stays batch=1.
22
-
23
- > **v2 execution plan:** this document remains the PRIMITIVE-design record
24
- > (masks, caches, lanes). The living gap/debt plan and sequencing for the
25
- > batching engine is [batching-v2-plan.md](batching-v2-plan.md). Landed from
26
- > it (2026-07-01, steps 1–3): cache-capability gate (hybrid/SSM models route
27
- > serial), slice-before-head admit, ONE lock domain (curve `/generate`,
28
- > `/signal`, adapter mount/unmount via `gateway.runExclusive`), per-row
29
- > onToken failure containment, drain-on-serial-waiter (no starvation), and
30
- > the engine-hygiene transplant (pipelined decode via asyncEval, clearCache
31
- > 1/256 steps, chunked admit prefill interleaved with decode steps).
32
- > Landed since: **SSM batched path** (Qwen3.5 batches, token-exact vs
33
- > mlx-lm B=2 — 2026-07-02, batching-perf-path.md P5); **per-row logits
34
- > processors** (repetition/presence/frequency penalties + logit_bias +
35
- > min_p/XTC batch — 2026-07-02, same doc); **grammar under batching** (B1
36
- > per-row matchers — structured-output.md); **KV-budget admission**
37
- > (`--kv-budget`, queue-don't-OOM, `/stats.batch` fields — 2026-07-03,
38
- > grammar-spec-batching-integration.md Phase D); **Tier-0 universal
39
- > coverage** (plain full-attention archs, e.g. Llama, batch via per-row
40
- > RoPE, token-exact vs mlx-lm B=2 — 2026-07-03, same doc's execution log;
41
- > maskArray + sliding universal archs stay serial).
42
-
43
- ## STATUS (2026-06-14) — LIVE for full-attention models
44
-
45
- **`--batch N` now serves B>1** for full-attention models (CPM). The numerics
46
- were proven first (the batched **forward primitive** `BatchedDecodeMaskCache` +
47
- per-layer fixes = bit-parity with mlx-lm B=N across all 4 models; static-B AND
48
- dynamic-B `mergeKVRows`/`filterKVRows` vs mlx-lm `BatchKVCache.merge`/`.extract`/
49
- `.filter`, CPM L1), then the **scheduler** was built and wired in:
50
- - `BatchScheduler` (`src/serve/batch-scheduler.ts`) — the detached async driver:
51
- admission queue → running batch → continuous inject (`mergeKVRows`) / evict
52
- (`filterKVRows`) → per-row sample + accounting. Gated teacher-forced (KL).
53
- - `GenerationGateway` (`src/serve/generation-gateway.ts`) — lane picker +
54
- `AsyncMutex` (serial↔batched GPU/`loraState` exclusivity) + the `_is_batchable`
55
- predicate. Wired into both `handleChat` call sites → per-row SSE fan-out.
56
- - End-to-end: `tests/batch-serving.test.ts` (live CPM `--batch 2` server).
57
-
58
- **Sliding-window (Gemma) dynamic-B is now served too** (2026-06-14):
59
- `BatchedRotatingCache` (`src/model/batched-rotating.ts`, port of mlx-lm
60
- `BatchRotatingKVCache` incl. the ring-wrap rolled mask) is bit-exact vs mlx-lm
61
- model-free (`tests/batched-rotating.test.ts`); the scheduler assembles each
62
- layer's cache by type and Gemma 12B scheduled greedy == the mlx-lm B=2 golden
63
- with eviction. So `--batch N` batches BOTH CPM and Gemma.
64
-
65
- **This meets the mlx-lm-parity bar.** mlx-lm's batched path is bf16 (its
66
- quantized batching is NYI — `BatchRotatingKVCache.to_quantized` raises), so bf16
67
- continuous batching IS the drop-in. `--batch N` is therefore a bf16 MODE: KV
68
- quant unset → bf16 (so the batch path engages out of the box, Option B); serial
69
- default stays mixed-precision (optiq parity); an explicit `--kv-quant` under
70
- `--batch N` routes those requests to serial (warned).
71
-
72
- **Batched + mixed-precision KV quant is NOT a parity gap** — no ancestor does it
73
- (mlx-lm NYI, optiq no batching), so there's no bit-exact oracle. It's an OPTIONAL
74
- novel extension: a memory-density win (batching × 4-bit KV compound), gated by
75
- KL + 6-task quality (per the tree framing), deferred. Other follow-ups (all
76
- optional polish): the `extend` join op (today joins re-merge), KV-budget
77
- admission (**LANDED 2026-07-03** — `--kv-budget`), prompt-cache reuse under
78
- batching, and **paged KV** (zero-padding-waste allocation).
79
-
80
- ## Why it helps (and when it doesn't)
81
-
82
- The win is **throughput via memory bandwidth**. Decode on Apple Silicon
83
- is bandwidth-bound (~273 GB/s on the M4 Pro): every token streams the
84
- entire weight set through the GPU to produce one token. Batch B
85
- sequences and you stream the weights **once** to produce **B** tokens.
86
- Aggregate tok/s scales ~linearly until the compute roofline.
87
-
88
- The crucial design point — **continuous batching, not static
89
- batching**:
90
-
91
- - *Static batching*: form a batch, run to completion, then accept the
92
- next. A request arriving mid-flight waits for the whole batch to
93
- finish. This is the wrong design; do not build it.
94
- - *Continuous batching* (iteration-level scheduling): the scheduler
95
- works per **decode step**. After each token it can inject a
96
- newly-arrived request (after a short prefill) and retire finished
97
- ones. A late request does **not** wait for the in-flight one to
98
- finish — it joins at the next step.
99
-
100
- So the benefit window is "any time two requests are alive in the same
101
- wall-clock window" — not "a backlog has formed." Because generations
102
- last seconds, requests overlap even at modest rates (Little's law:
103
- concurrent ≈ arrival_rate × mean_generation_time).
104
-
105
- When it does **not** help: traffic so light that requests never overlap
106
- in time. Then there's only ever one live sequence and batching is idle
107
- — which is exactly why `--batch 1` is and stays the default.
108
-
109
- ## Current code: what's batch=1 and what isn't
110
-
111
- Good news: the hot path is already written over a batch dim `B`.
112
-
113
- - `Attention.forward` reads `const [B, L] = x.shape` and threads `B`
114
- through every reshape/transpose/SDPA — `src/model/gemma4.ts:152`.
115
- - `KVCache` stores `[B, H, S, D]` and grows/slices generically —
116
- `src/model/gemma4-base.ts:204` (STEP=256 growth).
117
- - The byte-capped LRU prompt cache already accounts bytes, not count —
118
- `src/prompt-cache.ts` (the precedent for a byte-budgeted KV pool).
119
-
120
- `B=1` is baked into a finite, findable set of places:
121
-
122
- | Location | Hardcoded |
123
- | --- | --- |
124
- | `src/generate.ts:295,315,378` | prompt/decode tensors built `[1, L]` / `[1, 1]` |
125
- | `src/generate.ts:320,235` | `hLast` slice `[1, …]`; `sampleStep` reshape `[1, V]` |
126
- | `src/generate.ts:247` | sampler history `[1]`, one sampled token/step |
127
- | `src/model/gemma4.ts:647-653` | per-layer-input reshapes `[1, L, …]` *(pre-S1b inventory — this entry was fixed in S1b; e4b per-layer-input is now B-generic)* |
128
- | `src/model/gemma4-base.ts:210` | **one scalar `offset` per cache, shared across rows** |
129
- | `src/model/gemma4.ts:174,206` | **scalar RoPE offset** for the whole batch |
130
- | `src/model/gemma4-base.ts:256` | `makeMask` from that single offset (returns empty mask at N=1) |
131
-
132
- ## The one genuinely hard problem: per-sequence position
133
-
134
- Batched sequences are different lengths and at different decode
135
- positions, but the cache has a single `offset`, RoPE takes a scalar
136
- offset, and the mask is built from one offset.
137
-
138
- Solution (mlx-lm's, and ours): **left-padding + per-row mask + per-row
139
- RoPE offsets.**
140
-
141
- - Right-align every sequence so "the current token" is the same column
142
- for all rows → one global advancing offset still works.
143
- - Pad on the left; mask the padding with a real `[B, 1, 1, S]` boolean
144
- mask — **even at the N=1 decode step**, which today returns an empty
145
- mask (`gemma4-base.ts:257`). That shortcut is the first thing to change.
146
- - RoPE positions become a per-row vector. **We already have the
147
- primitive**: `ops.ropeDynamic` + the `cache.ropeOffsetArr` array-offset
148
- path (`gemma4.ts:179,206`), currently used only by compiled decode.
149
-
150
- Highest-risk sub-item: Gemma3 alternates sliding-window
151
- (`RotatingKVCache`) and full-attention layers. Per-row offsets through
152
- the ring cache's wrap semantics is the trickiest mask math in the
153
- project. Validate the full-attention mask path first; bring sliding
154
- layers online separately under teacher-forced parity.
155
-
156
- ### Reuse: batched PREFILL already exists (big de-risk, found 2026-06-14)
157
-
158
- The training path already solves batched padded forward and proves it:
159
- - `src/train/forward.ts` — `buildBatchedPadMask(B, L, validLengths, window)`
160
- builds the `[B,1,L,L]` mask (causal AND per-row key-validity, per distinct
161
- window) and `BatchedMaskCache` routes it through the stock attention by
162
- wrapping a real cache and overriding only `makeMask`. Comment confirms
163
- "KVCache/RotatingKVCache are already shape-generic over B."
164
- - `tests/train-batch-e2e.test.ts` proves a B=2 padded forward's per-row loss
165
- equals two independent B=1 forwards within bf16 tolerance — i.e. the mask
166
- makes a real row's logits independent of the other (padded) row. That is
167
- exactly the teacher-forced gate S1 needs, already passing.
168
- - `tests/train-batch.test.ts` unit-tests the mask (fast, no model).
169
-
170
- So batched **prefill** is ~done and reusable (offset-0, right-padded). The
171
- genuinely new work is batched **decode**: a growing per-row KV with per-row
172
- offsets, per-row RoPE (array-offset path), and a per-row N=1 key-validity
173
- mask. First decode brick landed 2026-06-14: `src/model/batched-mask.ts`
174
- `buildBatchedDecodeMask` (left-padded, nonzero-offset counterpart) +
175
- `tests/batched-decode-mask.test.ts` (fast). The two mask builders consolidate
176
- when prefill is wired into serving (S1a).
177
-
178
- ### S1b decode: design traps & sequencing (found 2026-06-14)
179
-
180
- Reading the attention path surfaced two real subtleties — the kind that
181
- produce plausible-but-wrong logits, so flagged before writing the cache:
182
-
183
- 1. **Prefill/decode padding conventions differ.** Training's batched prefill
184
- RIGHT-pads (real tokens left-aligned, positions 0..len-1 → scalar RoPE
185
- offset 0 is correct). Batched decode wants LEFT-padding (rows right-aligned
186
- so every row's next write lands in the same column → one advancing offset).
187
- Left-padding then forces **per-row RoPE** (real position = column −
188
- leftPad[b]) via the array-offset path, and means the prefill KV must be
189
- re-laid-out (right-aligned) before decode. First cut: prefill each request
190
- solo, copy its KV into the right-aligned row of a shared `[B,H,Smax,D]`
191
- decode buffer, then batch the decode.
192
- 2. **RoPE offset-timing trap (FIXED 2026-06-14).** `Attention.forward` read
193
- `cache.ropeOffsetArr` twice — for K *before* `updateAndFetch` and for Q
194
- *after* it (gemma4.ts ~179 vs ~195/201). Since `updateAndFetch` advances
195
- `offset`, a per-row position array derived from the live offset would give
196
- K and Q different positions. Fixed by capturing `offsetArr` once alongside
197
- `offset` (line ~174). Behavior-preserving (real caches leave it unset;
198
- compiled-decode passes a constant) — verified bit-exact against
199
- `tests/compiled-decode.test.ts` (12B, 7/7).
200
-
201
- **Sequencing (parity gate first, on the riskiest piece):**
202
- - **S1b.1** — gated teacher-forced *decode* parity harness: manually assemble a
203
- left-padded 2-row KV from two solo prefills, run K batched decode steps with
204
- a `BatchedDecodeMaskCache` (delegates KV, returns `buildBatchedDecodeMask`,
205
- exposes per-row `ropeOffsetArr` pinned to the pre-write offset), and assert
206
- each row's logits match its solo greedy decode within bf16 tolerance. Gated
207
- behind an opt-in env + local weights (like `train-batch-e2e`). Isolates
208
- decode-batching numerics from the prefill-assembly plumbing.
209
- **DONE 2026-06-14** — `BatchedDecodeMaskCache` (src/model/batched-mask.ts) +
210
- `tests/batched-decode-parity.test.ts` (`MLX_BUN_TEST_BATCH_DECODE=1`,
211
- MiniCPM5-1B). Passes: the unpadded row is **bit-exact** vs its solo decode
212
- (batching isolates rows); the left-padded row matches within bounded bf16
213
- reduction-order noise (≤0.23, stable over 8 steps — the shared buffer shifts
214
- its keys by leftPad columns, so the attention sum accumulates in a different
215
- order). **The gate earned its keep**: it caught `LlamaAttention`
216
- (src/model/minicpm5.ts) roping Q/K with the scalar `cache.offset` and ignoring
217
- `ropeOffsetArr` → left-padded rows mis-positioned (logit diff 8.7). Fixed to
218
- use the dynamic array-offset path when present; MiniCPM5 solo parity stays
219
- bit-exact vs the oracle. (Gemma4's `Attention` already had the array path; its
220
- capture-once fix is verified bit-exact vs compiled-decode.) Lesson: each model
221
- family's attention needs the per-row offset path wired separately.
222
- - **S1b.2** — production KV assembly (solo prefill → left-aligned decode buffer).
223
- - **S1b.3** — scheduler + B-token/step generate loop + per-row stream fan-out.
224
-
225
- ### The validation matrix — 3 parity layers × every model path
226
-
227
- **Current state (2026-06-14).** Roster: **CPM (MiniCPM5-1B) + 3 Gemmas
228
- (e4b / 12B / 26B)**. Every NEW model family (e.g. **Qwen**, planned) adds its
229
- OWN handler path and its own copy of the three layers below — nothing here is
230
- generic. This matrix is a snapshot; re-derive it when the roster changes.
231
-
232
- **Framing (refined 2026-06-14 — it's a TREE, not strict layers).** mlx-lm is the
233
- base library; **mlx-optiq and mlx-bun are both optimizations on top of it**
234
- (optiq = mixed-precision per-layer KV quant, "drop-in for mlx_lm.server", no
235
- batching; mlx-bun = per-model fused kernels + batched serving). Bit-parity with
236
- an ancestor is just an **easy correctness test where we overlap one** — not a
237
- porting goal. The "L1/L2" labels below are **parity anchors / correctness
238
- checkpoints**, not a strict stack: L1 = "matches mlx-lm (bf16)", L2 = "matches
239
- optiq (mixed-precision quant)"; beyond them is the no-oracle **Lab** class ("our
240
- own optimizations, no ancestor does them → no oracle" — formerly labeled "L3";
241
- the `--l3` product tier was deleted 2026-07-05,
242
- docs/design/unified-engine-frontier-plan.md §6). A combo no ancestor does (e.g. **batched mixed-precision
243
- serving** — mlx-lm can't do mixed-precision, optiq can't batch) is genuinely new
244
- territory: gate it by KL + the 6-task quality + benchmarks, never bit-exactness.
245
- The anchors are still useful — we can always dial back to a bit-exact-vs-ancestor
246
- config as a correctness checkpoint and build outward.
247
-
248
- - **L1 — mlx-lm parity (standard KV).** bf16 KV, **bit-exact vs mlx-lm**. The
249
- foundation oracle (and, since 2026-07-05, the naked default). Path: monolith
250
- `Gemma4Model` / `MiniCPM5Model`, `ops.sdpa`.
251
- - **L2 — mlx-optiq parity (mixed-precision KV quant).** quant/mixed KV,
252
- **bit-exact vs optiq**. Path: `QuantizedKVCache` + `quantizedSdpa` (fused-sdpa
253
- follows `--kv-quant`: on for `config` — the optiq golden composition; off →
254
- *unfused* for uniform/bf16).
255
- - **L3 — DELETED as a product mode 2026-07-05**
256
- (docs/design/unified-engine-frontier-plan.md §6): no output-changing kernel
257
- (perf-kernel, fused-decode, fused-gelu) beat the L1 baseline in a paired A/B,
258
- so the kernels were deleted and `--l3` hard-errors. Its no-oracle territory is
259
- now the **Lab**: output-changing experiments are env-flag items with a bench +
260
- expiry, gated by **KL vs the compatible upstream + the 6 eval-task mean
261
- (`src/eval/tasks/`) + perf numbers** — never bit-exactness — until one earns
262
- promotion.
263
-
264
- Degradation lever: bf16 KV drops L2→L1. The surviving kill switches
265
- (`--compiled-decode`, `--compiled-activations`, `--fused-sdpa`) are per-kernel
266
- escapes. Each layer must stand alone and stay reachable (don't-delete-
267
- optionality as a fallback ladder).
268
-
269
- So a feature like batched decode (`--batch`) is "done" only when green at
270
- **every (model × layer)** cell — and each cell may need its own fix:
271
-
272
- | Model · path | L1 (bf16 / mlx-lm) | L2 (quant / optiq) | ~~L3~~ (tier deleted 2026-07-05) |
273
- | --- | --- | --- | --- |
274
- | CPM (MiniCPM5) | ✅ **oracle-verified** (= mlx-lm B=2) | ☐ quant + unfused-mask | — |
275
- | Gemma 12B | ✅ **oracle-verified** (= mlx-lm B=2, short-ctx) | ☐ generated + sliding-window | — |
276
- | Gemma e4b | ✅ **oracle-verified** (= mlx-lm B=2, short-ctx) | ☐ generated + same | — |
277
- | Gemma 26B | ✅ **oracle-verified** (= mlx-lm B=2, short-ctx) | ☐ generated + MoE | — |
278
- | *(Qwen, future)* | *new path* | *new path* | — |
279
-
280
- **🎯 L1 batched decode COMPLETE (2026-06-14d): the entire L1 row is green** —
281
- all 4 model paths bit-parity with mlx-lm B=2 (CPM full-attn, Gemma 12B
282
- sliding+full, e4b per-layer-input+KV-share, 26B MoE). Only one per-path fix was
283
- needed (e4b's `[1,L,…]` per-layer-input hardcode); sliding (RotatingKVCache),
284
- KV-sharing, and MoE were all already B-generic. Caveat: short-context (pre-wrap)
285
- — ring-wrap (>window) is the one remaining L1 follow-up. Next row: **L2**
286
- (quantized KV → `quantizedSdpaUnfused` + 4-D mask, vs optiq). (The former
287
- "L3: perf kernels under batching" row is void — those kernels were deleted
288
- 2026-07-05, docs/design/unified-engine-frontier-plan.md §6.)
289
-
290
- Harness: `tests/batched-decode-parity.test.ts` has two paths — (a)
291
- `runBatchedDecodeParity` = the internal-consistency KL check (B=N vs our B=1,
292
- NOT the gate); (b) **`realBatchedGreedy` = the REAL gate**: left-pad → one
293
- `BatchedDecodeMaskCache` (handles prefill at offset 0 AND per-step decode) →
294
- batch-prefill → greedy batch-decode, trajectory compared bit-for-bit to the
295
- mlx-lm B=N oracle golden (`goldens/batched-golden-*.json`, machine-layered via
296
- `tests/goldens.ts` — per-GPU overrides in `goldens/<machine>/` — generated by
297
- `scripts/gen-batched-golden.py` in the oracle venv).
298
-
299
- **CPM L1 oracle-verified 2026-06-14d:** mlx-bun's real batched greedy trajectory
300
- == mlx-lm B=2 exactly (both rows incl. the left-padded one, 8 steps). Confirms
301
- `BatchedDecodeMaskCache` == mlx-lm `BatchKVCache` (left-pad + per-row offset RoPE
302
- + `j>=leftPad` mask + fused bool sdpa), and that the wrapper handles batched
303
- PREFILL too (offset 0 → left-pad prefill mask + `ropeOffsetArr = -leftPad`). The
304
- earlier "KL 0.26" was measured vs the wrong oracle (our own B=1).
305
-
306
- **Gate philosophy (revised twice, 2026-06-14):** batched decode is NOT bit-exact
307
- vs single-stream (batching changes the attention kernel's reduction order). The
308
- RIGHT oracle is **mlx-lm's OWN batch mode at the same B** — mlx-lm B=N ≡ mlx-bun
309
- B=N, **bit-exact**. Comparing our B=N to our own B=1 (the KL harness) measures
310
- the wrong thing and can't distinguish a real bug from inherent batched-kernel
311
- behavior. mlx-lm's batch path (oracle venv, confirmed 2026-06-14):
312
- `BatchKVCache`/`BatchRotatingKVCache(left_padding)` — left-padded, a **per-row
313
- `offset` array** (`-left_padding`, `+=1`/step → real per-row position, feeding
314
- RoPE), mask `j >= left_padding[b]` (`create_causal_mask(left_padding=…)`), and
315
- for bf16 the **same fused bool-mask `mx.fast.scaled_dot_product_attention`** we
316
- use. So our approach matches in principle and the **additive-mask idea is wrong**
317
- (would deviate). Oracle generator: `scripts/gen-batched-golden.py` (drives
318
- mlx-lm's real batch-prefill + greedy batch-decode, dumps per-row trajectories +
319
- last-position logits). CPM B=2 oracle captured 2026-06-14.
320
-
321
- **Remaining gap:** the KL harness solo-prefills each row then *assembles* a
322
- batched cache, and compares to *solo* decode. mlx-lm batch-PREFILLS (B=N) — so
323
- even prefill KV differs (B=1 vs B=N kernel). To gate faithfully we need a real
324
- mlx-bun **batched prefill + batched decode** path, compared bit-exact to the
325
- mlx-lm B=N golden. (The KL B2-vs-B1 harness stays as a cheap internal-consistency
326
- check, not the gate.)
327
-
328
- **Gemma 12B L1 — RESOLVED 2026-06-14d: it was the wrong oracle, not a bug.**
329
- The real `realBatchedGreedy` path (batch-prefill + decode) matches the mlx-lm
330
- B=2 oracle **exactly** for Gemma 12B (both rows incl. left-padded, sliding
331
- layers via RotatingKVCache→BatchRotatingKVCache, short-context). So the "padded
332
- row KL ~0.26" was entirely an artifact of the KL harness comparing our B=2 to
333
- our own B=1 (different prefill + reduction order) — mlx-lm B=2 diverges from its
334
- own B=1 the same way. No additive-mask fix was needed; the bool+fused path is
335
- correct (it's what mlx-lm uses). The KL B2-vs-B1 harness stays only as an
336
- internal-consistency check, never the gate.
337
-
338
- **Caveat:** verified at short context (Lmax ≪ 1024 window → pre-wrap, sliding
339
- window inactive). **Ring-wrap (context > window) is a separate validation** —
340
- needs a long-context golden (mlx-lm BatchRotatingKVCache wrapped) + mlx-bun
341
- RotatingKVCache wrapped past the window.
342
-
343
- Batched-decode landmines found by the modes analysis (2026-06-14):
344
- - **e4b**: `computePerLayerInputs` hardcodes `[1,L,…]` (gemma4.ts:647) → B>1
345
- breaks; plus KV-sharing (sharer `SharedKv`).
346
- - **Gemmas**: `RotatingKVCache` sliding-window ring — per-row decode mask + ring
347
- write (the hard one). 26B adds MoE (`GatherQMM`/`SwitchGLU`) batched routing.
348
- - **Generated handlers (all 3 Gemmas)** repeat the K/Q rope timing trap —
349
- they read `cache.ropeOffsetArr` twice around `updateAndFetch`
350
- (generated/gemma4-12b.ts:63 & :74). The fix goes in the GENERATOR
351
- `scripts/gen-model.ts`, then regenerate (the files are DO-NOT-EDIT).
352
- - **Quant + batched**: quant batched decode goes through `quantizedSdpaUnfused`
353
- — correct. (The old "perf kernel bypassed under batching" debt is resolved by
354
- deletion: the perf/fused-decode kernels were deleted 2026-07-05,
355
- docs/design/unified-engine-frontier-plan.md §6 — no batched-aware fused decode
356
- kernel is planned.) `quantizedSdpaUnfused` must be validated with a 4-D bool mask.
357
- - **All cells**: compiled-decode forced off under `--batch>1`; LoRA batches only
358
- within one adapter group (else drain).
359
-
360
- ## KV memory: dynamic allocation, not static partition
361
-
362
- The naive model — reserve `budget / slots` per row, fixed — is rejected:
363
- it wastes memory and imposes an arbitrary per-slot context cap. We
364
- allocate **by need**. Three rungs, in increasing order of "who needs it
365
- most" fidelity and cost:
366
-
367
- 1. **Static partition** — *not built.* Fixed per-slot ceiling. Wasteful.
368
- 2. **Dynamic shared-budget, contiguous caches (padded batch)** — the
369
- shippable path. Admission is governed by *total projected bytes*
370
- against one KV budget (mirrors the byte-capped `PromptCache`); each
371
- sequence grows its own contiguous cache on demand (KVCache already
372
- grows in 256-token steps). Short prompts use little; long ones take
373
- more. **Caveat**: a padded batch shares one `[B, H, S, D]` buffer, so
374
- the buffer width tracks the *longest live sequence* — short rows still
375
- carry padding to that width. Partial "who needs it most."
376
- 3. **Paged KV (the true goal)** — fixed-size KV blocks in a shared pool,
377
- gathered by a custom paged-attention Metal kernel. No padding waste;
378
- the neediest sequence grabs more blocks, others fewer; maximizes
379
- concurrent rows per GB. This is what "dynamically allocate based on
380
- who needs it most" actually means. Cost: a custom attention kernel +
381
- a block manager. Feasible — the project already ships custom Metal
382
- kernel infrastructure (`src/mlx/metal-kernel.ts`).
383
-
384
- Plan: ship rung 2 (S1–S2) for the throughput win + dynamic admission
385
- with no new kernel; build rung 3 (S3+) as the memory-density upgrade.
386
- Budget accounting is total-bytes from day one.
387
-
388
- **Current state (2026-06-14):** the batched *primitive* is **rung 2** —
389
- one shared `[B, H, S, D]` buffer per layer (`KVCache`/`RotatingKVCache`),
390
- growing in 256-token steps, width = longest live sequence, left-pad
391
- "waste" on short rows. Rung-2 allocation IS wired into serving via
392
- `BatchScheduler` + `GenerationGateway` (S1b SHIPPED). But the
393
- **budget/admission half of rung 2 is NOT built** (no `B × S_max`
394
- projection, no KV budget enforcement) — that's scheduler work (S2, NOT
395
- STARTED). *(Since LANDED 2026-07-03: `--kv-budget` aggregate projection
396
- gate in the scheduler — grammar-spec-batching-integration.md Phase D.)*
397
- And **rung 3 (paged): the v1 CACHE CLASS shipped** —
398
- `PagedKVCache`/`BlockPool` behind default-off `--paged-kv`
399
- (docs/design/paged-kv-cache.md): serial batch=1, Gemma4, bf16,
400
- gather-then-stock-sdpa (no custom kernel yet), gated bit-exact vs the
401
- plain path. The rung-3 PAYOFF (batched block allocation replacing the
402
- pad+concat merge/extend, block-level CoW prefix sharing, the fused paged
403
- kernel) is still the deferred follow-up — the shipped slice is the block
404
- manager + correctness proof, not the density win.
405
-
406
- KV quantization is a force multiplier: 4-bit KV (`generate.ts:67`,
407
- already supported) is ~4× smaller, so the same budget buys ~4× the slots
408
- or ~4× context per slot. Batching + KV-quant compound.
409
-
410
- Note: the existing `2e9` (`server.ts:494`) is the **prompt (prefix-reuse)
411
- cache**, not live decode KV. Live caches are made fresh per generation
412
- (`server.ts:517`). Slots introduce a *new* budget: the pool of live
413
- decode caches.
414
-
415
- ## The scheduler (port of mlx-lm `BatchGenerator` / `ResponseGenerator`)
416
-
417
- When `slots > 1`, the single promise chain (`server.ts:487`) is replaced
418
- by a scheduler owning:
419
-
420
- - a **pending queue** of admitted requests;
421
- - a **running batch** of ≤ `decodeConcurrency` rows;
422
- - **admission control** against the KV budget;
423
- - a **batchable gate** (mlx-lm `_is_batchable`): same model, compatible
424
- sampler params, **no fixed seed**, no vision, compatible adapter set;
425
- - **prefill phase** (≤ `promptConcurrency` rows) vs **decode phase**;
426
- - **draining**: an incompatible request waits for the batch to clear,
427
- then runs in its own batch.
428
-
429
- Streaming fan-out is the main plumbing rework: today `generate()` yields
430
- one token to one SSE stream (`server.ts:964,1037`). Batched, each step
431
- produces B tokens that must route to B independent response streams,
432
- each with its own `StopSequencer`/tool-call router. Touches
433
- `server.ts`, `responses.ts`, `anthropic.ts`.
434
-
435
- ### Concrete plan (the `--batch N` engine, build order — 2026-06-14)
436
-
437
- Started: `--batch N` flag landed (renamed from `--slots`; `--decode-concurrency`
438
- accepted as an mlx_lm.server alias). N=1 → today's serial path; N>1 → the engine
439
- below. The engine is a **mode** (whole server), not a load-fallback (keeps
440
- determinism + the drop-in promise).
441
-
442
- **Dynamic-B cache ops** (port of mlx-lm's cache methods — the new piece our
443
- fixed-B verified forward doesn't have):
444
- - **`merge(perRowCaches)`** — stack N single-row caches into one `[B,H,S,D]`
445
- batch, left-pad-aligned (generalizes `realBatchedGreedy`'s assemble).
446
- - **`filter(keep)`** — slice the B axis to drop finished rows (eviction).
447
- - **`extend(other)`** — append a freshly-prefilled batch's rows (insertion).
448
- - mlx-lm's flow: a joining request is **right-padded + prefilled** in a prompt
449
- batch, then `finalize()` (dynamic-roll) → left-pad, then `merge`/`extend` into
450
- the running generation batch. Our verified attention/mask/RoPE drop straight in.
451
-
452
- **Engine loop (Bun-async, NOT Python threads):** an async loop owns the running
453
- batch. Each iteration: (1) admit waiting+batchable requests up to N (prefill +
454
- `extend`); (2) one batched decode step (the verified forward); (3) sample
455
- per-row, push each row's token to its own SSE `ReadableStream` (per-row
456
- `StopSequencer` + tool parser + sampler); (4) `filter` out rows that hit
457
- EOS/stop/max-tokens, resolve their streams. HTTP handlers `await` their row's
458
- stream; no thread, just the event loop + `mx.async_eval` pipelining.
459
-
460
- **Build sequence:** (1) **DONE 2026-06-14** — dynamic-B cache `merge`/`filter` +
461
- a pure unit test, then a gated parity test vs an mlx-lm *dynamic* golden (rows
462
- join/leave mid-stream). `scripts/gen-batched-dynamic-golden.py` drives mlx-lm's
463
- real `BatchKVCache.merge`/`.extract`/`.filter` through {A,B}→join C→evict A→{B,C};
464
- `realDynamicBatchedGreedy` (`tests/batched-decode-parity.test.ts`) runs the same
465
- scenario through `mergeKVRows`/`filterKVRows` and matches all 3 per-row greedy
466
- trajectories token-for-token (CPM L1). Join = re-merge of extracted advanced-offset
467
- rows + a fresh solo prefill (proves `mergeKVRows` on non-fresh rows); `extend`
468
- (keep-the-running-batch optimization, mlx-lm's actual join op) is deferred into
469
- the scheduler. Added `BatchedDecodeMaskCache.releaseRopeArr()`. (Originally
470
- full-attention only; sliding-window `BatchedRotatingCache` landed 2026-06-14 —
471
- see the STATUS block above.)
472
- (2) **DONE 2026-06-14** — the async loop + per-row SSE wired into `createServer`
473
- behind `--batch N`. `BatchScheduler` (`src/serve/batch-scheduler.ts`) is the
474
- detached driver; `GenerationGateway` (`src/serve/generation-gateway.ts`) picks
475
- the lane and an `AsyncMutex` keeps serial and batched off the GPU/`loraState`
476
- together. Both `handleChat` call sites route through `gateway.run`; per-row
477
- onToken closures give per-row SSE fan-out for free. Gated: `tests/batch-scheduler.test.ts`
478
- (teacher-forced KL, evict + join) + `tests/batch-serving.test.ts` (live CPM
479
- server, concurrent + streaming + drain). No serial regression (server 17/17,
480
- tools 13/13).
481
- (3) **DONE 2026-06-14** — `_is_batchable` gate is the `GenerationGateway.willBatch`
482
- predicate (full-attention model + no vision/adapter/repetition-penalty/user-seed
483
- → batch; else serial). *(The predicate has since widened: repetition
484
- penalty + logits extras now batch via per-row processors, 2026-07-02.)*
485
- `B×S_max` memory admission is still TODO — **DONE 2026-07-03**
486
- (`--kv-budget`, see the v2-plan pointer above).
487
-
488
- ## Fallbacks (must degrade, never break)
489
-
490
- - **Vision** → always solo. Image embeddings + bidirectional mask need
491
- offset-0 single-sequence prefill (`gemma4.ts:690` throws otherwise).
492
- - **LoRA** → batch only requests sharing the same adapter set, else
493
- drain to solo. `loraState.active` is a single per-generation field
494
- (`generate.ts:187`); cache namespaces are adapter-keyed
495
- (`prompt-cache.ts:18`). Per-row adapters is a much harder problem;
496
- group-by-adapter + drain is accepted (confirmed fine for our use).
497
- - **Fixed seed / unusual sampler** → drain to solo (matches `_is_batchable`).
498
- - **Compiled decode** (`generate.ts:348`) → start **disabled** under
499
- batching (single-sequence shapeless replay); extend to `[B,1]` later.
500
- - **Prompt cache** → per-row take/put still works, but the
501
- "single queue ⇒ take/reinsert is race-free" invariant
502
- (`prompt-cache.ts:7`, `lora.ts:7`) now depends on the **scheduler**
503
- being the sole owner of cache mutations. Keep all take/put inside the
504
- scheduler loop.
505
-
506
- ## Config surface
507
-
508
- - `ServerOptions.batch` (default 1); `kvBudgetBytes` (shipped 2026-07-03);
509
- later `promptConcurrency`.
510
- - CLI: `--batch N` (shipped; `--slots N` was the original design name, `--decode-concurrency` accepted as mlx_lm.server alias); `--kv-budget <GB>` (shipped 2026-07-03).
511
- - `batch === 1` → today's serialized promise chain, untouched.
512
- `batch > 1` → scheduler.
513
-
514
- ## Phasing (incremental, parity-gated)
515
-
516
- - **S0 — config seam (DONE).** `--slots N` (now shipped as `--batch N`) / `ServerOptions.slots`
517
- plumbed, validated, surfaced (ready card + `/stats`). `N>1` warns that
518
- batched execution lands in S1 and runs serially. No behavior change.
519
- - **S1 — static 2-wide, base model only. (DONE — S1b LIVE as `--batch N`)** Split by the reuse finding:
520
- - **S1a (prefill)** — reuse the training machinery (`buildBatchedPadMask`
521
- / `BatchedMaskCache`); wire the serving path to prefill B prompts in one
522
- forward. Mostly generalization + consolidating the two mask builders.
523
- - **S1b (decode) — SHIPPED 2026-06-14.** `BatchScheduler`
524
- (`src/serve/batch-scheduler.ts`) + `GenerationGateway`
525
- (`src/serve/generation-gateway.ts`) wired into `createServer` behind
526
- `--batch N`. L1 oracle-verified for CPM + all 3 Gemmas (full-attn +
527
- sliding-window via `BatchedRotatingCache`). Per-row SSE fan-out live.
528
- See STATUS block above for details.
529
- **Teacher-forced gate**: a 2-row batch must produce per-row logits matching
530
- two solo runs within bf16 tolerance (the train-batch-e2e methodology, now
531
- applied to decode).
532
- - **S2 — N-wide + continuous injection/eviction. (DONE)** Rows retire on EOS;
533
- queued requests prefill into freed rows (shipped with the scheduler).
534
- Dynamic byte-budget admission landed 2026-07-03 as `--kv-budget`
535
- (aggregate projection gate, queue-don't-OOM —
536
- grammar-spec-batching-integration.md Phase D).
537
- - **S3+ — paged KV (v1 cache class SHIPPED; density payoff DEFERRED)** —
538
- the block manager (`src/model/paged-kv.ts`) shipped behind default-off
539
- `--paged-kv` at serial scope, bit-exact-gated (paged-kv-cache.md); the
540
- batched block allocation, block-level CoW, and the custom
541
- paged-attention kernel remain the follow-ups. KV-quant under batch
542
- landed separately (Phase 3.1 + milestone 2); LoRA-group batching open.
543
-
544
- Every phase ships default-off behind `batch=1`; the serialized path is
545
- never removed (only bypassed).
546
-
547
- ## Open questions / to measure
548
-
549
- - Sliding-window per-row masking (above) — retire this correctness risk
550
- early.
551
- - `async_eval` decode pipelining (`generate.ts:386`) builds step n+1
552
- before reading n; one row stopping mid-pipeline while others continue
553
- needs care.
554
- - Throughput ceiling: the B where we cross bandwidth→compute bound, and
555
- aggregate tok/s vs solo — measured via `benchmark.sh` (preflight-gated;
556
- loaded-machine numbers don't count).
557
-
558
- ## References
559
-
560
- - mlx-lm continuous batching: `BatchGenerator` / `ResponseGenerator`,
561
- flags `--decode-concurrency` (default 32), `--prompt-concurrency`
562
- (default 8); `_is_batchable` gate; batch draining.
563
- https://deepwiki.com/ml-explore/mlx-lm/3.3-http-server ·
564
- https://github.com/ml-explore/mlx-lm/issues/499
565
- - optiq: **no** batching — single-process serialized, advises external
566
- max-concurrency; each in-flight request keeps a resident KV cache.
567
- https://mlx-optiq.com/docs/serve