mlx-bun 0.0.4 → 0.0.5

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 (50) hide show
  1. package/README.md +166 -93
  2. package/docs/design/adapters-end-to-end.md +50 -46
  3. package/docs/design/compat-cli-surface-design.md +10 -8
  4. package/docs/design/docs-reorg-plan.md +14 -1
  5. package/docs/design/hlg-sampling.md +12 -2
  6. package/docs/design/memory-system.md +1 -1
  7. package/docs/design/optimization_plan.md +4 -2
  8. package/docs/design/orpo-future-enhancements.md +298 -0
  9. package/docs/design/orpo-training.md +1199 -0
  10. package/docs/design/parallel-slots.md +34 -26
  11. package/docs/design/runtime-isolation.md +1 -1
  12. package/docs/design/segmented-backward-training.md +58 -39
  13. package/docs/design/tauri-desktop-app.md +127 -0
  14. package/docs/{segmented-backward-handoff.md → investigations/segmented-backward-handoff.md} +1 -1
  15. package/docs/investigations/starter-model-port-handoff.md +32 -13
  16. package/docs/investigations/steel-flash-cce-handoff.md +431 -0
  17. package/docs/planning/IDEAS.md +29 -0
  18. package/docs/planning/PRODUCT_ROADMAP.md +147 -27
  19. package/docs/planning/chunk-task-roadmap.md +1 -1
  20. package/docs/planning/journal.md +161 -0
  21. package/docs/reference/distribution.md +61 -5
  22. package/docs/reference/library-api.md +29 -14
  23. package/docs/reference/orpo-quickstart.md +91 -0
  24. package/docs/reference/server-api.md +105 -7
  25. package/docs/reference/server-config.md +12 -5
  26. package/docs/reference/training.md +292 -0
  27. package/package.json +1 -1
  28. package/src/eval/ifeval.ts +138 -0
  29. package/src/eval/ultrafeedback.ts +60 -0
  30. package/src/lora.ts +12 -7
  31. package/src/mlx/metal-kernel.ts +18 -4
  32. package/src/model/flash-attention.ts +32 -16
  33. package/src/model/fused-geglu-kernel.ts +111 -0
  34. package/src/model/gemma4-base.ts +39 -1
  35. package/src/model/gemma4.ts +196 -21
  36. package/src/model/minicpm5.ts +44 -3
  37. package/src/server.ts +3 -3
  38. package/src/train/dataset.ts +18 -3
  39. package/src/train/flash-cce.ts +1162 -0
  40. package/src/train/job.ts +26 -2
  41. package/src/train/lora-params.ts +53 -4
  42. package/src/train/loss.ts +941 -0
  43. package/src/train/optimizer.ts +10 -3
  44. package/src/train/prefix-shared.ts +435 -0
  45. package/src/train/rank.ts +36 -0
  46. package/src/train/segmented.ts +1148 -3
  47. package/src/train/steel-qmm-header.ts +1461 -0
  48. package/src/train/trainer.ts +509 -52
  49. package/src/web/app.html +21 -5
  50. /package/{docs → src/assets}/curve-designer.html +0 -0
@@ -4,6 +4,19 @@
4
4
  (src/ and scripts/ moves — they change import paths / script references
5
5
  and need a test run, so deferred to their own pass). Authored 2026-06-14.
6
6
 
7
+ **Execution state (verified 2026-06-17):**
8
+ - P0 #1 — **DONE**: `AGENTS.md` is a symlink → `CLAUDE.md`; drift risk resolved.
9
+ - P0 #2 — **DONE**: `benchmarks/RESULTS.md` created; old committed benchmark artifacts `git rm`'d. Root untracked spew (`benchmarks-h2h-2026-06-13-*` etc.) is now cleaned — no untracked report files found at root.
10
+ - P0 #3 — **DONE**: root HTML/md spew cleared (no `benchmarks-h2h-*.md`, `benchmark-report-*.html`, `kernel-fusion-report-*.html`, or `project-review.html` at root).
11
+ - P0 #4 — **DONE**: hardcoded test count removed from CLAUDE.md (no "All 72 tests" present).
12
+ - P1 #5 — **DONE**: `STATUS.md` exists at root.
13
+ - P1 #6 — **DONE**: `docs/planning/` exists with `IDEAS.md`, `PRODUCT_ROADMAP.md`, `ResearchTopics.md`, `journal.md`.
14
+ - P1 #7 — open (optimization_plan.md still lives in docs/design/ as a standalone).
15
+ - P1 #8 — status unknown (archive/ not verified in this pass).
16
+ - P2 #9 — **NOT DONE**: `harness-pi.ts`, `pi-provider.ts`, `pi-terminal.ts`, `pi-web.ts` still live flat in `src/` root (no `src/pi/` subdirectory).
17
+ - P2 #10 — **PARTIALLY DONE**: ~57 one-off research/debug scripts moved to `scripts/experiments/`; production + tooling + bench-* + regen-* + parity-* + gen-model + eval/serve remain at `scripts/` root (the full bench/regen/build split was not applied).
18
+ - P2 #11 — **DONE**: CLI vocabulary reconciled.
19
+
7
20
  **Scope & ethos.** Private repo, single author + agents, MLX-by-identity.
8
21
  No OSS/community scaffolding (CONTRIBUTING, issue templates, badges) —
9
22
  out of scope by design. **Prefer move / consolidate over delete.** The
@@ -127,7 +140,7 @@ files, not benchmark output. Leave entirely alone.
127
140
  | 5 | Add `STATUS.md` (or a pinned top section in PLAN.md) holding only live handoff: current state + next action + blockers. Move PLAN.md's `NEXT UP` / `NEXT SESSION PICKUP` / `SESSION SWEEP` blocks into it. | Separates transient "do this next" from 2,475 lines of durable phase history; gives agents one entry point. |
128
141
  | 6 | Create `docs/planning/` and move `PRODUCT_ROADMAP.md`, `IDEAS.md`, `ResearchTopics.md`, `journal.md` under it. Leave only README + PLAN.md (+ STATUS.md) at root. Add a ~6-line "doc map" to README. | Root is currently a 6-file planning thicket; extends the taxonomy that already works in `docs/`. |
129
142
  | 7 | Pick one home for the optimization plan: fold `docs/design/optimization_plan.md` into PLAN.md Phase A–E, or have PLAN.md link out to it as canonical. | Two homes for the same plan = drift. |
130
- | 8 | Resolve the archive overlap: `archive/pi-builtin-investigation.html` duplicates [docs/investigations/pi-builtin-investigation.md](../investigations/pi-builtin-investigation.md). Decide whether `archive/` (tracked; 8 generated HTML files incl. a 374 KB pi-session transcript) should be tracked at all, or moved to an ignored location. Keep the `.md`, drop the duplicate. | Stops committing heavyweight generated HTML; one source per investigation. |
143
+ | 8 | Resolve the archive overlap: `archive/pi-builtin-investigation.html` duplicates [docs/investigations/pi-builtin-investigation.md](../investigations/pi-builtin-investigation.md). Decide whether `archive/` (tracked; 2 HTML files: `mlx-bun-lab-report.html` + `wwdc-mlx-bun.html`) should be tracked at all, or moved to an ignored location. Keep the `.md`, drop the duplicate. | Stops committing heavyweight generated HTML; one source per investigation. |
131
144
 
132
145
  ## P2 — Polish (optional)
133
146
 
@@ -1,6 +1,6 @@
1
1
  # HLG sampling — a piecewise tone curve on the logits
2
2
 
3
- Status: **design + phasing** (no code yet; this doc is the contract)
3
+ Status: **implemented, found neutral, superseded by Curve Designer** — `applyHlg` shipped in `src/sampler.ts`; investigation found HLG a wash vs plain temperature; the Curve Designer (`src/curve-sampler.ts`) is its v2 replacement. See [docs/investigations/hlg-sampling-investigation.md](../investigations/hlg-sampling-investigation.md) for findings. This doc remains as the design rationale.
4
4
  Owner: sampling layer (`src/sampler.ts`)
5
5
  Default: **off** (`--hlg-sampling off` = today's temperature sampler, untouched)
6
6
 
@@ -10,6 +10,12 @@ Default: **off** (`--hlg-sampling off` = today's temperature sampler, untouched)
10
10
  > [docs/reference/server-config.md](../reference/server-config.md) and
11
11
  > [docs/reference/server-api.md](../reference/server-api.md) once shipped.
12
12
 
13
+ ## Outcome / Retrospective
14
+
15
+ HLG was implemented through **Piece 2** (pure curve + neutrality tests + serial wiring, `applyHlg` live in `src/sampler.ts`). Evaluation found it **neutral vs plain temperature** — no measurable gain in diversity or quality at matched output entropy. As a result, HLG is **superseded by the Curve Designer** (`src/curve-sampler.ts`), which generalizes the same region-shaping idea with an editable control-point interface and cleaner UX. Full empirical findings are in [docs/investigations/hlg-sampling-investigation.md](../investigations/hlg-sampling-investigation.md).
16
+
17
+ The sections below (Goal through Correctness) remain as design rationale. Pieces 3–6 of Phasing and the Open Questions were never executed; see the [Phasing](#phasing-the-piece-n-increments) and [Open Questions](#open-questions--risks) sections for historical context.
18
+
13
19
  ## Goal
14
20
 
15
21
  Add a sampling transform — gated entirely behind `--hlg-sampling on` and a
@@ -290,6 +296,8 @@ quality + diversity rather than parity — except for the degeneracy gates, whic
290
296
 
291
297
  ## Phasing (the "piece N" increments)
292
298
 
299
+ > **Historical / superseded.** Pieces 1–2 shipped. Pieces 3–6 were never executed — HLG was evaluated after Piece 2 and found neutral; the feature was superseded by the Curve Designer before Piece 3 began.
300
+
293
301
  1. **Pure curve + neutrality tests** — `applyHlg(lp, params)` in `sampler.ts`,
294
302
  top-anchored pivot only; degeneracy + monotonicity tests green across all
295
303
  four models. (No flag wired yet — pure, fully-tested function.)
@@ -305,6 +313,8 @@ quality + diversity rather than parity — except for the degeneracy gates, whic
305
313
 
306
314
  ## Open questions / risks
307
315
 
316
+ > **Historical / superseded.** These questions were open at the time of design. HLG evaluation concluded with a neutral verdict before they were answered; the Curve Designer supersedes this path. Questions are preserved as design context.
317
+
308
318
  - **Default knob values** are calibration-pending — set empirically by the
309
319
  Piece 5 sweep, not guessed. Initial exploration points: `c ≈ 6`, `w ≈ 4`,
310
320
  `β_h ≈ 4`, `β_t ≈ 6` nats; confirm or replace with measured values.
@@ -337,4 +347,4 @@ for it. That a transfer function is standard practice in another domain says
337
347
  nothing about whether it helps token sampling — that is decided only by
338
348
  measurement (the KL characterization, the capability guardrail, and the
339
349
  diversity lens in [Correctness](#correctness--evaluation)). The full empirical
340
- write-up goes in `docs/investigations/hlg-sampling-investigation.md` once run.
350
+ write-up is in [docs/investigations/hlg-sampling-investigation.md](../investigations/hlg-sampling-investigation.md).
@@ -1,6 +1,6 @@
1
1
  # Memory system — design sketch
2
2
 
3
- Status: **design** (nothing built yet; capture prerequisite already landed)
3
+ Status: **aspirational — not scheduled; no PLAN.md phase** (nothing built yet; capture prerequisite already landed). Note: `src/jobs/` is the generic background-job runner for quantize/finetune/fit — it is NOT this memory system.
4
4
  Owner: assistant layer (pi-web / pi-terminal / web-tools / jobs)
5
5
  Default: **off** until the user opts in (vault + schedule are explicit, consented actions)
6
6
 
@@ -1,8 +1,10 @@
1
1
  # mlx-bun: gemma4.ts decompose & optimize — action plan
2
2
 
3
3
  > This is the canonical **design/rationale** for the optimization work.
4
- > Live **status/tracking** (checkboxes, findings) lives in PLAN.md under
5
- > "Optimization plan Phase A–E".
4
+ > Live **status/tracking** (checkboxes, findings) for Phases A–D lives in
5
+ > PLAN-archive.md; Phase E remains in PLAN.md.
6
+
7
+ **Status (verified 2026-06-17):** Phases A–D are **DONE** (`[x]` in PLAN-archive.md, all landed 2026-06-10/11). Phase E — per-config fused decode-SDPA kernel — is **in progress** (`[~]` in PLAN.md, started 2026-06-11; fused kernel exists and is quality-gated per PLAN.md Step 4). The body of this doc remains the durable design rationale; completed phase checkboxes and findings live in PLAN-archive.md; Phase E tracking lives in PLAN.md.
6
8
 
7
9
  **Premise (confirmed from the source read):** the current engine sits at
8
10
  parity-to-ahead vs mlx-lm on the 12B *while still interpreting the decode
@@ -0,0 +1,298 @@
1
+ # ORPO future enhancements
2
+
3
+ Performance-first follow-on ideas for mlx-bun's ORPO trainer, plus a short
4
+ appendix of objective variants that may be useful once the runtime path is
5
+ comfortable.
6
+
7
+ > **LANDED since this was written (2026-06-19):** the top items here are now in
8
+ > production — **Priority 1 (Cut Cross Entropy / fused linear CE)** and **§1
9
+ > (vocab-axis chunked CE)** shipped as the `[M,vocab]`-free **flash-CCE Metal head**
10
+ > (verbatim MLX steel GEMM, fwd+bwd; `orpo_flash_ce`), and **§3 (prefix sharing in the
11
+ > trainer)** shipped as `orpo_prefix_shared`, composed with the flash head AND the
12
+ > segmented backward for MiniCPM5 **and** e4b. See [training.md](../reference/training.md),
13
+ > [orpo-quickstart.md](../reference/orpo-quickstart.md), and the live record
14
+ > [steel-flash-cce-handoff.md](../investigations/steel-flash-cce-handoff.md). The
15
+ > remaining sections (§2, §4–§9, the objective variants) are still genuinely future.
16
+
17
+ Product relevance: ORPO is not only a researcher feature. It is the path to
18
+ small, downloadable task adapters that make the default local assistant better
19
+ without forcing every user onto a larger base model. The performance work below
20
+ matters when it turns adapter training from a fragile lab run into a repeatable
21
+ part of the mlx-bun product loop.
22
+
23
+ ## Priority 1: Cut Cross Entropy / Liger Fused Linear CE
24
+
25
+ The top ORPO performance priority is a Cut Cross Entropy / Liger-style Fused
26
+ Linear Cross Entropy path for the LM head. This should be treated as the next
27
+ major kernel project before lower-impact objective variants.
28
+
29
+ Why it is first:
30
+
31
+ - Gemma's vocab is huge, so materializing `[tokens, vocab]` logits dominates
32
+ peak memory in ORPO.
33
+ - ORPO needs chosen and rejected response log-probs, so the head cost is paid
34
+ twice unless the trainer can share or fuse more work.
35
+ - Token-axis chunking already helps fit the run, but vocab-axis chunking cuts
36
+ the largest remaining logits allocation at the source.
37
+ - The same fused engine can support SFT, DPO, ORPO, SimPO, IPO, and hinge-style
38
+ preference losses once it exposes mean log-prob with a custom upstream
39
+ cotangent.
40
+ - A Metal fused version can avoid extra MLX dispatches and intermediate memory
41
+ traffic in the hot training path.
42
+
43
+ The implementation target is not just "chunk logits"; it is a fused log-prob
44
+ head that computes the linear projection, online vocab logsumexp, target-logit
45
+ gather, loss reduction, and backward replay without ever storing full logits.
46
+
47
+ ## Performance focus
48
+
49
+ ### 1. Vocab-axis chunked cross entropy
50
+
51
+ The current ORPO head optimization is **token-axis chunking**: split the response
52
+ positions into chunks, run the LM head on `[chunk, vocab]`, and rematerialize
53
+ those chunks in the backward. That bounds peak logits by `chunk_tokens * vocab`.
54
+
55
+ The complementary trick is **vocab-axis chunking**: split the vocabulary instead,
56
+ run the head on `[tokens, vocab_chunk]`, and combine chunks with an
57
+ online-logsumexp recurrence. This is the idea behind **Cut Cross Entropy** and
58
+ Liger-style **Fused Linear Cross Entropy**. This is priority 1 for the ORPO
59
+ performance roadmap.
60
+
61
+ Exact forward sketch:
62
+
63
+ 1. For each vocab chunk, compute partial logits `h @ W_chunk`.
64
+ 2. Gather the target logit only if the target id falls inside this chunk.
65
+ 3. Maintain per-token online `logsumexp` across vocab chunks.
66
+ 4. At the end, `logp = target_logit - lse`.
67
+
68
+ Backward reconstructs the softmax chunk-by-chunk from the saved per-token LSE
69
+ and never materializes full `[tokens, vocab]` logits. For Gemma's 262k vocab,
70
+ this is likely the next real moat after segmented backward.
71
+
72
+ Two implementation levels:
73
+
74
+ - Pure MLX chunked VJP first: exact, easier to validate, probably enough to fit
75
+ longer contexts.
76
+ - Metal fused kernel later: fuse quantized head matmul + online LSE + target
77
+ gather + backward. This is the high-performance version.
78
+
79
+ ### 2. Fold ORPO gradients into the fused CE path
80
+
81
+ ORPO needs two mean log-probs, `lw` and `lr`, then applies the odds-ratio loss.
82
+ For a fused CE/log-prob kernel, the clean design is to feed the upstream scalar
83
+ gradient for each branch back into the chunked head backward. In other words,
84
+ the fused head should expose "mean logp with custom upstream cotangent", not
85
+ only cross-entropy.
86
+
87
+ This avoids materializing logits while still letting ORPO, SimPO, and future
88
+ preference losses share the same head engine.
89
+
90
+ ### 3. Prefix sharing in the trainer
91
+
92
+ `src/train/prefix-shared.ts` already proves the MiniCPM5 shared-prefix idea:
93
+ one forward over `[prompt; chosen; rejected]` with block-sparse masking and
94
+ block-wise RoPE. It is not yet wired into `orpoLoop`.
95
+
96
+ Next useful path:
97
+
98
+ - Add an opt-in flag, for example `orpo_prefix_sharing`.
99
+ - Gate it by prompt/response ratio so short-prompt data stays on the simple
100
+ two-forward path.
101
+ - Wire MiniCPM5 first.
102
+ - Port e4b later, where sliding-window masks, donor-KV reuse, and per-layer
103
+ input make it trickier.
104
+
105
+ ### 4. Sequence-chunk the MLP sub-block
106
+
107
+ The MLP is per-token independent. Inside segmented backward, run MLP blocks over
108
+ sequence chunks so the GeGLU/SwiGLU intermediate is `chunk_tokens * d_ff` instead
109
+ of `seq_len * d_ff`.
110
+
111
+ This stacks with depth segmentation and vocab-axis CE:
112
+
113
+ `layer segment -> layer -> MLP seq chunk -> vocab chunk`
114
+
115
+ Attention cannot be chunked this naively, but the MLP can.
116
+
117
+ ### 5. Rotor / budget-aware segment planner
118
+
119
+ Replace fixed `segment_size` with a planner that chooses segment boundaries from
120
+ estimated activation bytes and a target memory budget. The planner should isolate
121
+ expensive full-attention layers and larger MLP regions instead of treating every
122
+ layer equally.
123
+
124
+ This is the practical version of rotor/checkpoint scheduling for this codebase:
125
+ less hand tuning, better fit at 8K+.
126
+
127
+ ### 6. Faster flash-attention backward
128
+
129
+ The custom flash backward is now correctness-useful but too slow. A serious
130
+ performance version should mimic MLX's tuned SDPA style: simdgroup tiling,
131
+ larger threadgroups, and FA2-style backward that stores only per-row LSE and
132
+ recomputes attention probabilities.
133
+
134
+ This matters most when `ops.sdpa` backward's O(L^2) memory is the blocker.
135
+
136
+ ### 7. Fused LoRA epilogue
137
+
138
+ Today LoRA adds multiple ops after the quantized base matmul:
139
+
140
+ `base(x) + scale * ((x @ A) @ B)`
141
+
142
+ A fused dequant-GEMM + LoRA epilogue would reduce dispatches and intermediate
143
+ traffic. Backward can recompute the cheap rank projection:
144
+
145
+ - `h = x @ A`
146
+ - `dB = scale * h.T @ dy`
147
+ - `dA = scale * x.T @ (dy @ B.T)`
148
+ - `dx = dy @ W.T + scale * (dy @ B.T) @ A.T`
149
+
150
+ This is more kernel work, but it benefits SFT, DPO, ORPO, and serving with
151
+ adapters.
152
+
153
+ ### 8. Shape-specialized compile for training graphs
154
+
155
+ Shapeless compile is mainly required when a dimension changes every replay, such
156
+ as autoregressive decode with a growing KV cache:
157
+
158
+ `active_kv_len = prompt_len + generated_tokens`
159
+
160
+ ORPO training is different. Each dataset row already has concrete lengths:
161
+
162
+ - `P`: prompt length
163
+ - `Rc`: chosen response length
164
+ - `Rr`: rejected response length
165
+ - `T`: prefix-shared length, `P + Rc + Rr`
166
+
167
+ That means ORPO does not need shapeless compile in the same way decode does. A
168
+ training graph can be compiled for an exact `(P, Rc, Rr)` shape, or for a padded
169
+ length bucket, and cached under a key that includes the model, flags, segment
170
+ plan, token/vocab chunk sizes, and objective mode.
171
+
172
+ This is especially relevant for e4b prefix-sharing: the proof path should stay
173
+ eager first, but a later L3 speed tier can use shape-specialized compiled graphs
174
+ for known training shapes instead of requiring every primitive to support
175
+ shapeless output-shape inference.
176
+
177
+ The deeper L3 framing is **model-checksum-specific specialization**. For a fixed
178
+ model snapshot, we know the architecture, layer count, hidden sizes, quantization
179
+ layout, RoPE geometry, vocab, layer types, KV-sharing pattern, and supported
180
+ context budget in advance. It is acceptable to spend much more work up front if
181
+ that buys a faster hot path later.
182
+
183
+ Practical version:
184
+
185
+ - Treat the model snapshot checksum as part of the compilation key.
186
+ - Generate/compile/cache a table of exact graph variants for the shapes and
187
+ modes we care about.
188
+ - Bucket or pad examples into those graph variants when helpful.
189
+ - Fall back to eager only for shapes/modes not in the table.
190
+
191
+ This is effectively a lookup table of specialized execution plans: more compile
192
+ and analysis cost up front, less branching, shape handling, and dispatch overhead
193
+ at runtime.
194
+
195
+ ### 9. Fixed-shape decode cache as a compile route
196
+
197
+ Decode is harder because the active KV length grows by one token per step. The
198
+ current shapeless-compile path exists to avoid recompiling for:
199
+
200
+ `L = 17, 18, 19, 20, ...`
201
+
202
+ A separate route is to make decode shapes fixed:
203
+
204
+ 1. Preallocate K/V buffers to a known maximum context length.
205
+ 2. Write the new token into a dynamic position.
206
+ 3. Carry `active_length` / `write_position` as scalar array inputs.
207
+ 4. Mask out inactive cache positions, or preferably use an
208
+ active-length-aware attention kernel that simply does not scan them.
209
+
210
+ The correctness version is "fixed cache + mask". The performance version is
211
+ "fixed cache + active-length-aware attention"; otherwise early decode steps may
212
+ pay for the full max context even when only a short prefix is active.
213
+
214
+ There is also a more aggressive specialization route: prebuild a graph table for
215
+ specific active lengths or length buckets. Even if that means many compiled graph
216
+ variants, the trade can be valid for a fixed model/checksum if compile cost is
217
+ paid once and reused. In that regime, the important design question is not "can
218
+ one shapeless graph infer every shape?" but "which finite set of exact shapes and
219
+ runtime states should we specialize for?"
220
+
221
+ If every op in a chosen route is compile-safe, the whole decode graph can become
222
+ a stable compiled graph. Custom Metal kernels still need to stay outside compiled
223
+ regions unless the route is intentionally eager/custom-kernel-only or MLX compile
224
+ can accept the primitive's output contract.
225
+
226
+ ## Quality and robustness ideas
227
+
228
+ These are lower priority if the current goal is performance, but they are useful
229
+ once ORPO can run comfortably.
230
+
231
+ ### SimPO
232
+
233
+ SimPO is a reference-free preference loss that uses the **average log-probability
234
+ of each response** as the reward and adds a target reward margin. It reuses the
235
+ same `lw` / `lr` values ORPO already computes.
236
+
237
+ Rough shape:
238
+
239
+ `loss = softplus(-beta * ((lw - lr) - gamma))`
240
+
241
+ where `gamma` is the desired chosen-vs-rejected margin. It should be cheap to add
242
+ as a sibling objective because the expensive part, response mean log-probs, is
243
+ already implemented.
244
+
245
+ ### IPO
246
+
247
+ IPO means **Identity Preference Optimization**. It was introduced as a more
248
+ overfit-resistant alternative to the original DPO logistic objective. Instead of
249
+ driving preference probabilities toward hard 0/1 labels indefinitely, IPO uses a
250
+ smoother identity-style objective over preference gaps. The practical point:
251
+ when preference labels are noisy, IPO is often less eager to push margins to
252
+ extremes.
253
+
254
+ For mlx-bun, IPO is interesting mainly as a loss-family option after the shared
255
+ logp engine exists.
256
+
257
+ ### Hinge / SLiC-style loss
258
+
259
+ The hinge loss is a margin-ranking objective:
260
+
261
+ `loss = max(0, margin - reward_gap)`
262
+
263
+ For preference training, `reward_gap` is usually the chosen score minus rejected
264
+ score. Once the gap exceeds the margin, that pair contributes no gradient. This
265
+ can be useful when you want to stop over-optimizing already-solved pairs and
266
+ focus compute on pairs whose preference margin is still too small.
267
+
268
+ ### Label smoothing and robust weighting
269
+
270
+ Preference pairs are noisy. Cheap robustness knobs:
271
+
272
+ - `label_smoothing`: treat a pair as, for example, 95% chosen / 5% rejected
273
+ rather than a hard label.
274
+ - Per-example `weight`: let curated data carry confidence or score gap.
275
+ - Loss clipping / winsorization: cap extreme high-loss pairs so noisy examples
276
+ do not dominate gradients.
277
+ - Ambiguity filtering: skip pairs with tiny initial margin or near-identical
278
+ chosen/rejected responses.
279
+
280
+ ### Better curation
281
+
282
+ The current UltraFeedback curation mostly extracts rows and filters by length.
283
+ Quality can likely improve by filtering for:
284
+
285
+ - Large enough chosen-vs-rejected score gap when available.
286
+ - Similar response length, or at least bounded length ratio.
287
+ - High semantic contrast but not unrelated answers.
288
+ - Instruction-following categories if IFEval is the target metric.
289
+
290
+ Data quality may beat objective cleverness on small ORPO runs.
291
+
292
+ ## Short answer to "token vs vocab"
293
+
294
+ The thing to look for is **vocab-axis chunked / fused cross entropy**, especially
295
+ under the names **Cut Cross Entropy** or **Liger Fused Linear Cross Entropy**.
296
+ Token chunking slices `[tokens, vocab]` by rows. Vocab chunking slices it by
297
+ columns and combines chunks with online logsumexp so the full logits matrix is
298
+ never live.