mlx-bun 0.0.13 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (427) hide show
  1. package/README.md +71 -578
  2. package/bin/mlx-bun.mjs +1 -1
  3. package/docs/README.md +91 -0
  4. package/docs/{design → archive}/hlg-sampling.md +3 -3
  5. package/docs/{design → archive/investigations}/audio-input-plan.md +4 -4
  6. package/docs/{design → archive/investigations}/colibri-glm52-port.md +1 -1
  7. package/docs/{design → archive/investigations}/diffusion-gemma-port.md +3 -3
  8. package/docs/{design → archive/investigations}/faithful-l1-consolidation.md +4 -4
  9. package/docs/{investigations → archive/investigations}/hlg-sampling-investigation.md +1 -1
  10. package/docs/{design → archive/investigations}/minicpm5-decode-megakernel.md +11 -11
  11. package/docs/{design → archive/investigations}/optimization_plan.md +1 -1
  12. package/docs/archive/investigations/pareto-specialized-runtime-findings.md +92 -0
  13. package/docs/{design → archive/investigations}/pre-colibri-stabilization.md +5 -5
  14. package/docs/{design → archive/investigations}/the-dreaming-handoff.md +2 -2
  15. package/docs/{planning → archive/planning}/chat-ui-vision.md +1 -1
  16. package/docs/{planning → archive/planning}/chunk-task-roadmap.md +1 -1
  17. package/docs/{planning → archive/planning}/curve-sampler-research-plan.md +4 -4
  18. package/docs/{planning → archive/planning}/memory-docs-and-dag-plan.md +1 -1
  19. package/docs/{planning → archive/planning}/release-notes-v0.0.9.md +1 -1
  20. package/docs/archive/planning/release-notes-v0.2.0.md +49 -0
  21. package/docs/{planning → archive/planning}/website-readme-pass-plan.md +1 -1
  22. package/docs/design/batching.md +569 -0
  23. package/docs/design/decode-speed-program.md +3178 -169
  24. package/docs/design/dreaming-nightly-pipeline.md +675 -282
  25. package/docs/design/generic-model-support.md +579 -334
  26. package/docs/design/kv-cache.md +680 -0
  27. package/docs/design/orpo-training.md +673 -1239
  28. package/docs/design/speculative-decoding.md +1627 -0
  29. package/docs/design/turboquant.md +2102 -0
  30. package/docs/design/unified-engine-frontier-plan.md +1831 -727
  31. package/docs/design/web-chat-redesign.md +666 -1083
  32. package/docs/planning/PRODUCT_ROADMAP.md +34 -1
  33. package/docs/planning/release-notes-v0.3.0.md +35 -0
  34. package/docs/planning/video-series.md +421 -0
  35. package/docs/reference/benchmarks.md +1162 -0
  36. package/docs/reference/cli.md +395 -149
  37. package/docs/reference/distribution.md +321 -181
  38. package/docs/reference/environment.md +348 -0
  39. package/docs/reference/glossary.md +39 -0
  40. package/docs/reference/library-api.md +580 -142
  41. package/docs/reference/memory.md +227 -64
  42. package/docs/reference/models.md +137 -6
  43. package/docs/reference/server-api.md +938 -422
  44. package/docs/reference/server-config.md +652 -320
  45. package/docs/reference/training.md +393 -254
  46. package/docs/reference/troubleshooting.md +166 -0
  47. package/package.json +10 -5
  48. package/src/admission-plan.ts +18 -0
  49. package/src/anthropic.ts +92 -3
  50. package/src/audio/decode.ts +2 -2
  51. package/src/audio/features.ts +2 -2
  52. package/src/backends/mlx/autoregressive.ts +108 -0
  53. package/src/backends/mlx/batch-group.ts +1646 -0
  54. package/src/backends/mlx/diffusion.ts +39 -0
  55. package/src/backends/mlx/draft-target.ts +47 -0
  56. package/src/backends/mlx/evaluation.ts +105 -0
  57. package/src/backends/mlx/fill-append.ts +45 -0
  58. package/src/backends/mlx/gateway-binding.ts +71 -0
  59. package/src/backends/mlx/graph.ts +38 -0
  60. package/src/backends/mlx/memory-client.ts +189 -0
  61. package/src/backends/mlx/memory-guard.ts +40 -0
  62. package/src/backends/mlx/methods.ts +90 -0
  63. package/src/backends/mlx/model-prompt.ts +164 -0
  64. package/src/backends/mlx/model-serving.ts +79 -0
  65. package/src/backends/mlx/prefill.ts +33 -0
  66. package/src/backends/mlx/request-policy.ts +20 -0
  67. package/src/backends/mlx/rollback.ts +51 -0
  68. package/src/backends/mlx/serial-executor.ts +273 -0
  69. package/src/backends/mlx/speculative.ts +114 -0
  70. package/src/backends/mlx/state-views.ts +26 -0
  71. package/src/backends/mlx/text-engine.ts +73 -0
  72. package/src/cli.ts +189 -61
  73. package/src/client.ts +66 -0
  74. package/src/config.ts +21 -4
  75. package/src/contracts/completion.ts +16 -0
  76. package/src/contracts/execution.ts +49 -0
  77. package/src/contracts/generation.ts +103 -0
  78. package/src/contracts/host.ts +7 -0
  79. package/src/contracts/jobs.ts +66 -0
  80. package/src/contracts/pi-provider.ts +30 -0
  81. package/src/contracts/pi.ts +259 -0
  82. package/src/contracts/resources.ts +18 -0
  83. package/src/contracts/scheduling.ts +27 -0
  84. package/src/contracts/tsconfig.json +14 -0
  85. package/src/diffusion/diffusion-generate.ts +218 -153
  86. package/src/embed.ts +1 -1
  87. package/src/engine/admission.ts +59 -0
  88. package/src/engine/cancellation.ts +38 -0
  89. package/src/engine/engine.ts +56 -0
  90. package/src/engine/execution-coordinator.ts +70 -0
  91. package/src/engine/execution-plan.ts +51 -0
  92. package/src/engine/resources.ts +42 -0
  93. package/src/engine/scheduler.ts +67 -0
  94. package/src/engine/session.ts +247 -0
  95. package/src/eval/README.md +44 -1
  96. package/src/eval/kl.ts +263 -23
  97. package/src/eval/reference-logits.ts +207 -0
  98. package/src/eval/runner.ts +10 -79
  99. package/src/eval/tasks/bfcl.ts +2 -1
  100. package/src/eval/tasks/gsm8k.ts +2 -1
  101. package/src/eval/tasks/hashhop.ts +2 -1
  102. package/src/eval/tasks/humaneval.ts +5 -4
  103. package/src/eval/tasks/ifeval.ts +2 -1
  104. package/src/eval/tasks/mmlu.ts +2 -1
  105. package/src/expert-io.ts +3 -1
  106. package/src/expert-offload.ts +3 -1
  107. package/src/fill/echo-index.ts +188 -0
  108. package/src/fill/fill-session.ts +452 -0
  109. package/src/fill/proposal.ts +68 -0
  110. package/src/fill/schema-rows.ts +506 -0
  111. package/src/fill/tool-boundary.ts +103 -0
  112. package/src/fit.ts +24 -43
  113. package/src/generate.ts +740 -337
  114. package/src/grammar.ts +14 -8
  115. package/src/harness-pi.ts +15 -34
  116. package/src/hub-rest.ts +4 -4
  117. package/src/index.ts +27 -0
  118. package/src/inference/denoising.ts +17 -0
  119. package/src/inference/graph.ts +33 -0
  120. package/src/inference/prefill.ts +39 -0
  121. package/src/inference/rollback.ts +7 -0
  122. package/src/jobs/db.ts +4 -0
  123. package/src/jobs/index.ts +2 -0
  124. package/src/jobs/job-entry.ts +15 -14
  125. package/src/jobs/runner.ts +98 -13
  126. package/src/jobs/task-client.ts +19 -0
  127. package/src/jobs/types.ts +2 -58
  128. package/src/kv-scheme.ts +199 -0
  129. package/src/kv-store.ts +459 -234
  130. package/src/lab/README.md +18 -0
  131. package/src/{assets → lab/curve}/curve-designer.html +2 -2
  132. package/src/{curve-sampler.ts → lab/curve/curve-sampler.ts} +2 -2
  133. package/src/{expert-trace.ts → lab/expert-trace/expert-trace.ts} +7 -5
  134. package/src/{model → lab/paged-kv}/paged-kv.ts +9 -6
  135. package/src/library.ts +40 -0
  136. package/src/lora.ts +18 -0
  137. package/src/media-fetch.ts +20 -4
  138. package/src/memory/article.ts +1 -1
  139. package/src/memory/chunk.ts +1 -1
  140. package/src/memory/model.ts +32 -170
  141. package/src/memory/prompts.ts +1 -1
  142. package/src/memory/rest.ts +1 -1
  143. package/src/memory/schedule.ts +1 -1
  144. package/src/memory/stages.ts +4 -1
  145. package/src/memory/vault.ts +3 -4
  146. package/src/mlx/array.ts +9 -10
  147. package/src/mlx/compile.ts +12 -2
  148. package/src/mlx/ffi.ts +66 -22
  149. package/src/mlx/materialize.ts +23 -0
  150. package/src/mlx/metal-kernel.ts +10 -11
  151. package/src/mlx/ops.ts +64 -19
  152. package/src/mlx/quantized-rows.ts +36 -0
  153. package/src/mlx/token-bitmask.ts +52 -0
  154. package/src/mlx/turboquant-kv-decode.ts +82 -0
  155. package/src/mlx/turboquant-kv-inverse.ts +122 -0
  156. package/src/mlx/turboquant-ops.ts +33 -3
  157. package/src/mlx/turboquant-tables.ts +2 -2
  158. package/src/model/batched-mask.ts +9 -3
  159. package/src/model/batched-quant.ts +2 -2
  160. package/src/model/batched-rotating-quant.ts +45 -99
  161. package/src/model/batched-rotating-state.ts +73 -0
  162. package/src/model/batched-rotating.ts +57 -106
  163. package/src/model/batched-row-storage.ts +127 -0
  164. package/src/model/compiled-decode.ts +4 -3
  165. package/src/model/diffusion-gemma.ts +8 -8
  166. package/src/model/factory.ts +87 -49
  167. package/src/model/fingerprint.ts +1 -1
  168. package/src/model/flash-attention.ts +3 -3
  169. package/src/model/gemma4-base.ts +187 -17
  170. package/src/model/gemma4.ts +6 -6
  171. package/src/model/generated/gemma4-12b.ts +52 -148
  172. package/src/model/generated/gemma4-26b.ts +34 -94
  173. package/src/model/generated/gemma4-e4b.ts +28 -76
  174. package/src/model/glm52-cache.ts +19 -9
  175. package/src/model/glm52-dsa.ts +13 -9
  176. package/src/model/glm52-memory.ts +28 -1
  177. package/src/model/implementation.ts +69 -0
  178. package/src/model/minicpm5.ts +7 -8
  179. package/src/model/profile.ts +443 -0
  180. package/src/model/qwen-append.ts +12 -0
  181. package/src/model/qwen-conv.ts +52 -0
  182. package/src/model/qwen3-delta.ts +98 -2
  183. package/src/model/qwen3-mrope.ts +252 -0
  184. package/src/model/qwen3_5-checkpoint.ts +164 -0
  185. package/src/model/qwen3_5.ts +336 -60
  186. package/src/model/support.ts +1 -1
  187. package/src/model/trellis-balanced-scatter.ts +51 -0
  188. package/src/model/trellis-linear.ts +672 -0
  189. package/src/model/trellis-shared-m.ts +151 -0
  190. package/src/model/trellis-shared-scatter.ts +144 -0
  191. package/src/model/trellis-splitk-prefill.ts +158 -0
  192. package/src/model/trellis-tiled-prefill.ts +136 -0
  193. package/src/model/trellis-vector-expand.ts +62 -0
  194. package/src/model/trellis-wide-prefill.ts +94 -0
  195. package/src/model/universal/dense.ts +1 -1
  196. package/src/model/universal/modules.ts +8 -1
  197. package/src/native/frame_extract.swift +140 -0
  198. package/src/native-pack.ts +15 -7
  199. package/src/pi-provider.ts +6 -64
  200. package/src/pi-web.ts +14 -320
  201. package/src/prompt-cache.ts +97 -54
  202. package/src/quantize/allocator.ts +1 -1
  203. package/src/quantize/atomic-output.ts +38 -0
  204. package/src/quantize/calibration.ts +2 -1
  205. package/src/quantize/config-writer.ts +16 -3
  206. package/src/quantize/index.ts +28 -1
  207. package/src/quantize/job.ts +11 -0
  208. package/src/quantize/quantizer.ts +265 -100
  209. package/src/quantize/rotate.ts +603 -0
  210. package/src/quantize/safetensors-writer.ts +79 -1
  211. package/src/quantize/sensitivity.ts +41 -59
  212. package/src/quantize/trellis.ts +586 -0
  213. package/src/quantize/weight-transform.ts +292 -0
  214. package/src/responses.ts +130 -3
  215. package/src/runtime-config.ts +81 -0
  216. package/src/sampler.ts +276 -4
  217. package/src/serve/admin-routes.ts +184 -0
  218. package/src/serve/aux-routes.ts +132 -0
  219. package/src/serve/batch-scheduler.ts +8 -1558
  220. package/src/serve/chat-request.ts +391 -0
  221. package/src/serve/chat-stage.ts +195 -0
  222. package/src/serve/checkpoint-identity.ts +64 -0
  223. package/src/serve/completion-executor.ts +354 -0
  224. package/src/serve/completion-sink.ts +157 -0
  225. package/src/serve/discovery-routes.ts +204 -0
  226. package/src/serve/generation-gateway.ts +211 -243
  227. package/src/serve/http.ts +159 -0
  228. package/src/serve/inference-request.ts +79 -0
  229. package/src/serve/isolate.ts +297 -105
  230. package/src/serve/kv-budget.ts +14 -0
  231. package/src/serve/lab-routes.ts +341 -0
  232. package/src/serve/lane-registry.ts +2 -1
  233. package/src/serve/model-admin-routes.ts +155 -0
  234. package/src/serve/model-binding.ts +43 -0
  235. package/src/serve/model-host.ts +512 -0
  236. package/src/serve/openai-wire.ts +103 -0
  237. package/src/serve/parent-application.ts +56 -0
  238. package/src/serve/pipeline.ts +33 -0
  239. package/src/serve/preparation.ts +33 -0
  240. package/src/serve/prompt-response-trace.ts +147 -0
  241. package/src/serve/request-plan.ts +162 -0
  242. package/src/serve/request-prep.ts +346 -0
  243. package/src/serve/response-lease.ts +24 -0
  244. package/src/serve/responses-client.ts +69 -0
  245. package/src/serve/session-completion-engine.ts +110 -0
  246. package/src/serve/session-search.ts +1 -1
  247. package/src/serve/static-routes.ts +101 -0
  248. package/src/serve/text-completion-stage.ts +104 -0
  249. package/src/serve/token-streams.ts +519 -0
  250. package/src/server.ts +523 -3551
  251. package/src/spec/assistant-source.ts +10 -44
  252. package/src/spec/deepspec-source.ts +6 -6
  253. package/src/spec/dflash-source.ts +9 -8
  254. package/src/spec/dspark/ab-stats.ts +3 -3
  255. package/src/spec/dspark/calibration.ts +2 -2
  256. package/src/spec/dspark/deepspec-module.ts +2 -2
  257. package/src/spec/dspark/generate-dflash.ts +2 -2
  258. package/src/spec/dspark/generate.ts +2 -2
  259. package/src/spec/dspark/loader.ts +1 -1
  260. package/src/spec/dspark/module-dflash.ts +5 -4
  261. package/src/spec/dspark/module.ts +3 -3
  262. package/src/spec/dspark/quantize-drafter.ts +1 -1
  263. package/src/spec/generate.ts +1 -1
  264. package/src/spec/glm52-mtp-source.ts +9 -1
  265. package/src/spec/ngram-source.ts +1 -1
  266. package/src/spec/prefix-state.ts +45 -0
  267. package/src/spec/qwen-mtp-source.ts +528 -0
  268. package/src/spec/serve-loop.ts +179 -213
  269. package/src/spec/source.ts +42 -10
  270. package/src/spec/two-model.ts +1 -1
  271. package/src/ssd-cache.ts +95 -24
  272. package/src/ssd-durability.ts +207 -0
  273. package/src/tool-call.ts +51 -14
  274. package/src/train/flash-cce.ts +14 -13
  275. package/src/train/forward.ts +38 -1
  276. package/src/train/loss.ts +3 -2
  277. package/src/train/prefix-shared.ts +5 -1
  278. package/src/train/segmented.ts +9 -6
  279. package/src/train/trainer.ts +9 -8
  280. package/src/vision/preprocess.ts +1 -1
  281. package/src/vision/prompt.ts +48 -2
  282. package/src/vision/qwen3vl-preprocess.ts +409 -0
  283. package/src/vision/qwen3vl-prompt.ts +196 -0
  284. package/src/vision/qwen3vl-tower.ts +555 -0
  285. package/src/vision/video-frames.ts +148 -0
  286. package/src/web/app.html +1 -1
  287. package/src/web/app.js +15 -6
  288. package/src/web/src/adapters-panel.ts +3 -3
  289. package/src/web/src/assistant.ts +2 -36
  290. package/src/web/src/chat.ts +16 -9
  291. package/src/web/src/composer.ts +4 -4
  292. package/src/web/src/hub.ts +1 -1
  293. package/src/web/src/markdown.ts +0 -0
  294. package/src/web/src/memory-panel.ts +1 -1
  295. package/src/web/src/model-picker.ts +1 -1
  296. package/src/web/src/palette.ts +1 -1
  297. package/src/web/src/protocol.ts +3 -39
  298. package/src/web/src/sessions.ts +1 -1
  299. package/src/web/src/ui-catalog.ts +4 -18
  300. package/src/web-assets.ts +47 -0
  301. package/src/weights.ts +134 -33
  302. package/docs/archive/mlx-bun-lab-report.html +0 -275
  303. package/docs/archive/wwdc-mlx-bun.html +0 -848
  304. package/docs/design/adapters-end-to-end.md +0 -205
  305. package/docs/design/batching-perf-path.md +0 -255
  306. package/docs/design/batching-v2-plan.md +0 -529
  307. package/docs/design/bucketing-stage.md +0 -197
  308. package/docs/design/compat-cli-surface-design.md +0 -230
  309. package/docs/design/docs-reorg-plan.md +0 -165
  310. package/docs/design/dspark-serving-program.md +0 -371
  311. package/docs/design/dspark-speculative-decoding.md +0 -268
  312. package/docs/design/grammar-spec-batching-integration.md +0 -301
  313. package/docs/design/memory-inference-path.md +0 -422
  314. package/docs/design/memory-synthesis.md +0 -287
  315. package/docs/design/memory-system.md +0 -237
  316. package/docs/design/mlx-lm-tool-parity-plan.md +0 -545
  317. package/docs/design/omlx-adoption-map.md +0 -98
  318. package/docs/design/orpo-dynamic-lambda.md +0 -220
  319. package/docs/design/orpo-future-enhancements.md +0 -340
  320. package/docs/design/paged-kv-cache.md +0 -214
  321. package/docs/design/parallel-slots.md +0 -567
  322. package/docs/design/parity-tier-dag.md +0 -161
  323. package/docs/design/repo-cleanup-plan.md +0 -189
  324. package/docs/design/runtime-isolation.md +0 -263
  325. package/docs/design/segmented-backward-training.md +0 -543
  326. package/docs/design/spec-decode-larger-targets.md +0 -229
  327. package/docs/design/ssd-kv-cold-tier.md +0 -288
  328. package/docs/design/structured-output.md +0 -484
  329. package/docs/design/tauri-desktop-app.md +0 -127
  330. package/docs/design/the-dreaming-master-plan.md +0 -445
  331. package/docs/design/turboquant-kv.md +0 -235
  332. package/docs/design/w4a16-compute-precision-spike.md +0 -232
  333. package/docs/design/web-chat-beat-matrix.md +0 -315
  334. package/docs/design/write-pipeline-entity.md +0 -468
  335. package/docs/investigations/curve-runs/bisector-route.json +0 -98
  336. package/docs/investigations/curve-runs/bisector-trace.json +0 -1393
  337. package/docs/investigations/curve-runs/curve-terrain.json +0 -1179
  338. package/docs/investigations/curve-runs/curve-terrain.svg +0 -42
  339. package/docs/investigations/hlg-ab.html +0 -34
  340. package/docs/investigations/hlg-explorer.html +0 -238
  341. package/docs/investigations/hlg-figs/cliff.svg +0 -1
  342. package/docs/investigations/hlg-figs/cross.svg +0 -1
  343. package/docs/investigations/hlg-figs/edges.svg +0 -1
  344. package/docs/investigations/hlg-figs/k20-grids.svg +0 -1
  345. package/docs/investigations/hlg-figs/k20-peaks.svg +0 -1
  346. package/docs/investigations/hlg-figs/lw-curve.svg +0 -1
  347. package/docs/investigations/hlg-figs/terrain.svg +0 -1
  348. package/docs/investigations/hlg-report.html +0 -262
  349. package/docs/investigations/hlg-runs/hlg-ab.json +0 -149
  350. package/docs/investigations/hlg-runs/hlg-blind-prompts.md +0 -87
  351. package/docs/investigations/hlg-runs/hlg-blind.json +0 -107
  352. package/docs/investigations/hlg-runs/hlg-cliff.json +0 -197
  353. package/docs/investigations/hlg-runs/hlg-cross.json +0 -505
  354. package/docs/investigations/hlg-runs/hlg-edge.json +0 -388
  355. package/docs/investigations/hlg-runs/hlg-eval1.json +0 -25
  356. package/docs/investigations/hlg-runs/hlg-frontier.json +0 -86
  357. package/docs/investigations/hlg-runs/hlg-grid.json +0 -242
  358. package/docs/investigations/hlg-runs/hlg-grid2.json +0 -343
  359. package/docs/investigations/hlg-runs/hlg-lw-coarse.json +0 -110
  360. package/docs/investigations/hlg-runs/hlg-map.json +0 -383
  361. package/docs/investigations/hlg-runs/run0-original.log +0 -159
  362. package/docs/investigations/hlg-runs/runA-recommended.log +0 -131
  363. package/docs/investigations/hlg-runs/runAA-cliff-cpm5.log +0 -26
  364. package/docs/investigations/hlg-runs/runAB-grid2-e4b.log +0 -18
  365. package/docs/investigations/hlg-runs/runAC-peak-e4b-partial.log +0 -25
  366. package/docs/investigations/hlg-runs/runAD-ab-samples.log +0 -8
  367. package/docs/investigations/hlg-runs/runAE-edge-e4b.log +0 -51
  368. package/docs/investigations/hlg-runs/runAF-logit-calibration.log +0 -10
  369. package/docs/investigations/hlg-runs/runAG-eval-config2.log +0 -16
  370. package/docs/investigations/hlg-runs/runB-gain-variants.log +0 -192
  371. package/docs/investigations/hlg-runs/runC-pivots.log +0 -162
  372. package/docs/investigations/hlg-runs/runD-oetf.log +0 -132
  373. package/docs/investigations/hlg-runs/runE-eotf.log +0 -162
  374. package/docs/investigations/hlg-runs/runF-shaper-working.log +0 -86
  375. package/docs/investigations/hlg-runs/runG-shaper-sweep.log +0 -101
  376. package/docs/investigations/hlg-runs/runH-wsweep.log +0 -176
  377. package/docs/investigations/hlg-runs/runI-asweep.log +0 -176
  378. package/docs/investigations/hlg-runs/runJ-knobmap.log +0 -49
  379. package/docs/investigations/hlg-runs/runK-frontier.log +0 -19
  380. package/docs/investigations/hlg-runs/runL-widemap.log +0 -65
  381. package/docs/investigations/hlg-runs/runM-frontier-emb.log +0 -15
  382. package/docs/investigations/hlg-runs/runN-verify.log +0 -116
  383. package/docs/investigations/hlg-runs/runO-frontier-seed5000.log +0 -15
  384. package/docs/investigations/hlg-runs/runP-grid-AxSm.log +0 -15
  385. package/docs/investigations/hlg-runs/runQ-grid-tgW.log +0 -15
  386. package/docs/investigations/hlg-runs/runR-blind-transcript.log +0 -79
  387. package/docs/investigations/hlg-runs/runS-cross-coherence.log +0 -33
  388. package/docs/investigations/hlg-runs/runT-cross-cpm5.log +0 -33
  389. package/docs/investigations/hlg-runs/runU-cross-12b.log +0 -33
  390. package/docs/investigations/hlg-runs/runV-lw-e4b.log +0 -22
  391. package/docs/investigations/hlg-runs/runW-lw-12b.log +0 -22
  392. package/docs/investigations/hlg-runs/runX-lw-cpm5.log +0 -22
  393. package/docs/investigations/hlg-runs/runY-cliff-e4b.log +0 -26
  394. package/docs/investigations/hlg-runs/runZ-cliff-12b.log +0 -26
  395. package/docs/planning/IDEAS.md +0 -69
  396. package/docs/reference/embedding.md +0 -197
  397. package/docs/reference/features-matrix.md +0 -100
  398. package/docs/reference/orpo-quickstart.md +0 -135
  399. package/src/flags.ts +0 -13
  400. /package/docs/{investigations → archive/investigations}/chunk-finetune-experiment.md +0 -0
  401. /package/docs/{investigations → archive/investigations}/colibri-oracle-pin.md +0 -0
  402. /package/docs/{investigations → archive/investigations}/curve-bisector-routing.md +0 -0
  403. /package/docs/{investigations → archive/investigations}/decode-roofline-lookagain.md +0 -0
  404. /package/docs/{investigations → archive/investigations}/dspark-handoff.md +0 -0
  405. /package/docs/{investigations → archive/investigations}/expert-offload-single-user-moe.md +0 -0
  406. /package/docs/{investigations → archive/investigations}/indexshare-performance-spike.md +0 -0
  407. /package/docs/{investigations → archive/investigations}/kernel-perf-review-2026-07.md +0 -0
  408. /package/docs/{investigations → archive/investigations}/lab-build-journal.md +0 -0
  409. /package/docs/{investigations → archive/investigations}/openwiki-evaluation.md +0 -0
  410. /package/docs/{investigations → archive/investigations}/orpo-base-uf-experiment-and-directions.md +0 -0
  411. /package/docs/{investigations → archive/investigations}/orpo-flash-cce-pin-leak.md +0 -0
  412. /package/docs/{investigations → archive/investigations}/orpo-uf-testing-handoff.md +0 -0
  413. /package/docs/{investigations → archive/investigations}/pi-builtin-investigation.md +0 -0
  414. /package/docs/{investigations → archive/investigations}/segmented-backward-handoff.md +0 -0
  415. /package/docs/{investigations → archive/investigations}/starter-model-port-handoff.md +0 -0
  416. /package/docs/{investigations → archive/investigations}/steel-flash-cce-handoff.md +0 -0
  417. /package/docs/{investigations → archive/investigations}/trainer-validation-experiment.md +0 -0
  418. /package/docs/{planning → archive/planning}/chat-ui-vision-memory-first.md +0 -0
  419. /package/docs/{planning → archive/planning}/journal.md +0 -0
  420. /package/docs/{planning → archive/planning}/pi-integration-review.md +0 -0
  421. /package/docs/{planning → archive/planning}/release-notes-v0.0.10.md +0 -0
  422. /package/docs/{planning → archive/planning}/release-notes-v0.0.11.md +0 -0
  423. /package/docs/{planning → archive/planning}/release-notes-v0.0.12.md +0 -0
  424. /package/docs/{planning → archive/planning}/release-notes-v0.0.13.md +0 -0
  425. /package/docs/{planning → archive/planning}/web-ui-pass-plan.md +0 -0
  426. /package/{docs/investigations → src/serve/assets}/curve-terrain.html +0 -0
  427. /package/{docs/dag → src/serve/assets}/training-inference-map.html +0 -0
@@ -0,0 +1,82 @@
1
+ // Exact byte-packed TurboQuant K/V decoding. The caller owns output arrays.
2
+ // Rotation and cache policy belong to the existing codec/cache layers.
3
+ import { type MlxArray, gpuStream } from './array';
4
+ import { Dtype, type MlxHandle } from './ffi';
5
+ import { isShapelessTracing } from './compile';
6
+ import { MetalKernel } from './metal-kernel';
7
+
8
+ export type PackedKvArrays = readonly [
9
+ keyIndices: MlxArray, keyScales: MlxArray, keyZeros: MlxArray,
10
+ valueIndices: MlxArray, valueScales: MlxArray,
11
+ ];
12
+
13
+ const source = `
14
+ uint col = thread_position_in_grid.x, pos = thread_position_in_grid.y;
15
+ uint bh = thread_position_in_grid.z;
16
+ if (col >= D || pos >= vp_shape[2] || bh >= vp_shape[0] * vp_shape[1]) return;
17
+ uint b = bh / vp_shape[1], h = bh % vp_shape[1];
18
+ uint ko = b * kp_strides[0] + h * kp_strides[1] + pos * kp_strides[2];
19
+ uint vpo = b * vp_strides[0] + h * vp_strides[1] + pos * vp_strides[2];
20
+ uint kso = b * ks_strides[0] + h * ks_strides[1] + pos * ks_strides[2] + (col / KG) * ks_strides[3];
21
+ uint kzo = b * kz_strides[0] + h * kz_strides[1] + pos * kz_strides[2] + (col / KG) * kz_strides[3];
22
+ uint vso = b * vs_strides[0] + h * vs_strides[1] + pos * vs_strides[2] + (col / VG) * vs_strides[3];
23
+ int key_code;
24
+ if (KBITS == 8) key_code = int(kp[ko + col * kp_strides[3]]);
25
+ else {
26
+ uint bit = col * KBITS, byte = bit / 8u, shift = bit % 8u;
27
+ uint word = uint(kp[ko + byte * kp_strides[3]]);
28
+ if (shift + KBITS > 8u) word |= uint(kp[ko + (byte + 1u) * kp_strides[3]]) << 8u;
29
+ key_code = int((word >> shift) & ((1u << KBITS) - 1u));
30
+ }
31
+ // Keep the incumbent f32 addition before multiplication.
32
+ float shifted = float(key_code) + float(kz[kzo]);
33
+ uint i = (bh * vp_shape[2] + pos) * D + col;
34
+ outK[i] = OutK(shifted * float(ks[kso]));
35
+ uint bit = col * VBITS, byte = bit / 8u, shift = bit % 8u;
36
+ uint word = uint(vp[vpo + byte * vp_strides[3]]);
37
+ if (shift + VBITS > 8u) word |= uint(vp[vpo + (byte + 1u) * vp_strides[3]]) << 8u;
38
+ uint code = (word >> shift) & ((1u << VBITS) - 1u);
39
+ outV[i] = OutV(centroids[code] * float(vs[vso]));
40
+ `;
41
+
42
+ let kernel: MetalKernel | undefined;
43
+ function jointKernel(): MetalKernel {
44
+ return kernel ??= new MetalKernel({
45
+ name: 'turboquant_kv_decode',
46
+ inputNames: ['kp', 'ks', 'kz', 'vp', 'vs', 'centroids'],
47
+ outputNames: ['outK', 'outV'], source, ensureRowContiguous: false,
48
+ });
49
+ }
50
+
51
+ /** Return bf16 K and rotated V (f32 for eager inverse rotation, bf16 for
52
+ * deferred rotation). Null means the ordinary codec must handle this input.
53
+ * Input arrays and the shared centroid table remain borrowed. */
54
+ export function tryJointDecodePackedKv(
55
+ inputs: PackedKvArrays, centroids: MlxArray, kBits: number, vBits: number,
56
+ headDim: number, deferV: boolean, stream: MlxHandle = gpuStream,
57
+ ): [MlxArray, MlxArray] | null {
58
+ if (stream !== gpuStream || isShapelessTracing() ||
59
+ ![64, 128, 256, 512].includes(headDim) ||
60
+ ![2, 4, 5, 8].includes(kBits) || ![2, 3, 4, 5, 8].includes(vBits)) return null;
61
+ const [kp, ks, kz, vp, vs] = inputs;
62
+ if (kp.dtype !== (kBits === 8 ? Dtype.int8 : Dtype.uint8) ||
63
+ vp.dtype !== Dtype.uint8 || centroids.dtype !== Dtype.float32 ||
64
+ centroids.ndim !== 1 || centroids.size !== (1 << vBits)) return null;
65
+ const shapes = inputs.map(a => a.shape);
66
+ if (shapes.some(shape => shape.length !== 4)) return null;
67
+ const [b, h, n] = shapes[3]!;
68
+ if (!b || !h || !n || b * h * n * headDim >= 2 ** 32 ||
69
+ shapes.some(shape => shape[0] !== b || shape[1] !== h || shape[2] !== n) ||
70
+ shapes[0]![3] !== headDim * kBits / 8 || shapes[3]![3] !== headDim * vBits / 8 ||
71
+ shapes[1]![3] !== shapes[2]![3]) return null;
72
+ const keyGroup = headDim / shapes[1]![3]!, valueGroup = headDim / shapes[4]![3]!;
73
+ if (![32, 64].includes(keyGroup) || ![32, 64].includes(valueGroup) ||
74
+ [ks, kz, vs].some(a => ![Dtype.float16, Dtype.bfloat16, Dtype.float32].includes(a.dtype))) return null;
75
+ const shape = [b, h, n, headDim], valueDtype = deferV ? Dtype.bfloat16 : Dtype.float32;
76
+ return jointKernel().apply([...inputs, centroids], {
77
+ templateDtypes: { OutK: Dtype.bfloat16, OutV: valueDtype },
78
+ templateInts: { D: headDim, KBITS: kBits, VBITS: vBits, KG: keyGroup, VG: valueGroup },
79
+ grid: [headDim, n, b * h], threadGroup: [Math.min(headDim, 256), Math.max(1, 256 / headDim), 1],
80
+ outputs: [{ shape, dtype: Dtype.bfloat16 }, { shape, dtype: valueDtype }], stream,
81
+ }) as [MlxArray, MlxArray];
82
+ }
@@ -0,0 +1,122 @@
1
+ // Joint packed K/V decode with eager inverse rotation in the measured D256 regime.
2
+ // Hadamard butterfly/layout follows MLX 0.31.2,
3
+ // mlx/backend/metal/kernels/hadamard.h, Copyright © 2024 Apple Inc., MIT.
4
+ import { type MlxArray, gpuStream } from "./array";
5
+ import { Dtype, type MlxHandle } from "./ffi";
6
+ import { isShapelessTracing } from "./compile";
7
+ import { MetalKernel } from "./metal-kernel";
8
+ import { type PackedKvArrays } from "./turboquant-kv-decode";
9
+
10
+ const header = `
11
+ template <short R> inline void tq_inverse_radix(thread float* x) {
12
+ constexpr short logR = __builtin_ctz(R);
13
+ short h = 1;
14
+ #pragma unroll
15
+ for (short s = 0; s < logR; s++) {
16
+ #pragma unroll
17
+ for (short i = 0; i < R / 2; i++) {
18
+ short k = i & (h - 1), j = ((i - k) << 1) + k;
19
+ float a = x[j], b = x[j + h];
20
+ x[j] = a + b; x[j + h] = a - b;
21
+ }
22
+ h <<= 1;
23
+ }
24
+ }
25
+ `;
26
+ const source = `
27
+ constexpr short D = 256, R = 16, NT = 16, LOG_R = 4;
28
+ uint pos = thread_position_in_grid.y, bh = thread_position_in_grid.z;
29
+ short i = thread_position_in_grid.x;
30
+ bool valid = pos < vp_shape[2];
31
+ uint b = bh / vp_shape[1], hidx = bh % vp_shape[1];
32
+ uint ko = b * kp_strides[0] + hidx * kp_strides[1] + pos * kp_strides[2];
33
+ uint vo = b * vp_strides[0] + hidx * vp_strides[1] + pos * vp_strides[2];
34
+ uint kso = b * ks_strides[0] + hidx * ks_strides[1] + pos * ks_strides[2];
35
+ uint kzo = b * kz_strides[0] + hidx * kz_strides[1] + pos * kz_strides[2];
36
+ uint vso = b * vs_strides[0] + hidx * vs_strides[1] + pos * vs_strides[2];
37
+ uint row = (bh * vp_shape[2] + pos) * D;
38
+ threadgroup float storage[D * 16];
39
+ threadgroup float* buf = storage + thread_position_in_threadgroup.y * D;
40
+ #pragma unroll
41
+ for (short j = 0; j < R / 4; j++) {
42
+ short index = j * 4 * NT + i * 4;
43
+ #pragma unroll
44
+ for (short r = 0; r < 4; r++) {
45
+ uint col = index + r;
46
+ if (!valid) { buf[col] = 0.0f; continue; }
47
+ int key_code = int(kp[ko + col * kp_strides[3]]);
48
+ float shifted = float(key_code) + float(kz[kzo + (col / 32) * kz_strides[3]]);
49
+ outK[row + col] = OutK(shifted * float(ks[kso + (col / 32) * ks_strides[3]]));
50
+ uint bit = col * 3u, byte = bit / 8u, shift = bit % 8u;
51
+ uint word = uint(vp[vo + byte * vp_strides[3]]);
52
+ if (shift + 3u > 8u) word |= uint(vp[vo + (byte + 1u) * vp_strides[3]]) << 8u;
53
+ uint code = (word >> shift) & 7u;
54
+ buf[col] = centroids[code * centroids_strides[0]] * float(vs[vso + (col / 32) * vs_strides[3]]);
55
+ }
56
+ }
57
+ threadgroup_barrier(mem_flags::mem_threadgroup);
58
+ float x[R]; short h = 1;
59
+ #pragma unroll
60
+ for (short s = 0; s < 2; s++) {
61
+ short k = i & (h - 1), j = ((i - k) << LOG_R) + k;
62
+ #pragma unroll
63
+ for (short r = 0; r < R; r++) x[r] = buf[j + h * r];
64
+ tq_inverse_radix<R>(x);
65
+ #pragma unroll
66
+ for (short r = 0; r < R; r++) buf[j + h * r] = x[r];
67
+ h <<= LOG_R;
68
+ threadgroup_barrier(mem_flags::mem_threadgroup);
69
+ }
70
+ #pragma unroll
71
+ for (short j = 0; j < R / 4; j++) {
72
+ short index = j * 4 * NT + i * 4;
73
+ #pragma unroll
74
+ for (short r = 0; r < 4; r++) {
75
+ uint col = index + r;
76
+ float normalized = buf[col] * 0.0625f;
77
+ if (valid) outV[row + col] = OutV(normalized * signs[col * signs_strides[0]]);
78
+ }
79
+ }
80
+ `;
81
+
82
+ let kernel: MetalKernel | undefined;
83
+ function inverseKernel(): MetalKernel {
84
+ return kernel ??= new MetalKernel({
85
+ name: "turboquant_kv_inverse256",
86
+ inputNames: ["kp", "ks", "kz", "vp", "vs", "centroids", "signs"],
87
+ outputNames: ["outK", "outV"], header, source, ensureRowContiguous: false,
88
+ });
89
+ }
90
+
91
+ /** Borrow packed inputs and codec tables; return owned bf16 K/V or null.
92
+ * Only the measured eager D256/k8v3/B1/H4/fp16/group32 regime is eligible.
93
+ * Other regimes continue through the existing joint decoder or original codec.
94
+ * N affects dispatch size but is never a shader template parameter. */
95
+ export function tryDecodePackedKvInverse256(
96
+ inputs: PackedKvArrays, centroids: MlxArray, signs: MlxArray,
97
+ kBits: number, vBits: number, headDim: number, s: MlxHandle = gpuStream,
98
+ ): [MlxArray, MlxArray] | null {
99
+ if (s !== gpuStream || isShapelessTracing() || headDim !== 256 || kBits !== 8 || vBits !== 3)
100
+ return null;
101
+ const [kp, ks, kz, vp, vs] = inputs;
102
+ const shape = kp.shape;
103
+ if (shape.length !== 4 || shape[0] !== 1 || shape[1] !== 4 || shape[3] !== 256)
104
+ return null;
105
+ const n = shape[2]!;
106
+ if (n < 8192 || n * 1024 >= 2 ** 32) return null;
107
+ const samePrefix = (a: MlxArray, width: number) => {
108
+ const x = a.shape;
109
+ return x.length === 4 && x[0] === 1 && x[1] === 4 && x[2] === n && x[3] === width;
110
+ };
111
+ if (kp.dtype !== Dtype.int8 || vp.dtype !== Dtype.uint8 || !samePrefix(vp, 96)) return null;
112
+ if (![ks, kz, vs].every(a => a.dtype === Dtype.float16 && samePrefix(a, 8))) return null;
113
+ if (centroids.dtype !== Dtype.float32 || centroids.shape.length !== 1 || centroids.shape[0] !== 8)
114
+ return null;
115
+ if (signs.dtype !== Dtype.float32 || signs.shape.length !== 1 || signs.shape[0] !== 256)
116
+ return null;
117
+ return inverseKernel().apply([...inputs, centroids, signs], {
118
+ templateDtypes: { OutK: Dtype.bfloat16, OutV: Dtype.bfloat16 },
119
+ grid: [16, Math.ceil(n / 16) * 16, 4], threadGroup: [16, 16, 1],
120
+ outputs: [{ shape, dtype: Dtype.bfloat16 }, { shape, dtype: Dtype.bfloat16 }], stream: s,
121
+ }) as [MlxArray, MlxArray];
122
+ }
@@ -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
@@ -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,
@@ -242,7 +248,7 @@ export function extractKVRow(cache: KVCache, leftPad: number, i: number): KVCach
242
248
  const cut = (a: MlxArray): MlxArray => {
243
249
  const [, H, , D] = a.shape as [number, number, number, number];
244
250
  const view = a.slice([i, 0, leftPad, 0], [i + 1, H, S, D]);
245
- const own = ops.contiguous(view);
251
+ const own = ops.copyOf(view); // TRUE copy: contiguous(view) is a no-op VIEW when already contiguous — pins the source buffer (2026-08-20 DeltaNet conv leak class)
246
252
  view.dispose();
247
253
  return own;
248
254
  };
@@ -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";
@@ -122,7 +122,7 @@ export function extractQuantRow(
122
122
  tripleMap(t, (a) => {
123
123
  const [, H, , D] = a.shape as [number, number, number, number];
124
124
  const view = a.slice([i, 0, leftPad, 0], [i + 1, H, S, D]);
125
- const own = ops.contiguous(view);
125
+ const own = ops.copyOf(view); // TRUE copy: contiguous(view) is a no-op VIEW when already contiguous — pins the source buffer (2026-08-20 DeltaNet conv leak class)
126
126
  view.dispose();
127
127
  return own;
128
128
  });
@@ -22,9 +22,9 @@
22
22
  // every step; it self-tracks per-row offset/leftPad and the scalar ring
23
23
  // state. N=1 decode updates only (rows solo-prefill serially, then merge).
24
24
  //
25
- // Gates: tests/batched-rotating-quant.test.ts (model-free byte-identity vs
25
+ // Gates: tests/unit/batched-rotating-quant.test.ts (model-free byte-identity vs
26
26
  // the serial oracle per row, through ring wrap, B=1 and B=2) + the gemma
27
- // scheduler gate in tests/batched-kv-quant-parity.test.ts.
27
+ // scheduler gate in tests/parity/batched-kv-quant-parity.test.ts.
28
28
 
29
29
  import { MlxArray } from "../mlx/array";
30
30
  import * as ops from "../mlx/ops";
@@ -32,6 +32,12 @@ import { Dtype } from "../mlx/ffi";
32
32
  import { RotatingQuantizedKVCache, type Mask } from "./gemma4-base";
33
33
  import { buildBatchedRotatingMask } from "./batched-rotating";
34
34
  import type { QuantRow } from "./batched-quant";
35
+ import { BatchedRotatingState } from "./batched-rotating-state";
36
+ import {
37
+ mergeStorageRows,
38
+ quantizedRowStorage,
39
+ temporalStorageView,
40
+ } from "./batched-row-storage";
35
41
 
36
42
  const STEP = 256; // shared with RotatingQuantizedKVCache.STEP and the bf16 twin
37
43
 
@@ -49,9 +55,7 @@ const mapTriple = (
49
55
  * storage. Scalar ring state (`ringIdx`/`offset` reuse the base fields);
50
56
  * per-row `offsetArr` (absolute positions → RoPE) and `leftPad`. */
51
57
  export class BatchedRotatingQuantCache extends RotatingQuantizedKVCache {
52
- offsetArr: number[];
53
- leftPad: number[];
54
- #rotated = false;
58
+ readonly #rows: BatchedRotatingState;
55
59
  /** Per-row RoPE positions. STABLE ACROSS A STEP — refreshed only at
56
60
  * releaseRopeArr() (the scheduler's post-dispatch hook), never inside
57
61
  * the update: the monolith CAPTURES it pre-update and ropes Q after
@@ -67,13 +71,16 @@ export class BatchedRotatingQuantCache extends RotatingQuantizedKVCache {
67
71
  maxSize: number, groupSize: number, bits: number, leftPad: number[], offsets: number[],
68
72
  ) {
69
73
  super(maxSize, groupSize, bits);
70
- this.leftPad = [...leftPad];
71
- this.offsetArr = [...offsets];
74
+ this.#rows = new BatchedRotatingState(maxSize, leftPad, offsets);
72
75
  this.ropeOffsetArr = MlxArray.fromInt32(Int32Array.from(offsets), [offsets.length]);
73
76
  }
74
77
 
78
+ get offsetArr(): number[] { return this.#rows.offsets; }
79
+ get leftPad(): number[] { return this.#rows.leftPad; }
80
+ get batchSize(): number { return this.#rows.batchSize; }
81
+
75
82
  get #B(): number {
76
- return this.leftPad.length;
83
+ return this.#rows.batchSize;
77
84
  }
78
85
 
79
86
  #seqLen2(): number {
@@ -104,7 +111,7 @@ export class BatchedRotatingQuantCache extends RotatingQuantizedKVCache {
104
111
  mode: "array",
105
112
  arr: buildBatchedRotatingMask(
106
113
  this.#B, N, this.leftPad, this.maxSize, window,
107
- this.ringIdx, this.offset, this.#rotated,
114
+ this.#rows.ringIndex, this.#rows.totalOffset, this.#rows.rotated,
108
115
  ),
109
116
  };
110
117
  }
@@ -117,7 +124,7 @@ export class BatchedRotatingQuantCache extends RotatingQuantizedKVCache {
117
124
  const vD = v.shape[3]!;
118
125
  if (S !== 1)
119
126
  throw new Error("BatchedRotatingQuantCache supports N=1 decode updates only (solo-prefill then merge)");
120
- const prev = this.offset;
127
+ const prev = this.#rows.totalOffset;
121
128
 
122
129
  // Grow the buffer (in STEP chunks) until it reaches maxSize.
123
130
  if (!this.keys || (prev >= this.#seqLen2() && this.#seqLen2() < this.maxSize)) {
@@ -144,7 +151,8 @@ export class BatchedRotatingQuantCache extends RotatingQuantizedKVCache {
144
151
  this.keys = newK;
145
152
  this.values = newV;
146
153
  }
147
- this.ringIdx = prev;
154
+ this.#rows.markGrown(prev);
155
+ this.ringIdx = this.#rows.ringIndex;
148
156
  }
149
157
 
150
158
  // Trim any overshoot past maxSize (decrements left padding persistently).
@@ -159,16 +167,12 @@ export class BatchedRotatingQuantCache extends RotatingQuantizedKVCache {
159
167
  });
160
168
  this.keys = cutFrom(this.keys!);
161
169
  this.values = cutFrom(this.values!);
162
- this.ringIdx = this.maxSize;
163
- this.leftPad = this.leftPad.map((x) => x - trimSize);
170
+ this.#rows.trimOvershoot(trimSize);
171
+ this.ringIdx = this.#rows.ringIndex;
164
172
  }
165
173
 
166
174
  // Rotate when the write head reaches the end of the ring.
167
- if (this.ringIdx === this.maxSize) {
168
- this.#rotated = true;
169
- this.ringIdx = 0;
170
- }
171
- if (this.#rotated) this.leftPad = this.leftPad.map((x) => x - S);
175
+ this.ringIdx = this.#rows.beginWrite(S);
172
176
 
173
177
  // Quantize + write the new K/V at the ring head (six sliceUpdates).
174
178
  const kq = ops.quantize(k, this.groupSize, this.bits);
@@ -184,9 +188,9 @@ export class BatchedRotatingQuantCache extends RotatingQuantizedKVCache {
184
188
  disposeTriple(kq);
185
189
  disposeTriple(vq);
186
190
 
187
- this.offset += S;
188
- this.offsetArr = this.offsetArr.map((x) => x + S);
189
- this.ringIdx += S;
191
+ this.#rows.commitWrite(S);
192
+ this.offset = this.#rows.totalOffset;
193
+ this.ringIdx = this.#rows.ringIndex;
190
194
 
191
195
  // Return the populated prefix (ring not yet full) or the whole buffer.
192
196
  const upTo = this.offset < this.maxSize ? this.offset : this.#seqLen2();
@@ -209,27 +213,10 @@ export class BatchedRotatingQuantCache extends RotatingQuantizedKVCache {
209
213
  * overrides the base to use the BATCHED ring state. */
210
214
  override temporalView(): [ops.QuantizedTensor, ops.QuantizedTensor] {
211
215
  if (!this.keys || !this.values) throw new Error("cache is empty");
212
- const valid = Math.min(this.offset, this.maxSize);
213
- const order = (t: ops.QuantizedTensor): ops.QuantizedTensor =>
214
- mapTriple(t, (a) => {
215
- const [B, H, Sbuf, D] = a.shape as [number, number, number, number];
216
- let tt: MlxArray;
217
- if (this.ringIdx === Sbuf) {
218
- tt = a.slice([0, 0, 0, 0], [B, H, Sbuf, D]);
219
- } else if (this.ringIdx < this.offset) {
220
- const tail = a.slice([0, 0, this.ringIdx, 0], [B, H, Sbuf, D]);
221
- const head = a.slice([0, 0, 0, 0], [B, H, this.ringIdx, D]);
222
- tt = ops.concatAxis([tail, head], 2);
223
- tail.dispose();
224
- head.dispose();
225
- } else {
226
- tt = a.slice([0, 0, 0, 0], [B, H, this.ringIdx, D]);
227
- }
228
- const cut = tt.slice([0, 0, 0, 0], [B, H, valid, D]);
229
- tt.dispose();
230
- return cut;
231
- });
232
- return [order(this.keys), order(this.values)];
216
+ return [
217
+ temporalStorageView(quantizedRowStorage, this.keys, this.#rows),
218
+ temporalStorageView(quantizedRowStorage, this.values, this.#rows),
219
+ ];
233
220
  }
234
221
 
235
222
  /** Quantized twin of BatchedRotatingCache.extractRow (mlx-lm
@@ -244,34 +231,13 @@ export class BatchedRotatingQuantCache extends RotatingQuantizedKVCache {
244
231
  extractRow(i: number): RotatingQuantizedKVCache | null {
245
232
  if (!this.keys || !this.values) return null;
246
233
  const pad = Math.max(0, this.leftPad[i]!);
247
- const valid = Math.min(this.offset, this.maxSize);
248
- const cut = (t: ops.QuantizedTensor): ops.QuantizedTensor =>
249
- mapTriple(t, (a) => {
250
- const [, H, Sbuf, D] = a.shape as [number, number, number, number];
251
- const row = a.slice([i, 0, 0, 0], [i + 1, H, Sbuf, D]);
252
- let tt: MlxArray;
253
- if (this.ringIdx === Sbuf) {
254
- tt = row;
255
- } else if (this.ringIdx < this.offset) {
256
- const tail = row.slice([0, 0, this.ringIdx, 0], [1, H, Sbuf, D]);
257
- const head = row.slice([0, 0, 0, 0], [1, H, this.ringIdx, D]);
258
- tt = ops.concatAxis([tail, head], 2);
259
- tail.dispose();
260
- head.dispose();
261
- row.dispose();
262
- } else {
263
- tt = row.slice([0, 0, 0, 0], [1, H, this.ringIdx, D]);
264
- row.dispose();
265
- }
266
- const cutV = tt.slice([0, 0, pad, 0], [1, H, valid, D]);
267
- tt.dispose();
268
- const own = ops.contiguous(cutV);
269
- cutV.dispose();
270
- return own;
271
- });
272
234
  const c = new RotatingQuantizedKVCache(this.maxSize, this.groupSize, this.bits);
273
- const k = cut(this.keys);
274
- const v = cut(this.values);
235
+ const k = temporalStorageView(quantizedRowStorage, this.keys, this.#rows, {
236
+ row: i, from: pad, copy: true,
237
+ });
238
+ const v = temporalStorageView(quantizedRowStorage, this.values, this.#rows, {
239
+ row: i, from: pad, copy: true,
240
+ });
275
241
  c.restoreState(k, v, this.offsetArr[i]!, k.packed.shape[2]!);
276
242
  return c;
277
243
  }
@@ -279,22 +245,19 @@ export class BatchedRotatingQuantCache extends RotatingQuantizedKVCache {
279
245
  /** Keep only `keep` rows along the batch axis (eviction), in place. */
280
246
  filter(keep: number[]): void {
281
247
  if (this.keys && this.values) {
282
- const idxArr = MlxArray.fromInt32(Int32Array.from(keep), [keep.length]);
283
- const take = (t: ops.QuantizedTensor): ops.QuantizedTensor =>
284
- mapTriple(t, (a) => ops.takeAxis(a, idxArr, 0));
285
- const k = take(this.keys);
286
- const v = take(this.values);
287
- idxArr.dispose();
248
+ const k = quantizedRowStorage.takeRows(this.keys, keep);
249
+ const v = quantizedRowStorage.takeRows(this.values, keep);
288
250
  disposeTriple(this.keys);
289
251
  disposeTriple(this.values);
290
252
  this.keys = k;
291
253
  this.values = v;
292
254
  }
293
- this.offsetArr = keep.map((i) => this.offsetArr[i]!);
294
- this.leftPad = keep.map((i) => this.leftPad[i]!);
255
+ this.#rows.filter(keep);
295
256
  this.releaseRopeArr(); // safe here: filter runs between steps
296
257
  }
297
258
 
259
+ filterRows(keep: readonly number[]): void { this.filter([...keep]); }
260
+
298
261
  override isTrimmable(): boolean {
299
262
  return false; // batched rows never re-enter the prompt cache directly
300
263
  }
@@ -317,30 +280,13 @@ export class BatchedRotatingQuantCache extends RotatingQuantizedKVCache {
317
280
  const width = Math.max(...lens, 0);
318
281
  const leftPad = lens.map((l) => width - l);
319
282
  const cache = new BatchedRotatingQuantCache(maxSize, groupSize, bits, leftPad, offsets);
283
+ cache.#rows.restoreMerged(width, offsets);
284
+ cache.offset = cache.#rows.totalOffset;
285
+ cache.ringIdx = cache.#rows.ringIndex;
320
286
  if (width === 0) return cache;
321
287
 
322
- const padTo = (a: MlxArray, pad: number): MlxArray => {
323
- if (pad === 0) return a.slice([0, 0, 0, 0], a.shape as number[]);
324
- const [B, H, , D] = a.shape as [number, number, number, number];
325
- const z = ops.zeros([B, H, pad, D], a.dtype);
326
- const out = ops.concatAxis([z, a], 2);
327
- z.dispose();
328
- return out;
329
- };
330
- const padded = rows.map((r, i) => ({
331
- keys: mapTriple(r.keys, (a) => padTo(a, leftPad[i]!)),
332
- values: mapTriple(r.values, (a) => padTo(a, leftPad[i]!)),
333
- }));
334
- const cat = (pick: (r: QuantRow) => ops.QuantizedTensor): ops.QuantizedTensor => ({
335
- packed: ops.concatAxis(padded.map((r) => pick(r).packed), 0),
336
- scales: ops.concatAxis(padded.map((r) => pick(r).scales), 0),
337
- biases: ops.concatAxis(padded.map((r) => pick(r).biases), 0),
338
- });
339
- cache.keys = cat((r) => r.keys);
340
- cache.values = cat((r) => r.values);
341
- for (const r of padded) { disposeTriple(r.keys); disposeTriple(r.values); }
342
- cache.ringIdx = width;
343
- cache.offset = width;
288
+ cache.keys = mergeStorageRows(quantizedRowStorage, rows.map((row) => row.keys), leftPad);
289
+ cache.values = mergeStorageRows(quantizedRowStorage, rows.map((row) => row.values), leftPad);
344
290
  return cache;
345
291
  }
346
292
  }
@@ -0,0 +1,73 @@
1
+ /** Host-side row and ring bookkeeping shared by plain and quantized storage. */
2
+ export class BatchedRotatingState {
3
+ readonly maxSize: number;
4
+ offsets: number[];
5
+ leftPad: number[];
6
+ ringIndex = 0;
7
+ totalOffset = 0;
8
+ rotated = false;
9
+
10
+ constructor(maxSize: number, leftPad: readonly number[], offsets?: readonly number[]) {
11
+ this.maxSize = maxSize;
12
+ this.leftPad = [...leftPad];
13
+ this.offsets = offsets ? [...offsets] : leftPad.map((pad) => -pad);
14
+ }
15
+
16
+ get batchSize(): number { return this.leftPad.length; }
17
+ get validLength(): number { return Math.min(this.totalOffset, this.maxSize); }
18
+ get trimmable(): boolean { return this.totalOffset < this.maxSize; }
19
+
20
+ markGrown(previousOffset: number): void {
21
+ this.ringIndex = previousOffset;
22
+ }
23
+
24
+ trimOvershoot(tokens: number): void {
25
+ if (tokens <= 0) return;
26
+ this.ringIndex = this.maxSize;
27
+ this.leftPad = this.leftPad.map((pad) => pad - tokens);
28
+ }
29
+
30
+ /** Prepare an N-token write and return its physical ring column. */
31
+ beginWrite(tokens: number): number {
32
+ if (this.ringIndex === this.maxSize) {
33
+ this.rotated = true;
34
+ this.ringIndex = 0;
35
+ }
36
+ if (this.rotated) this.leftPad = this.leftPad.map((pad) => pad - tokens);
37
+ return this.ringIndex;
38
+ }
39
+
40
+ commitWrite(tokens: number): void {
41
+ this.totalOffset += tokens;
42
+ this.offsets = this.offsets.map((offset) => offset + tokens);
43
+ this.ringIndex += tokens;
44
+ }
45
+
46
+ filter(keep: readonly number[]): void {
47
+ this.offsets = keep.map((row) => this.offsets[row]!);
48
+ this.leftPad = keep.map((row) => this.leftPad[row]!);
49
+ }
50
+
51
+ trim(tokens: number): number {
52
+ const amount = Math.min(this.totalOffset, tokens);
53
+ this.totalOffset -= amount;
54
+ this.ringIndex -= amount;
55
+ this.offsets = this.offsets.map((offset) => offset - amount);
56
+ return amount;
57
+ }
58
+
59
+ restoreMerged(width: number, offsets: readonly number[]): void {
60
+ this.totalOffset = width;
61
+ this.ringIndex = width;
62
+ this.offsets = [...offsets];
63
+ this.rotated = false;
64
+ }
65
+
66
+ /** Physical ranges that expose a ring buffer in temporal order. */
67
+ temporalRanges(bufferLength: number): readonly [number, number][] {
68
+ if (this.ringIndex === bufferLength) return [[0, bufferLength]];
69
+ if (this.ringIndex < this.totalOffset)
70
+ return [[this.ringIndex, bufferLength], [0, this.ringIndex]];
71
+ return [[0, this.ringIndex]];
72
+ }
73
+ }