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
@@ -1,323 +1,462 @@
1
1
  # Training / fine-tuning reference
2
2
 
3
- How to fine-tune a served model with LoRA adapters: the entry points, the
4
- data formats, every config flag, and the methodology behind the knobs.
3
+ How to fine-tune a served model with LoRA adapters: the commands, the data
4
+ formats, every config field, the long-context memory stack, and the
5
+ methodology behind the knobs.
5
6
 
6
7
  mlx-bun trains **LoRA adapters** (the base quantized weights stay frozen),
7
- supports **SFT**, **DPO**, and **ORPO**, and runs on a single Apple-Silicon GPU. The
8
- output is a PEFT-compatible adapter you hot-swap into the server — see
9
- [adapters-end-to-end](../design/adapters-end-to-end.md) for the serving side
10
- and [segmented-backward-training](../design/segmented-backward-training.md)
11
- for the long-context memory mechanism.
8
+ supports **SFT**, **DPO**, and **ORPO**, and runs on a single Apple-Silicon
9
+ GPU. The output is a PEFT-compatible adapter directory you mount into the
10
+ server (`mlx-bun serve <model> --adapter <dir>`) or fold into the base
11
+ weights (`mlx-bun fuse`).
12
12
 
13
- > Source of truth: the config schema is `FinetuneSubmit` in
14
- > [`src/train/job.ts`](../../src/train/job.ts); defaults are
15
- > `DEFAULT_TRAIN_CONFIG` in [`src/train/trainer.ts`](../../src/train/trainer.ts).
16
- > This doc is generated against those — if they drift, the code wins.
13
+ > Source of truth: `mlx-bun train` in [`src/cli.ts`](../../src/cli.ts)
14
+ > (flags + defaults), the submit schema `FinetuneSubmit` in
15
+ > [`src/train/job.ts`](../../src/train/job.ts), and `DEFAULT_TRAIN_CONFIG` in
16
+ > [`src/train/trainer.ts`](../../src/train/trainer.ts). If this doc and the
17
+ > code disagree, the code wins.
17
18
 
18
19
  ## Entry points
19
20
 
20
- Training is reachable several ways — all of them drive the **same** runner
21
- (`finetuneRunner` in `src/train/job.ts`):
21
+ Every path drives the **same** runner (`finetuneRunner` in `src/train/job.ts`):
22
22
 
23
23
  | Path | How | Use when |
24
24
  |---|---|---|
25
- | **CLI** | `mlx-bun train <model> --data <dir> [options]` — foreground, full ORPO stack on by default; auto-detects e4b/Gemma to pick sensible `--seq`/adapter-path defaults; no env flags needed; `--save-every`, `--resume`, `--dry-run`, `--sft-scope full\|response`, `--grad-accum`, `--grad-clip`, `--seed`, `--val-size`; streams loss to the terminal. `mlx-bun help train` for all flags. Run long jobs detached: `nohup mlx-bun train … &` | **The default for CLI users** |
26
- | **Web UI** | `mlx-bun serve`, open `/finetune` — pick model → dataset → hyperparameters → train; watch live train/val loss; merge/export the adapter | Interactive, in the browser |
27
- | **HTTP API** | `POST /api/finetune/submit` (job id + SSE events); `POST /api/finetune/inspect-dataset` to probe a file; `POST /api/finetune/merge` to fold an adapter into base weights | Scripted / remote |
28
- | **ORPO launcher** | [`scripts/train-orpo.ts`](../../scripts/train-orpo.ts) — the same stack driven by env vars (`MODEL=`, `DATA=`, `RANK=`, `RESUME=`, …). See [orpo-quickstart](./orpo-quickstart.md) | Env-var scripting |
29
- | **Shell recipe** | [`scripts/ft-e4b-v2.sh`](../../scripts/ft-e4b-v2.sh) `probe`\|`train` — the actual e4b run we use; sets the required env (see [What we actually run](#what-we-actually-run-the-e4b-recipe)) | Reproducing our e4b fine-tune |
30
- | **Library** | `import { finetuneRunner } from "./src/train/job"` and call it with a config + emitter | Embedding training in your own TS |
25
+ | **CLI** | `mlx-bun train <model> --data <dir> [options]` — foreground, streams loss to the terminal, ORPO with the full memory stack by default. `mlx-bun help train` lists every flag. | **The default.** |
26
+ | **Live dashboard** | `mlx-bun train-watch <adapter-dir>` — tails `<adapter-dir>/metrics.jsonl` from another terminal. | Watching a detached run |
27
+ | **Fuse** | `mlx-bun fuse <model> --adapter <dir> [--save-path <out>]` — fold the adapter into the base weights → standalone snapshot. | Shipping a merged model |
28
+ | **Web app** | The Fine-tune tab (`#/finetune`, behind the app's Developer toggle): model → dataset → hyperparameters → train, live train/val loss, merge/export. | Interactive |
29
+ | **HTTP API** | `POST /api/finetune/{inspect-dataset,submit,merge,export}` — same runner as a subprocess job with SSE events; documented in [server-api.md](server-api.md). | Scripted / remote |
30
+ | **ORPO launcher** | [`scripts/train-orpo.ts`](../../scripts/train-orpo.ts) — the CLI's ORPO stack driven by env vars (`MODEL=`, `DATA=`, `SEQ=`, `SEG=`, `RESUME=`, …). | Env-var scripting |
31
+ | **Library** | `import { finetuneRunner } from "./src/train/job"` (snake_case submit record + event emitter) or `trainLora` / `DEFAULT_TRAIN_CONFIG` from `./src/train` (camelCase `TrainConfig`). | Embedding training in your own TS |
31
32
 
32
- The CLI and the launcher run the runner **in-process / foreground** (stream to the
33
- terminal, `nohup`-able); the Web UI and HTTP API run it as a **subprocess job** (crash
34
- isolation + a single GPU lease across concurrent server requests).
33
+ The CLI and the launcher run in-process in the foreground; the web app and
34
+ HTTP API run the job as a subprocess (crash isolation, one GPU lease across
35
+ concurrent server requests).
35
36
 
36
- ### Quick start (HTTP API)
37
+ ## Quickstart — ORPO on preference data
38
+
39
+ ORPO is the CLI default: reference-free, two forwards per step, and the
40
+ whole long-context stack (flash-CCE head + prefix-sharing + segmented
41
+ backward) is on unless you turn pieces off.
42
+
43
+ **1. Data.** `--data <dir>` must contain `train.jsonl` (+ optional
44
+ `valid.jsonl`); rows are `{"prompt": "...", "chosen": "...", "rejected": "..."}`
45
+ (`prompt` may also be a chat-messages array — it is rendered through the
46
+ model's chat template). To build one from UltraFeedback:
37
47
 
38
48
  ```bash
39
- curl -s localhost:8080/api/finetune/submit -X POST -H 'content-type: application/json' -d '{
40
- "model_dir": "/path/to/snapshot",
41
- "data_dir": "/path/to/dataset", // dir with train.jsonl (+ optional valid.jsonl)
42
- "adapter_path": "/path/to/output-adapter",
43
- "method": "sft",
44
- "rank": 16, "iters": 300, "learning_rate": 2e-4, "max_seq_length": 2048
45
- }'
46
- # → { "jobId": "..." } then stream events from the jobs SSE endpoint
49
+ # <input.jsonl> = HuggingFaceH4/ultrafeedback_binarized exported to JSONL (you fetch it)
50
+ bun scripts/curate-ultrafeedback.ts <input.jsonl> ./prefs [maxTokens=2048] [valFrac=0.02]
47
51
  ```
48
52
 
49
- `model_dir`, `data_dir`, and `adapter_path` are **required**; everything else
50
- falls back to the defaults below.
53
+ **2. Plan without training.** `--dry-run` inspects the dataset (counts,
54
+ detected format) and prints the resolved plan box: model, loop knobs, head,
55
+ stack, adapter path.
51
56
 
52
- ### Quick start (script)
57
+ ```bash
58
+ mlx-bun train e4b --data ./prefs --dry-run
59
+ ```
53
60
 
54
- [`scripts/chunk-finetune.ts`](../../scripts/chunk-finetune.ts) is the worked
55
- example (MiniCPM5 on the chunking task). It calls `finetuneRunner` directly,
56
- driven by env vars:
61
+ **3. Train — detached from your own shell.** The command blocks and streams
62
+ per-step loss, s/step, and peak memory until done. Save a mountable
63
+ checkpoint every N steps so a crash costs you nothing, and put the adapter
64
+ somewhere the server discovers it (see [Outputs](#outputs)).
57
65
 
58
66
  ```bash
59
- # MODEL unset → defaults to the MiniCPM5-1B-OptiQ-4bit snapshot
60
- DATA=/path/to/chunk ITERS=300 RANK=16 SEQ=2048 SEG=4 \
61
- bun scripts/chunk-finetune.ts
67
+ nohup mlx-bun train e4b --data ./prefs --iters 1000 --save-every 200 \
68
+ --adapter ~/.cache/mlx-bun/adapters/my-e4b > train.log 2>&1 &
62
69
  ```
63
70
 
64
- Env knobs — note the script applies its **own task-tuned defaults**, which
65
- differ from the trainer/API defaults in the table below:
71
+ **4. Watch it from another tab.**
72
+
73
+ ```bash
74
+ mlx-bun train-watch ~/.cache/mlx-bun/adapters/my-e4b
75
+ ```
76
+
77
+ **5. Resume** warm-starts the LoRA weights from a checkpoint or adapter dir
78
+ (optimizer state + LR schedule restart; rank/targets must match).
79
+
80
+ ```bash
81
+ mlx-bun train e4b --data ./prefs --iters 500 \
82
+ --resume ~/.cache/mlx-bun/adapters/my-e4b/checkpoints/step-00200-val<loss> \
83
+ --adapter ~/.cache/mlx-bun/adapters/my-e4b-2
84
+ ```
85
+
86
+ **6. Serve or fuse.**
87
+
88
+ ```bash
89
+ mlx-bun serve e4b --adapter ~/.cache/mlx-bun/adapters/my-e4b
90
+ mlx-bun fuse e4b --adapter ~/.cache/mlx-bun/adapters/my-e4b --save-path ./my-e4b-merged
91
+ mlx-bun serve ./my-e4b-merged
92
+ ```
93
+
94
+ `<model>` is a registry query or a snapshot path (`e4b`, `MiniCPM5`, …);
95
+ omit it to auto-pick the default model. Per-model defaults: Gemma/e4b gets
96
+ `--seq 8192`, everything else `4096`. ORPO defaults `--rank 16 --scale 2.0
97
+ --seg 2`; SFT/DPO default `--rank 8 --scale 1.0` and **no** segmented
98
+ backward unless you pass `--seg <n>`.
99
+
100
+ Measured memory/throughput for these runs live in
101
+ [benchmarks.md](benchmarks.md) — this doc records only the ceilings that
102
+ decide whether a run fits (see [Sequence-length ceilings](#sequence-length-ceilings-measured)).
66
103
 
67
- | Env | Maps to | Script default |
104
+ ### `mlx-bun train` flags
105
+
106
+ | Flag | Default | Meaning |
68
107
  |---|---|---|
69
- | `MODEL` | `model_dir` | MiniCPM5-1B-OptiQ-4bit snapshot (a **path** if set, not a name) |
70
- | `DATA` | `data_dir` | the lucien chunk dataset path |
71
- | `SEQ` | `max_seq_length` | `8192` |
72
- | `ITERS` | `iters` | `2` (probe; use 300 for a real run) |
73
- | `RANK` | `rank` | `16` |
74
- | `LR` | `learning_rate` | `1e-5` |
75
- | `SCALE` | `scale` | `20` |
76
- | `SEG` | `segment_size` | `0` (off) |
77
- | `EVAL_EVERY` | `steps_per_eval` | auto from `ITERS` |
78
- | `ADAPTER` | `adapter_path` | `~/.cache/mlx-bun-finetunes/minicpm5-chunk-seq<SEQ>` |
79
- | `CKPT` | `save_checkpoints` | on (`CKPT=0` disables) |
80
- | `GRAD_CKPT` | `grad_checkpoint` | off (`GRAD_CKPT=1` enables) |
81
-
82
- The script hard-codes `method=sft`, `batch_size=1`, `steps_per_report=1`, and
83
- uses the default `ops.sdpa` training attention (set `MLX_BUN_TRAIN_ATTN=flash`
84
- to override — experimental, much slower; see Methodology).
108
+ | `--data <dir>` | required | dataset dir with `train.jsonl` (+ `valid.jsonl`) |
109
+ | `--method <m>` | `orpo` | `sft` · `dpo` · `orpo` |
110
+ | `--adapter <dir>` | `~/.cache/mlx-bun/mlx-bun-finetunes/<method>-<model>` | output adapter dir (`<model>` = `e4b` for Gemma, else `cpm5`) |
111
+ | `--iters <n>` | 100 | training iterations |
112
+ | `--lr <f>` | orpo 1e-5 · dpo 5e-5 · sft 2e-4 | learning rate (ORPO/DPO: cosine schedule; ORPO warmup = min(10, iters/10)) |
113
+ | `--rank <n>` | orpo 16 · else 8 | LoRA rank (`by_bits`-scaled per layer) |
114
+ | `--scale <f>` | orpo 2.0 · else 1.0 | LoRA scale |
115
+ | `--seq <n>` | gemma 8192 · else 4096 | max sequence length |
116
+ | `--batch <n>` | 1 | batch size |
117
+ | `--grad-accum <n>` | 1 | gradient accumulation steps (effective batch = batch × grad-accum at batch-1 memory) |
118
+ | `--grad-clip <f>` | 1.0 | gradient-norm clip (0 = off) |
119
+ | `--seed <n>` | 0 | data-shuffle / init seed |
120
+ | `--val-size <n>` | 256 | max validation examples per eval |
121
+ | `--lambda <f>` | 0.1 | ORPO odds-ratio weight |
122
+ | `--sft-scope <s>` | `full` | ORPO chosen-NLL scope: `full` (paper/TRL-faithful prompt+response) · `response` (pre-2026-07 runs, bit-exact) |
123
+ | `--seg <n>` | orpo 2 · else 0 | layers per segment (segmented backward) |
124
+ | `--no-segment` | — | disable the segmented backward (all activations resident) |
125
+ | `--save-every <n>` | off | crash-safe mountable checkpoint every n steps (turns on `save_checkpoints`; eval runs at the same cadence) |
126
+ | `--resume <dir>` | — | warm-start LoRA weights from a checkpoint/adapter dir |
127
+ | `--no-flash` | — | ORPO: use the MLX fused linear-CE head instead of the flash-CCE Metal head |
128
+ | `--no-prefix` | — | ORPO: disable prefix-sharing (two-forward branches) |
129
+ | `--dry-run` | — | inspect the dataset + print the plan, don't train |
130
+
131
+ The CLI always sets `rank_scaling: by_bits`, `num_layers: -1`,
132
+ `steps_per_report: 1`, and for ORPO `orpo_chunk_size: 512` plus
133
+ `orpo_flash_ce` / `orpo_fused_ce` / `orpo_prefix_shared` from the flags
134
+ above. Anything not exposed as a flag (`lora_dropout`, `rs_lora`,
135
+ `lora_plus_ratio`, `grad_checkpoint`, `target_modules`, …) is reachable
136
+ through the submit record — see [Configuration reference](#configuration-reference).
137
+
138
+ ### The launcher (env-var alternative)
139
+
140
+ [`scripts/train-orpo.ts`](../../scripts/train-orpo.ts) runs the identical
141
+ ORPO stack from env vars. `MODEL` is a snapshot **path** (a dir with
142
+ `config.json`), not a query.
143
+
144
+ ```bash
145
+ MODEL=<snapshot-dir> DATA=./prefs ITERS=200 bun scripts/train-orpo.ts
146
+ ```
147
+
148
+ | Env | Default | Meaning |
149
+ |---|---|---|
150
+ | `SEQ` | e4b 8192 · cpm 4096 | max sequence length |
151
+ | `SEG` | 2 | layers per segment; `SEGOFF=1` disables the segmented backward |
152
+ | `ITERS` / `LR` | 100 / 1e-5 | iterations / learning rate (cosine + short warmup) |
153
+ | `RANK` / `SCALE` | 16 / 2.0 | LoRA rank (by_bits scaled) / scale |
154
+ | `LAMBDA` | 0.1 | ORPO odds-ratio weight |
155
+ | `SFT_SCOPE` | `full` | `full` · `response` (as `--sft-scope`) |
156
+ | `FLASH=0` | (on) | MLX fused head instead of the flash Metal head |
157
+ | `PREFIX=0` | (on) | disable prefix-sharing |
158
+ | `SAVE_EVERY` | 0 (off) | mountable checkpoint every N steps |
159
+ | `RESUME` | — | adapter/checkpoint dir to warm-start from |
160
+ | `ADAPTER` | `~/.cache/mlx-bun/mlx-bun-finetunes/orpo-<e4b\|cpm5>` | output dir |
85
161
 
86
162
  ## Data formats
87
163
 
88
- Each row of `train.jsonl` (and optional `valid.jsonl`) is auto-detected by its
89
- keys ([`src/train/dataset.ts`](../../src/train/dataset.ts)):
164
+ Each row of `train.jsonl` (and optional `valid.jsonl`) is auto-detected by
165
+ its keys ([`src/train/dataset.ts`](../../src/train/dataset.ts)):
90
166
 
91
167
  | Format | Shape | Loss boundary |
92
168
  |---|---|---|
93
- | `messages` | `{"messages": [{"role","content"}, …]}` | response-only — loss on the final turn, prompt = chat-template render of all prior turns |
169
+ | `messages` | `{"messages": [{"role","content"}, …]}` | response-only — loss on the final turn; prompt = chat-template render of all prior turns |
94
170
  | `prompt-completion` | `{"prompt": "...", "completion": "..."}` | loss on the completion only |
95
171
  | `text` | `{"text": "..."}` | full-sequence (no prompt mask) |
96
- | `dpo` *(method=dpo or orpo)* | `{"prompt", "chosen", "rejected"}` | preference loss on chosen vs rejected (the same preference format serves both DPO and ORPO) |
172
+ | `preference` *(dpo / orpo)* | `{"prompt", "chosen", "rejected"}` | preference loss on chosen vs rejected; `prompt` may be a string or a messages array |
97
173
 
98
- Probe a file before submitting: `POST /api/finetune/inspect-dataset` with
99
- `{"path": "..."}` returns `{ ok, n_train, n_valid, format }`.
174
+ `mlx-bun train --dry-run` (or `POST /api/finetune/inspect-dataset`) reports
175
+ `n_train`, `n_valid`, and the detected format before any weights load.
100
176
 
101
177
  ## SFT vs DPO vs ORPO
102
178
 
103
- - **SFT** (`method: "sft"`, default) — supervised fine-tune; response-only
104
- cross-entropy. Default LR `2e-4`. For instruction-following, formatting,
105
- task adaptation.
106
- - **DPO** (`method: "dpo"`) — Direct Preference Optimization on
179
+ - **SFT** (`--method sft`) — supervised fine-tune; response-only
180
+ cross-entropy. Default LR `2e-4`. Instruction-following, formatting, task
181
+ adaptation. Segmented backward is available (`--seg <n>`) but off by
182
+ default.
183
+ - **DPO** (`--method dpo`) — Direct Preference Optimization on
107
184
  chosen/rejected pairs; loss `-log σ(β·((π_c − ref_c) − (π_r − ref_r)))`
108
- with reference log-probs computed at LoRA scale 0. Default LR `5e-5`. Tune
109
- with `dpo_beta`, `dpo_warmup_iters`, `dpo_lr_schedule`.
110
- - **ORPO** (`method: "orpo"`) — Odds Ratio Preference Optimization (Hong et
111
- al. 2024): a **reference-free** monolithic objective combining the SFT term
112
- and a preference term, `L = L_NLL(chosen) + λ·L_OR`, with
113
- `L_OR = -log σ(log_odds)` and
114
- `log_odds = (ℓ_w − ℓ_r) − (log1mexp(ℓ_w) − log1mexp(ℓ_r))`, where `ℓ` is the
115
- **length-normalized** (mean over response tokens) log-prob. The SFT term's
116
- scope is `sft_scope`: **`full`** (default; paper/TRL-faithful) computes
117
- `L_NLL` as the token-mean cross-entropy over the **full prompt+response** of
118
- the chosen sequence (only padding excluded — TRL's `chosen_nll_loss`),
119
- from the same chosen forward (no extra forward); **`response`** uses
120
- `L_NLL = −ℓ_w` (response-only — the pre-2026-07 mlx-bun behavior, kept for
121
- reproducing old runs). The odds-ratio `ℓ_w/ℓ_r` stay response-only in **both**
122
- modes (that also matches TRL). Uses the same
123
- `{prompt, chosen, rejected}` preference data as DPO but needs **no reference
124
- model** (2 forwards/step vs DPO's 4). Default LR `1e-5` (lower than DPO — the
125
- loss carries a full SFT NLL term a high LR destabilizes). Tune with
126
- `orpo_lambda`, `orpo_warmup_iters`, `orpo_lr_schedule`, `sft_scope`. Design +
127
- optimization roadmap: [orpo-training](../design/orpo-training.md).
128
-
129
- The ORPO head + long-context machinery compose into one stack (all B=1), each
130
- piece independently toggled and each falling back cleanly:
131
-
132
- - **`orpo_flash_ce`** — the [M,vocab]-free flash-CCE Metal head (steel GEMM fwd
133
- **and** bwd; e4b backward 754 ms, peak 0.93 GB flat at M=8192). Implies the
134
- fused head. Falls back to the MLX fused head when the tiling isn't clean.
135
- **Auto-dispatch by M** (2026-07-02): batches with M below `MLX_BUN_FLASH_MIN_M`
136
- (default 1024) take the exact MLX fused head instead — it is faster at short
137
- M (e4b M=64 1.6×, M=512 1.13×; CPM5 at every M), while flash wins memory
138
- everywhere and time at long M on e4b (M≥2048; 8K: 10.7 vs 13.2 s with the
139
- filter/blockMax defaults). `MLX_BUN_FLASH_MIN_M=0` always honors flash.
140
- - **`segment_size`** — segmented backward (gradient-checkpointed layer
141
- activations) for long context; the head is ~free in memory, the activations
142
- are the wall.
143
- - **`orpo_prefix_shared`** — encode the shared prompt once; composes with both
144
- the flash head and the segmented backward (MiniCPM5 **and** e4b/Gemma4). Per-row
145
- two-forward fallback when chosen/rejected prompts differ.
146
- - **warm-start** — `scripts/train-orpo.ts RESUME=<adapter-dir>` continues from a
147
- checkpoint's weights (optimizer + LR schedule restart).
148
-
149
- The one-command way to run all of it is the **ORPO launcher**
150
- ([`scripts/train-orpo.ts`](../../scripts/train-orpo.ts)) — see
151
- [orpo-quickstart](./orpo-quickstart.md) for the command, the per-model defaults,
152
- and the measured memory/throughput (e.g. e4b full stack @ 8192 ≈ 13 GB,
153
- ~70 s/step on the M1 Max dev box). Run long jobs detached from your shell
154
- (`nohup … &`), not as a session-spawned background task.
185
+ with reference log-probs computed at LoRA scale 0 (4 forwards/step).
186
+ Default LR `5e-5`. Tune with `dpo_beta`, `dpo_warmup_iters`,
187
+ `dpo_lr_schedule`. No segmented backward on this path.
188
+ - **ORPO** (`--method orpo`, the default) — Odds Ratio Preference
189
+ Optimization (Hong et al. 2024): a **reference-free** objective
190
+ `L = L_NLL(chosen) + λ·L_OR`, `L_OR = -log σ(log_odds)`,
191
+ `log_odds = (ℓ_w − ℓ_r) − (log1mexp(ℓ_w) − log1mexp(ℓ_r))`, where `ℓ` is
192
+ the length-normalized (mean over response tokens) log-prob. `--sft-scope
193
+ full` (default; paper/TRL-faithful) computes `L_NLL` as the token-mean CE
194
+ over the **full prompt+response** of the chosen sequence from the same
195
+ forward; `response` uses `L_NLL = −ℓ_w` (pre-2026-07 behavior, kept for
196
+ reproducing old runs). The odds-ratio terms are response-only in both
197
+ modes. Same data as DPO, no reference model (2 forwards/step). Default LR
198
+ `1e-5` — the loss carries a full NLL term a high LR destabilizes. Design:
199
+ [orpo-training](../design/orpo-training.md).
200
+
201
+ ## The long-context stack (ORPO defaults)
202
+
203
+ Each piece is independently toggled, composes with the others (all B=1),
204
+ and falls back with a logged message when a precondition isn't met.
205
+
206
+ - **flash-CCE head** (`--no-flash` to disable; `orpo_flash_ce`) — the
207
+ `[M,vocab]`-free cross-entropy head in
208
+ [`src/train/flash-cce.ts`](../../src/train/flash-cce.ts): Apple's Cut
209
+ Cross-Entropy + Liger's fused linear-CE transpiled Triton → Metal, over
210
+ the **quantized** head (in-kernel dequant, MLX steel GEMM tiles, forward
211
+ and backward). Neither the logits nor a dequantized head ever touch HBM,
212
+ so head memory is flat in vocab and sequence length. Batches with
213
+ `M < MLX_BUN_FLASH_MIN_M` (default 1024) auto-take the exact MLX fused
214
+ head, which is faster at short M; `=0` always honors flash. Falls back to
215
+ the fused head when the tiling isn't clean. Two backward skips default ON
216
+ at `1e-5` (`MLX_BUN_CCE_BWD_FILTER_EPS`, the coefficient filter;
217
+ `MLX_BUN_CCE_BWD_BLOCK_EPS`, the lossless cold-block skip); set either to
218
+ `0` for exact gradients.
219
+ - **segmented backward** (`--seg <n>` / `--no-segment`; `segment_size`) —
220
+ [`src/train/segmented.ts`](../../src/train/segmented.ts). The forward
221
+ materializes and **detaches** the residual stream every n layers; the
222
+ head is differentiated against the last boundary; then each segment is
223
+ back-propagated in reverse via `mlx_vjp` with the incoming cotangent, so
224
+ only one segment's activations are live. Bit-identical to the plain
225
+ backward on MiniCPM5; bf16-class on e4b. Wired for MiniCPM5 and Gemma4
226
+ (SFT and ORPO); mutually exclusive with `grad_checkpoint`. Design + proofs:
227
+ [segmented-backward-training](../design/orpo-training.md).
228
+ - **prefix-sharing** (`--no-prefix`; `orpo_prefix_shared`) —
229
+ [`src/train/prefix-shared.ts`](../../src/train/prefix-shared.ts). One
230
+ forward over `[prompt; chosen; rejected]` with a block-sparse mask and
231
+ block-wise RoPE (each response resets to position P), so the shared
232
+ prompt is encoded once: layer-token cost `2(P+R) → P+2R`. Bit-exact with
233
+ the two-forward path; a big win when the prompt dominates, ~0 when the
234
+ response does. Rows whose chosen/rejected prompts aren't byte-identical
235
+ fall back to two-forward for that row (logged once). MiniCPM5 + Gemma4;
236
+ other models error out rather than silently degrade.
237
+ - **gradient checkpointing** (`grad_checkpoint`, API/library only) —
238
+ per-layer recompute; bit-identical. Cheaper than nothing, but it does not
239
+ stream the backward: at long context every layer's recompute is held at
240
+ once, which is what segmented backward fixes. `mlp_split` (Gemma4)
241
+ refines it to attention/MLP halves.
242
+ - **warm-start** (`--resume <dir>`; `warm_start_adapter`) — load LoRA
243
+ weights from an adapter/checkpoint dir; optimizer + schedule restart.
155
244
 
156
245
  ## Configuration reference
157
246
 
158
- All fields optional except `model_dir` / `data_dir` / `adapter_path`. Defaults
159
- are `DEFAULT_TRAIN_CONFIG` (trainer.ts:89).
247
+ The snake_case submit record (`FinetuneSubmit`) — what the CLI builds, what
248
+ the web app and `POST /api/finetune/submit` accept, and what
249
+ `finetuneRunner` takes. `model_dir`, `data_dir`, `adapter_path` are
250
+ required; defaults are `DEFAULT_TRAIN_CONFIG` (the CLI overrides several —
251
+ see the flags table).
160
252
 
161
- | Field (API) | Type | Default | Effect |
253
+ | Field | Type | Default | Effect |
162
254
  |---|---|---|---|
163
- | `method` | `sft` \| `dpo` \| `orpo` | `sft` | Training objective (see above) |
255
+ | `method` | `sft` \| `dpo` \| `orpo` | `sft` | training objective |
164
256
  | `rank` | int ≥2 | `8` | LoRA rank per adapted linear |
165
257
  | `scale` | float >0 | `1.0` | LoRA α (effective update = α·BA) |
166
- | `rank_scaling` | `constant` \| `by_bits` \| `by_kl` | `by_bits` | Per-layer rank policy (see Methodology) |
167
- | `target_modules` | string[] | `q,k,v,o,gate,up,down _proj` | Which linears get adapters |
258
+ | `rank_scaling` | `constant` \| `by_bits` \| `by_kl` | `by_bits` | per-layer rank policy (see Methodology) |
259
+ | `target_modules` | string[] | `q,k,v,o,gate,up,down _proj` | which linears get adapters |
168
260
  | `num_layers` | int | `-1` | `-1` = all layers; `N` = last N only |
169
- | `iters` | int >0 | `100` | Total training steps |
170
- | `learning_rate` | float >0 | `2e-4` (sft) / `1e-5` (orpo) / `5e-5` (dpo) | AdamW LR |
171
- | `max_seq_length` | int >0 | `512` | Truncate/pad sequences to this |
172
- | `batch_size` | int ≥1 | `1` | Rows per step (B=1 is the safe path; B>1 length-sorts + pads to 32) |
173
- | `grad_accumulation_steps` | int ≥1 | `1` | Accumulate grads over N micro-steps |
261
+ | `iters` | int >0 | `100` | total training steps |
262
+ | `learning_rate` | float >0 | `2e-4` | AdamW LR (CLI: orpo 1e-5 / dpo 5e-5) |
263
+ | `max_seq_length` | int >0 | `512` | truncate/pad sequences (CLI: 8192 / 4096) |
264
+ | `batch_size` | int ≥1 | `1` | rows per step (B=1 is the no-padding path; B>1 length-sorts + pads to 32) |
265
+ | `grad_accumulation_steps` | int ≥1 | `1` | accumulate grads over N micro-steps |
174
266
  | `seed` | int | `0` | RNG for shuffling + LoRA init |
175
- | `steps_per_report` | int >0 | `10` | Emit a train-loss metric every N steps |
176
- | `steps_per_eval` | int >0 | `50` | Eval on `valid.jsonl` every N steps |
267
+ | `steps_per_report` | int >0 | `10` | train-loss metric every N steps (CLI: 1) |
268
+ | `steps_per_eval` | int >0 | `50` | eval on `valid.jsonl` every N steps (CLI: `--save-every`, else never) |
177
269
  | `weight_decay` | float ≥0 | `0.01` | AdamW weight decay (β = `[0.9, 0.999]`, fixed) |
178
- | `lora_dropout` | float [0,1) | `0.0` | LoRA-input dropout (PEFT-style), training-only regularizer for small sets. Recompute-safe (mask keyed by step+layer, so segmented/checkpointed backward reproduces it) |
179
- | `rs_lora` | bool | `false` | rsLoRA — scale the update by α/√rank instead of α, so `rank_scaling` changes capacity not step size. Recorded in the adapter config; inference applies the same per-layer scale |
180
- | `lora_plus_ratio` | float ≥1 | `1.0` | LoRA+ — LR multiplier for the B leaves (A stays at base LR). >1 speeds the B-driven early learning (B is zero-init). 1 = off. The same optimizer policy applies to SFT, DPO, and ORPO |
181
- | `grad_checkpoint` | bool | `false` | Recompute layer activations in backward (memory↔compute; bit-identical) |
182
- | `mlp_split` | bool | `false` | Gradient-checkpoint MLP splitting (Gemma4-only, requires `grad_checkpoint`) |
183
- | `segment_size` | int | `0` (off) | `>0` enables segmented backward — layers per segment (see below) |
184
- | `save_checkpoints` | bool | `false` | Save every eval-step checkpoint + write `metrics.json` |
185
- | `grad_clip_norm` | float ≥0 | `1.0` | Gradient-norm clipping (on by default); `0` = off |
186
- | `val_max_examples` | int >0 | `256` | Caps the validation-set size used per eval |
187
- | `warm_start_adapter` | string | `""` (off) | Adapter dir to warm-start/resume LoRA weights from (the API field behind the CLI's `--resume`) |
188
- | `dpo_beta` | float >0 | `0.1` | DPO strength (dpo only) |
189
- | `dpo_warmup_iters` | int ≥0 | `0` | DPO LR warmup (dpo only) |
190
- | `dpo_lr_schedule` | `constant` \| `cosine` | `cosine` | DPO LR schedule (dpo only) |
191
- | `orpo_lambda` | float >0 | `0.1` | ORPO λ — weights **only** the odds-ratio term; the SFT-NLL term stays unweighted (orpo only) |
192
- | `orpo_warmup_iters` | int ≥0 | `0` | ORPO LR warmup (orpo only) |
193
- | `orpo_lr_schedule` | `constant` \| `cosine` | `cosine` | ORPO LR schedule (orpo only) |
194
- | `orpo_chunk_size` | int | `0` (off) | Token-chunk the response head, bounding the `[M,vocab]` logits to `[chunk,vocab]` (defaults to 512 when a fused head is on). Exact (orpo only) |
195
- | `orpo_fused_ce` | bool | `false` | Fused linear-CE head: one CustomVjp with an analytic softmax−onehot backward (MLX `quantizedMatmul` both ways) — no autograd through the head, no retained `[M,vocab]`. Exact; `[chunk,vocab]` transient (orpo only) |
196
- | `orpo_flash_ce` | bool | `false` | Route the fused head through the **flash-CCE Metal kernel** (verbatim MLX steel GEMM + ORPO epilogue, fwd **and** bwd): neither `[M,vocab]` nor a dequantized head touches HBM → `[M,vocab]`-free (e4b 0.93 GB flat @ M=8192). Implies the fused head. The Apple-CCE coeff filter + blockMax skip default ON at 1e-5 (2026-07-02; combined bwd 1.71× CPM5 / 3.16× e4b vs exact — `MLX_BUN_CCE_BWD_FILTER_EPS=0` / `_BLOCK_EPS=0` restore exact). Batches with M < `MLX_BUN_FLASH_MIN_M` (default 1024) auto-take the exact fused head (faster at short M; `=0` always honors flash). B=1 (orpo only) |
197
- | `orpo_prefix_shared` | bool | `false` | Shared prompt-prefix: one forward over `[prompt; chosen; rejected]` with a block-sparse mask + block-wise RoPE, so the shared prompt is encoded **once** (a big win when the prompt dominates). Falls back to two-forward per row on prompt mismatch. Composes with the flash head and the segmented backward (MiniCPM5 + e4b). B=1 (orpo only) |
198
- | `sft_scope` | `full` \| `response` | `full` | Scope of ORPO's chosen-NLL term. `full` (paper/TRL-faithful): token-mean CE over the **full prompt+response** (only padding excluded), from the same chosen forward. `response`: `L_NLL = −ℓ_w` — reproduces pre-2026-07 runs bit-exactly. The odds-ratio `ℓ_w/ℓ_r` are response-only in both modes. Applies to every ORPO path (naive/chunked/fused/flash/prefix/segmented) (orpo only). CLI spelling: `mlx-bun train … --sft-scope full\|response` |
270
+ | `lora_dropout` | float [0,1) | `0.0` | LoRA-input dropout (PEFT-style); mask keyed by step+layer so segmented/checkpointed recompute reproduces it |
271
+ | `rs_lora` | bool | `false` | rsLoRA — scale by α/√rank so `rank_scaling` changes capacity, not step size; recorded in the adapter config and honored at inference |
272
+ | `lora_plus_ratio` | float ≥1 | `1.0` | LoRA+ — LR multiplier for the B leaves (A stays at base LR); 1 = off |
273
+ | `grad_checkpoint` | bool | `false` | recompute layer activations in backward (bit-identical) |
274
+ | `mlp_split` | bool | `false` | attention/MLP split inside the checkpoint (Gemma4; needs `grad_checkpoint`) |
275
+ | `segment_size` | int | `0` | `>0` = segmented backward, layers per segment (CLI: orpo 2) |
276
+ | `save_checkpoints` | bool | `false` | keep every eval-step checkpoint + write `metrics.json` (CLI: on with `--save-every`) |
277
+ | `grad_clip_norm` | float ≥0 | `1.0` | global-norm gradient clipping; `0` = off |
278
+ | `val_max_examples` | int >0 | `256` | fixed validation subset per eval |
279
+ | `warm_start_adapter` | string | `""` | adapter dir to warm-start from (`--resume`) |
280
+ | `dpo_beta` | float >0 | `0.1` | DPO strength |
281
+ | `dpo_warmup_iters` | int ≥0 | `0` | DPO LR warmup |
282
+ | `dpo_lr_schedule` | `constant` \| `cosine` | `cosine` | DPO LR schedule |
283
+ | `orpo_lambda` | float >0 | `0.1` | ORPO λ — weights only the odds-ratio term |
284
+ | `orpo_warmup_iters` | int ≥0 | `0` | ORPO LR warmup (CLI: min(10, iters/10)) |
285
+ | `orpo_lr_schedule` | `constant` \| `cosine` | `cosine` | ORPO LR schedule |
286
+ | `orpo_chunk_size` | int | `0` | token-chunk the response head to `[chunk,vocab]`; exact (CLI: 512) |
287
+ | `orpo_fused_ce` | bool | `false` | fused linear-CE head: one CustomVjp with the analytic softmax−onehot backward, no retained `[M,vocab]` (CLI: on when `--no-flash`) |
288
+ | `orpo_flash_ce` | bool | `false` | route the fused head through the flash-CCE Metal kernel; implies fused (CLI: on) |
289
+ | `orpo_prefix_shared` | bool | `false` | shared prompt-prefix single forward (CLI: on) |
290
+ | `sft_scope` | `full` \| `response` | `full` | scope of ORPO's chosen-NLL term (`--sft-scope`) |
199
291
 
200
292
  ### Environment variables (training)
201
293
 
202
- | Env var | Set for training | Default | Why |
203
- |---|---|---|---|
204
- | `MLX_BUN_TRAIN_ATTN` | **leave unset** | unset → `ops.sdpa` | Default `ops.sdpa` **is** mlx's fused flash-attention kernel — the correct, working path. `flash` selects a *different* hand-rolled O(L)-memory kernel: its two port bugs (dK buffer-transpose, dQ divergent causal barrier) are fixed and FD-validated (`tests/flash-attention.test.ts`, T≤256), but it is ~30× slower and the historical e4b ≥2K crash has **not been re-validated at that scale** since the fix — leave unset unless memory-bound and re-verified. |
205
- | `MLX_BUN_MEM_LOG` | `1` to profile | off | Print per-step peak/active/cache memory |
206
-
207
- > Training needs **no flag sanitization**: the old requirement to export
208
- > `MLX_BUN_FUSED_GELU=0` / `MLX_BUN_PERF_KERNEL=0` (the no-vjp custom
209
- > kernels) is gone — those kernels and their env vars were deleted
210
- > 2026-07-05 ([unified-engine-frontier-plan.md](../design/unified-engine-frontier-plan.md)).
211
- > Exporting them in an old recipe is harmless but does nothing.
212
-
213
- ## What we actually run (the e4b recipe)
214
-
215
- Everything above is the full surface (*what you can do*). In practice the
216
- fine-tune we run is [`scripts/ft-e4b-v2.sh`](../../scripts/ft-e4b-v2.sh):
217
- e4b (gemma-4-e4b-it-OptiQ-4bit, pinned snapshot) on the lucien `chunk-v2-500`
218
- curated set (450 train convs) through the segmented-backward trainer. It wraps
219
- `chunk-finetune.ts` with a two-step workflow:
220
-
221
- ```bash
222
- scripts/ft-e4b-v2.sh probe # 2-iter memory/stability check (~1 min) — RUN FIRST
223
- scripts/ft-e4b-v2.sh train # the real run (~900 iters ≈ 2 epochs, batch_size 1)
224
- ITERS=750 SEQ=4096 SEG=1 scripts/ft-e4b-v2.sh train # override any knob inline
225
- ```
226
-
227
- What the recipe pins (and why it differs from the bare defaults):
228
-
229
- | Knob | Recipe value | Why |
294
+ | Env var | Default | Effect |
230
295
  |---|---|---|
231
- | model | e4b OptiQ-4bit, pinned snapshot `fcdb12d7…` | the validated e4b snapshot |
232
- | data | `chunk-v2-500` (450 train convs) | the curated chunk set |
233
- | `SEQ` | `8192` | long context |
234
- | `SEG` | `4` | **segmented backward**, 4 layers/segment — so 8K-ctx activations fit |
235
- | `RANK` / `SCALE` / `LR` | `16` / `20` / `1e-5` | task-tuned |
236
- | `ITERS` | `2` (probe) / `900` (train) | ~2 epochs over 450 examples |
237
- | attention | default `ops.sdpa` | mlx's fused flash kernel; **not** `MLX_BUN_TRAIN_ATTN=flash` (~30× slower; e4b ≥2K unvalidated since the kernel fix) |
238
-
239
- The non-negotiable for e4b: **segmented backward** (`SEG>0`, so the
240
- long-context activations fit). Always run `probe` before `train`.
296
+ | `MLX_BUN_TRAIN_ATTN` | unset → `ops.sdpa` | `ops.sdpa` **is** mlx's fused flash-attention kernel with an exact vjp — the working path. `flash` selects a hand-rolled O(L)-memory kernel: FD-validated at T≤256 (`tests/unit/flash-attention.test.ts`) but ~30× slower, and the trainer **refuses it for Gemma models** (e4b SIGTRAPed at seq ≥2048 and has not been re-validated at that scale). Leave unset. |
297
+ | `MLX_BUN_MEM_LOG` | off | `1` prints per-step peak/active/cache memory |
298
+ | `MLX_BUN_SEG_MEM_LOG` | off | `1` logs the within-step peak after each segmented phase (forward / head vjp / each segment) |
299
+ | `MLX_BUN_SEG_HEAD` | `checkpoint` | bounded head inside the segmented vjp: `checkpoint` (per-chunk recompute) or `fused` |
300
+ | `MLX_BUN_SEG_HEAD_CHUNK` | 512 | token-chunk for the segmented SFT head |
301
+ | `MLX_BUN_FLASH_MIN_M` | 1024 | rows shorter than this take the exact fused head instead of flash; `0` = always flash |
302
+ | `MLX_BUN_CCE_BWD_FILTER_EPS` / `MLX_BUN_CCE_BWD_BLOCK_EPS` | `1e-5` / `1e-5` | flash-CCE backward skips; `0` compiles each out (exact gradients) |
303
+
304
+ `MLX_BUN_CCE_*` beyond those two (`NOSTEEL`, `BWD_NOSTEEL`, `SCALAR`,
305
+ `LANE`, `BWD_SCALAR`, `SG_SKIPDQ`, `BWD_SKIP_P2*`) select kernel variants
306
+ for A/B work in `flash-cce.ts`; they are not user knobs. Training needs no
307
+ other env setup — the Gemma-specific flag sanitization older recipes
308
+ exported is gone (those kernels were deleted 2026-07-05; exporting the old
309
+ variables does nothing).
310
+
311
+ ## Sequence-length ceilings (measured)
312
+
313
+ What decides whether a run fits. Full tables (peak GB and s/step per
314
+ seq/config) are in [benchmarks.md](benchmarks.md); the mechanism and the
315
+ measurement method are in
316
+ [segmented-backward-training](../design/orpo-training.md).
317
+
318
+ - **MiniCPM5-1B (24 layers), M1 Max 32 GB, 2026-06-16** — segmented
319
+ backward (`segment_size` 4) @2048: peak live **10.91 → 3.29 GB** vs the
320
+ plain backward; @4096 the plain backward spikes to 21–26 GB while
321
+ segmented stays at 6–8 GB. Trains at 8192 comfortably.
322
+ - **e4b (Gemma4, 42 layers), M1 Max 32 GB, 2026-06-16** — without
323
+ segmentation, `ops.sdpa` + `grad_checkpoint` peaks at **23.2 GB live @2048**
324
+ and crashes at 4096 (linear: ≈ 7.7 GB + 7.6 MB/token). With segmented
325
+ backward all 42 layers train at **8192 in 17.5 GB** (`segment_size` 2),
326
+ where `mlx_lm.lora --grad-checkpoint` OOMs on all-42 @8192 and needs its
327
+ default 16 trainable layers (25.7 GB) to fit.
328
+ - **e4b, M1 Max 32 GB, 2026-07-02** — `segment_size` is the whole knob:
329
+ mlx's sdpa backward materializes O(L²) scores for every layer (sliding
330
+ window is only a mask), so the worst segment is set by layer count.
331
+ `segment_size` 1 = **14.59 GB @8K**, +3% step time, loss identical —
332
+ fits a 24 GB M4 Pro.
333
+ - **e4b ORPO full stack (flash + prefix-share + seg 2), M1 Max 32 GB** —
334
+ fits at 8192 with headroom; prefix-sharing is what makes prompt-dominant
335
+ data cheap (memory and step time both drop when the prompt is encoded
336
+ once). Numbers are from short probe runs — they prove fit and finite
337
+ decreasing loss, not training quality.
338
+
339
+ Rules of thumb: e4b at multi-K context **requires** segmented backward
340
+ (`--seg`, on by default for ORPO; pass it explicitly for SFT); memory is
341
+ linear in `--seq`; on a 24 GB machine use `--seg 1` at 8K.
342
+
343
+ ## Examples (task-specific recipes)
344
+
345
+ These are the scripts behind our own chunk-segmentation fine-tunes. They
346
+ carry task defaults (data paths, eval corpus) that point at a sibling
347
+ repo — override `DATA=` / `MODEL=` / `ADAPTER=` for your own data and treat
348
+ them as templates, not tools.
349
+
350
+ - [`scripts/examples/chunk-finetune.ts`](../../scripts/examples/chunk-finetune.ts)
351
+ — SFT via `finetuneRunner` directly, env-driven. `MODEL` defaults to a
352
+ MiniCPM5-1B-OptiQ-4bit snapshot; `SEQ` 8192, `ITERS` 2 (probe), `RANK`
353
+ 16, `LR` 1e-5, `SCALE` 20, `SEG` 0, `EVAL_EVERY` auto, `CKPT=0` disables
354
+ keep-all checkpoints, `GRAD_CKPT=1` enables gradient checkpointing.
355
+ ```bash
356
+ DATA=<dir> SEQ=8192 ITERS=2 bun scripts/examples/chunk-finetune.ts # memory/stability probe
357
+ DATA=<dir> SEQ=8192 ITERS=300 SEG=2 bun scripts/examples/chunk-finetune.ts # real run
358
+ ```
359
+ - [`scripts/examples/ft-e4b-v2.sh`](../../scripts/examples/ft-e4b-v2.sh)
360
+ `probe|train` — wraps `chunk-finetune.ts` for e4b with `SEQ=8192 SEG=4
361
+ RANK=16 SCALE=20 LR=1e-5`, pinning the validated e4b OptiQ-4bit snapshot;
362
+ `probe` = 2 iters (run first), `train` = 900 iters (~2 epochs over 450
363
+ rows at batch 1).
364
+ ```bash
365
+ DATA=<dir> scripts/examples/ft-e4b-v2.sh probe
366
+ DATA=<dir> ITERS=750 SEQ=4096 SEG=1 scripts/examples/ft-e4b-v2.sh train
367
+ ```
368
+ - [`scripts/examples/chunk-eval.ts`](../../scripts/examples/chunk-eval.ts)
369
+ — in-process task eval (no server): loads a model, optionally mounts
370
+ `ADAPTER=<dir>`, generates over a frozen holdout, and scores with the
371
+ task's metric. Its holdout/scorer imports live in the sibling repo; copy
372
+ the shape for your own task eval. Pick checkpoints by the task eval, not
373
+ by val loss.
241
374
 
242
375
  ## Methodology
243
376
 
244
377
  ### LoRA
245
- Adapters attach to the target linears; A is initialized uniform, B is zeros,
246
- so the adapted model equals the base model at step 0. Only A/B are
247
- differentiated — base quantized weights are frozen. Default targets are the 7
248
- attention+MLP projections per block (`q/k/v/o_proj`, `gate/up/down_proj`),
249
- following Unsloth. See [`src/train/lora-params.ts`](../../src/train/lora-params.ts).
378
+ Adapters attach to the target linears; A is initialized uniform, B zeros,
379
+ so the adapted model equals the base at step 0. Only A/B are differentiated.
380
+ Default targets are the 7 attention+MLP projections per block
381
+ (`q/k/v/o_proj`, `gate/up/down_proj`). See
382
+ [`src/train/lora-params.ts`](../../src/train/lora-params.ts).
250
383
 
251
384
  ### Rank scaling (`rank_scaling`)
252
385
  - `constant` — every target gets `rank`.
253
- - `by_bits` *(default)* — `rank × (bits / 4)`, clamped ≥2; gives **wider
254
- adapters to the more-sensitive (higher-bit) layers** (mixed precision: a
255
- 4-bit layer gets the base rank, an 8-bit layer 2×). This is optiq's "one
256
- sensitivity signal, two optimizations" — the same KL pass that assigns the
257
- bits also sets the rank. The per-layer bits are read straight from the loaded
258
- model's quant specs (auto-wired; no metadata file needed).
259
- - `by_kl` — scales by per-layer KL importance, clamped to [0.5×, 2×]; reads the
260
- KL values from `optiq_metadata.json` and falls back to `by_bits` if none are
261
- recorded.
262
-
263
- ### Long-context memory: segmented backward vs gradient checkpointing
264
- At long `max_seq_length`, activation memory dominates. Two levers:
265
-
266
- - `grad_checkpoint: true` — recompute each layer's activations during
267
- backward. Bit-identical; trades compute for memory.
268
- - `segment_size: N` — **segmented backward**: run the layer stack forward
269
- detaching the residual stream into graph-free boundary leaves every N
270
- layers, then backprop segment-by-segment via `mlx_vjp` (cotangent passed
271
- directly, *not* a surrogate-loss `value_and_grad`, which leaked). Only one
272
- segment's activations live at a time. This is the path to multi-K context;
273
- full mechanism, proofs, and measured peaks (e.g. MiniCPM5 10.91→3.29 GB
274
- @2048) are in
275
- [segmented-backward-training](../design/segmented-backward-training.md).
276
-
277
- ### Training attention kernel (`MLX_BUN_TRAIN_ATTN`)
278
- - **default `ops.sdpa`** — mlx's fused SDPA; correct (0.00% vs autograd),
279
- O(L²) backward memory. Use this.
280
- - **`flash`** — opt-in O(L)-memory path. Its two port bugs (dK
281
- buffer-transpose, dQ divergent causal barrier) were **fixed** and
282
- FD-validated (`scripts/experiments/flash-fd-check.ts`; regression test
283
- `tests/flash-attention.test.ts`, T≤256), but it remains ~30× slower than
284
- `ops.sdpa`, and the historical e4b multi-K (≥2K) SIGTRAP has **not been
285
- re-validated at that scale since the fix**
286
- (`scripts/experiments/segmented-grad-test-e4b.ts` is the repro harness).
287
- Prefer the default; treat `flash` as experimental on e4b long context.
288
- Detail in segmented-backward-training §6.
386
+ - `by_bits` *(default)* — `rank × (bits / 4)`, clamped ≥2: wider adapters
387
+ on the more-sensitive (higher-bit) layers of a mixed-precision model. The
388
+ per-layer bits come from the loaded model's quant specs.
389
+ - `by_kl` — scales by per-layer KL importance from `optiq_metadata.json`,
390
+ clamped to [0.5×, 2×]; falls back to `by_bits` when none is recorded.
391
+
392
+ ### Long-context memory
393
+ At long `max_seq_length` activation memory dominates, not the head. Two
394
+ levers, mutually exclusive: `grad_checkpoint` (per-layer recompute, does not
395
+ stream) and `segment_size` (segmented backward, streams one segment at a
396
+ time — the path to multi-K context). Detail, proofs, and the `mlx_vjp`
397
+ vs surrogate-`value_and_grad` leak lesson:
398
+ [segmented-backward-training](../design/orpo-training.md).
399
+
400
+ ### Training attention
401
+ `ops.sdpa` (mlx's fused SDPA, exact vjp, O(L²) backward memory) is the
402
+ only supported path for Gemma and the default everywhere. The opt-in
403
+ `MLX_BUN_TRAIN_ATTN=flash` kernel is an O(L)-memory experiment — ~30×
404
+ slower and blocked for Gemma by the trainer (see the env table).
289
405
 
290
406
  ## Outputs
291
407
 
292
408
  A finished run writes a PEFT-compatible adapter directory:
293
409
 
294
410
  - `adapters.safetensors` — the `lora_a` / `lora_b` tensors
295
- - `optiq_lora_config.json` — mlx-bun/optiq adapter metadata (per-layer ranks)
411
+ - `optiq_lora_config.json` — mlx-bun/optiq metadata (per-layer ranks, scale, base model)
296
412
  - `adapter_config.json` — PEFT-compatible config
297
-
298
- The save completes only after both config files are written, including
299
- eval-step checkpoints, so a successful process exit always leaves an
300
- immediately mountable directory. Serving accepts both mlx-lm tensor layout
301
- (`[in, rank]` / `[rank, out]`) and standard PEFT layout
302
- (`[rank, in]` / `[out, rank]`, including `base_model.model.*` names).
303
- Standard PEFT `use_rslora` metadata is honored as
304
- `lora_alpha / sqrt(rank)`, and saved rsLoRA adapters emit that metadata so
305
- their training and serving scales agree.
306
-
307
- When `save_checkpoints: true`, each eval step also writes
308
- `checkpoints/step-<NNNNN>-val<loss>/` and a durable `metrics.json` (config,
309
- wall seconds, peak GB, final/best train+val loss, full val trajectory).
310
-
311
- **Serving the adapter:** hot-swap it into a running server via the adapter
312
- API and select it per-request — see
313
- [adapters-end-to-end](../design/adapters-end-to-end.md) and the adapter
314
- endpoints in [server-api](server-api.md). Or fold it into the base weights
315
- with `POST /api/finetune/merge`.
413
+ - `metrics.jsonl` — append-only per-step log (what `train-watch` tails)
414
+
415
+ The save completes only after both config files are written — including
416
+ eval-step checkpoints — so a successful exit always leaves a mountable
417
+ directory. With `save_checkpoints` (`--save-every`), each eval step also
418
+ writes `checkpoints/step-<NNNNN>-val<loss>/` (each a full mountable adapter)
419
+ and `metrics.json` (config, wall seconds, peak GB, final/best train+val
420
+ loss, full val trajectory).
421
+
422
+ **Where to save.** The CLI default is
423
+ `~/.cache/mlx-bun/mlx-bun-finetunes/<method>-<model>`. The server's adapter
424
+ discovery (`GET /v1/adapters/available`, the web chat's adapter picker)
425
+ scans `~/.cache/mlx-bun-finetunes` and **`~/.cache/mlx-bun/adapters`** —
426
+ so pass `--adapter ~/.cache/mlx-bun/adapters/<name>` if you want the
427
+ adapter selectable without typing a path. `serve --adapter <dir>` and
428
+ `fuse --adapter <dir>` accept any directory.
429
+
430
+ Serving accepts both mlx-lm tensor layout (`[in, rank]` / `[rank, out]`)
431
+ and standard PEFT layout (`[rank, in]` / `[out, rank]`, including
432
+ `base_model.model.*` names); PEFT `use_rslora` metadata is honored as
433
+ `lora_alpha / sqrt(rank)`. Hot-swap and per-request selection:
434
+ [server-api.md](server-api.md) (Adapters) and
435
+ [adapters-end-to-end](../design/web-chat-redesign.md).
316
436
 
317
437
  ## Memory & performance tips
318
438
 
319
- - Start at `batch_size: 1` (the no-padding path); raise only with headroom.
320
- - OOM → lower `max_seq_length`, set `segment_size` (e.g. 2–4), or reduce
321
- `rank` / `num_layers`.
322
- - Set `MLX_BUN_MEM_LOG=1` to watch per-step peak memory.
323
- - `grad_accumulation_steps` raises effective batch without the memory cost.
439
+ - Start at `--batch 1` (the no-padding path); raise only with headroom.
440
+ `--grad-accum` raises the effective batch at batch-1 memory.
441
+ - OOM → lower `--seq`, lower `--seg` (1 at 8K on 24 GB), or reduce
442
+ `--rank`.
443
+ - `MLX_BUN_MEM_LOG=1` for per-step peak; `MLX_BUN_SEG_MEM_LOG=1` to see
444
+ which phase the peak lives in.
445
+ - Short rows already take the exact fused head automatically
446
+ (`MLX_BUN_FLASH_MIN_M`); on response-dominant data `--no-prefix` costs
447
+ nothing and `--no-flash` is worth an A/B.
448
+ - Select checkpoints by the downstream task eval or val accuracy/loss —
449
+ not by preference margin, which keeps rising after the model has
450
+ started overfitting.
451
+
452
+ ## Rules for agents working alongside a run
453
+
454
+ - **No GPU work while a training run is active.** The GPU is fully
455
+ occupied; a "quick" benchmark, parity test, or model load corrupts both
456
+ the probe's numbers and the run's throughput. Verify by reading source
457
+ and logs (`train-watch`, `train.log`, `metrics.jsonl`), not by executing.
458
+ - Long runs are launched by the user from their own shell (`nohup … &`),
459
+ never as a session-spawned background task.
460
+ - Docs for a training-surface change (flags, fields, defaults) land in the
461
+ same commit: this file, [cli.md](cli.md), and
462
+ [server-api.md](server-api.md) for the HTTP routes.