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
@@ -0,0 +1,69 @@
1
+ import type { ModelConfig } from "../config";
2
+ import {
3
+ assertResolvedModelProfile,
4
+ type GenerationLoop,
5
+ type ModelGraph,
6
+ type ModelLoader,
7
+ type ResolvedModelProfile,
8
+ } from "./profile";
9
+
10
+ /** Engine code implements this port. Source and Model remain backend-specific;
11
+ * selection introduces no tensor conversion or dispatch in the token loop.
12
+ * create borrows source; a failed construction must release its own resources. */
13
+ export interface ModelImplementation<Source, Model> {
14
+ readonly id: string;
15
+ readonly graph: ModelGraph;
16
+ readonly loader: ModelLoader;
17
+ readonly loop: GenerationLoop;
18
+ create(source: Source, config: ModelConfig, profile: ResolvedModelProfile): Model;
19
+ }
20
+
21
+ export interface ModelImplementationProvider<Source, Model> {
22
+ /** Validate identity and composition, then return one compatible binding.
23
+ * A declared implementation must never silently fall back to another. */
24
+ select(config: ModelConfig, profile: ResolvedModelProfile): ModelImplementation<Source, Model>;
25
+ }
26
+
27
+ /** Immutable composition, with identity matching delegated to model profiles.
28
+ * Exact declarations are requirements: absent/incompatible code is an error,
29
+ * never an invitation to retry with a different numerical implementation. */
30
+ export class ModelImplementationRegistry<Source, Model> implements ModelImplementationProvider<Source, Model> {
31
+ private readonly entries: ReadonlyMap<string, ModelImplementation<Source, Model>>;
32
+
33
+ constructor(implementations: readonly ModelImplementation<Source, Model>[]) {
34
+ const entries = new Map<string, ModelImplementation<Source, Model>>();
35
+ for (const implementation of implementations) {
36
+ if (!implementation.id.trim()) throw new Error("model implementation id must not be empty");
37
+ if (entries.has(implementation.id))
38
+ throw new Error(`duplicate model implementation ${implementation.id}`);
39
+ entries.set(implementation.id, Object.freeze({
40
+ id: implementation.id,
41
+ graph: implementation.graph,
42
+ loader: implementation.loader,
43
+ loop: implementation.loop,
44
+ create: implementation.create.bind(implementation),
45
+ }));
46
+ }
47
+ this.entries = entries;
48
+ }
49
+
50
+ /** Return a new composition; active models retain their existing binding. */
51
+ with(...implementations: readonly ModelImplementation<Source, Model>[]): ModelImplementationRegistry<Source, Model> {
52
+ return new ModelImplementationRegistry([...this.entries.values(), ...implementations]);
53
+ }
54
+
55
+ select(config: ModelConfig, profile: ResolvedModelProfile): ModelImplementation<Source, Model> {
56
+ assertResolvedModelProfile(config, profile);
57
+ const execution = profile.profile.execution;
58
+ // These default IDs preserve the existing dedicated/generated/family choice.
59
+ const id = execution.implementation ?? (execution.specialization === "generated"
60
+ ? `${execution.graph}-generated` : execution.graph);
61
+ const implementation = this.entries.get(id);
62
+ if (!implementation)
63
+ throw new Error(`model profile ${profile.profile.id} requires unavailable implementation ${id}; refusing to fall back`);
64
+ if (implementation.graph !== execution.graph || implementation.loader !== execution.loader ||
65
+ implementation.loop !== execution.loop)
66
+ throw new Error(`model implementation ${id} is incompatible with profile ${profile.profile.id}; refusing to fall back`);
67
+ return implementation;
68
+ }
69
+ }
@@ -0,0 +1,67 @@
1
+ import type { MlxArray } from "../mlx/array";
2
+ import * as ops from "../mlx/ops";
3
+ import type { Cache, KvAttentionView, KvDonorAttention } from "./gemma4-base";
4
+ import { quantizedSdpa, disposeTriple } from "./gemma4-base";
5
+
6
+ export function captureKvDonorAttention(cache: Cache): KvDonorAttention {
7
+ if (cache.captureDonorAttention) return cache.captureDonorAttention();
8
+ const view = cache.captureDonorRows!();
9
+ return {
10
+ width: view.keys.shape[2]!, dtype: view.keys.dtype,
11
+ offsets: view.offsets, starts: view.starts, ends: view.ends,
12
+ attend: (q, scale, mask) => ops.sdpa(q, view.keys, view.values, scale, mask.mode, mask.arr),
13
+ dispose() { view.keys.dispose(); view.values.dispose(); },
14
+ };
15
+ }
16
+
17
+ export function quantizedDonorAttention(keys: ops.QuantizedTensor, values: ops.QuantizedTensor,
18
+ validity: Pick<KvDonorAttention, "offsets" | "starts" | "ends">,
19
+ groupSize: number, bits: number): KvDonorAttention {
20
+ return {
21
+ width: keys.packed.shape[2]!, dtype: keys.scales.dtype, ...validity,
22
+ attend: (q, scale, mask) => quantizedSdpa(q, keys, values, scale, mask, groupSize, bits),
23
+ dispose() { disposeTriple(keys); disposeTriple(values); },
24
+ };
25
+ }
26
+
27
+ export function captureKvAttention(cache: Cache, k: MlxArray, v: MlxArray): KvAttentionView {
28
+ if (cache.attentionState) return cache.attentionState.appendAndFetch(k, v);
29
+ const quantized = cache.quantizedAttention;
30
+ if (quantized) {
31
+ const { groupSize, bits } = quantized;
32
+ const [keys, values] = quantized.updateAndFetchQuantized(k, v);
33
+ return {
34
+ attend: (q, scale, mask) => quantizedSdpa(q, keys, values, scale, mask, groupSize, bits),
35
+ dispose() { disposeTriple(keys); disposeTriple(values); },
36
+ };
37
+ }
38
+ const [keys, values] = cache.updateAndFetch(k, v);
39
+ return {
40
+ attend: (q, scale, mask) => ops.sdpa(q, keys, values, scale, mask.mode, mask.arr),
41
+ dispose() { keys.dispose(); values.dispose(); },
42
+ };
43
+ }
44
+
45
+
46
+ /** Takes ownership of already aligned per-row donor views. Each storage codec
47
+ * keeps its own attention arithmetic while the caller supplies one query batch. */
48
+ export function combineKvDonorAttention(rows: readonly KvDonorAttention[]): KvDonorAttention {
49
+ return { width: rows[0]!.width, dtype: rows[0]!.dtype,
50
+ offsets: rows.flatMap(row => [...row.offsets]), starts: rows.flatMap(row => [...row.starts]),
51
+ ends: rows.flatMap(row => [...row.ends]),
52
+ attend(q, scale, mask) {
53
+ const outputs: MlxArray[] = [];
54
+ try {
55
+ for (const [index, row] of rows.entries()) {
56
+ using query = q.slice([index, 0, 0, 0], [index + 1, q.shape[1]!, q.shape[2]!, q.shape[3]!]);
57
+ const from = mask.arr?.shape.map(() => 0), to = mask.arr ? [...mask.arr.shape] : undefined;
58
+ if (from && to && to.length === 4 && to[0]! > 1) { from[0] = index; to[0] = index + 1; }
59
+ using arr = mask.arr ? mask.arr.slice(from!, to!) : null;
60
+ outputs.push(row.attend(query, scale, { mode: mask.mode, arr }));
61
+ }
62
+ return outputs.length === 1 ? ops.contiguous(outputs[0]!) : ops.concatAxis(outputs, 0);
63
+ } finally { for (const output of outputs) output.dispose(); }
64
+ },
65
+ dispose() { for (const row of rows) row.dispose(); },
66
+ };
67
+ }
@@ -14,7 +14,6 @@ import {
14
14
  KVCache,
15
15
  LoraState,
16
16
  QuantizedKVCache,
17
- TurboQuantKVCache,
18
17
  QuantizedEmbedding,
19
18
  QuantizedLinear,
20
19
  quantizedSdpa,
@@ -30,7 +29,7 @@ import { runtimeValue } from "../runtime-config";
30
29
  * `@partial(mx.compile, shapeless=True) def swiglu(gate, x): return nn.silu(gate) * x`
31
30
  * mx.compile fuses sigmoid + mul + mul into ONE kernel (mlx-lm's decode
32
31
  * `CV2ISigmoid…Multiply`) instead of our three separate dispatches. Traced
33
- * once, replayed thereafter. Used on the decode (M=1) path only. */
32
+ * once, replayed thereafter for decode and prefill. */
34
33
  let _swigluClosure: CompiledFunction | null = null;
35
34
  export function compiledSwiglu(gate: MlxArray, up: MlxArray): MlxArray {
36
35
  if (!_swigluClosure) {
@@ -143,23 +142,28 @@ export class LlamaAttention {
143
142
  q = disposing(q, ropeStep(q));
144
143
  k = disposing(k, ropeStep(k));
145
144
  let attn: MlxArray;
146
- if (cache instanceof QuantizedKVCache) {
147
- const [keys, values] = cache.updateAndFetchQuantized(k, v);
145
+ if (cache.attentionState) {
146
+ const view = cache.attentionState.appendAndFetch(k, v);
147
+ k.dispose(); v.dispose();
148
+ try { attn = view.attend(q, this.scale, mask); } finally { view.dispose(); }
149
+ } else if (cache.quantizedAttention) {
150
+ const quantized = cache.quantizedAttention;
151
+ const [keys, values] = quantized.updateAndFetchQuantized(k, v);
148
152
  k.dispose();
149
153
  v.dispose();
150
- attn = quantizedSdpa(q, keys, values, this.scale, mask, cache.groupSize, cache.bits);
154
+ attn = quantizedSdpa(q, keys, values, this.scale, mask, quantized.groupSize, quantized.bits);
151
155
  disposeTriple(keys);
152
156
  disposeTriple(values);
153
- } else if (cache instanceof TurboQuantKVCache) {
157
+ } else if (cache.rotatedValueAttention) {
154
158
  // Deferred-V: sdpa runs on rotated values, then one InvFWHT on the
155
159
  // output (linear in V) — see TurboQuantKVCache.updateAndFetchDeferredV.
156
- const [keys, values] = cache.updateAndFetchDeferredV(k, v);
160
+ const [keys, values] = cache.rotatedValueAttention.updateAndFetchDeferredV(k, v);
157
161
  k.dispose();
158
162
  v.dispose();
159
163
  const rotated = ops.sdpa(q, keys, values, this.scale, mask.mode, mask.arr);
160
164
  keys.dispose();
161
165
  values.dispose();
162
- attn = tqUnrotateValues(rotated);
166
+ attn = cache.rotatedValueAttention.captureValueTransform?.()(rotated) ?? tqUnrotateValues(rotated);
163
167
  rotated.dispose();
164
168
  } else {
165
169
  const [keys, values] = cache.updateAndFetch(k, v);
@@ -200,15 +204,13 @@ export class LlamaMLP {
200
204
 
201
205
  /** MLP: returns mlp(x); the caller adds the residual when given. */
202
206
  forward(x: MlxArray, residual?: MlxArray): MlxArray {
203
- const H = x.shape[x.shape.length - 1]!;
204
- const M = x.shape.reduce((a, b) => a * b, 1) / H;
205
207
  const gate = this.gate.forward(x);
206
208
  const up = this.up.forward(x);
207
- // Decode (M=1): fuse silu·up into one kernel (mlx_lm activations.py swiglu).
208
- // Plain sigmoid+mul+mul otherwise (M>1 prefill/training, or inside a compile
209
- // trace) — identical math either way. MLX_BUN_COMPILED_SWIGLU=0 disables.
209
+ // Match mlx_lm activations.py at every sequence length. Separate kernels
210
+ // differ numerically on MLX 0.32.2 at larger prefill shapes. An enclosing
211
+ // compiled trace owns fusion itself; the explicit opt-out stays available.
210
212
  let hidden: MlxArray;
211
- if (M === 1 && !isCompiledTrace() && runtimeValue("MLX_BUN_COMPILED_SWIGLU") !== "0") {
213
+ if (!isCompiledTrace() && runtimeValue("MLX_BUN_COMPILED_SWIGLU") !== "0") {
212
214
  hidden = compiledSwiglu(gate, up);
213
215
  gate.dispose();
214
216
  up.dispose();
@@ -321,7 +323,7 @@ export class MiniCPM5Model {
321
323
  * NEVER disposed — the caller owns it (for segmented backward `h` is a leaf
322
324
  * whose lifetime is managed by the autograd closure); intra-range
323
325
  * intermediates ARE disposed. Building block for `forwardLayers` and the
324
- * segmented-backward training path (docs/design/segmented-backward-training.md).
326
+ * segmented-backward training path (docs/design/orpo-training.md).
325
327
  * MiniCPM5 is plain full-attention with no KV-sharing, so each layer reads
326
328
  * its own stateless `cache[i]` and a single causal mask. */
327
329
  runLayerRange(h: MlxArray, aIdx: number, bIdx: number, cache: Cache[]): MlxArray {
@@ -58,6 +58,9 @@ export interface ModelExecutionComposition {
58
58
  readonly graph: ModelGraph;
59
59
  readonly loop: GenerationLoop;
60
60
  readonly specialization: ModelSpecialization;
61
+ /** Engine-owned code registration. Model files never supply executable code.
62
+ * Omit to retain the existing graph implementation. */
63
+ readonly implementation?: string;
61
64
  }
62
65
 
63
66
  /** A profile declares construction only. Request methods such as MTP, KV
@@ -323,6 +326,10 @@ function executionCapabilities(profile: ModelProfile): EngineCapability[] {
323
326
 
324
327
  function validateProfile(profile: ModelProfile): void {
325
328
  if (!profile.id) throw new Error("model profile id must not be empty");
329
+ if (profile.execution.implementation !== undefined &&
330
+ (!profile.execution.implementation.trim() || !profile.artifactFingerprint ||
331
+ profile.execution.specialization !== "artifact"))
332
+ throw new Error(`model profile ${profile.id} must bind a named implementation to an exact artifact`);
326
333
  if ((profile.artifactFingerprint === undefined) !== (profile.configFingerprint === undefined))
327
334
  throw new Error(
328
335
  `model profile ${profile.id} must declare artifactFingerprint and configFingerprint together`,
@@ -413,6 +420,7 @@ export function assertResolvedModelProfile(
413
420
  config: ModelConfig,
414
421
  resolved: ResolvedModelProfile,
415
422
  ): void {
423
+ validateProfile(resolved.profile);
416
424
  const fingerprint = configFingerprint(config);
417
425
  if (resolved.artifact.configFingerprint !== fingerprint)
418
426
  throw new Error(
@@ -424,8 +432,10 @@ export function assertResolvedModelProfile(
424
432
  `model profile ${resolved.profile.id} selects ${resolved.profile.execution.graph}, ` +
425
433
  `which is incompatible with ${config.modelType}`,
426
434
  );
427
- if (resolved.exactArtifact &&
428
- resolved.profile.artifactFingerprint !== resolved.artifact.fingerprint)
435
+ if (resolved.exactArtifact !== (resolved.profile.artifactFingerprint !== undefined) ||
436
+ (resolved.exactArtifact &&
437
+ (resolved.profile.artifactFingerprint !== resolved.artifact.fingerprint ||
438
+ resolved.profile.configFingerprint !== fingerprint)))
429
439
  throw new Error(
430
440
  `model profile ${resolved.profile.id} does not match artifact ` +
431
441
  `${resolved.artifact.fingerprint ?? "<unidentified>"}`,
@@ -0,0 +1,12 @@
1
+ // MLX 0.32.2, mlx/backend/metal/scaled_dot_product_attention.cpp (MIT):
2
+ // https://github.com/ml-explore/mlx/blob/v0.32.2/mlx/backend/metal/scaled_dot_product_attention.cpp
3
+ // For the qualified Qwen GQA-6/D256 graph on applegpu_g16s, one-pass/vector
4
+ // selection and two-pass block counts change after these inclusive KV lengths.
5
+ // A committed span must stay within one arithmetic regime to match M=1.
6
+ const ATTENTION_BOUNDARIES = [1023, 1024, 8192, 32768, 65536];
7
+
8
+ export function qwenAppendChunkSize(cacheTokens: number): number {
9
+ for (const end of ATTENTION_BOUNDARIES)
10
+ if (cacheTokens < end) return Math.min(4, end - cacheTokens);
11
+ return 4;
12
+ }
@@ -0,0 +1,52 @@
1
+ // Experimental Qwen GDN convolution. Its two outputs are evaluated together:
2
+ // consuming the activation also materializes the small, independently owned
3
+ // state tail, without keeping a concatenated prefill buffer alive for it.
4
+ // Arithmetic follows MLX 0.31.2 depthwise_conv_1d and unary sigmoid (MIT,
5
+ // Copyright Apple Inc.), preserving the bf16 intermediate rounding.
6
+ import type { MlxArray } from "../mlx/array";
7
+ import { Dtype } from "../mlx/ffi";
8
+ import { MetalKernel } from "../mlx/metal-kernel";
9
+
10
+ /** Borrow all inputs; return owned activation and an independent state tail. */
11
+ export type QwenConvolution = (x: MlxArray, state: MlxArray, weight: MlxArray) => [MlxArray, MlxArray];
12
+
13
+ let kernel: MetalKernel | null = null;
14
+ export const fusedQwenConvolution: QwenConvolution = (x, state, weight) => {
15
+ const [B, M, D] = x.shape as [number, number, number];
16
+ const K = weight.shape[1]!;
17
+ if (x.shape.length !== 3 || weight.shape.length !== 3 || weight.shape[0] !== D ||
18
+ weight.shape[2] !== 1 || K !== 4 || state.shape.join() !== [B, K - 1, D].join() ||
19
+ ![Dtype.bfloat16, Dtype.float32].includes(x.dtype) || state.dtype !== x.dtype || weight.dtype !== x.dtype)
20
+ throw new Error("fused Qwen convolution requires matching bf16/f32 inputs, [B,M,D], [B,3,D], [D,4,1]");
21
+ kernel ??= new MetalKernel({ name: "qwen_causal_conv_silu_tail",
22
+ inputNames: ["state", "x", "w"], outputNames: ["out", "tail"],
23
+ source: String.raw`
24
+ const uint c = thread_position_in_grid.x;
25
+ const uint t = thread_position_in_grid.y;
26
+ const uint b = thread_position_in_grid.z;
27
+ if (c >= D) return;
28
+ if (t < M) {
29
+ float acc = 0.0f;
30
+ for (uint i = 0; i < K; ++i) {
31
+ const uint row = t + i;
32
+ const T value = row < K-1 ? state[(b*(K-1)+row)*D+c] : x[(b*M+row-(K-1))*D+c];
33
+ acc += float(value) * float(w[c*K+i]);
34
+ }
35
+ const T conv = T(acc);
36
+ // Preserve the pinned MLX sigmoid's typed operations and bf16
37
+ // intermediate rounding. Promoting this expression to f32 differs.
38
+ const auto y = 1 / (1 + metal::exp(metal::abs(conv)));
39
+ const T sig = conv < 0 ? y : 1-y;
40
+ out[(b*M+t)*D+c] = conv * sig;
41
+ }
42
+ if (t < K-1) {
43
+ const uint row = M + t;
44
+ tail[(b*(K-1)+t)*D+c] = row < K-1 ? state[(b*(K-1)+row)*D+c] : x[(b*M+row-(K-1))*D+c];
45
+ }
46
+ ` });
47
+ return kernel.apply([state, x, weight], {
48
+ outputs: [{ shape: [B, M, D], dtype: x.dtype }, { shape: [B, K - 1, D], dtype: x.dtype }],
49
+ grid: [D, Math.max(M, K - 1), B], threadGroup: [256, 1, 1],
50
+ templateDtypes: { T: x.dtype }, templateInts: { D, M, K },
51
+ }) as [MlxArray, MlxArray];
52
+ };
@@ -0,0 +1,74 @@
1
+ import { MlxArray } from "../mlx/array";
2
+ import { Dtype } from "../mlx/ffi";
3
+ import { MetalKernel } from "../mlx/metal-kernel";
4
+ import * as ops from "../mlx/ops";
5
+ import { computeG } from "./qwen3-delta";
6
+
7
+ // The same recurrent arithmetic as gated_delta_step, with no query/output
8
+ // calculation. Each row replays only its retained prefix of the verify window.
9
+ const SOURCE = String.raw`
10
+ const int T = Tin[0];
11
+ auto n = thread_position_in_grid.z;
12
+ auto b_idx = n / Hv;
13
+ auto hv_idx = n % Hv;
14
+ auto hk_idx = hv_idx / (Hv / Hk);
15
+ constexpr int n_per_t = Dk / 32;
16
+ auto dk_idx = thread_position_in_threadgroup.x;
17
+ auto dv_idx = thread_position_in_grid.y;
18
+ auto k_ = k + b_idx * T * Hk * Dk + hk_idx * Dk;
19
+ auto v_ = v + b_idx * T * Hv * Dv + hv_idx * Dv;
20
+ auto g_ = g + b_idx * T * Hv;
21
+ auto beta_ = beta + b_idx * T * Hv;
22
+ auto i_state = state_in + (n * Dv + dv_idx) * Dk;
23
+ auto o_state = state_out + (n * Dv + dv_idx) * Dk;
24
+ float state[n_per_t];
25
+ for (int i = 0; i < n_per_t; ++i)
26
+ state[i] = static_cast<float>(i_state[n_per_t * dk_idx + i]);
27
+ const int keep = lengths[b_idx];
28
+ for (int t = 0; t < keep; ++t) {
29
+ float kv_mem = 0.0f;
30
+ for (int i = 0; i < n_per_t; ++i) {
31
+ auto s_idx = n_per_t * dk_idx + i;
32
+ state[i] = state[i] * g_[hv_idx];
33
+ kv_mem += state[i] * k_[s_idx];
34
+ }
35
+ kv_mem = simd_sum(kv_mem);
36
+ auto delta = (v_[dv_idx] - kv_mem) * beta_[hv_idx];
37
+ for (int i = 0; i < n_per_t; ++i) {
38
+ auto s_idx = n_per_t * dk_idx + i;
39
+ state[i] = state[i] + k_[s_idx] * delta;
40
+ }
41
+ k_ += Hk * Dk;
42
+ v_ += Hv * Dv;
43
+ g_ += Hv;
44
+ beta_ += Hv;
45
+ }
46
+ for (int i = 0; i < n_per_t; ++i)
47
+ o_state[n_per_t * dk_idx + i] = state[i];
48
+ `;
49
+
50
+ let kernel: MetalKernel | undefined;
51
+
52
+ /** State-only prefix replay. Inputs keep their original [B,T,...] strides;
53
+ * lengths are request-owned accepted counts, never scheduling decisions. */
54
+ export function gatedDeltaState(
55
+ k: MlxArray, v: MlxArray, a: MlxArray, b: MlxArray,
56
+ aLog: MlxArray, dtBias: MlxArray, state: MlxArray | null,
57
+ lengths: readonly number[],
58
+ ): MlxArray {
59
+ const [B, T, Hk, Dk] = k.shape as [number, number, number, number];
60
+ const [, , Hv, Dv] = v.shape as [number, number, number, number];
61
+ using beta = ops.sigmoid(b);
62
+ using g = computeG(aLog, a, dtBias);
63
+ using zero = state ? null : ops.zeros([B, Hv, Dv, Dk], Dtype.float32);
64
+ using t = ops.fromInt32([T], [1]);
65
+ using kept = ops.fromInt32([...lengths], [B]);
66
+ kernel ??= new MetalKernel({ name: "gated_delta_prefix_state",
67
+ inputNames: ["k", "v", "g", "beta", "state_in", "Tin", "lengths"],
68
+ outputNames: ["state_out"], source: SOURCE, ensureRowContiguous: true });
69
+ return kernel.apply([k, v, g, beta, state ?? zero!, t, kept], {
70
+ outputs: [{ shape: [B, Hv, Dv, Dk], dtype: Dtype.float32 }],
71
+ grid: [32, Dv, B * Hv], threadGroup: [32, 4, 1],
72
+ templateInts: { Dk, Dv, Hk, Hv },
73
+ })[0]!;
74
+ }
@@ -5,8 +5,8 @@
5
5
  // default (use_kernel = not training), and its float accumulation / simd_sum
6
6
  // reduction order differ from the pure-ops fallback — so BIT-EXACT parity with
7
7
  // mlx-lm requires the SAME kernel, dispatched with the SAME grid/threadgroup.
8
- // We port the non-vectorized, non-masked variant (g.ndim == 3, mask is None),
9
- // which is the B=1 single-stream path (ssm_mask is None at batch 1).
8
+ // We port the non-vectorized variant (g.ndim == 3). Padded prefill selects
9
+ // the oracle masked specialization; unpadded execution retains its kernel.
10
10
  //
11
11
  // Numerics (must match the reference dtypes exactly — mlx infers the kernel's
12
12
  // pointer element types from the input arrays):
@@ -20,10 +20,12 @@
20
20
  // GQA is handled inside the kernel (hk_idx = hv_idx / (Hv/Hk)); q/k stay at Hk.
21
21
 
22
22
  import { MlxArray } from "../mlx/array";
23
+ import { materializeCopy } from "../mlx/materialize";
23
24
  import { Dtype } from "../mlx/ffi";
24
25
  import { MetalKernel } from "../mlx/metal-kernel";
25
26
  import { CompiledFunction } from "../mlx/compile";
26
27
  import * as ops from "../mlx/ops";
28
+ import type { SsmPrefillPadding } from "./ssm-prefill-padding";
27
29
  import type { Cache, Mask } from "./gemma4-base";
28
30
 
29
31
  // Verbatim body of mlx-lm's gated_delta_step (has_mask=False, vectorized=False),
@@ -104,7 +106,15 @@ const SOURCE = String.raw`
104
106
  `;
105
107
 
106
108
  let kernel: MetalKernel | null = null;
107
- function getKernel(): MetalKernel {
109
+ let maskedKernel: MetalKernel | null = null;
110
+ function getKernel(masked = false): MetalKernel {
111
+ if (masked) return maskedKernel ??= new MetalKernel({
112
+ name: "gated_delta_step_mask",
113
+ inputNames: ["q", "k", "v", "g", "beta", "state_in", "Tin", "mask"],
114
+ outputNames: ["y", "state_out"],
115
+ source: SOURCE.replace("if (true)", "if (mask[b_idx * T + t])"),
116
+ ensureRowContiguous: true,
117
+ });
108
118
  if (!kernel)
109
119
  kernel = new MetalKernel({
110
120
  name: "gated_delta_step",
@@ -154,7 +164,7 @@ export function computeG(aLog: MlxArray, a: MlxArray, dtBias: MlxArray): MlxArra
154
164
  * state: [B, Hv, Dv, Dk] f32 (or null → zeros) */
155
165
  export function gatedDeltaUpdate(
156
166
  q: MlxArray, k: MlxArray, v: MlxArray, a: MlxArray, b: MlxArray,
157
- aLog: MlxArray, dtBias: MlxArray, state: MlxArray | null,
167
+ aLog: MlxArray, dtBias: MlxArray, state: MlxArray | null, mask: MlxArray | null = null,
158
168
  ): [MlxArray, MlxArray] {
159
169
  const [B, , Hk, Dk] = q.shape as [number, number, number, number];
160
170
  const [, , Hv, Dv] = v.shape as [number, number, number, number];
@@ -171,7 +181,9 @@ export function gatedDeltaUpdate(
171
181
 
172
182
  const T = q.shape[1]!;
173
183
  const tArr = MlxArray.fromInt32(new Int32Array([T]), [1]);
174
- const [y, stateOut] = getKernel().apply([q, k, v, g, beta, stateIn, tArr], {
184
+ const inputs = [q, k, v, g, beta, stateIn, tArr];
185
+ if (mask) inputs.push(mask);
186
+ const [y, stateOut] = getKernel(mask !== null).apply(inputs, {
175
187
  outputs: [
176
188
  { shape: [B, T, Hv, Dv], dtype: q.dtype },
177
189
  { shape: [B, Hv, Dv, Dk], dtype: Dtype.float32 },
@@ -207,6 +219,7 @@ export interface SsmSpecRound {
207
219
  prevConv: MlxArray | null;
208
220
  prevRecurrent: MlxArray | null;
209
221
  prevOffset: number;
222
+ prevOffsets: number[] | null;
210
223
  /** Pre-conv in_proj output [B,S,convDim] — position-local. */
211
224
  qkv: MlxArray | null;
212
225
  /** Gate/beta projections [B,S,Hv] — position-local. */
@@ -216,14 +229,15 @@ export interface SsmSpecRound {
216
229
  S: number;
217
230
  /** Layer-bound prefix replay: called AFTER the snapshot is restored onto
218
231
  * the cache; advances conv/recurrent/offset by `keep` tokens. */
219
- replay: ((cache: SSMCache, keep: number) => void) | null;
232
+ replay: ((cache: SSMCache, keep: number | readonly number[]) => void) | null;
220
233
  }
221
234
 
222
235
  export class SSMCache implements Cache {
223
236
  conv: MlxArray | null = null;
224
237
  recurrent: MlxArray | null = null;
225
238
  offset = 0;
226
- /** Live speculative verify round (serial spec lane only; null otherwise). */
239
+ prefillPadding: SsmPrefillPadding | null = null;
240
+ /** Live speculative verify round; null outside a verify transaction. */
227
241
  specRound: SsmSpecRound | null = null;
228
242
  /** Per-row token coverage (batch lane only; null on serial B=1 caches,
229
243
  * where `offset` IS the row's count). mlx-lm's ArraysCache tracks no
@@ -248,15 +262,13 @@ export class SSMCache implements Cache {
248
262
  throw new Error("SSMCache has no KV updateAndFetch (gated-DeltaNet layer)");
249
263
  }
250
264
 
251
- makeMask(_N: number, _windowSize: number | null): Mask {
252
- // ssm_mask is None both single-stream and in the batch lane: rows
253
- // solo-prefill UNPADDED at B=1 (state never sees pad tokens), and
254
- // batched decode feeds one real token per row — so unlike mlx-lm's
255
- // left-padded batch-prefill there is never a pad position to mask.
256
- return { mode: "", arr: null };
265
+ makeMask(N: number, _windowSize: number | null): Mask {
266
+ const arr = this.prefillPadding?.makeMask(N) ?? null;
267
+ return { mode: arr ? "array" : "", arr };
257
268
  }
258
269
 
259
270
  advance(n: number): void {
271
+ if (this.prefillPadding) { this.advanceRows(this.prefillPadding.advance(n)); return; }
260
272
  this.offset += n;
261
273
  // Batched rows step together (one forward advances every row), so the
262
274
  // per-row counts move in lockstep; they DIFFER only in their merge-time
@@ -264,6 +276,13 @@ export class SSMCache implements Cache {
264
276
  if (this.offsets) for (let i = 0; i < this.offsets.length; i++) this.offsets[i]! += n;
265
277
  }
266
278
 
279
+ /** Methods may retain different verified prefixes for each active request. */
280
+ advanceRows(counts: readonly number[]): void {
281
+ if (!this.offsets) { this.offset += counts[0]!; return; }
282
+ for (let row = 0; row < this.offsets.length; row++) this.offsets[row]! += counts[row]!;
283
+ this.offset = Math.max(...this.offsets);
284
+ }
285
+
267
286
  state(): MlxArray[] {
268
287
  const out: MlxArray[] = [];
269
288
  if (this.conv) out.push(this.conv);
@@ -279,7 +298,7 @@ export class SSMCache implements Cache {
279
298
  throw new Error("SSMCache is not trimmable");
280
299
  }
281
300
 
282
- // --- speculative verify-round support (serial spec lane, B=1) ------------
301
+ // --- speculative verify-round support -----------------------------------
283
302
  // The serve loop arms a round before the verify forward and resolves it
284
303
  // after the accept walk. The layer's forward, seeing an armed round, hands
285
304
  // its replaced state slots to the round instead of disposing them, records
@@ -294,6 +313,7 @@ export class SSMCache implements Cache {
294
313
  prevConv: null,
295
314
  prevRecurrent: null,
296
315
  prevOffset: this.offset,
316
+ prevOffsets: this.offsets ? [...this.offsets] : null,
297
317
  qkv: null,
298
318
  a: null,
299
319
  b: null,
@@ -306,12 +326,12 @@ export class SSMCache implements Cache {
306
326
  this.#dropSpecRound();
307
327
  }
308
328
 
309
- specRoundRollback(keep: number): void {
329
+ specRoundRollback(keep: number | readonly number[]): void {
310
330
  const r = this.specRound;
311
331
  if (!r) throw new Error("SSMCache.specRoundRollback without an armed round");
312
332
  if (r.armed)
313
333
  throw new Error("SSMCache.specRoundRollback before the verify forward recorded");
314
- if (keep < 0 || keep > r.S)
334
+ if (typeof keep === "number" ? keep < 0 || keep > r.S : keep.some(count => count < 0 || count > r.S))
315
335
  throw new Error(`SSMCache.specRoundRollback keep=${keep} outside window S=${r.S}`);
316
336
  // Restore the pre-round snapshot (ownership moves back to the cache).
317
337
  this.conv?.dispose();
@@ -321,9 +341,8 @@ export class SSMCache implements Cache {
321
341
  r.prevConv = null;
322
342
  r.prevRecurrent = null;
323
343
  this.offset = r.prevOffset;
324
- if (this.offsets)
325
- throw new Error("SSMCache.specRoundRollback on a batched cache (serial lane only)");
326
- if (keep > 0) {
344
+ this.offsets = r.prevOffsets;
345
+ if (typeof keep === "number" ? keep > 0 : keep.some(count => count > 0)) {
327
346
  if (!r.replay) throw new Error("SSMCache.specRoundRollback with no recorded replay");
328
347
  r.replay(this, keep); // advances conv/recurrent/offset by `keep`
329
348
  }
@@ -347,6 +366,7 @@ export class SSMCache implements Cache {
347
366
  this.recurrent?.dispose();
348
367
  this.conv = null;
349
368
  this.recurrent = null;
369
+ this.prefillPadding = null;
350
370
  }
351
371
 
352
372
  // --- batch-lane dynamic-B ops (BatchScheduler only) ----------------------
@@ -386,25 +406,30 @@ export class SSMCache implements Cache {
386
406
 
387
407
  /** Evict rows not in `keep` (ascending row indices), in place. */
388
408
  filter(keep: number[]): void {
389
- if (!this.conv || !this.recurrent) return;
390
- const idx = ops.fromInt32(keep, [keep.length]);
391
- const conv = ops.takeAxis(this.conv, idx, 0);
392
- const recurrent = ops.takeAxis(this.recurrent, idx, 0);
393
- idx.dispose();
394
- this.conv.dispose();
395
- this.recurrent.dispose();
396
- this.conv = conv;
397
- this.recurrent = recurrent;
398
- if (this.offsets) this.offsets = keep.map((i) => this.offsets![i]!);
409
+ if (this.conv && this.recurrent) {
410
+ const idx = ops.fromInt32(keep, [keep.length]);
411
+ const conv = ops.takeAxis(this.conv, idx, 0);
412
+ const recurrent = ops.takeAxis(this.recurrent, idx, 0);
413
+ idx.dispose();
414
+ this.conv.dispose();
415
+ this.recurrent.dispose();
416
+ this.conv = conv;
417
+ this.recurrent = recurrent;
418
+ }
419
+ this.prefillPadding?.filter(keep);
420
+ if (this.offsets) {
421
+ this.offsets = keep.map((i) => this.offsets![i]!);
422
+ this.offset = Math.max(0, ...this.offsets);
423
+ }
399
424
  }
400
425
 
401
426
  filterRows(keep: readonly number[]): void { this.filter([...keep]); }
402
427
 
403
428
  /** Row `i` as a fresh SERIAL cache — port of mlx-lm ArraysCache.extract
404
429
  * (cache.py:673-676: `cache.cache = [c[idx : idx + 1] for c in self.cache]`,
405
- * a B-axis slice of every state slot). Ours are OWNED contiguous copies
406
- * (the entry outlives the batch; the batched buffers must free when the
407
- * batch moves on — same rule as extractKVRow, batched-mask.ts). Offset =
430
+ * a B-axis slice of every state slot). A proper subset gets compact storage
431
+ * so retaining one row does not pin the other rows. A singleton retains an
432
+ * independent handle over its complete allocation. Offset =
408
433
  * the row's OWN coverage, not the shared max. */
409
434
  extractRow(i: number): SSMCache {
410
435
  if (!this.conv || !this.recurrent)
@@ -414,10 +439,8 @@ export class SSMCache implements Cache {
414
439
  lo[0] = i;
415
440
  const hi = [...a.shape];
416
441
  hi[0] = i + 1;
417
- const view = a.slice(lo, hi);
418
- 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)
419
- view.dispose();
420
- return own;
442
+ using view = a.slice(lo, hi);
443
+ return a.shape[0] === 1 ? ops.contiguous(view) : materializeCopy(view);
421
444
  };
422
445
  const out = new SSMCache();
423
446
  out.conv = cut(this.conv);