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
@@ -23,7 +23,7 @@ import type { Weights } from "../weights";
23
23
  import { MlxArray } from "../mlx/array";
24
24
  import { Dtype } from "../mlx/ffi";
25
25
  import * as ops from "../mlx/ops";
26
- import { isExpertTracing, recordRouting } from "../expert-trace";
26
+ import { isExpertTracing, recordRouting } from "../lab/expert-trace/expert-trace";
27
27
 
28
28
  // The shared, config-independent machinery lives in gemma4-base.ts
29
29
  // (Phase B extraction); this file keeps the architecture-specific
@@ -45,18 +45,18 @@ import {
45
45
  RMSNorm,
46
46
  RotatingKVCache,
47
47
  RotatingQuantizedKVCache,
48
- TurboQuantKVCache,
49
48
  quantizedSdpa,
50
49
  type Cache,
51
50
  type Mask,
52
51
  type SharedKv,
52
+ captureRopeOffsets,
53
53
  type LoraWeights,
54
54
  } from "./gemma4-base";
55
55
  import { Checkpoint } from "../mlx/checkpoint";
56
56
  import { flashAttention, getTrainingAttn, flashSupported } from "./flash-attention";
57
57
  import { unrotateValues as tqUnrotateValues } from "../mlx/turboquant-ops";
58
58
  import { CompiledFunction } from "../mlx/compile";
59
- import { flagOn } from "../flags";
59
+ import { flagOn } from "../runtime-config";
60
60
 
61
61
  /** Verbatim port of mlx_lm/models/gemma4_text.py:
62
62
  * `@partial(mx.compile, shapeless=True) def geglu(gate, x): return nn.gelu_approx(gate) * x`
@@ -242,7 +242,7 @@ class Attention {
242
242
  /** A number offset takes the static fast::rope; an array offset (set by
243
243
  * compiled-decode trace adapters) takes the dynamic variant — same
244
244
  * kernel, offset read from the array (bit-exactness asserted in
245
- * tests/compile.test.ts). */
245
+ * tests/unit/compile.test.ts). */
246
246
  rope(x: MlxArray, offset: number | MlxArray): MlxArray {
247
247
  return typeof offset === "number"
248
248
  ? ops.rope(x, this.headDim, this.ropeBase, offset, this.ropeFreqs)
@@ -304,15 +304,11 @@ class Attention {
304
304
  }
305
305
 
306
306
  const offset = cache.offset;
307
- // Capture the RoPE offset array ONCE (like `offset` above): the
308
- // updateAndFetch below advances cache.offset, so re-reading
309
- // cache.ropeOffsetArr for Q *after* the write would hand K and Q
310
- // different positions. Harmless today (real caches leave it unset;
311
- // compiled-decode passes a constant trace input), but a hard
312
- // prerequisite for batched decode, where ropeOffsetArr carries per-row
313
- // positions derived from the pre-write offset and MUST be identical for
314
- // this step's K and Q.
315
- const offsetArr = cache.ropeOffsetArr;
307
+ // Own the pre-write positions through every query and shared-KV
308
+ // consumer. Advancing a row cache may replace and release its borrowed
309
+ // offset array before Q's RoPE is built. The fetched shared state owns
310
+ // this retained handle and releases it with K/V at the end of the pass.
311
+ const offsetArr = captureRopeOffsets(cache);
316
312
 
317
313
  const kNormed = this.kNorm!.forward(k);
318
314
  const kT = ops.transposeAxes(kNormed, [0, 2, 1, 3]);
@@ -328,23 +324,29 @@ class Attention {
328
324
  if (v !== k) v.dispose();
329
325
  k.dispose();
330
326
 
331
- if (cache instanceof QuantizedKVCache || cache instanceof RotatingQuantizedKVCache) {
332
- const [kq, vq] = cache.updateAndFetchQuantized(kRoped, vT);
327
+ if (cache.attentionState) {
328
+ const attention = cache.attentionState.appendAndFetch(kRoped, vT);
329
+ kRoped.dispose(); vT.dispose();
330
+ shared = { kind: "view", attention, offset, offsetArr };
331
+ } else if (cache.quantizedAttention) {
332
+ const quantized = cache.quantizedAttention;
333
+ const [kq, vq] = quantized.updateAndFetchQuantized(kRoped, vT);
333
334
  kRoped.dispose();
334
335
  vT.dispose();
335
336
  shared = {
336
337
  kind: "quant", keys: kq, values: vq, offset,
337
- groupSize: cache.groupSize, bits: cache.bits,
338
+ groupSize: quantized.groupSize, bits: quantized.bits,
338
339
  offsetArr,
339
340
  };
340
- } else if (cache instanceof TurboQuantKVCache) {
341
+ } else if (cache.rotatedValueAttention) {
341
342
  // Deferred-V read: values stay rotated; the attention output is
342
343
  // un-rotated below (shared.vRotated) — including by KV-shared
343
344
  // consumer layers, which see the flag through sharedIn.
344
- const [keys, values] = cache.updateAndFetchDeferredV(kRoped, vT);
345
+ const [keys, values] = cache.rotatedValueAttention.updateAndFetchDeferredV(kRoped, vT);
345
346
  kRoped.dispose();
346
347
  vT.dispose();
347
- shared = { kind: "plain", keys, values, offset, offsetArr, vRotated: true };
348
+ shared = { kind: "plain", keys, values, offset, offsetArr, vRotated: true,
349
+ restoreValues: cache.rotatedValueAttention.captureValueTransform?.() };
348
350
  } else {
349
351
  const [keys, values] = cache.updateAndFetch(kRoped, vT);
350
352
  kRoped.dispose();
@@ -360,7 +362,9 @@ class Attention {
360
362
 
361
363
  let attn: MlxArray;
362
364
  const ta = getTrainingAttn();
363
- if (shared.kind === "quant") {
365
+ if (shared.kind === "view") {
366
+ attn = shared.attention.attend(q, 1.0, mask);
367
+ } else if (shared.kind === "quant") {
364
368
  attn = quantizedSdpa(q, shared.keys, shared.values, 1.0, mask, shared.groupSize, shared.bits);
365
369
  } else if (
366
370
  ta === "flash" && flashSupported(q) && (mask.mode === "causal" || mask.mode === "array")
@@ -370,7 +374,7 @@ class Attention {
370
374
  // verified route: its dQ/dK/dV vjps are exact (finite-difference checked;
371
375
  // it's what mlx-lm's tuner differentiates). The two historical bugs were
372
376
  // in THIS flash kernel (spurious dK transpose, divergent dQ barrier),
373
- // both fixed and gated by tests/flash-attention.test.ts vs the ops.sdpa
377
+ // both fixed and gated by tests/unit/flash-attention.test.ts vs the ops.sdpa
374
378
  // oracle — see flash-attention.ts's routing comment. Flash also remains
375
379
  // Gemma-guarded in the trainer (e4b >=2K SIGTRAP not yet re-validated).
376
380
  // Full layers → window 0 (pure causal); sliding layers → their window.
@@ -383,7 +387,7 @@ class Attention {
383
387
  if (shared.kind === "plain" && shared.vRotated) {
384
388
  // Attention is linear in V: one InvFWHT on the [B,H,L,D] output
385
389
  // replaces one per cached token (TurboQuant deferred-V).
386
- attn = disposing(attn, tqUnrotateValues(attn));
390
+ attn = disposing(attn, shared.restoreValues ? shared.restoreValues(attn) : tqUnrotateValues(attn));
387
391
  }
388
392
  const attnT = ops.transposeAxes(attn, [0, 2, 1, 3]);
389
393
  attn.dispose();
@@ -851,6 +855,13 @@ export class Gemma4Model {
851
855
  );
852
856
  }
853
857
 
858
+ /** Computed model constants must retain their constructor arithmetic when
859
+ * a restored request enters compiled decode without a prefill first. */
860
+ materializeGraphConstants(): void {
861
+ const constants = this.layers.flatMap(layer => layer.attn.ropeFreqs ? [layer.attn.ropeFreqs] : []);
862
+ if (constants.length) ops.evalAll(constants);
863
+ }
864
+
854
865
  /** ids [1, L] → final-norm hidden states [1, L, hidden]. */
855
866
  forwardHidden(ids: MlxArray, cache: Cache[]): MlxArray {
856
867
  let h = this.embed.encode(ids);
@@ -885,7 +896,7 @@ export class Gemma4Model {
885
896
  * which attend bidirectionally among themselves (use_bidirectional_attention:
886
897
  * "vision" — text stays causal; audio prompts pass NO bidir mask, and a
887
898
  * mixed image+audio prompt drops the image overlay too, §3.3 Q1 of
888
- * docs/design/audio-input-plan.md). `ids` ([1, L], the spliced token ids)
899
+ * docs/design/generic-model-support.md). `ids` ([1, L], the spliced token ids)
889
900
  * is required for per-layer-input models (e2b/e4b): multimodal soft-token
890
901
  * positions get token 0's per-layer embedding, and their tower content
891
902
  * enters only through the projection term (the merged hidden). Matches
@@ -1000,13 +1011,7 @@ export class Gemma4Model {
1000
1011
  for (let i = 0; i < this.numDonors; i++) {
1001
1012
  const s = intermediates[i];
1002
1013
  if (!s) continue;
1003
- if (s.kind === "plain") {
1004
- s.keys.dispose();
1005
- s.values.dispose();
1006
- } else {
1007
- for (const t of [s.keys, s.values])
1008
- for (const a of [t.packed, t.scales, t.biases]) a.dispose();
1009
- }
1014
+ Gemma4Model.disposeSharedKv(s);
1010
1015
  }
1011
1016
  for (const m of masks.values()) m.arr?.dispose();
1012
1017
 
@@ -1015,7 +1020,7 @@ export class Gemma4Model {
1015
1020
  return h;
1016
1021
  }
1017
1022
 
1018
- // --- Segmented-backward support (docs/design/segmented-backward-training.md
1023
+ // --- Segmented-backward support (docs/design/orpo-training.md
1019
1024
  // §4 Phase B). These are ADDITIVE — forwardLayers is untouched. The segmented
1020
1025
  // driver builds masks + per-layer inputs ONCE, then drives runLayerRange per
1021
1026
  // segment, threading the KV-shared donor K/V across segment boundaries.
@@ -1056,7 +1061,10 @@ export class Gemma4Model {
1056
1061
  /** Dispose a fetched donor K/V (plain or quantized) — mirrors forwardLayers'
1057
1062
  * donor cleanup. */
1058
1063
  private static disposeSharedKv(s: SharedKv): void {
1059
- if (s.kind === "plain") {
1064
+ s.offsetArr?.dispose();
1065
+ if (s.kind === "view") {
1066
+ s.attention.dispose();
1067
+ } else if (s.kind === "plain") {
1060
1068
  s.keys.dispose();
1061
1069
  s.values.dispose();
1062
1070
  } else {
@@ -4,8 +4,8 @@
4
4
  // fingerprint: 9f812d2eb461fcbe
5
5
  //
6
6
  // Branch-resolved, unrolled forward pass for this architecture
7
- // (docs/design/optimization_plan.md Phase C). Bit-exactness vs the monolith is the
8
- // generator's gate: tests/generated-parity.test.ts. The cache-signature
7
+ // (docs/archive/investigations/optimization_plan.md Phase C). Bit-exactness vs the monolith is the
8
+ // generator's gate: tests/parity/generated-parity.test.ts. The cache-signature
9
9
  // guard falls back to the monolith for anything this file wasn't
10
10
  // generated for (bf16 compat runs, vision bidir prefill).
11
11
 
@@ -34,7 +34,10 @@ export const FINGERPRINT = "9f812d2eb461fcbe";
34
34
  export let generatedForwardUses = 0;
35
35
 
36
36
  function disposeSharedKv(s: SharedKv): void {
37
- if (s.kind === "plain") {
37
+ if (s.kind === "view") {
38
+ s.attention.dispose();
39
+ s.offsetArr?.dispose();
40
+ } else if (s.kind === "plain") {
38
41
  s.keys.dispose();
39
42
  s.values.dispose();
40
43
  } else {
@@ -4,8 +4,8 @@
4
4
  // fingerprint: c9dd67ed5a525231
5
5
  //
6
6
  // Branch-resolved, unrolled forward pass for this architecture
7
- // (docs/design/optimization_plan.md Phase C). Bit-exactness vs the monolith is the
8
- // generator's gate: tests/generated-parity.test.ts. The cache-signature
7
+ // (docs/archive/investigations/optimization_plan.md Phase C). Bit-exactness vs the monolith is the
8
+ // generator's gate: tests/parity/generated-parity.test.ts. The cache-signature
9
9
  // guard falls back to the monolith for anything this file wasn't
10
10
  // generated for (bf16 compat runs, vision bidir prefill).
11
11
 
@@ -34,7 +34,10 @@ export const FINGERPRINT = "c9dd67ed5a525231";
34
34
  export let generatedForwardUses = 0;
35
35
 
36
36
  function disposeSharedKv(s: SharedKv): void {
37
- if (s.kind === "plain") {
37
+ if (s.kind === "view") {
38
+ s.attention.dispose();
39
+ s.offsetArr?.dispose();
40
+ } else if (s.kind === "plain") {
38
41
  s.keys.dispose();
39
42
  s.values.dispose();
40
43
  } else {
@@ -4,8 +4,8 @@
4
4
  // fingerprint: 418e9adc386ea67c
5
5
  //
6
6
  // Branch-resolved, unrolled forward pass for this architecture
7
- // (docs/design/optimization_plan.md Phase C). Bit-exactness vs the monolith is the
8
- // generator's gate: tests/generated-parity.test.ts. The cache-signature
7
+ // (docs/archive/investigations/optimization_plan.md Phase C). Bit-exactness vs the monolith is the
8
+ // generator's gate: tests/parity/generated-parity.test.ts. The cache-signature
9
9
  // guard falls back to the monolith for anything this file wasn't
10
10
  // generated for (bf16 compat runs, vision bidir prefill).
11
11
 
@@ -35,7 +35,10 @@ export const FINGERPRINT = "418e9adc386ea67c";
35
35
  export let generatedForwardUses = 0;
36
36
 
37
37
  function disposeSharedKv(s: SharedKv): void {
38
- if (s.kind === "plain") {
38
+ if (s.kind === "view") {
39
+ s.attention.dispose();
40
+ s.offsetArr?.dispose();
41
+ } else if (s.kind === "plain") {
39
42
  s.keys.dispose();
40
43
  s.values.dispose();
41
44
  } else {
@@ -11,6 +11,8 @@
11
11
  // row's logical position without reconstructing per-head K/V.
12
12
 
13
13
  import { MlxArray } from "../mlx/array";
14
+ import { FullPrefillPadding } from "./full-prefill-padding";
15
+ import type { PrefillPadding } from "./gemma4-base";
14
16
  import { Dtype } from "../mlx/ffi";
15
17
  import * as ops from "../mlx/ops";
16
18
  import {
@@ -210,6 +212,8 @@ export class Glm52Cache implements BatchableCache {
210
212
  batchSize: number | null = null;
211
213
  rowOffsets: number[] = [];
212
214
  leftPad: number[] = [];
215
+ #beforeRound: number[] | undefined;
216
+ readonly #padding = new FullPrefillPadding(1);
213
217
 
214
218
  constructor(geometry: MLACacheGeometry) {
215
219
  positiveInteger("MLA kvLoraRank", geometry.kvLoraRank);
@@ -482,6 +486,56 @@ export class Glm52Cache implements BatchableCache {
482
486
  this.rowOffsets = this.rowOffsets.map((value) => value - n);
483
487
  }
484
488
 
489
+ preparePrefill(padding: PrefillPadding): void { this.#padding.prepare(this, padding); }
490
+ finalizePrefill(): void {
491
+ const next = this.#padding.finalize(this.state(), this);
492
+ if (!next) return;
493
+ this.latent?.dispose(); this.rope?.dispose();
494
+ this.latent = next[0] ?? null; this.rope = next[1] ?? null;
495
+ if (this.dsa && next[2]) this.dsa.restoreState(next[2], this.offset);
496
+ }
497
+
498
+ specRoundBegin(): void { this.#beforeRound = [...this.rowOffsets]; }
499
+ specRoundCommit(): void { this.#beforeRound = undefined; }
500
+
501
+ /** Keep each accepted prefix in compressed form. Right alignment lets the
502
+ * next shared append reuse the existing MLA/DSA attention geometry. */
503
+ specRoundRollback(keep: number | readonly number[]): void {
504
+ const nextOffsets = this.#beforeRound!.map((before, row) => before +
505
+ (typeof keep === "number" ? keep : keep[row]!));
506
+ const rejected = this.rowOffsets.map((offset, row) => offset - nextOffsets[row]!);
507
+ if (rejected.every(count => count === rejected[0])) {
508
+ const count = this.batchSize!;
509
+ this.trim(rejected[0]!);
510
+ if (this.batchSize === null) {
511
+ const empty = this.makeEmptyBatch();
512
+ try { this.mergeRows(Array.from({ length: count }, () => empty)); }
513
+ finally { empty.dispose(); }
514
+ }
515
+ this.#beforeRound = undefined; return;
516
+ }
517
+ const width = this.offset;
518
+ const pads = this.leftPad.map((pad, row) => pad + rejected[row]!);
519
+ const removable = Math.min(...pads);
520
+ using indices = ops.fromInt32(rejected.flatMap(count =>
521
+ Array.from({ length: width }, (_, column) => (column - count + width) % width)),
522
+ [this.batchSize!, width, 1]);
523
+ const next: MlxArray[] = [];
524
+ try {
525
+ for (const array of this.state()) {
526
+ using shifted = ops.takeAlongAxis(array, indices, 1);
527
+ next.push(shifted.slice([0, removable, 0], [this.batchSize!, width, array.shape[2]!]));
528
+ }
529
+ } catch (error) { for (const array of next) array.dispose(); throw error; }
530
+ this.latent!.dispose(); this.rope!.dispose();
531
+ this.latent = next[0]!; this.rope = next[1]!;
532
+ if (this.dsa) this.dsa.restoreState(next[2]!, width - removable);
533
+ this.offset = width - removable;
534
+ this.rowOffsets = nextOffsets;
535
+ this.leftPad = pads.map(pad => pad - removable);
536
+ this.#beforeRound = undefined;
537
+ }
538
+
485
539
  /** Exact logical bytes of valid f32 compressed state. */
486
540
  get byteLength(): number {
487
541
  if (this.batchSize === null) return 0;
@@ -529,8 +583,16 @@ export class Glm52Cache implements BatchableCache {
529
583
  ) {
530
584
  throw new Error("MLA mergeRows cache geometry does not match");
531
585
  }
532
- if (generic.batchSize === null || generic.offset === 0)
533
- throw new Error("MLA mergeRows cannot merge an empty row");
586
+ if (generic.batchSize === null || generic.offset === 0) {
587
+ const count = generic.batchSize ?? 1;
588
+ for (let row = 0; row < count; row++) {
589
+ states.push({ latent: ops.zeros([1, 0, this.kvLoraRank], Dtype.float32),
590
+ rope: ops.zeros([1, 0, this.ropeHeadDim], Dtype.float32),
591
+ dsa: this.dsa ? ops.zeros([1, 0, this.dsa.headDim], Dtype.float32) : null });
592
+ offsets.push(0);
593
+ }
594
+ continue;
595
+ }
534
596
  for (let row = 0; row < generic.batchSize; row++) {
535
597
  states.push(generic.fetchRow(row));
536
598
  offsets.push(generic.rowOffset(row));
@@ -577,7 +639,10 @@ export class Glm52Cache implements BatchableCache {
577
639
  this.offset = width;
578
640
  this.rowOffsets = [...offsets];
579
641
  this.leftPad = offsets.map((value) => width - value);
580
- if (this.dsa) this.dsa.restoreState(dsa!, width);
642
+ if (this.dsa) {
643
+ if (width) this.dsa.restoreState(dsa!, width);
644
+ else { this.dsa.data = dsa!; this.dsa.batchSize = offsets.length; }
645
+ }
581
646
  } finally {
582
647
  for (const state of states) {
583
648
  state.latent.dispose();
@@ -591,6 +656,7 @@ export class Glm52Cache implements BatchableCache {
591
656
  extractRow(row: number): Glm52Cache {
592
657
  if (this.batchSize === null) throw new Error("MLA cache is empty");
593
658
  validateRowIndex(row, this.batchSize, "MLA extract");
659
+ if (this.rowOffsets[row] === 0) return this.makeEmptyBatch();
594
660
  const state = this.fetchRow(row);
595
661
  const copy = (array: MlxArray): MlxArray => ops.mulScalar(array, 1);
596
662
  const latent = copy(state.latent);
@@ -666,7 +732,15 @@ export class Glm52Cache implements BatchableCache {
666
732
  this.offset = nextWidth;
667
733
  this.rowOffsets = nextOffsets;
668
734
  this.leftPad = selectedPad.map((value) => value - removablePad);
669
- if (this.dsa) this.dsa.restoreState(dsa!, nextWidth);
735
+ this.#padding.filter(keep);
736
+ if (this.dsa) {
737
+ if (nextWidth) this.dsa.restoreState(dsa!, nextWidth);
738
+ else {
739
+ // A retained cold row still owns batch membership, even though the
740
+ // strict persisted-state restore contract requires nonempty tokens.
741
+ this.dsa.dispose(); this.dsa.data = dsa!; this.dsa.batchSize = keep.length;
742
+ }
743
+ }
670
744
  }
671
745
 
672
746
  projectedBytes(tokens: number): number {
@@ -709,6 +783,8 @@ export class Glm52Cache implements BatchableCache {
709
783
  this.batchSize = null;
710
784
  this.rowOffsets = [];
711
785
  this.leftPad = [];
786
+ this.#beforeRound = undefined;
787
+ this.#padding.clear();
712
788
  }
713
789
  }
714
790
 
@@ -291,7 +291,7 @@ function getDsaRankKeyKernel(): MetalKernel {
291
291
  ? ~bits
292
292
  : (bits ^ 0x80000000u);
293
293
  const uint descending = ~ascending;
294
- keys[i] = (ulong(descending) << 32) | ulong(i);
294
+ keys[i] = descending;
295
295
  `,
296
296
  });
297
297
  }
@@ -308,7 +308,8 @@ function getDsaContractOrderKeyKernel(): MetalKernel {
308
308
  const uint i = thread_position_in_grid.x;
309
309
  const uint position = selected_positions[i];
310
310
  const uint threshold_class = selected_scores[i] > threshold ? 0u : 1u;
311
- keys[i] = (ulong(threshold_class) << 32) | ulong(position);
311
+ // MLX axis lengths are signed int32; positions leave the high bit free.
312
+ keys[i] = (threshold_class << 31) | position;
312
313
  `,
313
314
  });
314
315
  }
@@ -318,8 +319,8 @@ function getDsaContractOrderKeyKernel(): MetalKernel {
318
319
  /**
319
320
  * Deterministic on-device equivalent of `selectDsaThresholdTiesF32`.
320
321
  *
321
- * The first uint64 key sorts by score descending and then position ascending,
322
- * making the top-k set deterministic even at the threshold. The second key
322
+ * Stable sorting of the first uint32 key orders scores descending and keeps
323
+ * equal-score positions ascending, including threshold ties. The second key
323
324
  * restores Colibri's observable two-scan order: all scores strictly above the
324
325
  * threshold in position order, followed by threshold ties in position order.
325
326
  * The caller owns the returned arrays and must call `dispose()` once the last
@@ -340,18 +341,21 @@ export function selectGlm52DsaDevice(
340
341
  ? scores
341
342
  : scores.astype(Dtype.float32);
342
343
  const [rankKeys] = getDsaRankKeyKernel().apply([scoresF32], {
343
- outputs: [{ shape: [contextLength], dtype: Dtype.uint64 }],
344
+ outputs: [{ shape: [contextLength], dtype: Dtype.uint32 }],
344
345
  grid: [contextLength, 1, 1],
345
346
  threadGroup: [Math.min(256, contextLength), 1, 1],
346
347
  });
347
- const partition = ops.argpartitionAxis(rankKeys!, topK - 1, 0);
348
- const selectedUnordered = partition.slice([0], [topK]);
348
+ // Stable argsort retains ascending positions for equal score bits, so the
349
+ // ranking key needs no position field. MLX 0.31.2's GPU argpartition also
350
+ // sorts fully, but does not promise this tie order in its public contract.
351
+ const rankedPositions = ops.argsortAxis(rankKeys!, 0);
352
+ const selectedUnordered = rankedPositions.slice([0], [topK]);
349
353
  const selectedScores = ops.takeAlongAxis(scoresF32, selectedUnordered, 0);
350
354
  const threshold = ops.minAxis(selectedScores, 0, false);
351
355
  const [orderKeys] = getDsaContractOrderKeyKernel().apply(
352
356
  [selectedScores, selectedUnordered, threshold],
353
357
  {
354
- outputs: [{ shape: [topK], dtype: Dtype.uint64 }],
358
+ outputs: [{ shape: [topK], dtype: Dtype.uint32 }],
355
359
  grid: [topK, 1, 1],
356
360
  threadGroup: [Math.min(256, topK), 1, 1],
357
361
  },
@@ -360,7 +364,7 @@ export function selectGlm52DsaDevice(
360
364
  const positions = ops.takeAlongAxis(selectedUnordered, order, 0);
361
365
 
362
366
  rankKeys!.dispose();
363
- partition.dispose();
367
+ rankedPositions.dispose();
364
368
  selectedUnordered.dispose();
365
369
  selectedScores.dispose();
366
370
  orderKeys!.dispose();
@@ -4,7 +4,7 @@ import {
4
4
  type Glm52Config,
5
5
  } from "./glm52-config";
6
6
  import { ColibriGlm52Container } from "./glm52-container";
7
- import type { MemoryPlan } from "../memory-plan";
7
+ import type { MemoryPlan } from "../admission-plan";
8
8
 
9
9
  export const GLM52_G5_GIB = 1024 ** 3;
10
10
  export const GLM52_G5_DEFAULT_MACHINE_BYTES = 32 * GLM52_G5_GIB;
@@ -484,7 +484,7 @@ export class Glm52Mla {
484
484
  }
485
485
  return this.attendAbsorbed(projection, state, selectedPositions);
486
486
  }
487
- return this.attendReconstructed(projection, state);
487
+ return this.attendReconstructed(projection, state, cache.leftPad);
488
488
  } finally {
489
489
  disposeState(state);
490
490
  disposeProjection(projection);
@@ -495,6 +495,7 @@ export class Glm52Mla {
495
495
  attendReconstructed(
496
496
  projection: Pick<Glm52MlaProjection, "qNope" | "qRope">,
497
497
  state: Pick<MLACompressedState, "latent" | "rope">,
498
+ leftPadding: readonly number[] = [],
498
499
  ): MlxArray {
499
500
  const [batch, tokens] = projection.qNope.shape;
500
501
  const keys = state.latent.shape[1]!;
@@ -570,14 +571,19 @@ export class Glm52Mla {
570
571
 
571
572
  const prefixTokens = keys - tokens!;
572
573
  let maskedScores = scores;
573
- if (tokens! > 1) {
574
- const maskValues = new Float32Array(tokens! * keys);
575
- for (let queryToken = 0; queryToken < tokens!; queryToken++) {
574
+ const padded = leftPadding.some(value => value > 0);
575
+ if (tokens! > 1 || padded) {
576
+ const maskRows = padded ? batch! : 1;
577
+ const maskValues = new Float32Array(maskRows * tokens! * keys);
578
+ for (let row = 0; row < maskRows; row++) for (let queryToken = 0; queryToken < tokens!; queryToken++) {
576
579
  const lastVisible = prefixTokens + queryToken;
580
+ const start = (row * tokens! + queryToken) * keys;
581
+ for (let keyToken = 0; keyToken < (leftPadding[row] ?? 0); keyToken++)
582
+ maskValues[start + keyToken] = -Infinity;
577
583
  for (let keyToken = lastVisible + 1; keyToken < keys; keyToken++)
578
- maskValues[queryToken * keys + keyToken] = -Infinity;
584
+ maskValues[start + keyToken] = -Infinity;
579
585
  }
580
- const mask = MlxArray.fromFloat32(maskValues, [1, 1, tokens!, keys]);
586
+ const mask = MlxArray.fromFloat32(maskValues, [maskRows, 1, tokens!, keys]);
581
587
  maskedScores = ops.add(scores, mask);
582
588
  mask.dispose();
583
589
  scores.dispose();
@@ -144,8 +144,6 @@ class Glm52DsaIndexer {
144
144
  forceDense = false,
145
145
  ): DsaStep {
146
146
  const [batch, tokens, hidden] = input.shape;
147
- if (batch! > 1 && tokens !== 1)
148
- throw new Error("batched DSA supports ordinary single-token decode only");
149
147
  if (hidden !== this.config.hiddenSize)
150
148
  throw new Error(`DSA hidden width ${hidden} != ${this.config.hiddenSize}`);
151
149
 
@@ -228,7 +226,10 @@ class Glm52DsaIndexer {
228
226
  }
229
227
  const contextLength = allKeys.shape[1]!;
230
228
  let selectedPositions: MlxArray | Glm52MlaBatchedSelection | null;
231
- if (batch! > 1) {
229
+ if (forceDense || contextLength <= this.config.indexTopk) {
230
+ state.selectFullDense(this.layer, contextLength);
231
+ selectedPositions = null;
232
+ } else if (batch! > 1) {
232
233
  const rowSelections: Array<MlxArray | null> = [];
233
234
  for (let row = 0; row < batch!; row++) {
234
235
  const rowContext = (cache.rowOffsets[row] ?? 0) + tokens!;
@@ -269,15 +270,6 @@ class Glm52DsaIndexer {
269
270
  scores.dispose();
270
271
  }
271
272
  selectedPositions = { rows: rowSelections };
272
- } else if (forceDense) {
273
- state.selectFullDense(this.layer, contextLength);
274
- selectedPositions = null;
275
- } else if (contextLength <= this.config.indexTopk) {
276
- state.selectFull(
277
- this.layer,
278
- new Float32Array(contextLength),
279
- );
280
- selectedPositions = null;
281
273
  } else {
282
274
  if (tokens !== 1) {
283
275
  if (allKeys !== key) allKeys.dispose();
@@ -533,6 +525,7 @@ export class Glm52DecoderLayer {
533
525
  this.inputNorm,
534
526
  this.config.rmsNormEps,
535
527
  );
528
+ const batch = input.shape[0]!;
536
529
  const tokens = input.shape[1]!;
537
530
  const denseBenchmarkPrefill =
538
531
  this.denseBenchmarkPrefillTokens > 0 &&
@@ -552,10 +545,10 @@ export class Glm52DecoderLayer {
552
545
  for (let token = 0; token < tokens; token++) {
553
546
  const row = normalized.slice(
554
547
  [0, token, 0],
555
- [1, token + 1, this.config.hiddenSize],
548
+ [batch, token + 1, this.config.hiddenSize],
556
549
  );
557
550
  let dsaKey: MlxArray | null = null;
558
- let positions: MlxArray | null = null;
551
+ let positions: MlxArray | Glm52MlaBatchedSelection | null = null;
559
552
  try {
560
553
  if (this.dsa) {
561
554
  const step = this.dsa.projectAndSelect(
@@ -564,18 +557,12 @@ export class Glm52DecoderLayer {
564
557
  dsaState,
565
558
  );
566
559
  dsaKey = step.key;
567
- if (
568
- step.selectedPositions !== null &&
569
- !(step.selectedPositions instanceof MlxArray)
570
- ) {
571
- throw new Error("serial DSA verification received batched selection");
572
- }
573
560
  positions = step.selectedPositions;
574
561
  } else {
575
- positions = dsaState.selectSharedPositions(
576
- this.layer,
577
- cache.offset + 1,
578
- );
562
+ positions = batch > 1
563
+ ? { rows: Array.from({ length: batch }, (_, row) =>
564
+ dsaState.selectSharedPositions(this.layer, (cache.rowOffsets[row] ?? 0) + 1, row)) }
565
+ : dsaState.selectSharedPositions(this.layer, cache.offset + 1);
579
566
  }
580
567
  rows.push(this.mla.forward(
581
568
  row,
@@ -609,7 +596,9 @@ export class Glm52DecoderLayer {
609
596
  dsaKey = step.key;
610
597
  positions = step.selectedPositions;
611
598
  } else if (dsaState) {
612
- positions = cache.batchSize !== null && cache.batchSize > 1
599
+ positions = denseBenchmarkPrefill || cache.offset + tokens <= this.config.indexTopk
600
+ ? null
601
+ : cache.batchSize !== null && cache.batchSize > 1
613
602
  ? {
614
603
  rows: cache.rowOffsets.map((offset, row) =>
615
604
  dsaState.selectSharedPositions(
@@ -871,18 +860,13 @@ export class Glm52Model {
871
860
  tokenCount > GLM52_DSA_SPARSE_DECODE_BATCH_MAX &&
872
861
  firstOffset + tokenCount > this.glmConfig.indexTopk
873
862
  ) {
874
- if (ids.shape.length !== 2 || ids.shape[0] !== 1) {
875
- throw new Error(
876
- "G2 DSA sparse prefill requires serial [1,T] token ids",
877
- );
878
- }
879
- // Colibri selects independently for every query row. G2 preserves that
880
- // behavior with a deliberately serial correctness fallback once a
881
- // multi-token prefill crosses index_topk; G6 may vectorize it.
863
+ // Bound sparse-selection state for long prefills while retaining all
864
+ // request rows in each forward. Short verify windows keep the residual
865
+ // and expert graph batched across both requests and query positions.
882
866
  const rows: MlxArray[] = [];
883
867
  try {
884
868
  for (let token = 0; token < tokenCount; token++) {
885
- const row = ids.slice([0, token], [1, token + 1]);
869
+ const row = ids.slice([0, token], [ids.shape[0]!, token + 1]);
886
870
  try {
887
871
  rows.push(this.forwardHidden(row, cache));
888
872
  } finally {
@@ -922,15 +906,10 @@ export class Glm52Model {
922
906
  tokenCount > GLM52_DSA_SPARSE_DECODE_BATCH_MAX &&
923
907
  firstOffset + tokenCount > this.glmConfig.indexTopk
924
908
  ) {
925
- if (ids.shape.length !== 2 || ids.shape[0] !== 1) {
926
- throw new Error(
927
- "G3 DSA sparse prefill requires serial [1,T] token ids",
928
- );
929
- }
930
909
  const rows: MlxArray[] = [];
931
910
  try {
932
911
  for (let token = 0; token < tokenCount; token++) {
933
- const row = ids.slice([0, token], [1, token + 1]);
912
+ const row = ids.slice([0, token], [ids.shape[0]!, token + 1]);
934
913
  try {
935
914
  rows.push(await this.forwardHiddenAsync(row, cache));
936
915
  } finally {