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
package/src/eval/kl.ts CHANGED
@@ -22,14 +22,15 @@
22
22
 
23
23
  import { existsSync } from "node:fs";
24
24
  import { MlxArray } from "../mlx/array";
25
- import { Dtype } from "../mlx/ffi";
25
+ import { clearCache, Dtype } from "../mlx/ffi";
26
26
  import * as ops from "../mlx/ops";
27
27
  import { Registry } from "../registry";
28
28
  import { Weights } from "../weights";
29
29
  import { loadModelConfig, type ModelConfig, type TurboQuantScheme } from "../config";
30
30
  import { createModel, type RuntimeModel } from "../model/factory";
31
31
  import { loadTokenizer, type LoadedTokenizer } from "../tokenizer";
32
- import { maybeQuantizeKv } from "../generate";
32
+ import { evalCacheState, maybeQuantizeKv } from "../generate";
33
+ import { ReferenceLogitsDump, type ReferenceRecords } from "./reference-logits";
33
34
  import {
34
35
  configureRuntime,
35
36
  runtimeKey,
@@ -310,7 +311,7 @@ export async function evaluateKlServingDecode(opts: {
310
311
  /** KV-scheme A/B on the teacher-forced serving-decode path: bf16 baseline
311
312
  * (no kvOverride) vs an explicit KV scheme (e.g. TurboQuant), same model /
312
313
  * same prompts / same weight load — the quality-vs-bpw curve gate
313
- * (scripts/eval-turboquant-curve.ts). Unlike evaluateKlServingDecode (env-flag
314
+ * (scripts/turboquant/eval-turboquant-curve.ts). Unlike evaluateKlServingDecode (env-flag
314
315
  * self-flip), the two arms differ by an actual GenerateOptions kv scheme
315
316
  * object, so this is the seam for schemes that aren't env-lever-shaped. */
316
317
  export async function evaluateKlKvArm(opts: {
@@ -355,3 +356,235 @@ export async function evaluateKlKvArm(opts: {
355
356
  : `bf16 vs kv-scheme (${decodeSteps} steps)`,
356
357
  };
357
358
  }
359
+
360
+ // --- saved-teacher KL (--reference-logits) --------------------------------
361
+ // The two-model mode above needs both arms resident; a bf16 teacher never
362
+ // fits beside a candidate on 32 GB. So the teacher is dumped ONCE from
363
+ // Python (top-k logits + full-vocab logsumexp per position; format and
364
+ // rationale in reference-logits.ts) and every candidate is scored against
365
+ // the dump through OUR engine.
366
+ //
367
+ // Per position, with the teacher's top-k slice j ∈ K:
368
+ // p_j = exp(ref_logit_j − ref_logsumexp) (full-vocab softmax)
369
+ // log q_j = cand_logit_j − logsumexp(cand, FULL vocab)
370
+ // KL = Σ_j p_j · (log p_j − log q_j)
371
+ // The sum is truncated to K, so it under-counts by the tail p carries
372
+ // outside the top-k — `meanCapturedMass` (Σ_j p_j) reports exactly how much
373
+ // of p the number covers, and a run with low captured mass is not a
374
+ // comparable KL. Everything is float32; the candidate's logsumexp is over
375
+ // its FULL vocab (never the top-k slice), so log q is a real log-probability.
376
+ //
377
+ // The forward is the SERVING path (model.makeCache + chunked forwardHidden +
378
+ // logitsFromHidden), NOT trainForward: trainForward's cache stub has no
379
+ // DeltaNet SSMCache.advance, which is why `mlx-bun perplexity` cannot score
380
+ // qwen3_5 at all (docs/design/turboquant.md, "Known engine gaps"). KV stays
381
+ // bf16 — this is a weights-only instrument.
382
+
383
+ export interface RefLogitsKLResult extends KLResult {
384
+ /** Positions actually scored (Σ over sequences). */
385
+ nPositions: number;
386
+ /** Fraction of positions where argmax(ref) == argmax(cand) over full vocab. */
387
+ top1Agreement: number;
388
+ /** Mean Σ_{j∈topK} p_j — how much of the teacher's mass the KL sum covers. */
389
+ meanCapturedMass: number;
390
+ topK: number;
391
+ /** manifest.model — the teacher these numbers are against. */
392
+ refModel: string;
393
+ }
394
+
395
+ /** Teacher-forced candidate logits for one sequence, emitted in
396
+ * position-ordered chunks: `onChunk(startPos, logits[1, C, V])`. The callee
397
+ * owns the array and disposes it after `onChunk` returns. This is the seam
398
+ * the model-free test replaces (no weights, no registry). */
399
+ export type TeacherForcedLogits = (
400
+ tokens: number[],
401
+ onChunk: (startPos: number, logits: MlxArray) => void,
402
+ ) => void;
403
+
404
+ /** The real engine forward: one cache for the whole sequence, prefilled in
405
+ * chunks, lm_head applied per chunk so full-vocab logits for a 4k context
406
+ * never exist at once (4096 × 151k × 4 B ≈ 2.5 GB). */
407
+ export function engineTeacherForcedLogits(
408
+ model: RuntimeModel,
409
+ chunkSize: number,
410
+ ): TeacherForcedLogits {
411
+ return (tokens, onChunk) => {
412
+ const cache = model.makeCache();
413
+ try {
414
+ for (let pos = 0; pos < tokens.length; pos += chunkSize) {
415
+ const end = Math.min(pos + chunkSize, tokens.length);
416
+ const ids = ops.fromInt32(tokens.slice(pos, end), [1, end - pos]);
417
+ let h: MlxArray;
418
+ try {
419
+ h = model.forwardHidden(ids, cache);
420
+ } finally {
421
+ ids.dispose();
422
+ }
423
+ let logits: MlxArray;
424
+ try {
425
+ logits = model.logitsFromHidden(h);
426
+ } finally {
427
+ h.dispose();
428
+ }
429
+ try {
430
+ onChunk(pos, logits);
431
+ } finally {
432
+ logits.dispose();
433
+ }
434
+ evalCacheState(cache);
435
+ clearCache(); // mlx-lm _prefill cadence: don't let chunk transients pile up
436
+ }
437
+ } finally {
438
+ for (const c of cache) c.dispose();
439
+ }
440
+ };
441
+ }
442
+
443
+ interface ChunkScore {
444
+ kl: Float32Array; // [rows]
445
+ mass: Float32Array; // [rows]
446
+ top1: Uint8Array; // [rows] 1 = candidate argmax == teacher argmax
447
+ }
448
+
449
+ /** Score `rows` candidate positions against the matching teacher records.
450
+ * `logits` is [1, C, V]; rows [rowLo, rowHi) of it line up with
451
+ * `records` rows [0, count). */
452
+ function scoreChunkAgainstRecords(
453
+ logits: MlxArray, rowLo: number, rowHi: number, records: ReferenceRecords,
454
+ ): ChunkScore {
455
+ const rows = rowHi - rowLo;
456
+ const K = records.topK;
457
+ const V = logits.shape[2]!;
458
+ if (records.count !== rows)
459
+ throw new Error(`reference-logits: ${records.count} records for ${rows} candidate rows`);
460
+
461
+ // Teacher indices must exist in the candidate's vocabulary.
462
+ let maxIdx = -1;
463
+ for (const v of records.indices) if (v > maxIdx) maxIdx = v;
464
+ if (maxIdx >= V || records.indices.some((v) => v < 0))
465
+ throw new Error(
466
+ `reference-logits: teacher index ${maxIdx} out of range for candidate vocab ${V} ` +
467
+ `— the dump was made with a different tokenizer`,
468
+ );
469
+
470
+ const owned: MlxArray[] = [];
471
+ try {
472
+ const sliced = logits.slice([0, rowLo, 0], [1, rowHi, V]);
473
+ owned.push(sliced);
474
+ const f32 = sliced.dtype === Dtype.float32 ? sliced : sliced.astype(Dtype.float32);
475
+ if (f32 !== sliced) owned.push(f32);
476
+
477
+ const lse = ops.logsumexpAxis(f32, -1, false); // [1, rows], full vocab
478
+ owned.push(lse);
479
+ const argmax = ops.argmaxAxis(f32, -1); // [1, rows], full vocab
480
+ owned.push(argmax);
481
+ const idx = MlxArray.fromInt32(records.indices, [1, rows, K]);
482
+ owned.push(idx);
483
+ const gathered = ops.takeAlongAxis(f32, idx, 2); // [1, rows, K]
484
+ owned.push(gathered);
485
+
486
+ const candTop = gathered.toFloat32();
487
+ const candLse = lse.toFloat32();
488
+ const candArgmax = argmax.toIntTokens();
489
+
490
+ const kl = new Float32Array(rows);
491
+ const mass = new Float32Array(rows);
492
+ const top1 = new Uint8Array(rows);
493
+ for (let r = 0; r < rows; r++) {
494
+ const refLse = records.logsumexp[r]!;
495
+ const qLse = candLse[r]!;
496
+ const base = r * K;
497
+ let acc = 0;
498
+ let m = 0;
499
+ for (let j = 0; j < K; j++) {
500
+ const logP = records.logits[base + j]! - refLse;
501
+ const p = Math.exp(logP);
502
+ if (p === 0) continue;
503
+ m += p;
504
+ acc += p * (logP - (candTop[base + j]! - qLse));
505
+ }
506
+ kl[r] = acc;
507
+ mass[r] = m;
508
+ top1[r] = candArgmax[r] === records.indices[base]! ? 1 : 0;
509
+ }
510
+ return { kl, mass, top1 };
511
+ } finally {
512
+ for (const a of owned) a.dispose();
513
+ }
514
+ }
515
+
516
+ /** KL(saved bf16 teacher ‖ candidate) at EVERY teacher-forced position. */
517
+ export async function evaluateKlVsReferenceLogits(opts: {
518
+ candidate: string;
519
+ /** Dump directory (runs/kl-teacher/<tag>). */
520
+ referenceDir: string;
521
+ /** Cap on sequences scored (default: all in the manifest). */
522
+ nSeqs?: number;
523
+ /** Positions per candidate forward (default 512 — see the memory note). */
524
+ chunkSize?: number;
525
+ /** Test seam: use this forward instead of loading `candidate`. */
526
+ forward?: TeacherForcedLogits;
527
+ }): Promise<RefLogitsKLResult> {
528
+ const t0 = Date.now();
529
+ const dump = ReferenceLogitsDump.open(opts.referenceDir);
530
+ const chunkSize = Math.max(1, opts.chunkSize ?? 512);
531
+ const nSeqs = Math.max(1, Math.min(opts.nSeqs ?? dump.nSeqs, dump.nSeqs));
532
+
533
+ const forward = opts.forward
534
+ ?? engineTeacherForcedLogits((await loadRunnable(opts.candidate)).model, chunkSize);
535
+
536
+ try {
537
+ const perSeq: Float32Array[] = [];
538
+ let nPositions = 0;
539
+ let top1Hits = 0;
540
+ let massSum = 0;
541
+
542
+ for (let s = 0; s < nSeqs; s++) {
543
+ const tokens = dump.tokens(s);
544
+ const scored = Math.min(dump.positionsPerSeq, tokens.length - 1);
545
+ const kls = new Float32Array(scored);
546
+ let filled = 0;
547
+
548
+ forward(tokens, (startPos, logits) => {
549
+ const chunkLen = logits.shape[1]!;
550
+ const lo = startPos;
551
+ const hi = Math.min(startPos + chunkLen, scored);
552
+ if (hi <= lo) return; // trailing positions have no teacher record
553
+ const records = dump.readRecords(s, lo, hi);
554
+ const sc = scoreChunkAgainstRecords(logits, lo - startPos, hi - startPos, records);
555
+ for (let r = 0; r < sc.kl.length; r++) {
556
+ kls[lo + r] = sc.kl[r]!;
557
+ massSum += sc.mass[r]!;
558
+ top1Hits += sc.top1[r]!;
559
+ }
560
+ filled += hi - lo;
561
+ });
562
+
563
+ if (filled !== scored)
564
+ throw new Error(
565
+ `reference-logits: candidate forward covered ${filled} of ${scored} positions ` +
566
+ `on sequence ${s}`,
567
+ );
568
+ perSeq.push(kls);
569
+ nPositions += scored;
570
+ }
571
+
572
+ const agg = aggregate(perSeq);
573
+ return {
574
+ nPrompts: perSeq.length,
575
+ seqLen: dump.ctxLen,
576
+ meanKl: agg.mean,
577
+ medianKl: agg.median,
578
+ p95Kl: agg.p95,
579
+ elapsedSec: (Date.now() - t0) / 1000,
580
+ refLabel: `ref-logits:${dump.manifest.model} top${dump.topK} (${opts.referenceDir})`,
581
+ nPositions,
582
+ top1Agreement: nPositions ? top1Hits / nPositions : 0,
583
+ meanCapturedMass: nPositions ? massSum / nPositions : 0,
584
+ topK: dump.topK,
585
+ refModel: dump.manifest.model,
586
+ };
587
+ } finally {
588
+ dump.close();
589
+ }
590
+ }
@@ -0,0 +1,207 @@
1
+ // Reader for a saved bf16 TEACHER-LOGITS dump (format_version 1).
2
+ //
3
+ // Why the format exists: a bf16 teacher can never be resident on a 32 GB box
4
+ // alongside the candidate, so the teacher's distributions are computed ONCE
5
+ // (Python side, one model at a time) and written to disk as a top-k slice
6
+ // plus the full-vocab logsumexp. Any candidate loaded through OUR engine can
7
+ // then be scored against it (src/eval/kl.ts `evaluateKlVsReferenceLogits`)
8
+ // without the teacher ever being co-resident.
9
+ //
10
+ // On-disk contract (fixed — this module reads it, it does not negotiate it):
11
+ //
12
+ // <dir>/manifest.json { format_version: 1, model, corpus_sha256, ctx_len,
13
+ // n_seqs, top_k, positions_per_seq, created, notes }
14
+ // <dir>/tokens.bin int32 LE [n_seqs][ctx_len] — teacher-forced ids
15
+ // <dir>/seq-<i>.bin one record per r = 0..ctx_len-2; record r is the
16
+ // distribution over the token at index r+1, i.e. the
17
+ // distribution predicted from tokens[0..r] — so record
18
+ // r lines up with candidate logits at POSITION r:
19
+ // int32[top_k] indices, descending by logit
20
+ // float16[top_k] reference logits at those indices
21
+ // float32 logsumexp over the FULL vocab
22
+ //
23
+ // Records are fixed-width (6·top_k + 4 bytes), so a range is one pread — the
24
+ // scorer streams a chunk of records at a time and never holds a whole
25
+ // sequence file (top_k 2048 × ctx 4096 ≈ 50 MB) in the JS heap.
26
+
27
+ import { closeSync, existsSync, openSync, readFileSync, readSync, statSync } from "node:fs";
28
+
29
+ export interface ReferenceLogitsManifest {
30
+ format_version: number;
31
+ model: string;
32
+ corpus_sha256: string;
33
+ ctx_len: number;
34
+ n_seqs: number;
35
+ top_k: number;
36
+ positions_per_seq: number;
37
+ created?: string;
38
+ notes?: string;
39
+ }
40
+
41
+ /** A contiguous run of records, flattened: row i of `indices`/`logits` is
42
+ * `[i*topK, (i+1)*topK)`. Logits are decoded from float16 to float32. */
43
+ export interface ReferenceRecords {
44
+ /** Absolute record index of row 0. */
45
+ start: number;
46
+ count: number;
47
+ topK: number;
48
+ indices: Int32Array; // [count · topK]
49
+ logits: Float32Array; // [count · topK]
50
+ logsumexp: Float32Array; // [count] — over the FULL teacher vocab
51
+ }
52
+
53
+ const SUPPORTED_FORMAT_VERSION = 1;
54
+
55
+ /** float16 bits → float32 (host-side; same expansion as MlxArray.toFloat32Host,
56
+ * kept local so reading a dump never touches the GPU). */
57
+ function decodeFloat16(bits: number): number {
58
+ const sign = bits & 0x8000;
59
+ const exponent = (bits >>> 10) & 0x1f;
60
+ const fraction = bits & 0x03ff;
61
+ let value: number;
62
+ if (exponent === 0) value = fraction === 0 ? 0 : fraction * 2 ** -24;
63
+ else if (exponent === 0x1f) value = fraction === 0 ? Infinity : NaN;
64
+ else value = (1 + fraction / 1024) * 2 ** (exponent - 15);
65
+ return sign ? -value : value;
66
+ }
67
+
68
+ export class ReferenceLogitsDump {
69
+ readonly dir: string;
70
+ readonly manifest: ReferenceLogitsManifest;
71
+ readonly topK: number;
72
+ readonly ctxLen: number;
73
+ readonly nSeqs: number;
74
+ /** Records per sequence file — derived from the file, cross-checked
75
+ * against the manifest. */
76
+ readonly positionsPerSeq: number;
77
+ readonly recordBytes: number;
78
+
79
+ #tokens: Int32Array;
80
+ #fd: number | null = null;
81
+ #fdSeq = -1;
82
+
83
+ private constructor(dir: string, manifest: ReferenceLogitsManifest, tokens: Int32Array, positionsPerSeq: number) {
84
+ this.dir = dir;
85
+ this.manifest = manifest;
86
+ this.topK = manifest.top_k;
87
+ this.ctxLen = manifest.ctx_len;
88
+ this.nSeqs = manifest.n_seqs;
89
+ this.positionsPerSeq = positionsPerSeq;
90
+ this.recordBytes = manifest.top_k * 6 + 4;
91
+ this.#tokens = tokens;
92
+ }
93
+
94
+ static open(dir: string): ReferenceLogitsDump {
95
+ const manifestPath = `${dir}/manifest.json`;
96
+ if (!existsSync(manifestPath))
97
+ throw new Error(`reference-logits: no manifest.json in ${dir}`);
98
+ const manifest = JSON.parse(readFileSync(manifestPath, "utf8")) as ReferenceLogitsManifest;
99
+ if (manifest.format_version !== SUPPORTED_FORMAT_VERSION)
100
+ throw new Error(
101
+ `reference-logits: format_version ${manifest.format_version} unsupported ` +
102
+ `(this reader implements ${SUPPORTED_FORMAT_VERSION})`,
103
+ );
104
+ for (const k of ["ctx_len", "n_seqs", "top_k"] as const)
105
+ if (!Number.isInteger(manifest[k]) || manifest[k] <= 0)
106
+ throw new Error(`reference-logits: manifest.${k} must be a positive integer (got ${manifest[k]})`);
107
+
108
+ const tokensBytes = readFileSync(`${dir}/tokens.bin`);
109
+ const wantTokenBytes = manifest.n_seqs * manifest.ctx_len * 4;
110
+ if (tokensBytes.byteLength !== wantTokenBytes)
111
+ throw new Error(
112
+ `reference-logits: tokens.bin is ${tokensBytes.byteLength} B, expected ` +
113
+ `${wantTokenBytes} B (n_seqs ${manifest.n_seqs} × ctx_len ${manifest.ctx_len} × int32)`,
114
+ );
115
+ // Copy: the Buffer's byteOffset need not be int32-aligned for a view.
116
+ const tokens = new Int32Array(
117
+ tokensBytes.buffer.slice(tokensBytes.byteOffset, tokensBytes.byteOffset + tokensBytes.byteLength),
118
+ );
119
+
120
+ const recordBytes = manifest.top_k * 6 + 4;
121
+ const seq0 = `${dir}/seq-0.bin`;
122
+ if (!existsSync(seq0)) throw new Error(`reference-logits: missing ${seq0}`);
123
+ const size = statSync(seq0).size;
124
+ if (size % recordBytes !== 0)
125
+ throw new Error(
126
+ `reference-logits: seq-0.bin is ${size} B, not a multiple of the ` +
127
+ `${recordBytes} B record (top_k ${manifest.top_k})`,
128
+ );
129
+ const positions = size / recordBytes;
130
+ if (positions > manifest.ctx_len - 1)
131
+ throw new Error(
132
+ `reference-logits: seq-0.bin holds ${positions} records but ctx_len ` +
133
+ `${manifest.ctx_len} allows at most ${manifest.ctx_len - 1}`,
134
+ );
135
+ if (Number.isInteger(manifest.positions_per_seq) && manifest.positions_per_seq !== positions)
136
+ console.warn(
137
+ `[reference-logits] manifest.positions_per_seq=${manifest.positions_per_seq} but ` +
138
+ `seq-0.bin holds ${positions} records — using the file`,
139
+ );
140
+ return new ReferenceLogitsDump(dir, manifest, tokens, positions);
141
+ }
142
+
143
+ /** Teacher-forced token ids of sequence `seq` (length ctx_len). */
144
+ tokens(seq: number): number[] {
145
+ this.#assertSeq(seq);
146
+ const off = seq * this.ctxLen;
147
+ return Array.from(this.#tokens.subarray(off, off + this.ctxLen));
148
+ }
149
+
150
+ /** Records [lo, hi) of sequence `seq` — one pread, decoded to float32. */
151
+ readRecords(seq: number, lo: number, hi: number): ReferenceRecords {
152
+ this.#assertSeq(seq);
153
+ if (lo < 0 || hi > this.positionsPerSeq || hi <= lo)
154
+ throw new Error(
155
+ `reference-logits: record range [${lo},${hi}) out of bounds for ` +
156
+ `${this.positionsPerSeq} records`,
157
+ );
158
+ const count = hi - lo;
159
+ const K = this.topK;
160
+ const buf = Buffer.allocUnsafe(count * this.recordBytes);
161
+ const fd = this.#open(seq);
162
+ const read = readSync(fd, buf, 0, buf.byteLength, lo * this.recordBytes);
163
+ if (read !== buf.byteLength)
164
+ throw new Error(`reference-logits: short read on seq-${seq}.bin (${read}/${buf.byteLength} B)`);
165
+
166
+ const dv = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
167
+ const indices = new Int32Array(count * K);
168
+ const logits = new Float32Array(count * K);
169
+ const logsumexp = new Float32Array(count);
170
+ for (let r = 0; r < count; r++) {
171
+ const base = r * this.recordBytes;
172
+ const row = r * K;
173
+ for (let j = 0; j < K; j++) indices[row + j] = dv.getInt32(base + j * 4, true);
174
+ const lbase = base + K * 4;
175
+ for (let j = 0; j < K; j++) logits[row + j] = decodeFloat16(dv.getUint16(lbase + j * 2, true));
176
+ logsumexp[r] = dv.getFloat32(lbase + K * 2, true);
177
+ }
178
+ return { start: lo, count, topK: K, indices, logits, logsumexp };
179
+ }
180
+
181
+ close(): void {
182
+ if (this.#fd !== null) closeSync(this.#fd);
183
+ this.#fd = null;
184
+ this.#fdSeq = -1;
185
+ }
186
+
187
+ #open(seq: number): number {
188
+ if (this.#fdSeq === seq && this.#fd !== null) return this.#fd;
189
+ if (this.#fd !== null) closeSync(this.#fd);
190
+ const path = `${this.dir}/seq-${seq}.bin`;
191
+ if (!existsSync(path)) throw new Error(`reference-logits: missing ${path}`);
192
+ const size = statSync(path).size;
193
+ if (size < this.positionsPerSeq * this.recordBytes)
194
+ throw new Error(
195
+ `reference-logits: seq-${seq}.bin is ${size} B, short of the ` +
196
+ `${this.positionsPerSeq * this.recordBytes} B seq-0.bin defines`,
197
+ );
198
+ this.#fd = openSync(path, "r");
199
+ this.#fdSeq = seq;
200
+ return this.#fd;
201
+ }
202
+
203
+ #assertSeq(seq: number): void {
204
+ if (!Number.isInteger(seq) || seq < 0 || seq >= this.nSeqs)
205
+ throw new Error(`reference-logits: sequence ${seq} out of range (n_seqs ${this.nSeqs})`);
206
+ }
207
+ }
@@ -1,3 +1,4 @@
1
+ import type { GenerateOptions } from "../generate";
1
2
  // Task-eval runner: load a model and greedily complete prompts through the
2
3
  // REAL serving path. Unlike the M0 KL (compat forward() + plain caches),
3
4
  // this drives generate() with the model's kv_config, so generation runs the
@@ -5,9 +6,6 @@
5
6
 
6
7
  import { homedir } from "node:os";
7
8
  import { readFileSync } from "node:fs";
8
- import { generate, type GenerateOptions } from "../generate";
9
- import { clearCache } from "../mlx/ffi";
10
- import * as ops from "../mlx/ops";
11
9
  import { ChatTemplate } from "../chat-template";
12
10
  import { loadModelConfig, type ModelConfig } from "../config";
13
11
  import { runtimeValue } from "../runtime-config";
@@ -17,13 +15,12 @@ import {
17
15
  type RuntimeModel,
18
16
  } from "../model/factory";
19
17
  import { isGlm52Config } from "../model/support";
20
- import { DiffusionGemmaModel } from "../model/diffusion-gemma";
21
18
  import { loadTokenizer, type LoadedTokenizer } from "../tokenizer";
22
19
  import { resolveModelDir } from "./kl";
23
20
 
24
21
  export const EVAL_DATA_DIR = runtimeValue("MLX_BUN_EVAL_DATA") ?? `${homedir()}/.cache/mlx-bun/eval-data`;
25
22
 
26
- /** Read a jsonl dataset exported by scripts/eval/export-datasets.py. */
23
+ /** Read a jsonl dataset exported by scripts/oracle/export-datasets.py. */
27
24
  export function loadJsonl<T = Record<string, unknown>>(name: string): T[] {
28
25
  const path = `${EVAL_DATA_DIR}/${name}.jsonl`;
29
26
  const out: T[] = [];
@@ -55,6 +52,7 @@ export function sampleIndices(total: number, n: number, seed = 42): number[] {
55
52
  type SamplerArm = Partial<Pick<GenerateOptions, "temperature" | "topP" | "topK" | "seed" | "hlg" | "curve">>;
56
53
 
57
54
  export interface TaskModel {
55
+ completionClient?: import("../contracts/completion").CompletionClient<{ body: string; options?: GenOpts }, string>;
58
56
  model: RuntimeModel;
59
57
  tokenizer: LoadedTokenizer;
60
58
  template: ChatTemplate | null;
@@ -117,96 +115,10 @@ export interface GenOpts {
117
115
  kvScheme?: Pick<GenerateOptions, "kvBits" | "kvConfig" | "quantizedKvStart" | "turboQuant">;
118
116
  }
119
117
 
120
- /** Bit-exact greedy decode: raw `model.forward` + argmax loop, which matches mlx-lm
121
- * token-for-token (gen8k: 8000/8000). The product `generate()` decode wrapper
122
- * (forwardHidden/logitsFromHidden + pipelined sampler) diverges from the raw forward
123
- * on near-ties past ~32 tokens, so the EVAL decodes bit-exactly itself rather than
124
- * alter the serving path. Greedy + full-precision KV only. */
125
- export function greedyDecodeBitExact(tm: TaskModel, ids: number[], maxTokens: number): string {
126
- if (tm.activeAdapters) tm.model.loraState.active = tm.activeAdapters; // apply the mounted adapter
127
- const cache = tm.model.makeCache();
128
- try {
129
- // Chunked, LM-head-free prefill. The old single-shot `forward(prompt)`
130
- // materialized [1, L, vocab] logits just to discard them — at a 248k
131
- // vocab that's ~1 GB per 1k prompt tokens, and with no allocator-cache
132
- // clears the Metal buffer cache retained every giant freed tensor
133
- // (the 27B capability-suite 73 GB swap-thrash). Prefill only needs the
134
- // cache state, which forwardHidden advances identically (the LM head
135
- // never touches the cache) — bit-exact by construction. Chunk size and
136
- // clear cadence mirror mlx-lm generate_step / our generate().
137
- const PREFILL_CHUNK = 2048;
138
- const prompt = ids.slice(0, -1);
139
- for (let s = 0; s < prompt.length; s += PREFILL_CHUNK) {
140
- const chunk = prompt.slice(s, s + PREFILL_CHUNK);
141
- const cIds = ops.fromInt32(chunk, [1, chunk.length]);
142
- tm.model.forwardHidden(cIds, cache).dispose();
143
- cIds.dispose();
144
- clearCache();
145
- }
146
- let last = ids[ids.length - 1]!;
147
- const eos = new Set(tm.config.eosTokenIds);
148
- const out: number[] = [];
149
- for (let i = 0; i < maxTokens; i++) {
150
- const lg = tm.model.forward([last], cache);
151
- const am = ops.argmaxAxis(lg, -1); // GPU argmax (mlx-lm greedy: lowest-index tie-break)
152
- lg.dispose();
153
- const best = ops.itemUint32(am);
154
- am.dispose();
155
- if (i % 256 === 0) clearCache(); // mlx-lm decode cadence
156
- if (eos.has(best)) break; // mlx-lm halts on EOS and excludes it from the output
157
- out.push(best);
158
- last = best;
159
- }
160
- return tm.tokenizer.decode(out, true);
161
- } finally {
162
- for (const c of cache) c.dispose();
163
- }
164
- }
165
-
166
- /** Complete `body` via the model's real quantized-KV path. Greedy by default;
167
- * pass `opts.sampler` to drive temperature/HLG/etc. */
168
- export async function generateText(tm: TaskModel, body: string, opts: GenOpts = {}): Promise<string> {
169
- const maxTokens = opts.maxTokens ?? 256;
170
- const enableThinking = opts.enableThinking ?? runtimeValue("MLX_BUN_EVAL_THINK") === "1";
171
- const templated = opts.useChat !== false && tm.template !== null;
172
- const text = templated
173
- ? tm.template!.render([{ role: "user", content: body }], { addGenerationPrompt: true, enableThinking })
174
- : body;
175
- // The chat template ALREADY emits the BOS; encoding it with add_special_tokens on
176
- // would prepend a SECOND BOS (<s><s>…), corrupting generation. Raw bodies (no
177
- // template) get the BOS added as normal.
178
- const ids = tm.tokenizer.encode(text, /* addSpecialTokens */ !templated);
179
-
180
- // optiq's PUBLISHED eval generates with a FULL-PRECISION KV cache — its eval calls
181
- // plain mlx_lm.generate (kv-quant lives in the serving runtime, not the eval). So
182
- // for like-for-like parity we default the eval to UNQUANTIZED KV; MMLU's argmax
183
- // path was already unquantized, which is why it matched while generation didn't.
184
- // MLX_BUN_EVAL_KV_QUANT=1 generates through the model's quantized KV (serving
185
- // config); an explicit opts.kvScheme (cli generate's resolved tier route) wins.
186
- const envKv: GenOpts["kvScheme"] | undefined =
187
- (runtimeValue("MLX_BUN_EVAL_KV_QUANT") === "1" && tm.config.kvQuant?.length)
188
- ? { kvConfig: tm.config.kvQuant, quantizedKvStart: 0 } : undefined;
189
- const kvScheme = opts.kvScheme ?? envKv;
190
- const kvActive = !!(kvScheme && (kvScheme.kvBits || kvScheme.kvConfig?.length || kvScheme.turboQuant));
191
-
192
- // Parity default — greedy + full-precision KV + no sampler arm: decode bit-exactly
193
- // via the raw forward (matches mlx-lm token-for-token). Sampler arms / kv-quant fall
194
- // through to the product generate(). DiffusionGemma is non-autoregressive (no AR
195
- // forward) — always go through generate(), which routes it to the denoising engine.
196
- const isDiffusion = tm.model instanceof DiffusionGemmaModel;
197
- if (!isDiffusion && !kvActive && !opts.sampler && !tm.samplerOverride) {
198
- return greedyDecodeBitExact(tm, ids, maxTokens);
199
- }
200
- const gen = generate(tm.model, ids, {
201
- maxTokens,
202
- temperature: 0, // greedy default — deterministic head-to-head arms
203
- adapters: tm.activeAdapters ?? [], // per-request activation of the mounted eval-adapter
204
- ...(kvActive ? kvScheme : {}),
205
- ...(opts.sampler ?? {}), // overrides temperature when provided
206
- ...(tm.samplerOverride ?? {}), // arm override wins over the task's own sampler
207
- });
118
+ export { greedyDecodeBitExact } from "../backends/mlx/evaluation";
119
+ import { createEvalCompletionClient } from "../backends/mlx/evaluation";
208
120
 
209
- const out: number[] = [];
210
- for await (const { token } of gen) out.push(token);
211
- return tm.tokenizer.decode(out, true);
121
+ /** Application entry point; selection and model math belong to its client. */
122
+ export function generateText(tm: TaskModel, body: string, opts: GenOpts = {}): Promise<string> {
123
+ return (tm.completionClient ?? createEvalCompletionClient(tm)).complete({ body, options: opts });
212
124
  }