mlx-bun 0.2.0 → 0.4.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 (433) hide show
  1. package/README.md +71 -588
  2. package/docs/README.md +94 -0
  3. package/docs/{design → archive/investigations}/audio-input-plan.md +4 -4
  4. package/docs/{design → archive/investigations}/diffusion-gemma-port.md +3 -3
  5. package/docs/{design → archive/investigations}/faithful-l1-consolidation.md +4 -4
  6. package/docs/{design → archive/investigations}/minicpm5-decode-megakernel.md +11 -11
  7. package/docs/archive/investigations/openjarvis-fit.md +198 -0
  8. package/docs/{design → archive/investigations}/optimization_plan.md +1 -1
  9. package/docs/{design → archive/investigations}/pre-colibri-stabilization.md +5 -5
  10. package/docs/archive/investigations/qwen38-27b-campaign-log.md +1092 -0
  11. package/docs/{design → archive/investigations}/the-dreaming-handoff.md +2 -2
  12. package/docs/archive/planning/release-notes-v0.3.0.md +35 -0
  13. package/docs/design/batching.md +1841 -0
  14. package/docs/design/decode-speed-program.md +2346 -169
  15. package/docs/design/dreaming-nightly-pipeline.md +675 -282
  16. package/docs/design/generic-model-support.md +655 -334
  17. package/docs/design/kv-cache.md +824 -0
  18. package/docs/design/orpo-training.md +673 -1239
  19. package/docs/design/speculative-decoding.md +1990 -0
  20. package/docs/design/turboquant.md +2212 -0
  21. package/docs/design/unified-engine-frontier-plan.md +1833 -727
  22. package/docs/design/web-chat-redesign.md +666 -1083
  23. package/docs/planning/PRODUCT_ROADMAP.md +50 -1
  24. package/docs/planning/release-notes-v0.4.0.md +45 -0
  25. package/docs/planning/video-series.md +2 -2
  26. package/docs/reference/benchmarks.md +3613 -0
  27. package/docs/reference/cli.md +434 -160
  28. package/docs/reference/distribution.md +328 -182
  29. package/docs/reference/environment.md +398 -0
  30. package/docs/reference/glossary.md +39 -0
  31. package/docs/reference/library-api.md +566 -168
  32. package/docs/reference/memory.md +227 -64
  33. package/docs/reference/models.md +113 -6
  34. package/docs/reference/server-api.md +944 -429
  35. package/docs/reference/server-config.md +740 -338
  36. package/docs/reference/training.md +393 -254
  37. package/docs/reference/troubleshooting.md +158 -0
  38. package/package.json +9 -4
  39. package/src/audio/decode.ts +2 -2
  40. package/src/audio/features.ts +2 -2
  41. package/src/backends/mlx/assistant-target.ts +46 -0
  42. package/src/backends/mlx/autoregressive-draft-rows.ts +120 -0
  43. package/src/backends/mlx/autoregressive.ts +108 -0
  44. package/src/backends/mlx/batch-group.ts +1790 -0
  45. package/src/backends/mlx/cache-attachment.ts +67 -0
  46. package/src/backends/mlx/cache-layout.ts +31 -0
  47. package/src/backends/mlx/checkpoint-state.ts +53 -0
  48. package/src/backends/mlx/continuation-persistence.ts +106 -0
  49. package/src/backends/mlx/continuation-request.ts +43 -0
  50. package/src/backends/mlx/continuation.ts +77 -0
  51. package/src/backends/mlx/diffusion.ts +39 -0
  52. package/src/backends/mlx/draft-target.ts +32 -0
  53. package/src/backends/mlx/evaluation.ts +105 -0
  54. package/src/backends/mlx/fill-append.ts +45 -0
  55. package/src/backends/mlx/full-kv-row-append.ts +32 -0
  56. package/src/backends/mlx/full-kv-row-donor.ts +54 -0
  57. package/src/backends/mlx/full-transitioning-kv-rows.ts +65 -0
  58. package/src/backends/mlx/gateway-binding.ts +130 -0
  59. package/src/backends/mlx/graph.ts +38 -0
  60. package/src/backends/mlx/kv-codec.ts +9 -0
  61. package/src/backends/mlx/kv-maintenance.ts +133 -0
  62. package/src/backends/mlx/kv-tensor-rows.ts +212 -0
  63. package/src/backends/mlx/memory-client.ts +189 -0
  64. package/src/backends/mlx/methods.ts +90 -0
  65. package/src/backends/mlx/model-prompt.ts +164 -0
  66. package/src/backends/mlx/model-serving.ts +79 -0
  67. package/src/backends/mlx/prefill-cohort.ts +89 -0
  68. package/src/backends/mlx/prefill-rows.ts +200 -0
  69. package/src/backends/mlx/prefill.ts +33 -0
  70. package/src/backends/mlx/projected-context-rows.ts +72 -0
  71. package/src/backends/mlx/request-policy.ts +20 -0
  72. package/src/backends/mlx/request-state-policy.ts +41 -0
  73. package/src/backends/mlx/rollback.ts +89 -0
  74. package/src/backends/mlx/row-sampling.ts +49 -0
  75. package/src/backends/mlx/serial-executor.ts +242 -0
  76. package/src/backends/mlx/speculative-group.ts +359 -0
  77. package/src/backends/mlx/speculative-round.ts +161 -0
  78. package/src/backends/mlx/speculative.ts +114 -0
  79. package/src/backends/mlx/state-rows.ts +104 -0
  80. package/src/backends/mlx/state-views.ts +46 -0
  81. package/src/backends/mlx/target-layout-capability.ts +35 -0
  82. package/src/backends/mlx/text-engine.ts +73 -0
  83. package/src/backends/mlx/transitioning-kv-rows.ts +165 -0
  84. package/src/cli.ts +66 -58
  85. package/src/client.ts +66 -0
  86. package/src/config.ts +21 -4
  87. package/src/contracts/completion.ts +16 -0
  88. package/src/contracts/execution.ts +60 -0
  89. package/src/contracts/generation.ts +103 -0
  90. package/src/contracts/host.ts +7 -0
  91. package/src/contracts/jobs.ts +66 -0
  92. package/src/contracts/pi-provider.ts +30 -0
  93. package/src/contracts/pi.ts +259 -0
  94. package/src/contracts/prefix-cache.ts +23 -0
  95. package/src/contracts/resources.ts +25 -0
  96. package/src/contracts/sampling.ts +23 -0
  97. package/src/contracts/scheduling.ts +45 -0
  98. package/src/contracts/tsconfig.json +14 -0
  99. package/src/diffusion/diffusion-generate.ts +218 -153
  100. package/src/embed.ts +1 -1
  101. package/src/engine/admission.ts +59 -0
  102. package/src/engine/cancellation.ts +38 -0
  103. package/src/engine/engine.ts +56 -0
  104. package/src/engine/execution-coordinator.ts +70 -0
  105. package/src/engine/execution-plan.ts +53 -0
  106. package/src/engine/resources.ts +53 -0
  107. package/src/engine/scheduler.ts +72 -0
  108. package/src/engine/session.ts +247 -0
  109. package/src/eval/README.md +44 -1
  110. package/src/eval/kl.ts +236 -3
  111. package/src/eval/reference-logits.ts +207 -0
  112. package/src/eval/runner.ts +8 -96
  113. package/src/fill/echo-index.ts +188 -0
  114. package/src/fill/fill-session.ts +452 -0
  115. package/src/fill/proposal.ts +68 -0
  116. package/src/fill/schema-rows.ts +506 -0
  117. package/src/fill/tool-boundary.ts +103 -0
  118. package/src/fit.ts +30 -16
  119. package/src/generate.ts +709 -417
  120. package/src/grammar.ts +12 -7
  121. package/src/harness-pi.ts +14 -33
  122. package/src/hub-rest.ts +4 -4
  123. package/src/index.ts +7 -0
  124. package/src/inference/denoising.ts +17 -0
  125. package/src/inference/draft-acceptance.ts +41 -0
  126. package/src/inference/draft-output.ts +49 -0
  127. package/src/inference/graph.ts +33 -0
  128. package/src/inference/prefill.ts +39 -0
  129. package/src/inference/rollback.ts +7 -0
  130. package/src/jobs/db.ts +4 -0
  131. package/src/jobs/index.ts +2 -0
  132. package/src/jobs/job-entry.ts +12 -12
  133. package/src/jobs/runner.ts +98 -13
  134. package/src/jobs/task-client.ts +19 -0
  135. package/src/jobs/types.ts +2 -58
  136. package/src/kv-scheme.ts +70 -12
  137. package/src/kv-store.ts +177 -63
  138. package/src/lab/README.md +18 -0
  139. package/src/{assets → lab/curve}/curve-designer.html +2 -2
  140. package/src/{curve-sampler.ts → lab/curve/curve-sampler.ts} +2 -2
  141. package/src/{expert-trace.ts → lab/expert-trace/expert-trace.ts} +5 -5
  142. package/src/lab/paged-kv/paged-kv-rows.ts +83 -0
  143. package/src/{model → lab/paged-kv}/paged-kv.ts +32 -15
  144. package/src/library.ts +40 -0
  145. package/src/lora.ts +18 -0
  146. package/src/media-fetch.ts +1 -1
  147. package/src/memory/article.ts +1 -1
  148. package/src/memory/chunk.ts +1 -1
  149. package/src/memory/model.ts +30 -169
  150. package/src/memory/prompts.ts +1 -1
  151. package/src/memory/rest.ts +1 -1
  152. package/src/memory/schedule.ts +1 -1
  153. package/src/memory/stages.ts +4 -1
  154. package/src/memory/vault.ts +1 -3
  155. package/src/mlx/array.ts +11 -11
  156. package/src/mlx/compile.ts +12 -2
  157. package/src/mlx/ffi.ts +58 -11
  158. package/src/mlx/materialize.ts +23 -0
  159. package/src/mlx/metal-kernel.ts +10 -11
  160. package/src/mlx/ops.ts +33 -13
  161. package/src/mlx/quantized-rows.ts +36 -0
  162. package/src/mlx/token-bitmask.ts +52 -0
  163. package/src/mlx/turboquant-kv-decode.ts +82 -0
  164. package/src/mlx/turboquant-kv-inverse.ts +122 -0
  165. package/src/mlx/turboquant-ops.ts +33 -3
  166. package/src/mlx/turboquant-tables.ts +2 -2
  167. package/src/model/artifact-identity.ts +73 -0
  168. package/src/model/batched-kv.ts +235 -0
  169. package/src/model/batched-mask.ts +20 -6
  170. package/src/model/batched-quant.ts +9 -3
  171. package/src/model/batched-quantized-kv.ts +144 -0
  172. package/src/model/batched-rotating-quant.ts +78 -17
  173. package/src/model/batched-rotating-state.ts +76 -0
  174. package/src/model/batched-rotating.ts +65 -23
  175. package/src/model/batched-row-storage.ts +52 -0
  176. package/src/model/batched-ssm.ts +52 -0
  177. package/src/model/batched-turboquant-kv.ts +93 -0
  178. package/src/model/compiled-decode.ts +8 -2
  179. package/src/model/decoded-kv-donor.ts +12 -0
  180. package/src/model/delayed-quantized-kv.ts +72 -0
  181. package/src/model/delayed-rotating-quantized-kv.ts +93 -0
  182. package/src/model/delayed-turboquant-kv.ts +55 -0
  183. package/src/model/diffusion-gemma.ts +8 -8
  184. package/src/model/factory.ts +76 -33
  185. package/src/model/fingerprint.ts +1 -1
  186. package/src/model/flash-attention.ts +3 -3
  187. package/src/model/full-prefill-padding.ts +56 -0
  188. package/src/model/full-prefill-row.ts +105 -0
  189. package/src/model/gemma4-base.ts +179 -80
  190. package/src/model/gemma4.ts +40 -32
  191. package/src/model/generated/gemma4-12b.ts +6 -3
  192. package/src/model/generated/gemma4-26b.ts +6 -3
  193. package/src/model/generated/gemma4-e4b.ts +6 -3
  194. package/src/model/glm52-cache.ts +80 -4
  195. package/src/model/glm52-dsa.ts +13 -9
  196. package/src/model/glm52-memory.ts +1 -1
  197. package/src/model/glm52-mla.ts +12 -6
  198. package/src/model/glm52.ts +19 -40
  199. package/src/model/implementation.ts +69 -0
  200. package/src/model/kv-attention-view.ts +67 -0
  201. package/src/model/minicpm5.ts +17 -15
  202. package/src/model/profile.ts +12 -2
  203. package/src/model/qwen-append.ts +12 -0
  204. package/src/model/qwen-conv.ts +52 -0
  205. package/src/model/qwen3-delta-state.ts +74 -0
  206. package/src/model/qwen3-delta.ts +59 -36
  207. package/src/model/qwen3_5-checkpoint.ts +164 -0
  208. package/src/model/qwen3_5.ts +202 -78
  209. package/src/model/rotating-kv-donor.ts +35 -0
  210. package/src/model/rotating-kv-layout.ts +262 -0
  211. package/src/model/rotating-row-transaction.ts +35 -0
  212. package/src/model/speculative-rotating-kv.ts +101 -0
  213. package/src/model/ssm-prefill-padding.ts +39 -0
  214. package/src/model/support.ts +1 -1
  215. package/src/model/trellis-balanced-scatter.ts +51 -0
  216. package/src/model/trellis-linear.ts +672 -0
  217. package/src/model/trellis-shared-m.ts +151 -0
  218. package/src/model/trellis-shared-scatter.ts +144 -0
  219. package/src/model/trellis-splitk-prefill.ts +158 -0
  220. package/src/model/trellis-tiled-prefill.ts +136 -0
  221. package/src/model/trellis-vector-expand.ts +62 -0
  222. package/src/model/trellis-wide-prefill.ts +94 -0
  223. package/src/model/turboquant-codec.ts +83 -0
  224. package/src/model/universal/dense.ts +15 -14
  225. package/src/model/universal/modules.ts +8 -1
  226. package/src/native-pack.ts +6 -5
  227. package/src/pi-provider.ts +6 -64
  228. package/src/pi-web.ts +8 -315
  229. package/src/preflight.ts +31 -15
  230. package/src/prompt-cache.ts +110 -69
  231. package/src/quantize/allocator.ts +1 -1
  232. package/src/quantize/config-writer.ts +10 -3
  233. package/src/quantize/rotate.ts +4 -4
  234. package/src/quantize/trellis.ts +586 -0
  235. package/src/responses.ts +28 -1
  236. package/src/runtime-config.ts +18 -4
  237. package/src/sampler.ts +40 -29
  238. package/src/serve/batch-scheduler.ts +8 -1671
  239. package/src/serve/chat-request.ts +391 -0
  240. package/src/serve/chat-stage.ts +195 -0
  241. package/src/serve/checkpoint-identity.ts +64 -0
  242. package/src/serve/completion-executor.ts +15 -7
  243. package/src/serve/discovery-routes.ts +12 -13
  244. package/src/serve/generation-gateway.ts +183 -213
  245. package/src/serve/http.ts +159 -0
  246. package/src/serve/inference-request.ts +79 -0
  247. package/src/serve/isolate.ts +297 -105
  248. package/src/serve/lab-routes.ts +3 -0
  249. package/src/serve/lane-registry.ts +2 -1
  250. package/src/serve/model-admin-routes.ts +7 -5
  251. package/src/serve/model-binding.ts +43 -0
  252. package/src/serve/model-host.ts +511 -0
  253. package/src/serve/openai-wire.ts +103 -0
  254. package/src/serve/parent-application.ts +56 -0
  255. package/src/serve/pipeline.ts +33 -0
  256. package/src/serve/preparation.ts +33 -0
  257. package/src/serve/request-plan.ts +23 -18
  258. package/src/serve/request-prep.ts +344 -0
  259. package/src/serve/response-lease.ts +24 -0
  260. package/src/serve/responses-client.ts +69 -0
  261. package/src/serve/session-completion-engine.ts +110 -0
  262. package/src/serve/session-search.ts +1 -1
  263. package/src/serve/static-routes.ts +5 -5
  264. package/src/serve/text-completion-stage.ts +104 -0
  265. package/src/serve/token-streams.ts +519 -0
  266. package/src/server.ts +335 -2711
  267. package/src/spec/assistant-rows.ts +92 -0
  268. package/src/spec/assistant-source.ts +31 -55
  269. package/src/spec/cache-identity.ts +22 -0
  270. package/src/spec/deepspec-rows.ts +29 -0
  271. package/src/spec/deepspec-source.ts +16 -14
  272. package/src/spec/dflash-source.ts +36 -12
  273. package/src/spec/drafter.ts +153 -142
  274. package/src/spec/dspark/ab-stats.ts +3 -3
  275. package/src/spec/dspark/calibration.ts +2 -2
  276. package/src/spec/dspark/deepspec-module.ts +97 -122
  277. package/src/spec/dspark/generate-dflash.ts +2 -2
  278. package/src/spec/dspark/generate.ts +2 -2
  279. package/src/spec/dspark/loader.ts +1 -1
  280. package/src/spec/dspark/module-dflash.ts +129 -172
  281. package/src/spec/dspark/module.ts +3 -3
  282. package/src/spec/dspark/quantize-drafter.ts +1 -1
  283. package/src/spec/generate.ts +1 -1
  284. package/src/spec/glm52-mtp-graph.ts +29 -0
  285. package/src/spec/glm52-mtp-rows.ts +132 -0
  286. package/src/spec/glm52-mtp-source.ts +54 -137
  287. package/src/spec/glm52-mtp-state.ts +33 -0
  288. package/src/spec/ngram-source.ts +93 -21
  289. package/src/spec/projected-draft-rows.ts +73 -0
  290. package/src/spec/qwen-mtp-module.ts +171 -0
  291. package/src/spec/qwen-mtp-rows.ts +219 -0
  292. package/src/spec/qwen-mtp-source.ts +147 -195
  293. package/src/spec/qwen-mtp-state.ts +30 -0
  294. package/src/spec/serve-loop.ts +201 -201
  295. package/src/spec/source.ts +110 -13
  296. package/src/spec/two-model-rows.ts +61 -0
  297. package/src/spec/two-model.ts +44 -102
  298. package/src/ssd-cache.ts +99 -19
  299. package/src/ssd-durability.ts +36 -20
  300. package/src/tool-call.ts +51 -14
  301. package/src/train/flash-cce.ts +2 -2
  302. package/src/train/forward.ts +6 -0
  303. package/src/train/loss.ts +1 -1
  304. package/src/train/prefix-shared.ts +5 -1
  305. package/src/train/segmented.ts +5 -3
  306. package/src/train/trainer.ts +6 -6
  307. package/src/vision/prompt.ts +1 -1
  308. package/src/vision/qwen3vl-tower.ts +52 -10
  309. package/src/web/app.html +1 -1
  310. package/src/web/app.js +62 -60
  311. package/src/web/src/adapters-panel.ts +3 -3
  312. package/src/web/src/assistant.ts +2 -36
  313. package/src/web/src/chat.ts +4 -4
  314. package/src/web/src/composer.ts +4 -4
  315. package/src/web/src/hub.ts +1 -1
  316. package/src/web/src/markdown.ts +0 -0
  317. package/src/web/src/memory-panel.ts +1 -1
  318. package/src/web/src/model-picker.ts +1 -1
  319. package/src/web/src/palette.ts +1 -1
  320. package/src/web/src/protocol.ts +3 -39
  321. package/src/web/src/sessions.ts +1 -1
  322. package/src/web/src/ui-catalog.ts +4 -18
  323. package/src/web-assets.ts +47 -0
  324. package/src/weights.ts +55 -9
  325. package/docs/archive/investigations/curve-runs/bisector-route.json +0 -98
  326. package/docs/archive/investigations/curve-runs/bisector-trace.json +0 -1393
  327. package/docs/archive/investigations/curve-runs/curve-terrain.json +0 -1179
  328. package/docs/archive/investigations/curve-runs/curve-terrain.svg +0 -42
  329. package/docs/archive/investigations/hlg-ab.html +0 -34
  330. package/docs/archive/investigations/hlg-explorer.html +0 -238
  331. package/docs/archive/investigations/hlg-figs/cliff.svg +0 -1
  332. package/docs/archive/investigations/hlg-figs/cross.svg +0 -1
  333. package/docs/archive/investigations/hlg-figs/edges.svg +0 -1
  334. package/docs/archive/investigations/hlg-figs/k20-grids.svg +0 -1
  335. package/docs/archive/investigations/hlg-figs/k20-peaks.svg +0 -1
  336. package/docs/archive/investigations/hlg-figs/lw-curve.svg +0 -1
  337. package/docs/archive/investigations/hlg-figs/terrain.svg +0 -1
  338. package/docs/archive/investigations/hlg-report.html +0 -262
  339. package/docs/archive/investigations/hlg-runs/hlg-ab.json +0 -149
  340. package/docs/archive/investigations/hlg-runs/hlg-blind-prompts.md +0 -87
  341. package/docs/archive/investigations/hlg-runs/hlg-blind.json +0 -107
  342. package/docs/archive/investigations/hlg-runs/hlg-cliff.json +0 -197
  343. package/docs/archive/investigations/hlg-runs/hlg-cross.json +0 -505
  344. package/docs/archive/investigations/hlg-runs/hlg-edge.json +0 -388
  345. package/docs/archive/investigations/hlg-runs/hlg-eval1.json +0 -25
  346. package/docs/archive/investigations/hlg-runs/hlg-frontier.json +0 -86
  347. package/docs/archive/investigations/hlg-runs/hlg-grid.json +0 -242
  348. package/docs/archive/investigations/hlg-runs/hlg-grid2.json +0 -343
  349. package/docs/archive/investigations/hlg-runs/hlg-lw-coarse.json +0 -110
  350. package/docs/archive/investigations/hlg-runs/hlg-map.json +0 -383
  351. package/docs/archive/investigations/hlg-runs/run0-original.log +0 -159
  352. package/docs/archive/investigations/hlg-runs/runA-recommended.log +0 -131
  353. package/docs/archive/investigations/hlg-runs/runAA-cliff-cpm5.log +0 -26
  354. package/docs/archive/investigations/hlg-runs/runAB-grid2-e4b.log +0 -18
  355. package/docs/archive/investigations/hlg-runs/runAC-peak-e4b-partial.log +0 -25
  356. package/docs/archive/investigations/hlg-runs/runAD-ab-samples.log +0 -8
  357. package/docs/archive/investigations/hlg-runs/runAE-edge-e4b.log +0 -51
  358. package/docs/archive/investigations/hlg-runs/runAF-logit-calibration.log +0 -10
  359. package/docs/archive/investigations/hlg-runs/runAG-eval-config2.log +0 -16
  360. package/docs/archive/investigations/hlg-runs/runB-gain-variants.log +0 -192
  361. package/docs/archive/investigations/hlg-runs/runC-pivots.log +0 -162
  362. package/docs/archive/investigations/hlg-runs/runD-oetf.log +0 -132
  363. package/docs/archive/investigations/hlg-runs/runE-eotf.log +0 -162
  364. package/docs/archive/investigations/hlg-runs/runF-shaper-working.log +0 -86
  365. package/docs/archive/investigations/hlg-runs/runG-shaper-sweep.log +0 -101
  366. package/docs/archive/investigations/hlg-runs/runH-wsweep.log +0 -176
  367. package/docs/archive/investigations/hlg-runs/runI-asweep.log +0 -176
  368. package/docs/archive/investigations/hlg-runs/runJ-knobmap.log +0 -49
  369. package/docs/archive/investigations/hlg-runs/runK-frontier.log +0 -19
  370. package/docs/archive/investigations/hlg-runs/runL-widemap.log +0 -65
  371. package/docs/archive/investigations/hlg-runs/runM-frontier-emb.log +0 -15
  372. package/docs/archive/investigations/hlg-runs/runN-verify.log +0 -116
  373. package/docs/archive/investigations/hlg-runs/runO-frontier-seed5000.log +0 -15
  374. package/docs/archive/investigations/hlg-runs/runP-grid-AxSm.log +0 -15
  375. package/docs/archive/investigations/hlg-runs/runQ-grid-tgW.log +0 -15
  376. package/docs/archive/investigations/hlg-runs/runR-blind-transcript.log +0 -79
  377. package/docs/archive/investigations/hlg-runs/runS-cross-coherence.log +0 -33
  378. package/docs/archive/investigations/hlg-runs/runT-cross-cpm5.log +0 -33
  379. package/docs/archive/investigations/hlg-runs/runU-cross-12b.log +0 -33
  380. package/docs/archive/investigations/hlg-runs/runV-lw-e4b.log +0 -22
  381. package/docs/archive/investigations/hlg-runs/runW-lw-12b.log +0 -22
  382. package/docs/archive/investigations/hlg-runs/runX-lw-cpm5.log +0 -22
  383. package/docs/archive/investigations/hlg-runs/runY-cliff-e4b.log +0 -26
  384. package/docs/archive/investigations/hlg-runs/runZ-cliff-12b.log +0 -26
  385. package/docs/archive/mlx-bun-lab-report.html +0 -275
  386. package/docs/archive/wwdc-mlx-bun.html +0 -848
  387. package/docs/design/adapters-end-to-end.md +0 -205
  388. package/docs/design/batching-perf-path.md +0 -255
  389. package/docs/design/batching-v2-plan.md +0 -529
  390. package/docs/design/bucketing-stage.md +0 -197
  391. package/docs/design/compat-cli-surface-design.md +0 -230
  392. package/docs/design/docs-reorg-plan.md +0 -165
  393. package/docs/design/dspark-serving-program.md +0 -371
  394. package/docs/design/dspark-speculative-decoding.md +0 -318
  395. package/docs/design/grammar-spec-batching-integration.md +0 -301
  396. package/docs/design/memory-inference-path.md +0 -422
  397. package/docs/design/memory-synthesis.md +0 -287
  398. package/docs/design/memory-system.md +0 -237
  399. package/docs/design/mlx-lm-serving-execution-seam.md +0 -81
  400. package/docs/design/mlx-lm-tool-parity-plan.md +0 -545
  401. package/docs/design/omlx-adoption-map.md +0 -98
  402. package/docs/design/orpo-dynamic-lambda.md +0 -220
  403. package/docs/design/orpo-future-enhancements.md +0 -340
  404. package/docs/design/paged-kv-cache.md +0 -214
  405. package/docs/design/parallel-slots.md +0 -567
  406. package/docs/design/parity-tier-dag.md +0 -161
  407. package/docs/design/repo-cleanup-plan.md +0 -231
  408. package/docs/design/runtime-isolation.md +0 -263
  409. package/docs/design/segmented-backward-training.md +0 -543
  410. package/docs/design/spec-decode-larger-targets.md +0 -229
  411. package/docs/design/ssd-kv-cold-tier.md +0 -415
  412. package/docs/design/structured-output.md +0 -484
  413. package/docs/design/tauri-desktop-app.md +0 -127
  414. package/docs/design/the-dreaming-master-plan.md +0 -445
  415. package/docs/design/turboquant-kv.md +0 -235
  416. package/docs/design/turboquant-weights.md +0 -467
  417. package/docs/design/w4a16-compute-precision-spike.md +0 -232
  418. package/docs/design/web-chat-beat-matrix.md +0 -315
  419. package/docs/design/write-pipeline-entity.md +0 -468
  420. package/docs/planning/IDEAS.md +0 -69
  421. package/docs/reference/embedding.md +0 -197
  422. package/docs/reference/features-matrix.md +0 -101
  423. package/docs/reference/orpo-quickstart.md +0 -135
  424. package/src/flags.ts +0 -11
  425. /package/docs/{design → archive/investigations}/colibri-glm52-port.md +0 -0
  426. /package/docs/{planning → archive/planning}/chunk-task-roadmap.md +0 -0
  427. /package/docs/{planning → archive/planning}/curve-sampler-research-plan.md +0 -0
  428. /package/docs/{planning → archive/planning}/memory-docs-and-dag-plan.md +0 -0
  429. /package/docs/{planning → archive/planning}/pi-integration-review.md +0 -0
  430. /package/docs/{planning → archive/planning}/release-notes-v0.2.0.md +0 -0
  431. /package/src/{memory-plan.ts → admission-plan.ts} +0 -0
  432. /package/{docs/archive/investigations → src/serve/assets}/curve-terrain.html +0 -0
  433. /package/{docs/dag → src/serve/assets}/training-inference-map.html +0 -0
@@ -1,8 +1,8 @@
1
- // TurboQuant KV-cache codec — pure mlx-op composition, ported op-for-op
1
+ // TurboQuant KV-cache codec — reference operations ported op-for-op
2
2
  // from the vendored vllm-metal reference
3
3
  // (lab/repro/vllm-metal-turboquant/turboquant_reference.py). See
4
- // docs/design/turboquant-kv.md for the algorithm writeup and
5
- // goldens/turboquant.json (via tests/turboquant-ops.test.ts) for the
4
+ // docs/design/turboquant.md for the algorithm writeup and
5
+ // goldens/turboquant.json (via tests/research/turboquant-ops.test.ts) for the
6
6
  // bit-exactness oracle. Quantization groups run along the LAST axis
7
7
  // (head_dim) only — never the token axis, per the standing invariant.
8
8
  //
@@ -27,6 +27,10 @@ import {
27
27
  type TurboQuantHeadDim, type TurboQuantValueBits,
28
28
  } from "./turboquant-tables";
29
29
 
30
+ import { tryJointDecodePackedKv, type PackedKvArrays } from "./turboquant-kv-decode";
31
+ import { tryDecodePackedKvInverse256 } from "./turboquant-kv-inverse";
32
+ import { isShapelessTracing } from "./compile";
33
+
30
34
  type S = MlxHandle;
31
35
 
32
36
  const BLOCK_SIZE = 32;
@@ -421,6 +425,32 @@ export function decodeValuesRotated(
421
425
  return outBf16;
422
426
  }
423
427
 
428
+ /** Optional exact packed K/V decode. Inputs stay borrowed; the caller owns
429
+ * the returned pair. The eager path keeps the reference inverse rotation. */
430
+ export function tryDecodePackedKv(
431
+ inputs: PackedKvArrays, kBits: number, vBits: number, headDim: number,
432
+ deferV = false, s: S = gpuStream,
433
+ ): [MlxArray, MlxArray] | null {
434
+ // Reject unsupported value widths before requesting their centroid table.
435
+ if (![2, 3, 4, 5, 8].includes(vBits)) return null;
436
+ if (!deferV && headDim === 256 && kBits === 8 && vBits === 3 &&
437
+ s === gpuStream && !isShapelessTracing() && inputs[0].shape[2]! >= 8192) {
438
+ const decoded = tryDecodePackedKvInverse256(
439
+ inputs, centroidsFor(vBits), signArray(256), kBits, vBits, headDim, s,
440
+ );
441
+ if (decoded) return decoded;
442
+ }
443
+ const decoded = tryJointDecodePackedKv(inputs, centroidsFor(vBits), kBits, vBits, headDim, deferV, s);
444
+ if (!decoded || deferV) return decoded;
445
+ const [key, rotated] = decoded;
446
+ try {
447
+ const value = fwht(rotated, false, s);
448
+ try { return [key, value.astype(Dtype.bfloat16, s)]; }
449
+ finally { value.dispose(); }
450
+ } catch (error) { key.dispose(); throw error; }
451
+ finally { rotated.dispose(); }
452
+ }
453
+
424
454
  /** Undo the value rotation on an attention output computed against
425
455
  * decodeValuesRotated values. f32 through the transform (matching the
426
456
  * eager decode path's precision), returned in the input's dtype. */
@@ -1,5 +1,5 @@
1
- // GENERATED by scripts/regen-turboquant-goldens.ts — DO NOT EDIT.
2
- // Regenerate: bun scripts/regen-turboquant-goldens.ts
1
+ // GENERATED by scripts/regen.ts turboquant — DO NOT EDIT.
2
+ // Regenerate: bun scripts/regen.ts turboquant
3
3
  //
4
4
  // Source: lab/repro/vllm-metal-turboquant/turboquant_reference.py (vendored
5
5
  // vllm-metal oracle, see PROVENANCE.md in that directory), executed under
@@ -0,0 +1,73 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import { mkdir, readFile, realpath, rename, rm, stat, writeFile } from "node:fs/promises";
3
+ import { homedir } from "node:os";
4
+ import { join } from "node:path";
5
+
6
+ /** Identity calculation owns bytes; this interface owns memo persistence. */
7
+ export interface ArtifactIdentityStore {
8
+ get(key: string): Promise<string | undefined>;
9
+ put(key: string, digest: string): void;
10
+ }
11
+
12
+ export class FileArtifactIdentityStore implements ArtifactIdentityStore {
13
+ readonly #memory = new Map<string, string>();
14
+ readonly #pending = new Set<Promise<void>>();
15
+ constructor(readonly directory: string) {}
16
+
17
+ async get(key: string): Promise<string | undefined> {
18
+ const memory = this.#memory.get(key);
19
+ if (memory) return memory;
20
+ try {
21
+ const digest = await readFile(join(this.directory, key), "utf8");
22
+ if (!/^[0-9a-f]{64}$/.test(digest)) return undefined;
23
+ this.#memory.set(key, digest);
24
+ return digest;
25
+ } catch { return undefined; }
26
+ }
27
+
28
+ put(key: string, digest: string): void {
29
+ this.#memory.set(key, digest);
30
+ const temporary = join(this.directory, `${key}.${process.pid}.${randomUUID()}.tmp`);
31
+ const write = async () => {
32
+ try {
33
+ await mkdir(this.directory, { recursive: true });
34
+ await writeFile(temporary, digest);
35
+ await rename(temporary, join(this.directory, key));
36
+ } catch { await rm(temporary, { force: true }).catch(() => {}); }
37
+ };
38
+ const pending = write().finally(() => this.#pending.delete(pending));
39
+ this.#pending.add(pending);
40
+ }
41
+
42
+ async flush(): Promise<void> { await Promise.all(this.#pending); }
43
+ }
44
+
45
+ const defaultStore = new FileArtifactIdentityStore(join(homedir(), ".cache", "mlx-bun", "artifact-identities"));
46
+ export interface ArtifactIdentityFile { name: string; path: string; }
47
+
48
+ async function revision(files: readonly ArtifactIdentityFile[]): Promise<string> {
49
+ return JSON.stringify(await Promise.all(files.map(async file => {
50
+ const path = await realpath(file.path), info = await stat(path, { bigint: true });
51
+ return [file.name, path, info.dev, info.ino, info.size, info.mtimeNs, info.ctimeNs].map(String);
52
+ })));
53
+ }
54
+
55
+ /** Preserve the existing seed/name/byte digest, reusing it while file revisions
56
+ * are unchanged. ctime catches same-size edits even when mtime is restored.
57
+ * A memo miss computes the full identity; persistence never delays its caller. */
58
+ export async function artifactIdentity(seed: string, input: readonly ArtifactIdentityFile[],
59
+ store: ArtifactIdentityStore = defaultStore): Promise<string> {
60
+ const files = [...input].sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
61
+ const before = await revision(files);
62
+ const key = createHash("sha256").update(JSON.stringify([seed, before])).digest("hex");
63
+ const cached = await store.get(key);
64
+ if (cached) return cached;
65
+ const digest = createHash("sha256").update(seed);
66
+ for (const file of files) {
67
+ digest.update(file.name);
68
+ for await (const chunk of Bun.file(file.path).stream()) digest.update(chunk);
69
+ }
70
+ const value = digest.digest("hex");
71
+ if (before === await revision(files)) store.put(key, value);
72
+ return value;
73
+ }
@@ -0,0 +1,235 @@
1
+ import { MlxArray } from "../mlx/array";
2
+ import { Dtype } from "../mlx/ffi";
3
+ import * as ops from "../mlx/ops";
4
+ import { materializeCopy } from "../mlx/materialize";
5
+ import { KVCache, isPlainKvCache, type BatchableCache, type Cache, type Mask, type PaddedPrefillCache, type PrefillPadding } from "./gemma4-base";
6
+ import { FullPrefillPadding } from "./full-prefill-padding";
7
+
8
+ /** Full-attention storage with independently advancing row positions.
9
+ * Speculative rollback changes coverage, without moving the retained KV.
10
+ * Subsequent appends overwrite each row's rejected suffix in place when MLX
11
+ * can donate the buffer. Queue membership is outside this layout. */
12
+ export class BatchedKVCache implements BatchableCache, PaddedPrefillCache {
13
+ keys: MlxArray | null = null;
14
+ values: MlxArray | null = null;
15
+ rowOffsets: number[] = [];
16
+ leftPad: number[] = [];
17
+ #rope: MlxArray | undefined;
18
+ #beforeRound: number[] | undefined;
19
+ readonly #padding = new FullPrefillPadding();
20
+
21
+ restorePrefillEnds(ends: readonly number[] | undefined): void { this.#padding.restoreEnds(ends); }
22
+ preparePrefill(padding: PrefillPadding): void {
23
+ this.#padding.prepare(this, padding); this.#positionsChanged();
24
+ }
25
+ finalizePrefill(): void {
26
+ const next = this.#padding.finalize(this.state(), this);
27
+ if (!next) return;
28
+ this.keys?.dispose(); this.values?.dispose();
29
+ this.keys = next[0] ?? null; this.values = next[1] ?? null;
30
+ this.#positionsChanged();
31
+ }
32
+
33
+ captureDonorRows(): import("./gemma4-base").KvDonorRows {
34
+ const B = this.rowOffsets.length, width = this.offset;
35
+ const keys = this.keys!.slice([0,0,0,0], [B,this.keys!.shape[1]!,width,this.keys!.shape[3]!]);
36
+ let values: MlxArray;
37
+ try { values = this.values!.slice([0,0,0,0], [B,this.values!.shape[1]!,width,this.values!.shape[3]!]); }
38
+ catch (error) { keys.dispose(); throw error; }
39
+ return { keys, values, offsets: [...this.rowOffsets], starts: [...this.leftPad], ends: this.#ends() };
40
+ }
41
+
42
+ signature(): string { return "kv:batched-positions"; }
43
+ get batchSize(): number | null { return this.rowOffsets.length || null; }
44
+ get offset(): number { return Math.max(0, ...this.#ends()); }
45
+ #ends(): number[] { return this.rowOffsets.map((offset, row) => offset + this.leftPad[row]!); }
46
+ get ropeOffsetArr(): MlxArray | undefined {
47
+ // A common unpadded position uses the existing scalar RoPE operation.
48
+ if (this.leftPad.every(pad => pad === 0) && this.rowOffsets.every(offset => offset === this.rowOffsets[0])) return undefined;
49
+ return this.#rope ??= ops.fromInt32(this.rowOffsets, [this.rowOffsets.length]);
50
+ }
51
+ #positionsChanged(): void { this.#rope?.dispose(); this.#rope = undefined; }
52
+ makeEmptyBatch(): BatchedKVCache { return new BatchedKVCache(); }
53
+ bytesPerToken(): number {
54
+ if (!this.keys || !this.values) return 0;
55
+ return (this.keys.nbytes + this.values.nbytes) / (this.rowOffsets.length * this.keys.shape[2]!);
56
+ }
57
+ projectedBytes(tokens: number): number { return this.bytesPerToken() * tokens; }
58
+ state(): MlxArray[] { return this.keys && this.values ? [this.keys, this.values] : []; }
59
+ isTrimmable(): boolean { return true; }
60
+ trim(count: number): void {
61
+ this.rowOffsets = this.rowOffsets.map(offset => offset - count);
62
+ this.#positionsChanged();
63
+ }
64
+ specRoundBegin(): void { this.#beforeRound = [...this.rowOffsets]; }
65
+ specRoundCommit(): void { this.#beforeRound = undefined; }
66
+ specRoundRollback(keep: number | readonly number[]): void {
67
+ this.rowOffsets = this.#beforeRound!.map((offset, row) => offset + (typeof keep === "number" ? keep : keep[row]!));
68
+ this.#beforeRound = undefined;
69
+ this.#positionsChanged();
70
+ }
71
+
72
+ makeMask(N: number, window: number | null): Mask {
73
+ const B = this.rowOffsets.length, ends = this.#ends(), S = this.offset + N;
74
+ if (window === null && this.leftPad.every(pad => pad === 0) && ends.every(end => end === ends[0]))
75
+ return { mode: N === 1 ? "" : "causal", arr: null };
76
+ using starts = ops.fromInt32(ends, [B, 1, 1, 1]);
77
+ using relative = ops.arange(0, N, 1, Dtype.int32);
78
+ using queries = ops.reshape(relative, [1, 1, N, 1]);
79
+ using position = ops.add(starts, queries);
80
+ using indices = ops.arange(0, S, 1, Dtype.int32);
81
+ using keys = ops.reshape(indices, [1, 1, 1, S]);
82
+ using pads = ops.fromInt32(this.leftPad, [B, 1, 1, 1]);
83
+ using causal = ops.lessEqual(keys, position);
84
+ using valid = ops.greaterEqual(keys, pads);
85
+ const mask = ops.logicalAnd(causal, valid);
86
+ if (window === null) return { mode: "array", arr: mask };
87
+ using windowSize = ops.fromInt32([window], []);
88
+ using limit = ops.add(keys, windowSize);
89
+ using within = ops.less(position, limit);
90
+ try { return { mode: "array", arr: ops.logicalAnd(mask, within) }; }
91
+ finally { mask.dispose(); }
92
+ }
93
+
94
+ updateAndFetch(k: MlxArray, v: MlxArray): [MlxArray, MlxArray] {
95
+ const B = this.rowOffsets.length, N = k.shape[2]!, ends = this.#ends();
96
+ const needed = Math.max(...ends) + N;
97
+ const grow = (array: MlxArray) => {
98
+ const [rows, heads, capacity, width] = array.shape as [number, number, number, number];
99
+ if (capacity >= needed) return array;
100
+ using padding = ops.zeros([rows, heads, Math.ceil(needed / 256) * 256 - capacity, width], array.dtype);
101
+ const grown = ops.concatAxis([array, padding], 2);
102
+ array.dispose();
103
+ return grown;
104
+ };
105
+ const allocate = (input: MlxArray) => ops.zeros(
106
+ [B, input.shape[1]!, Math.ceil(needed / 256) * 256, input.shape[3]!], input.dtype);
107
+ this.keys = this.keys ? grow(this.keys) : allocate(k);
108
+ this.values = this.values ? grow(this.values) : allocate(v);
109
+ const common = ends.every(end => end === ends[0]);
110
+ using positions = common ? null : ops.fromInt32(ends.flatMap(end =>
111
+ Array.from({ length: N }, (_, position) => end + position)), [B, 1, N, 1]);
112
+ const append = (array: MlxArray, values: MlxArray) => common
113
+ ? ops.sliceUpdate(array, values, [0, 0, ends[0]!, 0], [B, array.shape[1]!, ends[0]! + N, array.shape[3]!])
114
+ : ops.putAlongAxis(array, positions!, values, 2);
115
+ const keys = append(this.keys, k);
116
+ let values: MlxArray;
117
+ try { values = append(this.values, v); }
118
+ catch (error) { keys.dispose(); throw error; }
119
+ this.keys.dispose(); this.values.dispose();
120
+ this.keys = keys; this.values = values;
121
+ this.rowOffsets = this.rowOffsets.map(offset => offset + N);
122
+ this.#positionsChanged();
123
+ return [keys.slice([0, 0, 0, 0], [B, keys.shape[1]!, needed, keys.shape[3]!]),
124
+ values.slice([0, 0, 0, 0], [B, values.shape[1]!, needed, values.shape[3]!])];
125
+ }
126
+
127
+ mergeRows(rows: readonly Cache[]): void {
128
+ const only = rows.length === 1 ? rows[0] : undefined;
129
+ if (only && (only instanceof BatchedKVCache || isPlainKvCache(only))) {
130
+ // Preserve allocated capacity when a request starts alone. Immutable
131
+ // aliases transfer ownership without copying its prefill storage.
132
+ const keys = only.keys ? ops.copyOf(only.keys) : null;
133
+ let values: MlxArray | null;
134
+ try { values = only.values ? ops.copyOf(only.values) : null; }
135
+ catch (error) { keys?.dispose(); throw error; }
136
+ const offsets = only instanceof BatchedKVCache ? [...only.rowOffsets] : [only.offset];
137
+ const pads = only instanceof BatchedKVCache ? [...only.leftPad] : [0];
138
+ this.dispose(); this.keys = keys; this.values = values;
139
+ this.rowOffsets = offsets; this.leftPad = pads;
140
+ return;
141
+ }
142
+ const chunks: Array<{ keys: MlxArray | null; values: MlxArray | null; offsets: number[]; pads: number[]; width: number }> = [];
143
+ const held: MlxArray[] = [];
144
+ try {
145
+ for (const cache of rows) {
146
+ let keys: MlxArray | null, values: MlxArray | null, offsets: number[], pads: number[];
147
+ if (cache instanceof BatchedKVCache && cache.offset === 0) {
148
+ keys = values = null; offsets = [...cache.rowOffsets]; pads = [...cache.leftPad];
149
+ } else if (cache instanceof BatchedKVCache) {
150
+ const B = cache.rowOffsets.length, width = cache.offset;
151
+ keys = cache.keys!.slice([0, 0, 0, 0], [B, cache.keys!.shape[1]!, width, cache.keys!.shape[3]!]);
152
+ held.push(keys);
153
+ values = cache.values!.slice([0, 0, 0, 0], [B, cache.values!.shape[1]!, width, cache.values!.shape[3]!]);
154
+ held.push(values);
155
+ offsets = [...cache.rowOffsets]; pads = [...cache.leftPad];
156
+ } else if (isPlainKvCache(cache)) {
157
+ if (cache.offset === 0) keys = values = null;
158
+ else { [keys, values] = cache.temporalView(); held.push(keys, values); }
159
+ offsets = [cache.offset]; pads = [0];
160
+ } else throw new Error(`full-attention batch cannot merge ${cache.signature()}`);
161
+ chunks.push({ keys, values, offsets, pads, width: keys?.shape[2] ?? 0 });
162
+ }
163
+ const width = Math.max(0, ...chunks.map(chunk => chunk.width));
164
+ const padded = (array: MlxArray, count: number) => {
165
+ if (!count) return array;
166
+ using padding = ops.zeros([array.shape[0]!, array.shape[1]!, count, array.shape[3]!], array.dtype);
167
+ const result = ops.concatAxis([padding, array], 2); held.push(result); return result;
168
+ };
169
+ const merge = (field: "keys" | "values") => {
170
+ if (!width) return null; // Geometry arrives with the first append.
171
+ const prototype = chunks.find(chunk => chunk[field])![field]!;
172
+ return ops.concatAxis(chunks.map(chunk => {
173
+ const array = chunk[field];
174
+ if (array) return padded(array, width - chunk.width);
175
+ const empty = ops.zeros([chunk.offsets.length, prototype.shape[1]!, width, prototype.shape[3]!], prototype.dtype);
176
+ held.push(empty); return empty;
177
+ }), 0);
178
+ };
179
+ const keys = merge("keys");
180
+ let values: MlxArray | null;
181
+ try { values = merge("values"); }
182
+ catch (error) { keys?.dispose(); throw error; }
183
+ this.dispose();
184
+ this.keys = keys; this.values = values;
185
+ this.rowOffsets = chunks.flatMap(chunk => chunk.offsets);
186
+ this.leftPad = chunks.flatMap(chunk => chunk.pads.map(pad => pad + width - chunk.width));
187
+ } finally { for (const array of held) array.dispose(); }
188
+ }
189
+
190
+ filterRows(rows: readonly number[]): void {
191
+ if (!rows.length) { this.dispose(); return; }
192
+ const pads = rows.map(row => this.leftPad[row]!);
193
+ const sharedPadding = Math.min(...pads);
194
+ using indices = this.keys ? ops.fromInt32([...rows], [rows.length]) : null;
195
+ const select = (array: MlxArray | null): MlxArray | null => {
196
+ if (!array) return null;
197
+ const selected = ops.takeAxis(array, indices!, 0);
198
+ if (!sharedPadding) return selected;
199
+ // mlx-lm BatchKVCache.filter removes padding common to all survivors.
200
+ // Keeping it changes attention reduction geometry after retirement.
201
+ try { return selected.slice([0, 0, sharedPadding, 0], [...selected.shape]); }
202
+ finally { selected.dispose(); }
203
+ };
204
+ const keys = select(this.keys);
205
+ let values: MlxArray | null;
206
+ try { values = select(this.values); }
207
+ catch (error) { keys?.dispose(); throw error; }
208
+ this.keys?.dispose(); this.values?.dispose();
209
+ this.keys = keys; this.values = values;
210
+ this.rowOffsets = rows.map(row => this.rowOffsets[row]!);
211
+ this.leftPad = pads.map(pad => pad - sharedPadding);
212
+ this.#padding.filter(rows);
213
+ this.#positionsChanged();
214
+ }
215
+
216
+ extractRow(row: number): KVCache {
217
+ const start = this.leftPad[row]!, count = this.rowOffsets[row]!;
218
+ if (count === 0) return new KVCache();
219
+ const copy = (array: MlxArray) => {
220
+ using view = array.slice([row, 0, start, 0], [row + 1, array.shape[1]!, start + count, array.shape[3]!]);
221
+ return materializeCopy(view);
222
+ };
223
+ const keys = copy(this.keys!);
224
+ let values: MlxArray;
225
+ try { values = copy(this.values!); }
226
+ catch (error) { keys.dispose(); throw error; }
227
+ const cache = new KVCache(); cache.restoreState(keys, values, count); return cache;
228
+ }
229
+
230
+ dispose(): void {
231
+ this.keys?.dispose(); this.values?.dispose(); this.#positionsChanged();
232
+ this.keys = this.values = null;
233
+ this.rowOffsets = []; this.leftPad = []; this.#beforeRound = undefined; this.#padding.clear();
234
+ }
235
+ }
@@ -2,11 +2,11 @@
2
2
  //
3
3
  // Companion to src/train/forward.ts's buildBatchedPadMask, which builds the
4
4
  // PREFILL mask ([B,1,L,L], offset 0, right-padded — the training case, already
5
- // parity-proven by tests/train-batch-e2e.test.ts). This module covers the
5
+ // parity-proven by tests/parity/train-batch-e2e.test.ts). This module covers the
6
6
  // DECODE case for batched serving: left-padded rows sharing one growing
7
7
  // [B,H,S,D] KV buffer, where a step forwards N new query tokens at a nonzero
8
8
  // offset. (The two builders should be consolidated when batched prefill is
9
- // wired into the serving path — phase S1a, docs/design/parallel-slots.md.)
9
+ // wired into the serving path — phase S1a, docs/design/batching.md.)
10
10
  //
11
11
  // Why left-padding for decode: with rows right-aligned in the KV buffer, every
12
12
  // row's next write lands in the SAME column, so one advancing offset serves
@@ -15,6 +15,7 @@
15
15
  // thing the mask must encode is (a) causality, (b) each row's left padding,
16
16
  // and (c) the sliding window, if any.
17
17
 
18
+ import { cacheSignature } from "./gemma4-base";
18
19
  import { MlxArray } from "../mlx/array";
19
20
  import * as ops from "../mlx/ops";
20
21
  import { createCausalMask, KVCache, type Cache, type Mask } from "./gemma4-base";
@@ -92,6 +93,11 @@ export class BatchedDecodeMaskCache implements Cache {
92
93
  #ropeArr: MlxArray | null = null;
93
94
  #ropeForOffset = -1;
94
95
 
96
+ /** A decorator, not a storage kind: never claims the inner cache's
97
+ * identity (isPlainKvCache(wrapper) must stay false — it is not a
98
+ * KVCache), but records what it wraps for diagnostics/persistence. */
99
+ signature(): string { return `kv:batched-mask:${cacheSignature(this.inner)}`; }
100
+
95
101
  constructor(
96
102
  private readonly inner: Cache,
97
103
  private readonly B: number,
@@ -253,13 +259,21 @@ export function extractKVRow(cache: KVCache, leftPad: number, i: number): KVCach
253
259
 
254
260
  /** mlx-lm `filter`: keep only `keep` (sorted row indices) along the batch
255
261
  * axis — eviction of finished sequences from a batched [B,H,S,D] buffer.
256
- * Caller owns the result; inputs are not disposed. */
262
+ * `trimLeft` removes padding shared by all survivors (the caller updates
263
+ * offsets and per-row padding). Caller owns the result; inputs stay owned. */
257
264
  export function filterKVRows(
258
- keys: MlxArray, values: MlxArray, keep: number[],
265
+ keys: MlxArray, values: MlxArray, keep: number[], trimLeft = 0,
259
266
  ): { keys: MlxArray; values: MlxArray } {
260
267
  const idx = MlxArray.fromInt32(Int32Array.from(keep), [keep.length]);
261
- const k = ops.takeAxis(keys, idx, 0);
262
- const v = ops.takeAxis(values, idx, 0);
268
+ const take = (a: MlxArray) => {
269
+ const rows = ops.takeAxis(a, idx, 0);
270
+ if (trimLeft === 0) return rows;
271
+ const trimmed = rows.slice([0, 0, trimLeft, 0], rows.shape);
272
+ rows.dispose();
273
+ return trimmed;
274
+ };
275
+ const k = take(keys);
276
+ const v = take(values);
263
277
  idx.dispose();
264
278
  return { keys: k, values: v };
265
279
  }
@@ -17,7 +17,7 @@
17
17
  // (maybeQuantizeKv at chunk boundaries — see BatchScheduler.#quantizeSolo),
18
18
  // so a row's quantized bytes are bit-exact vs serial `--kv-quant config` by
19
19
  // construction; this module only re-arranges those bytes across the batch
20
- // axis. Gates: tests/batched-kv-quant-parity.test.ts.
20
+ // axis. Gates: tests/parity/batched-kv-quant-parity.test.ts.
21
21
 
22
22
  import { MlxArray } from "../mlx/array";
23
23
  import * as ops from "../mlx/ops";
@@ -134,11 +134,17 @@ export function extractQuantRow(
134
134
  /** Quantized twin of filterKVRows: keep `keep` rows along the batch axis.
135
135
  * Caller owns the result; inputs are not disposed. */
136
136
  export function filterQuantRows(
137
- keys: ops.QuantizedTensor, values: ops.QuantizedTensor, keep: number[],
137
+ keys: ops.QuantizedTensor, values: ops.QuantizedTensor, keep: number[], trimLeft = 0,
138
138
  ): { keys: ops.QuantizedTensor; values: ops.QuantizedTensor } {
139
139
  const idx = MlxArray.fromInt32(Int32Array.from(keep), [keep.length]);
140
140
  const take = (t: ops.QuantizedTensor): ops.QuantizedTensor =>
141
- tripleMap(t, (a) => ops.takeAxis(a, idx, 0));
141
+ tripleMap(t, (a) => {
142
+ const rows = ops.takeAxis(a, idx, 0);
143
+ if (trimLeft === 0) return rows;
144
+ const trimmed = rows.slice([0, 0, trimLeft, 0], rows.shape);
145
+ rows.dispose();
146
+ return trimmed;
147
+ });
142
148
  const k = take(keys);
143
149
  const v = take(values);
144
150
  idx.dispose();
@@ -0,0 +1,144 @@
1
+ import type { MlxArray } from "../mlx/array";
2
+ import * as ops from "../mlx/ops";
3
+ import { cleanupFailure, disposeResources } from "../engine/resources";
4
+ import { BatchedKVCache } from "./batched-kv";
5
+ import { KVCache, QuantizedKVCache, isQuantizedKvCache,
6
+ type BatchableCache, type Cache, type Mask, type QuantizedAttentionState, type PaddedPrefillCache, type PrefillPadding } from "./gemma4-base";
7
+
8
+ import { KvTensorRows } from "../backends/mlx/kv-tensor-rows";
9
+ import { quantizedDonorAttention } from "./kv-attention-view";
10
+
11
+ const FIELDS = ["packed", "scales", "biases"] as const;
12
+
13
+ /** Affine KV uses the same row-position storage operations for its packed
14
+ * values, scales and biases. No dequantization or requantization is needed
15
+ * for merge, rollback, retirement or extraction. Inputs share this codec. */
16
+ export class BatchedQuantizedKVCache implements BatchableCache, QuantizedAttentionState, PaddedPrefillCache {
17
+ #planes = FIELDS.map(() => new BatchedKVCache());
18
+ #minimum: number[] = [];
19
+ get minimumReusableOffset(): number { return Math.max(0, ...this.#minimum); }
20
+ constructor(readonly groupSize: number, readonly bits: number) {}
21
+ restorePrefillEnds(ends: readonly number[] | undefined): void { for (const plane of this.#planes) plane.restorePrefillEnds(ends); }
22
+ preparePrefill(padding: PrefillPadding): void {
23
+ for (const plane of this.#planes) plane.preparePrefill(padding);
24
+ if (!this.#minimum.length) this.#minimum = padding.lengths.map(() => 0);
25
+ }
26
+ finalizePrefill(): void { for (const plane of this.#planes) plane.finalizePrefill(); }
27
+ get quantizedAttention(): QuantizedAttentionState { return this; }
28
+ captureDonorAttention(): import("./gemma4-base").KvDonorAttention {
29
+ const views: import("./gemma4-base").KvDonorRows[] = [];
30
+ try {
31
+ for (const plane of this.#planes) views.push(plane.captureDonorRows());
32
+ const field = (name: "keys" | "values") => ({
33
+ packed: views[0]![name], scales: views[1]![name], biases: views[2]![name],
34
+ });
35
+ const { offsets, starts, ends } = views[0]!;
36
+ return quantizedDonorAttention(field("keys"), field("values"), { offsets, starts, ends }, this.groupSize, this.bits);
37
+ } catch (error) { disposeResources(views.flatMap(view => [view.keys, view.values])); throw error; }
38
+ }
39
+ signature(): string { return `kv:batched-quant:${this.bits}:${this.groupSize}`; }
40
+ get rowOffsets(): readonly number[] { return this.#planes[0]!.rowOffsets; }
41
+ get leftPad(): readonly number[] { return this.#planes[0]!.leftPad; }
42
+ get batchSize(): number | null { return this.#planes[0]!.batchSize; }
43
+ get offset(): number { return this.#planes[0]!.offset; }
44
+ get ropeOffsetArr(): MlxArray | undefined { return this.#planes[0]!.ropeOffsetArr; }
45
+ makeEmptyBatch(): BatchedQuantizedKVCache { return new BatchedQuantizedKVCache(this.groupSize, this.bits); }
46
+ bytesPerToken(): number { return this.#planes.reduce((n, plane) => n + plane.bytesPerToken(), 0); }
47
+ projectedBytes(tokens: number): number { return this.bytesPerToken() * tokens; }
48
+ makeMask(tokens: number, window: number | null): Mask { return this.#planes[0]!.makeMask(tokens, window); }
49
+ isTrimmable(): boolean { return true; }
50
+ trim(count: number): void { for (const plane of this.#planes) plane.trim(count); }
51
+ specRoundBegin(): void { for (const plane of this.#planes) plane.specRoundBegin(); }
52
+ specRoundCommit(): void { for (const plane of this.#planes) plane.specRoundCommit(); }
53
+ specRoundRollback(keep: number | readonly number[]): void {
54
+ for (const plane of this.#planes) plane.specRoundRollback(keep);
55
+ }
56
+ updateAndFetch(): [MlxArray, MlxArray] { throw new Error("quantized attention uses updateAndFetchQuantized"); }
57
+
58
+ updateAndFetchQuantized(k: MlxArray, v: MlxArray): [ops.QuantizedTensor, ops.QuantizedTensor] {
59
+ using inputs = new DisposableStack();
60
+ const key = ops.quantize(k, this.groupSize, this.bits);
61
+ for (const field of FIELDS) inputs.use(key[field]);
62
+ const value = ops.quantize(v, this.groupSize, this.bits);
63
+ for (const field of FIELDS) inputs.use(value[field]);
64
+ const held: MlxArray[] = [], keys: Partial<ops.QuantizedTensor> = {}, values: Partial<ops.QuantizedTensor> = {};
65
+ try {
66
+ for (const [index, field] of FIELDS.entries()) {
67
+ const [kk, vv] = this.#planes[index]!.updateAndFetch(key[field], value[field]);
68
+ held.push(kk, vv); keys[field] = kk; values[field] = vv;
69
+ }
70
+ return [keys as ops.QuantizedTensor, values as ops.QuantizedTensor];
71
+ } catch (error) { return cleanupFailure(error, () => disposeResources(held)); }
72
+ }
73
+
74
+ /** Borrow prepared request state. All planes stage replacements before
75
+ * releasing the current group, preserving donor ownership on failure. */
76
+ mergeRows(rows: readonly Cache[]): void {
77
+ const next: BatchedKVCache[] = [];
78
+ try {
79
+ for (const [index, field] of FIELDS.entries()) {
80
+ const held: KVCache[] = [], inputs: Cache[] = [];
81
+ try {
82
+ for (const row of rows) {
83
+ if (row instanceof BatchedQuantizedKVCache) inputs.push(row.#planes[index]!);
84
+ else if (isQuantizedKvCache(row)) {
85
+ const view = new KVCache(); held.push(view);
86
+ if (row.offset > 0) {
87
+ const key = ops.copyOf(row.keys![field]);
88
+ let value: MlxArray;
89
+ try { value = ops.copyOf(row.values![field]); }
90
+ catch (error) { key.dispose(); throw error; }
91
+ view.restoreState(key, value, row.offset);
92
+ }
93
+ inputs.push(view);
94
+ } else throw new Error(`quantized row layout cannot merge ${row.signature()}`);
95
+ }
96
+ const plane = new BatchedKVCache(); next.push(plane); plane.mergeRows(inputs);
97
+ } finally { disposeResources(held); }
98
+ }
99
+ } catch (error) { return cleanupFailure(error, () => disposeResources(next)); }
100
+ const previous = this.#planes; this.#planes = next; disposeResources(previous);
101
+ this.#minimum = rows.flatMap(row => row instanceof BatchedQuantizedKVCache ? row.#minimum : [row.minimumReusableOffset ?? 0]);
102
+ }
103
+
104
+ alignRows(leftPad: readonly number[]): void {
105
+ if (leftPad.every((pad, row) => pad === this.leftPad[row])) return;
106
+ const aligned = new KvTensorRows();
107
+ try {
108
+ aligned.mergeRows([{ planes: this.#planes.flatMap(plane => plane.keys ? [plane.keys, plane.values!] : []),
109
+ rowOffsets: this.rowOffsets, leftPad: this.leftPad }]);
110
+ aligned.alignRows(leftPad);
111
+ for (const plane of this.#planes) {
112
+ plane.keys?.dispose(); plane.values?.dispose();
113
+ plane.keys = aligned.planes.shift() ?? null; plane.values = aligned.planes.shift() ?? null;
114
+ plane.leftPad = [...leftPad];
115
+ }
116
+ } finally { aligned.dispose(); }
117
+ }
118
+ filterRows(keep: readonly number[]): void {
119
+ for (const plane of this.#planes) plane.filterRows(keep);
120
+ this.#minimum = keep.map(row => this.#minimum[row]!);
121
+ }
122
+ state(): MlxArray[] {
123
+ if (!this.#planes[0]!.keys) return [];
124
+ return [...this.#planes.map(plane => plane.keys!), ...this.#planes.map(plane => plane.values!)];
125
+ }
126
+
127
+ /** Return the existing persisted representation, owning compact arrays. */
128
+ extractRow(row: number): QuantizedKVCache {
129
+ const offset = this.rowOffsets[row]!, result = new QuantizedKVCache(this.groupSize, this.bits);
130
+ if (this.#minimum[row]) result.minimumReusableOffset = this.#minimum[row];
131
+ if (offset === 0) return result;
132
+ const held: KVCache[] = [];
133
+ try {
134
+ for (const plane of this.#planes) held.push(plane.extractRow(row));
135
+ result.restoreState(
136
+ { packed: held[0]!.keys!, scales: held[1]!.keys!, biases: held[2]!.keys! },
137
+ { packed: held[0]!.values!, scales: held[1]!.values!, biases: held[2]!.values! }, offset);
138
+ for (const cache of held) cache.keys = cache.values = null;
139
+ return result;
140
+ } finally { disposeResources(held); }
141
+ }
142
+
143
+ dispose(): void { disposeResources(this.#planes); this.#minimum = []; }
144
+ }