mlx-bun 0.0.13 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (427) hide show
  1. package/README.md +71 -578
  2. package/bin/mlx-bun.mjs +1 -1
  3. package/docs/README.md +91 -0
  4. package/docs/{design → archive}/hlg-sampling.md +3 -3
  5. package/docs/{design → archive/investigations}/audio-input-plan.md +4 -4
  6. package/docs/{design → archive/investigations}/colibri-glm52-port.md +1 -1
  7. package/docs/{design → archive/investigations}/diffusion-gemma-port.md +3 -3
  8. package/docs/{design → archive/investigations}/faithful-l1-consolidation.md +4 -4
  9. package/docs/{investigations → archive/investigations}/hlg-sampling-investigation.md +1 -1
  10. package/docs/{design → archive/investigations}/minicpm5-decode-megakernel.md +11 -11
  11. package/docs/{design → archive/investigations}/optimization_plan.md +1 -1
  12. package/docs/archive/investigations/pareto-specialized-runtime-findings.md +92 -0
  13. package/docs/{design → archive/investigations}/pre-colibri-stabilization.md +5 -5
  14. package/docs/{design → archive/investigations}/the-dreaming-handoff.md +2 -2
  15. package/docs/{planning → archive/planning}/chat-ui-vision.md +1 -1
  16. package/docs/{planning → archive/planning}/chunk-task-roadmap.md +1 -1
  17. package/docs/{planning → archive/planning}/curve-sampler-research-plan.md +4 -4
  18. package/docs/{planning → archive/planning}/memory-docs-and-dag-plan.md +1 -1
  19. package/docs/{planning → archive/planning}/release-notes-v0.0.9.md +1 -1
  20. package/docs/archive/planning/release-notes-v0.2.0.md +49 -0
  21. package/docs/{planning → archive/planning}/website-readme-pass-plan.md +1 -1
  22. package/docs/design/batching.md +569 -0
  23. package/docs/design/decode-speed-program.md +3178 -169
  24. package/docs/design/dreaming-nightly-pipeline.md +675 -282
  25. package/docs/design/generic-model-support.md +579 -334
  26. package/docs/design/kv-cache.md +680 -0
  27. package/docs/design/orpo-training.md +673 -1239
  28. package/docs/design/speculative-decoding.md +1627 -0
  29. package/docs/design/turboquant.md +2102 -0
  30. package/docs/design/unified-engine-frontier-plan.md +1831 -727
  31. package/docs/design/web-chat-redesign.md +666 -1083
  32. package/docs/planning/PRODUCT_ROADMAP.md +34 -1
  33. package/docs/planning/release-notes-v0.3.0.md +35 -0
  34. package/docs/planning/video-series.md +421 -0
  35. package/docs/reference/benchmarks.md +1162 -0
  36. package/docs/reference/cli.md +395 -149
  37. package/docs/reference/distribution.md +321 -181
  38. package/docs/reference/environment.md +348 -0
  39. package/docs/reference/glossary.md +39 -0
  40. package/docs/reference/library-api.md +580 -142
  41. package/docs/reference/memory.md +227 -64
  42. package/docs/reference/models.md +137 -6
  43. package/docs/reference/server-api.md +938 -422
  44. package/docs/reference/server-config.md +652 -320
  45. package/docs/reference/training.md +393 -254
  46. package/docs/reference/troubleshooting.md +166 -0
  47. package/package.json +10 -5
  48. package/src/admission-plan.ts +18 -0
  49. package/src/anthropic.ts +92 -3
  50. package/src/audio/decode.ts +2 -2
  51. package/src/audio/features.ts +2 -2
  52. package/src/backends/mlx/autoregressive.ts +108 -0
  53. package/src/backends/mlx/batch-group.ts +1646 -0
  54. package/src/backends/mlx/diffusion.ts +39 -0
  55. package/src/backends/mlx/draft-target.ts +47 -0
  56. package/src/backends/mlx/evaluation.ts +105 -0
  57. package/src/backends/mlx/fill-append.ts +45 -0
  58. package/src/backends/mlx/gateway-binding.ts +71 -0
  59. package/src/backends/mlx/graph.ts +38 -0
  60. package/src/backends/mlx/memory-client.ts +189 -0
  61. package/src/backends/mlx/memory-guard.ts +40 -0
  62. package/src/backends/mlx/methods.ts +90 -0
  63. package/src/backends/mlx/model-prompt.ts +164 -0
  64. package/src/backends/mlx/model-serving.ts +79 -0
  65. package/src/backends/mlx/prefill.ts +33 -0
  66. package/src/backends/mlx/request-policy.ts +20 -0
  67. package/src/backends/mlx/rollback.ts +51 -0
  68. package/src/backends/mlx/serial-executor.ts +273 -0
  69. package/src/backends/mlx/speculative.ts +114 -0
  70. package/src/backends/mlx/state-views.ts +26 -0
  71. package/src/backends/mlx/text-engine.ts +73 -0
  72. package/src/cli.ts +189 -61
  73. package/src/client.ts +66 -0
  74. package/src/config.ts +21 -4
  75. package/src/contracts/completion.ts +16 -0
  76. package/src/contracts/execution.ts +49 -0
  77. package/src/contracts/generation.ts +103 -0
  78. package/src/contracts/host.ts +7 -0
  79. package/src/contracts/jobs.ts +66 -0
  80. package/src/contracts/pi-provider.ts +30 -0
  81. package/src/contracts/pi.ts +259 -0
  82. package/src/contracts/resources.ts +18 -0
  83. package/src/contracts/scheduling.ts +27 -0
  84. package/src/contracts/tsconfig.json +14 -0
  85. package/src/diffusion/diffusion-generate.ts +218 -153
  86. package/src/embed.ts +1 -1
  87. package/src/engine/admission.ts +59 -0
  88. package/src/engine/cancellation.ts +38 -0
  89. package/src/engine/engine.ts +56 -0
  90. package/src/engine/execution-coordinator.ts +70 -0
  91. package/src/engine/execution-plan.ts +51 -0
  92. package/src/engine/resources.ts +42 -0
  93. package/src/engine/scheduler.ts +67 -0
  94. package/src/engine/session.ts +247 -0
  95. package/src/eval/README.md +44 -1
  96. package/src/eval/kl.ts +263 -23
  97. package/src/eval/reference-logits.ts +207 -0
  98. package/src/eval/runner.ts +10 -79
  99. package/src/eval/tasks/bfcl.ts +2 -1
  100. package/src/eval/tasks/gsm8k.ts +2 -1
  101. package/src/eval/tasks/hashhop.ts +2 -1
  102. package/src/eval/tasks/humaneval.ts +5 -4
  103. package/src/eval/tasks/ifeval.ts +2 -1
  104. package/src/eval/tasks/mmlu.ts +2 -1
  105. package/src/expert-io.ts +3 -1
  106. package/src/expert-offload.ts +3 -1
  107. package/src/fill/echo-index.ts +188 -0
  108. package/src/fill/fill-session.ts +452 -0
  109. package/src/fill/proposal.ts +68 -0
  110. package/src/fill/schema-rows.ts +506 -0
  111. package/src/fill/tool-boundary.ts +103 -0
  112. package/src/fit.ts +24 -43
  113. package/src/generate.ts +740 -337
  114. package/src/grammar.ts +14 -8
  115. package/src/harness-pi.ts +15 -34
  116. package/src/hub-rest.ts +4 -4
  117. package/src/index.ts +27 -0
  118. package/src/inference/denoising.ts +17 -0
  119. package/src/inference/graph.ts +33 -0
  120. package/src/inference/prefill.ts +39 -0
  121. package/src/inference/rollback.ts +7 -0
  122. package/src/jobs/db.ts +4 -0
  123. package/src/jobs/index.ts +2 -0
  124. package/src/jobs/job-entry.ts +15 -14
  125. package/src/jobs/runner.ts +98 -13
  126. package/src/jobs/task-client.ts +19 -0
  127. package/src/jobs/types.ts +2 -58
  128. package/src/kv-scheme.ts +199 -0
  129. package/src/kv-store.ts +459 -234
  130. package/src/lab/README.md +18 -0
  131. package/src/{assets → lab/curve}/curve-designer.html +2 -2
  132. package/src/{curve-sampler.ts → lab/curve/curve-sampler.ts} +2 -2
  133. package/src/{expert-trace.ts → lab/expert-trace/expert-trace.ts} +7 -5
  134. package/src/{model → lab/paged-kv}/paged-kv.ts +9 -6
  135. package/src/library.ts +40 -0
  136. package/src/lora.ts +18 -0
  137. package/src/media-fetch.ts +20 -4
  138. package/src/memory/article.ts +1 -1
  139. package/src/memory/chunk.ts +1 -1
  140. package/src/memory/model.ts +32 -170
  141. package/src/memory/prompts.ts +1 -1
  142. package/src/memory/rest.ts +1 -1
  143. package/src/memory/schedule.ts +1 -1
  144. package/src/memory/stages.ts +4 -1
  145. package/src/memory/vault.ts +3 -4
  146. package/src/mlx/array.ts +9 -10
  147. package/src/mlx/compile.ts +12 -2
  148. package/src/mlx/ffi.ts +66 -22
  149. package/src/mlx/materialize.ts +23 -0
  150. package/src/mlx/metal-kernel.ts +10 -11
  151. package/src/mlx/ops.ts +64 -19
  152. package/src/mlx/quantized-rows.ts +36 -0
  153. package/src/mlx/token-bitmask.ts +52 -0
  154. package/src/mlx/turboquant-kv-decode.ts +82 -0
  155. package/src/mlx/turboquant-kv-inverse.ts +122 -0
  156. package/src/mlx/turboquant-ops.ts +33 -3
  157. package/src/mlx/turboquant-tables.ts +2 -2
  158. package/src/model/batched-mask.ts +9 -3
  159. package/src/model/batched-quant.ts +2 -2
  160. package/src/model/batched-rotating-quant.ts +45 -99
  161. package/src/model/batched-rotating-state.ts +73 -0
  162. package/src/model/batched-rotating.ts +57 -106
  163. package/src/model/batched-row-storage.ts +127 -0
  164. package/src/model/compiled-decode.ts +4 -3
  165. package/src/model/diffusion-gemma.ts +8 -8
  166. package/src/model/factory.ts +87 -49
  167. package/src/model/fingerprint.ts +1 -1
  168. package/src/model/flash-attention.ts +3 -3
  169. package/src/model/gemma4-base.ts +187 -17
  170. package/src/model/gemma4.ts +6 -6
  171. package/src/model/generated/gemma4-12b.ts +52 -148
  172. package/src/model/generated/gemma4-26b.ts +34 -94
  173. package/src/model/generated/gemma4-e4b.ts +28 -76
  174. package/src/model/glm52-cache.ts +19 -9
  175. package/src/model/glm52-dsa.ts +13 -9
  176. package/src/model/glm52-memory.ts +28 -1
  177. package/src/model/implementation.ts +69 -0
  178. package/src/model/minicpm5.ts +7 -8
  179. package/src/model/profile.ts +443 -0
  180. package/src/model/qwen-append.ts +12 -0
  181. package/src/model/qwen-conv.ts +52 -0
  182. package/src/model/qwen3-delta.ts +98 -2
  183. package/src/model/qwen3-mrope.ts +252 -0
  184. package/src/model/qwen3_5-checkpoint.ts +164 -0
  185. package/src/model/qwen3_5.ts +336 -60
  186. package/src/model/support.ts +1 -1
  187. package/src/model/trellis-balanced-scatter.ts +51 -0
  188. package/src/model/trellis-linear.ts +672 -0
  189. package/src/model/trellis-shared-m.ts +151 -0
  190. package/src/model/trellis-shared-scatter.ts +144 -0
  191. package/src/model/trellis-splitk-prefill.ts +158 -0
  192. package/src/model/trellis-tiled-prefill.ts +136 -0
  193. package/src/model/trellis-vector-expand.ts +62 -0
  194. package/src/model/trellis-wide-prefill.ts +94 -0
  195. package/src/model/universal/dense.ts +1 -1
  196. package/src/model/universal/modules.ts +8 -1
  197. package/src/native/frame_extract.swift +140 -0
  198. package/src/native-pack.ts +15 -7
  199. package/src/pi-provider.ts +6 -64
  200. package/src/pi-web.ts +14 -320
  201. package/src/prompt-cache.ts +97 -54
  202. package/src/quantize/allocator.ts +1 -1
  203. package/src/quantize/atomic-output.ts +38 -0
  204. package/src/quantize/calibration.ts +2 -1
  205. package/src/quantize/config-writer.ts +16 -3
  206. package/src/quantize/index.ts +28 -1
  207. package/src/quantize/job.ts +11 -0
  208. package/src/quantize/quantizer.ts +265 -100
  209. package/src/quantize/rotate.ts +603 -0
  210. package/src/quantize/safetensors-writer.ts +79 -1
  211. package/src/quantize/sensitivity.ts +41 -59
  212. package/src/quantize/trellis.ts +586 -0
  213. package/src/quantize/weight-transform.ts +292 -0
  214. package/src/responses.ts +130 -3
  215. package/src/runtime-config.ts +81 -0
  216. package/src/sampler.ts +276 -4
  217. package/src/serve/admin-routes.ts +184 -0
  218. package/src/serve/aux-routes.ts +132 -0
  219. package/src/serve/batch-scheduler.ts +8 -1558
  220. package/src/serve/chat-request.ts +391 -0
  221. package/src/serve/chat-stage.ts +195 -0
  222. package/src/serve/checkpoint-identity.ts +64 -0
  223. package/src/serve/completion-executor.ts +354 -0
  224. package/src/serve/completion-sink.ts +157 -0
  225. package/src/serve/discovery-routes.ts +204 -0
  226. package/src/serve/generation-gateway.ts +211 -243
  227. package/src/serve/http.ts +159 -0
  228. package/src/serve/inference-request.ts +79 -0
  229. package/src/serve/isolate.ts +297 -105
  230. package/src/serve/kv-budget.ts +14 -0
  231. package/src/serve/lab-routes.ts +341 -0
  232. package/src/serve/lane-registry.ts +2 -1
  233. package/src/serve/model-admin-routes.ts +155 -0
  234. package/src/serve/model-binding.ts +43 -0
  235. package/src/serve/model-host.ts +512 -0
  236. package/src/serve/openai-wire.ts +103 -0
  237. package/src/serve/parent-application.ts +56 -0
  238. package/src/serve/pipeline.ts +33 -0
  239. package/src/serve/preparation.ts +33 -0
  240. package/src/serve/prompt-response-trace.ts +147 -0
  241. package/src/serve/request-plan.ts +162 -0
  242. package/src/serve/request-prep.ts +346 -0
  243. package/src/serve/response-lease.ts +24 -0
  244. package/src/serve/responses-client.ts +69 -0
  245. package/src/serve/session-completion-engine.ts +110 -0
  246. package/src/serve/session-search.ts +1 -1
  247. package/src/serve/static-routes.ts +101 -0
  248. package/src/serve/text-completion-stage.ts +104 -0
  249. package/src/serve/token-streams.ts +519 -0
  250. package/src/server.ts +523 -3551
  251. package/src/spec/assistant-source.ts +10 -44
  252. package/src/spec/deepspec-source.ts +6 -6
  253. package/src/spec/dflash-source.ts +9 -8
  254. package/src/spec/dspark/ab-stats.ts +3 -3
  255. package/src/spec/dspark/calibration.ts +2 -2
  256. package/src/spec/dspark/deepspec-module.ts +2 -2
  257. package/src/spec/dspark/generate-dflash.ts +2 -2
  258. package/src/spec/dspark/generate.ts +2 -2
  259. package/src/spec/dspark/loader.ts +1 -1
  260. package/src/spec/dspark/module-dflash.ts +5 -4
  261. package/src/spec/dspark/module.ts +3 -3
  262. package/src/spec/dspark/quantize-drafter.ts +1 -1
  263. package/src/spec/generate.ts +1 -1
  264. package/src/spec/glm52-mtp-source.ts +9 -1
  265. package/src/spec/ngram-source.ts +1 -1
  266. package/src/spec/prefix-state.ts +45 -0
  267. package/src/spec/qwen-mtp-source.ts +528 -0
  268. package/src/spec/serve-loop.ts +179 -213
  269. package/src/spec/source.ts +42 -10
  270. package/src/spec/two-model.ts +1 -1
  271. package/src/ssd-cache.ts +95 -24
  272. package/src/ssd-durability.ts +207 -0
  273. package/src/tool-call.ts +51 -14
  274. package/src/train/flash-cce.ts +14 -13
  275. package/src/train/forward.ts +38 -1
  276. package/src/train/loss.ts +3 -2
  277. package/src/train/prefix-shared.ts +5 -1
  278. package/src/train/segmented.ts +9 -6
  279. package/src/train/trainer.ts +9 -8
  280. package/src/vision/preprocess.ts +1 -1
  281. package/src/vision/prompt.ts +48 -2
  282. package/src/vision/qwen3vl-preprocess.ts +409 -0
  283. package/src/vision/qwen3vl-prompt.ts +196 -0
  284. package/src/vision/qwen3vl-tower.ts +555 -0
  285. package/src/vision/video-frames.ts +148 -0
  286. package/src/web/app.html +1 -1
  287. package/src/web/app.js +15 -6
  288. package/src/web/src/adapters-panel.ts +3 -3
  289. package/src/web/src/assistant.ts +2 -36
  290. package/src/web/src/chat.ts +16 -9
  291. package/src/web/src/composer.ts +4 -4
  292. package/src/web/src/hub.ts +1 -1
  293. package/src/web/src/markdown.ts +0 -0
  294. package/src/web/src/memory-panel.ts +1 -1
  295. package/src/web/src/model-picker.ts +1 -1
  296. package/src/web/src/palette.ts +1 -1
  297. package/src/web/src/protocol.ts +3 -39
  298. package/src/web/src/sessions.ts +1 -1
  299. package/src/web/src/ui-catalog.ts +4 -18
  300. package/src/web-assets.ts +47 -0
  301. package/src/weights.ts +134 -33
  302. package/docs/archive/mlx-bun-lab-report.html +0 -275
  303. package/docs/archive/wwdc-mlx-bun.html +0 -848
  304. package/docs/design/adapters-end-to-end.md +0 -205
  305. package/docs/design/batching-perf-path.md +0 -255
  306. package/docs/design/batching-v2-plan.md +0 -529
  307. package/docs/design/bucketing-stage.md +0 -197
  308. package/docs/design/compat-cli-surface-design.md +0 -230
  309. package/docs/design/docs-reorg-plan.md +0 -165
  310. package/docs/design/dspark-serving-program.md +0 -371
  311. package/docs/design/dspark-speculative-decoding.md +0 -268
  312. package/docs/design/grammar-spec-batching-integration.md +0 -301
  313. package/docs/design/memory-inference-path.md +0 -422
  314. package/docs/design/memory-synthesis.md +0 -287
  315. package/docs/design/memory-system.md +0 -237
  316. package/docs/design/mlx-lm-tool-parity-plan.md +0 -545
  317. package/docs/design/omlx-adoption-map.md +0 -98
  318. package/docs/design/orpo-dynamic-lambda.md +0 -220
  319. package/docs/design/orpo-future-enhancements.md +0 -340
  320. package/docs/design/paged-kv-cache.md +0 -214
  321. package/docs/design/parallel-slots.md +0 -567
  322. package/docs/design/parity-tier-dag.md +0 -161
  323. package/docs/design/repo-cleanup-plan.md +0 -189
  324. package/docs/design/runtime-isolation.md +0 -263
  325. package/docs/design/segmented-backward-training.md +0 -543
  326. package/docs/design/spec-decode-larger-targets.md +0 -229
  327. package/docs/design/ssd-kv-cold-tier.md +0 -288
  328. package/docs/design/structured-output.md +0 -484
  329. package/docs/design/tauri-desktop-app.md +0 -127
  330. package/docs/design/the-dreaming-master-plan.md +0 -445
  331. package/docs/design/turboquant-kv.md +0 -235
  332. package/docs/design/w4a16-compute-precision-spike.md +0 -232
  333. package/docs/design/web-chat-beat-matrix.md +0 -315
  334. package/docs/design/write-pipeline-entity.md +0 -468
  335. package/docs/investigations/curve-runs/bisector-route.json +0 -98
  336. package/docs/investigations/curve-runs/bisector-trace.json +0 -1393
  337. package/docs/investigations/curve-runs/curve-terrain.json +0 -1179
  338. package/docs/investigations/curve-runs/curve-terrain.svg +0 -42
  339. package/docs/investigations/hlg-ab.html +0 -34
  340. package/docs/investigations/hlg-explorer.html +0 -238
  341. package/docs/investigations/hlg-figs/cliff.svg +0 -1
  342. package/docs/investigations/hlg-figs/cross.svg +0 -1
  343. package/docs/investigations/hlg-figs/edges.svg +0 -1
  344. package/docs/investigations/hlg-figs/k20-grids.svg +0 -1
  345. package/docs/investigations/hlg-figs/k20-peaks.svg +0 -1
  346. package/docs/investigations/hlg-figs/lw-curve.svg +0 -1
  347. package/docs/investigations/hlg-figs/terrain.svg +0 -1
  348. package/docs/investigations/hlg-report.html +0 -262
  349. package/docs/investigations/hlg-runs/hlg-ab.json +0 -149
  350. package/docs/investigations/hlg-runs/hlg-blind-prompts.md +0 -87
  351. package/docs/investigations/hlg-runs/hlg-blind.json +0 -107
  352. package/docs/investigations/hlg-runs/hlg-cliff.json +0 -197
  353. package/docs/investigations/hlg-runs/hlg-cross.json +0 -505
  354. package/docs/investigations/hlg-runs/hlg-edge.json +0 -388
  355. package/docs/investigations/hlg-runs/hlg-eval1.json +0 -25
  356. package/docs/investigations/hlg-runs/hlg-frontier.json +0 -86
  357. package/docs/investigations/hlg-runs/hlg-grid.json +0 -242
  358. package/docs/investigations/hlg-runs/hlg-grid2.json +0 -343
  359. package/docs/investigations/hlg-runs/hlg-lw-coarse.json +0 -110
  360. package/docs/investigations/hlg-runs/hlg-map.json +0 -383
  361. package/docs/investigations/hlg-runs/run0-original.log +0 -159
  362. package/docs/investigations/hlg-runs/runA-recommended.log +0 -131
  363. package/docs/investigations/hlg-runs/runAA-cliff-cpm5.log +0 -26
  364. package/docs/investigations/hlg-runs/runAB-grid2-e4b.log +0 -18
  365. package/docs/investigations/hlg-runs/runAC-peak-e4b-partial.log +0 -25
  366. package/docs/investigations/hlg-runs/runAD-ab-samples.log +0 -8
  367. package/docs/investigations/hlg-runs/runAE-edge-e4b.log +0 -51
  368. package/docs/investigations/hlg-runs/runAF-logit-calibration.log +0 -10
  369. package/docs/investigations/hlg-runs/runAG-eval-config2.log +0 -16
  370. package/docs/investigations/hlg-runs/runB-gain-variants.log +0 -192
  371. package/docs/investigations/hlg-runs/runC-pivots.log +0 -162
  372. package/docs/investigations/hlg-runs/runD-oetf.log +0 -132
  373. package/docs/investigations/hlg-runs/runE-eotf.log +0 -162
  374. package/docs/investigations/hlg-runs/runF-shaper-working.log +0 -86
  375. package/docs/investigations/hlg-runs/runG-shaper-sweep.log +0 -101
  376. package/docs/investigations/hlg-runs/runH-wsweep.log +0 -176
  377. package/docs/investigations/hlg-runs/runI-asweep.log +0 -176
  378. package/docs/investigations/hlg-runs/runJ-knobmap.log +0 -49
  379. package/docs/investigations/hlg-runs/runK-frontier.log +0 -19
  380. package/docs/investigations/hlg-runs/runL-widemap.log +0 -65
  381. package/docs/investigations/hlg-runs/runM-frontier-emb.log +0 -15
  382. package/docs/investigations/hlg-runs/runN-verify.log +0 -116
  383. package/docs/investigations/hlg-runs/runO-frontier-seed5000.log +0 -15
  384. package/docs/investigations/hlg-runs/runP-grid-AxSm.log +0 -15
  385. package/docs/investigations/hlg-runs/runQ-grid-tgW.log +0 -15
  386. package/docs/investigations/hlg-runs/runR-blind-transcript.log +0 -79
  387. package/docs/investigations/hlg-runs/runS-cross-coherence.log +0 -33
  388. package/docs/investigations/hlg-runs/runT-cross-cpm5.log +0 -33
  389. package/docs/investigations/hlg-runs/runU-cross-12b.log +0 -33
  390. package/docs/investigations/hlg-runs/runV-lw-e4b.log +0 -22
  391. package/docs/investigations/hlg-runs/runW-lw-12b.log +0 -22
  392. package/docs/investigations/hlg-runs/runX-lw-cpm5.log +0 -22
  393. package/docs/investigations/hlg-runs/runY-cliff-e4b.log +0 -26
  394. package/docs/investigations/hlg-runs/runZ-cliff-12b.log +0 -26
  395. package/docs/planning/IDEAS.md +0 -69
  396. package/docs/reference/embedding.md +0 -197
  397. package/docs/reference/features-matrix.md +0 -100
  398. package/docs/reference/orpo-quickstart.md +0 -135
  399. package/src/flags.ts +0 -13
  400. /package/docs/{investigations → archive/investigations}/chunk-finetune-experiment.md +0 -0
  401. /package/docs/{investigations → archive/investigations}/colibri-oracle-pin.md +0 -0
  402. /package/docs/{investigations → archive/investigations}/curve-bisector-routing.md +0 -0
  403. /package/docs/{investigations → archive/investigations}/decode-roofline-lookagain.md +0 -0
  404. /package/docs/{investigations → archive/investigations}/dspark-handoff.md +0 -0
  405. /package/docs/{investigations → archive/investigations}/expert-offload-single-user-moe.md +0 -0
  406. /package/docs/{investigations → archive/investigations}/indexshare-performance-spike.md +0 -0
  407. /package/docs/{investigations → archive/investigations}/kernel-perf-review-2026-07.md +0 -0
  408. /package/docs/{investigations → archive/investigations}/lab-build-journal.md +0 -0
  409. /package/docs/{investigations → archive/investigations}/openwiki-evaluation.md +0 -0
  410. /package/docs/{investigations → archive/investigations}/orpo-base-uf-experiment-and-directions.md +0 -0
  411. /package/docs/{investigations → archive/investigations}/orpo-flash-cce-pin-leak.md +0 -0
  412. /package/docs/{investigations → archive/investigations}/orpo-uf-testing-handoff.md +0 -0
  413. /package/docs/{investigations → archive/investigations}/pi-builtin-investigation.md +0 -0
  414. /package/docs/{investigations → archive/investigations}/segmented-backward-handoff.md +0 -0
  415. /package/docs/{investigations → archive/investigations}/starter-model-port-handoff.md +0 -0
  416. /package/docs/{investigations → archive/investigations}/steel-flash-cce-handoff.md +0 -0
  417. /package/docs/{investigations → archive/investigations}/trainer-validation-experiment.md +0 -0
  418. /package/docs/{planning → archive/planning}/chat-ui-vision-memory-first.md +0 -0
  419. /package/docs/{planning → archive/planning}/journal.md +0 -0
  420. /package/docs/{planning → archive/planning}/pi-integration-review.md +0 -0
  421. /package/docs/{planning → archive/planning}/release-notes-v0.0.10.md +0 -0
  422. /package/docs/{planning → archive/planning}/release-notes-v0.0.11.md +0 -0
  423. /package/docs/{planning → archive/planning}/release-notes-v0.0.12.md +0 -0
  424. /package/docs/{planning → archive/planning}/release-notes-v0.0.13.md +0 -0
  425. /package/docs/{planning → archive/planning}/web-ui-pass-plan.md +0 -0
  426. /package/{docs/investigations → src/serve/assets}/curve-terrain.html +0 -0
  427. /package/{docs/dag → src/serve/assets}/training-inference-map.html +0 -0
package/src/eval/kl.ts CHANGED
@@ -9,7 +9,7 @@
9
9
  //
10
10
  // Two reference modes:
11
11
  // * self-flag (DEFAULT, the kernel-drift gate): one model, one weight
12
- // load; forward each prompt twice with an env lever set to its
12
+ // load; forward each prompt twice with a runtime lever set to its
13
13
  // reference value vs its candidate value. Directly measures the drift
14
14
  // a perf lever introduces. Fits in RAM trivially (no second model).
15
15
  // * two-model (optiq-style absolute quality): a separate reference
@@ -22,14 +22,19 @@
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";
34
+ import {
35
+ configureRuntime,
36
+ runtimeKey,
37
+ } from "../runtime-config";
33
38
 
34
39
  export interface KLResult {
35
40
  nPrompts: number;
@@ -135,23 +140,28 @@ export async function evaluateKlSelfFlag(opts: {
135
140
  const { model, tokenizer } = await loadRunnable(opts.candidate);
136
141
  const tokenized = prepPrompts(opts.prompts, tokenizer, seqLen, n);
137
142
 
138
- const prev = process.env[opts.flag];
143
+ const key = runtimeKey(opts.flag);
139
144
  const perPrompt: Float32Array[] = [];
140
- try {
141
- for (const ids of tokenized) {
142
- process.env[opts.flag] = opts.refValue;
143
- const p = forwardLogits(model, ids);
144
- process.env[opts.flag] = opts.candValue;
145
- const q = forwardLogits(model, ids);
146
- const T = Math.min(p.shape[1]!, q.shape[1]!);
147
- const pT = p.slice([0, 0, 0], [1, T, p.shape[2]!]);
148
- const qT = q.slice([0, 0, 0], [1, T, q.shape[2]!]);
149
- perPrompt.push(klPerToken(pT, qT));
150
- for (const a of [p, q, pT, qT]) a.dispose();
145
+ for (const ids of tokenized) {
146
+ let p: MlxArray;
147
+ const restoreRef = configureRuntime({ [key]: opts.refValue });
148
+ try {
149
+ p = forwardLogits(model, ids);
150
+ } finally {
151
+ restoreRef();
151
152
  }
152
- } finally {
153
- if (prev === undefined) delete process.env[opts.flag];
154
- else process.env[opts.flag] = prev;
153
+ let q: MlxArray;
154
+ const restoreCandidate = configureRuntime({ [key]: opts.candValue });
155
+ try {
156
+ q = forwardLogits(model, ids);
157
+ } finally {
158
+ restoreCandidate();
159
+ }
160
+ const T = Math.min(p.shape[1]!, q.shape[1]!);
161
+ const pT = p.slice([0, 0, 0], [1, T, p.shape[2]!]);
162
+ const qT = q.slice([0, 0, 0], [1, T, q.shape[2]!]);
163
+ perPrompt.push(klPerToken(pT, qT));
164
+ for (const a of [p, q, pT, qT]) a.dispose();
155
165
  }
156
166
 
157
167
  const agg = aggregate(perPrompt);
@@ -232,8 +242,7 @@ function decodeArm(
232
242
  flag: string, value: string,
233
243
  kvOverride?: Parameters<typeof maybeQuantizeKv>[1],
234
244
  ): Float32Array[] {
235
- const prev = process.env[flag];
236
- process.env[flag] = value;
245
+ const restore = configureRuntime({ [runtimeKey(flag)]: value });
237
246
  try {
238
247
  const cache = model.makeCache();
239
248
  try {
@@ -256,8 +265,7 @@ function decodeArm(
256
265
  for (const c of cache) c.dispose();
257
266
  }
258
267
  } finally {
259
- if (prev === undefined) delete process.env[flag];
260
- else process.env[flag] = prev;
268
+ restore();
261
269
  }
262
270
  }
263
271
 
@@ -303,7 +311,7 @@ export async function evaluateKlServingDecode(opts: {
303
311
  /** KV-scheme A/B on the teacher-forced serving-decode path: bf16 baseline
304
312
  * (no kvOverride) vs an explicit KV scheme (e.g. TurboQuant), same model /
305
313
  * same prompts / same weight load — the quality-vs-bpw curve gate
306
- * (scripts/eval-turboquant-curve.ts). Unlike evaluateKlServingDecode (env-flag
314
+ * (scripts/turboquant/eval-turboquant-curve.ts). Unlike evaluateKlServingDecode (env-flag
307
315
  * self-flip), the two arms differ by an actual GenerateOptions kv scheme
308
316
  * object, so this is the seam for schemes that aren't env-lever-shaped. */
309
317
  export async function evaluateKlKvArm(opts: {
@@ -348,3 +356,235 @@ export async function evaluateKlKvArm(opts: {
348
356
  : `bf16 vs kv-scheme (${decodeSteps} steps)`,
349
357
  };
350
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,23 +6,21 @@
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 * as ops from "../mlx/ops";
10
9
  import { ChatTemplate } from "../chat-template";
11
10
  import { loadModelConfig, type ModelConfig } from "../config";
11
+ import { runtimeValue } from "../runtime-config";
12
12
  import {
13
13
  openModel,
14
14
  type Glm52RuntimeOpenOptions,
15
15
  type RuntimeModel,
16
16
  } from "../model/factory";
17
17
  import { isGlm52Config } from "../model/support";
18
- import { DiffusionGemmaModel } from "../model/diffusion-gemma";
19
18
  import { loadTokenizer, type LoadedTokenizer } from "../tokenizer";
20
19
  import { resolveModelDir } from "./kl";
21
20
 
22
- export const EVAL_DATA_DIR = process.env.MLX_BUN_EVAL_DATA ?? `${homedir()}/.cache/mlx-bun/eval-data`;
21
+ export const EVAL_DATA_DIR = runtimeValue("MLX_BUN_EVAL_DATA") ?? `${homedir()}/.cache/mlx-bun/eval-data`;
23
22
 
24
- /** Read a jsonl dataset exported by scripts/eval/export-datasets.py. */
23
+ /** Read a jsonl dataset exported by scripts/oracle/export-datasets.py. */
25
24
  export function loadJsonl<T = Record<string, unknown>>(name: string): T[] {
26
25
  const path = `${EVAL_DATA_DIR}/${name}.jsonl`;
27
26
  const out: T[] = [];
@@ -53,6 +52,7 @@ export function sampleIndices(total: number, n: number, seed = 42): number[] {
53
52
  type SamplerArm = Partial<Pick<GenerateOptions, "temperature" | "topP" | "topK" | "seed" | "hlg" | "curve">>;
54
53
 
55
54
  export interface TaskModel {
55
+ completionClient?: import("../contracts/completion").CompletionClient<{ body: string; options?: GenOpts }, string>;
56
56
  model: RuntimeModel;
57
57
  tokenizer: LoadedTokenizer;
58
58
  template: ChatTemplate | null;
@@ -115,79 +115,10 @@ export interface GenOpts {
115
115
  kvScheme?: Pick<GenerateOptions, "kvBits" | "kvConfig" | "quantizedKvStart" | "turboQuant">;
116
116
  }
117
117
 
118
- /** Bit-exact greedy decode: raw `model.forward` + argmax loop, which matches mlx-lm
119
- * token-for-token (gen8k: 8000/8000). The product `generate()` decode wrapper
120
- * (forwardHidden/logitsFromHidden + pipelined sampler) diverges from the raw forward
121
- * on near-ties past ~32 tokens, so the EVAL decodes bit-exactly itself rather than
122
- * alter the serving path. Greedy + full-precision KV only. */
123
- export function greedyDecodeBitExact(tm: TaskModel, ids: number[], maxTokens: number): string {
124
- if (tm.activeAdapters) tm.model.loraState.active = tm.activeAdapters; // apply the mounted adapter
125
- const cache = tm.model.makeCache();
126
- try {
127
- if (ids.length > 1) tm.model.forward(ids.slice(0, -1), cache).dispose(); // prefill
128
- let last = ids[ids.length - 1]!;
129
- const eos = new Set(tm.config.eosTokenIds);
130
- const out: number[] = [];
131
- for (let i = 0; i < maxTokens; i++) {
132
- const lg = tm.model.forward([last], cache);
133
- const am = ops.argmaxAxis(lg, -1); // GPU argmax (mlx-lm greedy: lowest-index tie-break)
134
- lg.dispose();
135
- const best = ops.itemUint32(am);
136
- am.dispose();
137
- if (eos.has(best)) break; // mlx-lm halts on EOS and excludes it from the output
138
- out.push(best);
139
- last = best;
140
- }
141
- return tm.tokenizer.decode(out, true);
142
- } finally {
143
- for (const c of cache) c.dispose();
144
- }
145
- }
146
-
147
- /** Complete `body` via the model's real quantized-KV path. Greedy by default;
148
- * pass `opts.sampler` to drive temperature/HLG/etc. */
149
- export async function generateText(tm: TaskModel, body: string, opts: GenOpts = {}): Promise<string> {
150
- const maxTokens = opts.maxTokens ?? 256;
151
- const enableThinking = opts.enableThinking ?? process.env.MLX_BUN_EVAL_THINK === "1";
152
- const templated = opts.useChat !== false && tm.template !== null;
153
- const text = templated
154
- ? tm.template!.render([{ role: "user", content: body }], { addGenerationPrompt: true, enableThinking })
155
- : body;
156
- // The chat template ALREADY emits the BOS; encoding it with add_special_tokens on
157
- // would prepend a SECOND BOS (<s><s>…), corrupting generation. Raw bodies (no
158
- // template) get the BOS added as normal.
159
- const ids = tm.tokenizer.encode(text, /* addSpecialTokens */ !templated);
160
-
161
- // optiq's PUBLISHED eval generates with a FULL-PRECISION KV cache — its eval calls
162
- // plain mlx_lm.generate (kv-quant lives in the serving runtime, not the eval). So
163
- // for like-for-like parity we default the eval to UNQUANTIZED KV; MMLU's argmax
164
- // path was already unquantized, which is why it matched while generation didn't.
165
- // MLX_BUN_EVAL_KV_QUANT=1 generates through the model's quantized KV (serving
166
- // config); an explicit opts.kvScheme (cli generate's resolved tier route) wins.
167
- const envKv: GenOpts["kvScheme"] | undefined =
168
- (process.env.MLX_BUN_EVAL_KV_QUANT === "1" && tm.config.kvQuant?.length)
169
- ? { kvConfig: tm.config.kvQuant, quantizedKvStart: 0 } : undefined;
170
- const kvScheme = opts.kvScheme ?? envKv;
171
- const kvActive = !!(kvScheme && (kvScheme.kvBits || kvScheme.kvConfig?.length || kvScheme.turboQuant));
172
-
173
- // Parity default — greedy + full-precision KV + no sampler arm: decode bit-exactly
174
- // via the raw forward (matches mlx-lm token-for-token). Sampler arms / kv-quant fall
175
- // through to the product generate(). DiffusionGemma is non-autoregressive (no AR
176
- // forward) — always go through generate(), which routes it to the denoising engine.
177
- const isDiffusion = tm.model instanceof DiffusionGemmaModel;
178
- if (!isDiffusion && !kvActive && !opts.sampler && !tm.samplerOverride) {
179
- return greedyDecodeBitExact(tm, ids, maxTokens);
180
- }
181
- const gen = generate(tm.model, ids, {
182
- maxTokens,
183
- temperature: 0, // greedy default — deterministic head-to-head arms
184
- adapters: tm.activeAdapters ?? [], // per-request activation of the mounted eval-adapter
185
- ...(kvActive ? kvScheme : {}),
186
- ...(opts.sampler ?? {}), // overrides temperature when provided
187
- ...(tm.samplerOverride ?? {}), // arm override wins over the task's own sampler
188
- });
118
+ export { greedyDecodeBitExact } from "../backends/mlx/evaluation";
119
+ import { createEvalCompletionClient } from "../backends/mlx/evaluation";
189
120
 
190
- const out: number[] = [];
191
- for await (const { token } of gen) out.push(token);
192
- 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 });
193
124
  }