mlx-bun 0.0.9 → 0.0.11

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 (179) hide show
  1. package/README.md +66 -21
  2. package/THIRD_PARTY_LICENSES.md +1 -0
  3. package/docs/archive/mlx-bun-lab-report.html +275 -0
  4. package/docs/archive/wwdc-mlx-bun.html +848 -0
  5. package/docs/design/audio-input-plan.md +356 -0
  6. package/docs/design/batching-perf-path.md +255 -0
  7. package/docs/design/batching-v2-plan.md +23 -9
  8. package/docs/design/bucketing-stage.md +2 -1
  9. package/docs/design/compat-cli-surface-design.md +4 -3
  10. package/docs/design/decode-speed-program.md +172 -0
  11. package/docs/design/docs-reorg-plan.md +1 -1
  12. package/docs/design/dspark-serving-program.md +371 -0
  13. package/docs/design/dspark-speculative-decoding.md +167 -5
  14. package/docs/design/faithful-l1-consolidation.md +310 -0
  15. package/docs/design/generic-model-support.md +8 -5
  16. package/docs/design/grammar-spec-batching-integration.md +301 -0
  17. package/docs/design/minicpm5-decode-megakernel.md +16 -11
  18. package/docs/design/mlx-lm-tool-parity-plan.md +11 -1
  19. package/docs/design/omlx-adoption-map.md +98 -0
  20. package/docs/design/orpo-training.md +34 -30
  21. package/docs/design/paged-kv-cache.md +214 -0
  22. package/docs/design/parallel-slots.md +82 -49
  23. package/docs/design/parity-tier-dag.md +43 -33
  24. package/docs/design/repo-cleanup-plan.md +189 -0
  25. package/docs/design/runtime-isolation.md +19 -3
  26. package/docs/design/segmented-backward-training.md +18 -2
  27. package/docs/design/ssd-kv-cold-tier.md +288 -0
  28. package/docs/design/structured-output.md +484 -0
  29. package/docs/design/the-dreaming-master-plan.md +2 -3
  30. package/docs/design/turboquant-kv.md +235 -0
  31. package/docs/design/unified-engine-frontier-plan.md +774 -0
  32. package/docs/design/web-chat-beat-matrix.md +315 -0
  33. package/docs/design/web-chat-redesign.md +1112 -0
  34. package/docs/investigations/decode-roofline-lookagain.md +92 -6
  35. package/docs/investigations/dspark-handoff.md +182 -16
  36. package/docs/investigations/kernel-perf-review-2026-07.md +105 -26
  37. package/docs/investigations/lab-build-journal.md +2 -2
  38. package/docs/investigations/openwiki-evaluation.md +249 -0
  39. package/docs/planning/IDEAS.md +14 -0
  40. package/docs/planning/PRODUCT_ROADMAP.md +2 -3
  41. package/docs/planning/chat-ui-vision-memory-first.md +199 -0
  42. package/docs/planning/chat-ui-vision.md +50 -0
  43. package/docs/planning/memory-docs-and-dag-plan.md +15 -6
  44. package/docs/planning/release-notes-v0.0.10.md +57 -0
  45. package/docs/planning/release-notes-v0.0.11.md +239 -0
  46. package/docs/reference/cli.md +35 -16
  47. package/docs/reference/distribution.md +13 -6
  48. package/docs/reference/embedding.md +35 -14
  49. package/docs/reference/features-matrix.md +97 -0
  50. package/docs/reference/library-api.md +30 -9
  51. package/docs/reference/memory.md +26 -6
  52. package/docs/reference/models.md +11 -1
  53. package/docs/reference/orpo-quickstart.md +3 -4
  54. package/docs/reference/server-api.md +634 -22
  55. package/docs/reference/server-config.md +251 -119
  56. package/docs/reference/training.md +20 -16
  57. package/package.json +5 -3
  58. package/src/anthropic.ts +13 -0
  59. package/src/audio/conformer.ts +647 -0
  60. package/src/audio/decode.ts +133 -0
  61. package/src/audio/features.ts +202 -0
  62. package/src/audio/transcode.ts +67 -0
  63. package/src/cli.ts +402 -107
  64. package/src/config.ts +49 -2
  65. package/src/download.ts +40 -0
  66. package/src/eval/README.md +7 -6
  67. package/src/eval/kl.ts +59 -3
  68. package/src/eval/quality-db.ts +2 -2
  69. package/src/eval/runner.ts +3 -3
  70. package/src/expert-offload.ts +15 -2
  71. package/src/flags.ts +13 -0
  72. package/src/generate.ts +330 -16
  73. package/src/grammar.ts +585 -0
  74. package/src/hub-rest.ts +242 -0
  75. package/src/kv-store.ts +618 -122
  76. package/src/lora.ts +8 -0
  77. package/src/md-modules.d.ts +42 -0
  78. package/src/media-fetch.ts +284 -0
  79. package/src/memory/rest.ts +245 -0
  80. package/src/memory/vault.ts +74 -0
  81. package/src/mlx/array.ts +127 -19
  82. package/src/mlx/autograd.ts +2 -2
  83. package/src/mlx/ffi.ts +27 -3
  84. package/src/mlx/metal-kernel.ts +7 -4
  85. package/src/mlx/ops.ts +179 -23
  86. package/src/mlx/turboquant-ops.ts +831 -0
  87. package/src/mlx/turboquant-tables.ts +237 -0
  88. package/src/model/batched-mask.ts +62 -1
  89. package/src/model/batched-quant.ts +206 -0
  90. package/src/model/batched-rotating-quant.ts +346 -0
  91. package/src/model/batched-rotating.ts +46 -1
  92. package/src/model/compiled-decode.ts +4 -15
  93. package/src/model/factory.ts +10 -4
  94. package/src/model/gemma4-base.ts +391 -34
  95. package/src/model/gemma4.ts +122 -33
  96. package/src/model/generated/gemma4-12b.ts +9 -13
  97. package/src/model/generated/gemma4-26b.ts +10 -5
  98. package/src/model/generated/gemma4-e4b.ts +14 -9
  99. package/src/model/minicpm5.ts +58 -102
  100. package/src/model/paged-kv.ts +253 -0
  101. package/src/model/qwen3-delta.ts +123 -15
  102. package/src/model/qwen3-moe.ts +404 -0
  103. package/src/model/qwen3.ts +9 -8
  104. package/src/model/qwen3_5.ts +99 -25
  105. package/src/model/support.ts +12 -1
  106. package/src/model/universal/dense.ts +40 -5
  107. package/src/model/universal/rope.ts +36 -21
  108. package/src/pi-web.ts +1299 -82
  109. package/src/preflight.ts +27 -8
  110. package/src/prompt-cache.ts +338 -32
  111. package/src/quantize/quantizer.ts +16 -1
  112. package/src/registry.ts +61 -3
  113. package/src/serve/batch-scheduler.ts +1001 -76
  114. package/src/serve/generation-gateway.ts +292 -30
  115. package/src/serve/isolate.ts +417 -0
  116. package/src/serve/lane-registry.ts +44 -0
  117. package/src/serve/session-search.ts +326 -0
  118. package/src/server.ts +1460 -105
  119. package/src/spec/assistant-source.ts +150 -0
  120. package/src/spec/deepspec-source.ts +143 -0
  121. package/src/spec/dflash-source.ts +124 -0
  122. package/src/spec/dspark/ab-stats.ts +136 -0
  123. package/src/spec/dspark/calibration.ts +112 -0
  124. package/src/spec/dspark/deepspec-module.ts +801 -0
  125. package/src/spec/dspark/generate-dflash.ts +72 -20
  126. package/src/spec/dspark/loader.ts +23 -0
  127. package/src/spec/dspark/module-dflash.ts +278 -36
  128. package/src/spec/dspark/quantize-drafter.ts +91 -0
  129. package/src/spec/generate.ts +59 -25
  130. package/src/spec/ngram-source.ts +132 -0
  131. package/src/spec/serve-loop.ts +536 -0
  132. package/src/spec/source.ts +93 -0
  133. package/src/spec/two-model.ts +171 -0
  134. package/src/ssd-cache.ts +285 -0
  135. package/src/tokenizer.ts +288 -2
  136. package/src/tool-approvals.ts +131 -0
  137. package/src/tool-call.ts +408 -20
  138. package/src/train/flash-cce.ts +29 -26
  139. package/src/train/job.ts +0 -2
  140. package/src/train/loss.ts +53 -11
  141. package/src/train/prefix-shared.ts +30 -7
  142. package/src/train/segmented.ts +150 -46
  143. package/src/train/trainer.ts +22 -15
  144. package/src/vision/prompt.ts +217 -48
  145. package/src/web/app.html +1184 -1789
  146. package/src/web/app.js +5667 -0
  147. package/src/web/icon.svg +13 -0
  148. package/src/web/manifest.webmanifest +24 -0
  149. package/src/web/src/adapters-panel.ts +288 -0
  150. package/src/web/src/api.ts +57 -0
  151. package/src/web/src/assistant.ts +485 -0
  152. package/src/web/src/chat.ts +1050 -0
  153. package/src/web/src/composer.ts +959 -0
  154. package/src/web/src/dataset.ts +132 -0
  155. package/src/web/src/finetune.ts +267 -0
  156. package/src/web/src/hub.ts +428 -0
  157. package/src/web/src/main.ts +98 -0
  158. package/src/web/src/markdown.ts +0 -0
  159. package/src/web/src/memory-panel.ts +572 -0
  160. package/src/web/src/model-picker.ts +159 -0
  161. package/src/web/src/palette.ts +346 -0
  162. package/src/web/src/protocol.ts +62 -0
  163. package/src/web/src/quantize.ts +167 -0
  164. package/src/web/src/rag.ts +274 -0
  165. package/src/web/src/sessions.ts +469 -0
  166. package/src/web/src/shell.ts +804 -0
  167. package/src/web/src/status.ts +252 -0
  168. package/src/web/src/ui-catalog.ts +92 -0
  169. package/src/web/sw.js +71 -0
  170. package/src/web/vendor/README.md +61 -0
  171. package/src/web/vendor/hljs-LICENSE +29 -0
  172. package/src/web/vendor/hljs-theme.css +59 -0
  173. package/src/web/vendor/hljs.js +26 -0
  174. package/src/model/fused-decode-kernel.ts +0 -312
  175. package/src/model/fused-geglu-kernel.ts +0 -198
  176. package/src/model/fused-mlp-kernel.ts +0 -169
  177. package/src/model/fused-swiglu-kernel.ts +0 -240
  178. package/src/model/steel-linear-kernel.ts +0 -96
  179. package/src/status-page.html +0 -497
@@ -0,0 +1,301 @@
1
+ # Grammar × spec decode × batching — integration plan + feature-matrix benchmark
2
+
3
+ Status: **ALL PHASES EXECUTED** (A/B/C/E 2026-07-03; D completed
4
+ 2026-07-04: KV-budget admission + vectorized homogeneous sampling +
5
+ extend-join, each oracle/A-B gated — see the execution log). Remaining
6
+ follow-ups live in "Later / parked" with their triggers.
7
+ Execution log at the bottom of this doc. Successor plan tying together
8
+ three workstreams so they compose deliberately instead of colliding:
9
+
10
+ - **Structured output** — SHIPPED on main (serial + batched lanes;
11
+ docs/design/structured-output.md). Open debt: B2 model-gated batch
12
+ tests + bench, F4 compiler cache, F5 regex, F6 escaping, F7 structural
13
+ tags, U1/U2 engine upgrades.
14
+ - **Batching perf path** — P5 done; P0–P4 open
15
+ (docs/design/batching-perf-path.md).
16
+ - **Speculative decoding** — research proven (assistant drafter bit-exact
17
+ vs optiq, 12B γ=1 ≈ 1.09× win, docs/design/spec-decode-larger-targets.md;
18
+ DSpark architecture verified, docs/investigations/dspark-handoff.md) but
19
+ **not served**: no `--draft-model`. Build plan already written:
20
+ docs/design/mlx-lm-tool-parity-plan.md §7 (the `DraftSource` interface,
21
+ two-model L1 source, serve loop, admission, prompt-cache composition).
22
+
23
+ This doc does NOT restate those plans — it sequences them, defines the
24
+ composition rules where they meet, and specifies the benchmark that
25
+ measures every combination. The per-feature designs stay canonical in
26
+ their own docs.
27
+
28
+ ## The composition matrix (v1 target state)
29
+
30
+ `GenerationGateway.willBatch` is the single routing point; every rule
31
+ below is one predicate there. "Serial" routing is a v1 rule, not a
32
+ limitation to hide — upstream mlx-lm routes spec serial too
33
+ (`is_batchable = draft_model is None`, server.py:371).
34
+
35
+ | Request shape | Lane | Mechanism | Status |
36
+ | --- | --- | --- | --- |
37
+ | plain | batch (or serial) | shipped engine | DONE |
38
+ | grammar only | batch (or serial) | B1 per-row matchers / serial mask loop | DONE (B2 gates open) |
39
+ | spec only | **serial** | `hasDraft` routes serial (upstream parity) | Phase B |
40
+ | grammar + spec | **serial** | constrained verify walk (novel — §Phase C) | Phase C |
41
+ | batch + spec | serial per request | `hasDraft` wins over batch | Phase B (routing) |
42
+ | batch + grammar + spec | serial per request | `hasDraft` wins | falls out of B+C |
43
+
44
+ Non-goals (explicitly out of scope, revisit only on measured demand):
45
+ per-row spec inside the batch scheduler (variable accept-lengths break
46
+ uniform-B; a research project, not a feature gap), DSpark as a served
47
+ source before its 27B retrain/data-scale lands, grammar on the
48
+ `/v1/responses` shim beyond what chat inheritance already gives.
49
+
50
+ ## Phases
51
+
52
+ Ordering rationale: A is small and closes the trust gap on what already
53
+ shipped. B is the flagship parity feature and lays the `DraftSource`
54
+ seam C needs. C is the novel composition. D runs parallel to B/C
55
+ (scheduler files vs serial-loop files — near-zero overlap). E needs all
56
+ of them and produces the numbers.
57
+
58
+ ### Phase A — structured-output debt (B2 + F4) [S, 2–3 days]
59
+
60
+ 1. **B2 model-gated scheduler tests** (spec'd in structured-output.md):
61
+ mixed batch mask isolation; **all-grammar B=4 with four different
62
+ schemas** (THE bug-class gate — row↔matcher misalignment after
63
+ eviction/join); early termination + churn + joiner; max_tokens
64
+ mid-JSON truncation under batch; prefill-terminated row never merges.
65
+ 2. **F4 compiler cache**: one `GrammarCompiler` per `TokenizerInfo`
66
+ (both vocab-structural; `ownsCompiler=false` path already exists).
67
+ Measure compile time cold vs cached on a real JSON schema — this is
68
+ the agentic-replay win and a benchmark row in Phase E.
69
+ 3. Opportunistic: F6 choice escaping (tiny), F3 explicit padded-vocab
70
+ comment. F5/F7 stay parked for the structural-tag pass (see Later).
71
+
72
+ Exit: gated suites green on cpm5 + e4b; compile-cache A/B number
73
+ recorded.
74
+
75
+ ### Phase B — `serve --draft-model` (two-model, serial lane) [M, ~1 wk]
76
+
77
+ Execute mlx-lm-tool-parity-plan.md §7 as written (it is already the
78
+ hand-to-an-implementer plan). Integration deltas this plan adds:
79
+
80
+ - **`RequestShape.hasDraft`** beside `hasGrammar`; `willBatch` returns
81
+ false for it. Lane-debug line includes it. (The B0-grammar routing
82
+ pattern, reused verbatim.)
83
+ - **The serve verify loop lands grammar-aware from day one**: the loop
84
+ emits bursts of ≤ n+1 tokens; the stop-sequence/detokenizer audit in
85
+ §7 ("risky parts") must treat the grammar mask hook as a first-class
86
+ participant even while it's unused in this phase — i.e. the per-round
87
+ walk is structured as `for pos in verifyWindow: sample(logits[pos],
88
+ maskHook?)` so Phase C is a hook implementation, not a loop rewrite.
89
+ - **Kill switch** `MLX_BUN_SPEC=0` (degrade: ignore the flag, log once),
90
+ house style.
91
+
92
+ Oracle: L1 spec-vs-spec token equality with `mlx_lm.server
93
+ --draft-model` from the oracle venv (§7.8). Acceptance telemetry
94
+ (`drafted/accepted/targetCalls`) in stats — Phase E consumes it.
95
+
96
+ Exit: greedy token-for-token vs mlx-lm spec on a fixed pair; rotating
97
+ wrap falls back non-spec (no 500); `--batch 4` + `--draft-model` routes
98
+ spec requests serial and batches the rest; admission accounts for draft
99
+ weights + KV.
100
+
101
+ ### Phase C — grammar × spec: the constrained verify walk [M, ~1 wk]
102
+
103
+ Novel territory — **no oracle serves grammar+spec** (mlx-lm has no
104
+ grammar; oMLX has no spec). No-oracle (Lab-class) gating per the
105
+ parity-tier contract: equivalence + validity gates, not bit-parity to an
106
+ ancestor.
107
+
108
+ Design (v1 = verify-side masking, drafter free-running):
109
+
110
+ 1. **Drafter is unconstrained.** Drafts are proposals; correctness never
111
+ depends on them (a grammar-invalid draft is simply rejected at
112
+ verify). No matcher state touches the draft path.
113
+ 2. **The verify walk applies the mask sequentially.** One batched target
114
+ forward over `[pending, ...drafts]` (n+1 positions) as in Phase B;
115
+ then the host-side accept walk goes position by position:
116
+ `mask_i = matcher.bitmask` → apply to `logits[i]` → sample → if
117
+ sampled == draft_i: `matcher.accept(draft_i)`, continue; else emit
118
+ the masked correction token, `matcher.accept(correction)`, stop.
119
+ The matcher only ever advances on **emitted** tokens — no rollback
120
+ needed in v1. Sequential bitmask fills cost 0.004–0.19 ms each
121
+ (measured, structured-output.md); at γ≤3 that's ≤0.8 ms/round against
122
+ a 12B target step of ~40 ms — noise.
123
+ 3. **Grammar termination mid-burst** truncates the accepted prefix at
124
+ the terminating token (same all-`-inf` guarantee as serial/batched:
125
+ nothing is sampled past termination), finish_reason "stop".
126
+ 4. **The measured fork** ([[flag-and-try-both-on-forks]]): on
127
+ grammar-tight spans (e.g. `guided_choice`, closing braces) a
128
+ free-running drafter's acceptance may crater — every draft off-grammar
129
+ is a wasted round. If Phase E shows that, the alternative is built
130
+ behind `MLX_BUN_SPEC_GRAMMAR_DRAFT=1`: mask the drafter's logits too
131
+ (matcher advances per draft, `rollBack(n − kAccept)` on rejection —
132
+ the WASM API exposes rollBack; this is its use case). Build the flag
133
+ only if the default's acceptance number demands it; don't pre-build.
134
+
135
+ Gates (no external oracle):
136
+ - **Equivalence**: greedy grammar+spec long-prefix-equal to greedy
137
+ grammar-only serial on fixed prompts (exact-match acceptance means
138
+ spec must reproduce the non-spec masked-greedy stream, modulo the
139
+ documented batched-verify-head knife-edges — same caveat class as L1
140
+ spec-vs-spec).
141
+ - **Validity**: every grammar+spec output parses / conforms to its
142
+ schema (100%, no exceptions), matcher never rejects an emitted token.
143
+ - **Composition regression fence**: grammar-only and spec-only suites
144
+ byte-unchanged.
145
+
146
+ ### Phase D — batching P0 (+ admission slice of P3) [M, ~1 wk, parallel with B/C]
147
+
148
+ From batching-perf-path.md, the subset that the benchmark needs and that
149
+ doesn't collide with B/C's serial-loop work:
150
+
151
+ 1. **P0**: `scripts/bench-serving-load.ts` (N concurrent vs live server,
152
+ aggregate tok/s + TTFT p50/p95 at the SSE first-token event) —
153
+ Phase E builds on this harness; `extend` join (kill the O(B·S)
154
+ re-merge); vectorized homogeneous-sampler fast path.
155
+ - `extend` + grammar rows: the B1 read-before-build step must hold
156
+ across an extend-join — add one B2-style churn test with a joiner
157
+ landing while a grammar row is live.
158
+ 2. **P3 admission slice only**: `projectKvBytes` + `--kv-budget`
159
+ queueing (the OOM backstop the combined benchmark needs before it
160
+ pushes B=4 × long prompts on 24 GB). Prompt-cache-under-batching,
161
+ adapters, and the default flip stay in batching-perf-path P3.
162
+
163
+ P1 (quantized KV at B>1) is NOT in this plan's critical path — it's the
164
+ next wave after the matrix benchmark exists, and the benchmark is exactly
165
+ what will size its payoff. (P2 — perf kernel at B>1 — is OBSOLETE: the
166
+ kernel was deleted 2026-07-05, docs/design/unified-engine-frontier-plan.md §6.)
167
+
168
+ ### Phase E — the feature-matrix benchmark [S–M, 3–4 days]
169
+
170
+ One harness answering "what does each feature — and each legal
171
+ combination — cost or buy, per model?" Extends P0's
172
+ `bench-serving-load.ts`; preflight-gated like benchmark.sh (dirty-machine
173
+ numbers are garbage; quotable rows need the clean-machine protocol).
174
+
175
+ **Cells** (skip-if-unavailable, like the model-gated suites):
176
+
177
+ - Lanes/features: `serial`, `batch4`, `serial+grammar`, `batch4+grammar`,
178
+ `serial+spec`, `serial+grammar+spec` — the six legal shapes from the
179
+ matrix. (batch+spec cells route serial by design; the harness records
180
+ them as routing checks, not perf rows.)
181
+ - Models: cpm5 + e4b (fast movers, both lanes), 12B (+ its assistant
182
+ drafter — the measured spec win), Qwen3.5 (batch + thinking-model
183
+ grammar canary; grammar cells expected to expose F7 until structural
184
+ tags land — record, don't hide).
185
+ - Workloads: short-chat (128 tok), JSON-extraction (a real schema,
186
+ grammar cells), long-form (512 tok). 4 concurrent for batch cells,
187
+ 1 for serial cells; N=3 median.
188
+
189
+ **Metrics per cell**: aggregate + per-stream decode tok/s · TTFT
190
+ p50/p95 (SSE first token, wall-clock) · spec acceptance
191
+ (drafted/accepted/targetCalls) · grammar overhead vs the same cell
192
+ without grammar (%) · schema-conformance rate (**must be 100% in every
193
+ grammar cell — a conformance failure fails the run, it's a correctness
194
+ gate wearing a benchmark costume**) · peak RSS.
195
+
196
+ **Denominator discipline** (spec-decode-larger-targets.md): every
197
+ feature cell is compared to the SAME model+lane baseline cell from the
198
+ SAME run. No cross-model or cross-run speedup claims.
199
+
200
+ **Output**: eval-DB rows tagged `feature-matrix[<host>]` + a dated
201
+ markdown artifact (gitignored, like benchmark.sh's); promoted rows land
202
+ in benchmarks/RESULTS.md under a new "composition" table. Josh-gated
203
+ clean-machine run for anything quotable.
204
+
205
+ Exit: the matrix runs end-to-end on this machine; a first
206
+ (loaded-machine, unquotable) sweep sanity-checks every cell; the
207
+ clean-machine run is queued in STATUS Josh-gated.
208
+
209
+ ## Later / parked (pointers, so they aren't lost)
210
+
211
+ - **F7 + F5 structural-tag pass** (thinking-model grammar + real regex)
212
+ — try the installed WASM package's `fromStructuralTag`/`RegexFormat`
213
+ first; U1 rebuild only if its Dec-2025 core predates the semantics
214
+ (structured-output.md addendum). Qwen3.5's grammar cells in Phase E
215
+ are the forcing function.
216
+ - **U2 native XGrammar-2 via TVM-FFI** — trigger conditions unchanged
217
+ (B≫8, spec+grammar draft-tree masking via `traverse_draft_tree`, or
218
+ compile time visible despite F4). Phase C's v1 walk deliberately
219
+ avoids needing it.
220
+ - **Batching P1** (quant-KV at B>1) — next wave, sized by the matrix.
221
+ (P2, perf kernel at B>1, is obsolete — kernel deleted 2026-07-05.)
222
+ - **P4 device-side step chaining** — orthogonal serial-decode work; the
223
+ matrix's serial baselines will show its headroom.
224
+ - **Assistant/DSpark sources behind `--draft-model`** —
225
+ mlx-lm-tool-parity-plan §7.9; DSpark stays research until 27B
226
+ retrain + data scale (dspark-handoff.md).
227
+ - **Spec inside the batch lane** — research question, not scheduled.
228
+
229
+ ## Execution log (2026-07-03)
230
+
231
+ - **Phase A** ✅ — B2 gates in `tests/batch-grammar.test.ts` (all five
232
+ scenarios + a sixth found later, below); F4 compiler cache went through a
233
+ bring-up fix (single-flight rebuild — concurrent compiles after a
234
+ tokenizer switch double-disposed the previous compiler); F6, F3 landed.
235
+ - **Phase B** ✅ — `serve --draft-model`/`--num-draft-tokens`;
236
+ `src/spec/{source,two-model,serve-loop}.ts`. **L1 gate: 48/48
237
+ token-for-token vs mlx-lm spec** (Llama 3B+1B greedy, 65% acceptance,
238
+ `scripts/oracle-spec-two-model.py`). Deviations as designed (ring-wrap
239
+ degrades pre-pollution via an offset+n+1 gate — upstream's post-hoc check
240
+ would already have polluted the ring; prompt cache bypassed v1).
241
+ - **Phase C** ✅ — the constrained verify walk in `samplePos`; gates green
242
+ (100% validity; 12/12 token-identical to grammar-only serial; choice
243
+ terminates mid-spec). Drafter-masking flag NOT built (acceptance 6/15 on
244
+ a tight schema — usable; revisit only if Phase E's clean run says so).
245
+ - **Phase E** ✅ (harness) — `scripts/bench-feature-matrix.ts` (the planned
246
+ bench-serving-load extension became its own script: that file already
247
+ exists as a client-only stack-vs-stack tool and stays one). Six cells
248
+ green end-to-end on Llama 3B+1B; `usage.speculation` telemetry on all
249
+ four API usage blocks. Clean-machine run for RESULTS.md is Josh-gated.
250
+ - **The conformance gate paid for the whole phase in one smoke run** —
251
+ three real bugs: (1) snake_case wire fields never reached the grammar
252
+ resolver (structured output was DEAD over HTTP since it landed);
253
+ (2) `#flushPipeline` didn't advance matchers → stale masks on mid-decode
254
+ joins (regression test added); (3) **UniversalDenseModel batched RoPE
255
+ uses the scalar cache.offset** → uneven-row batches decode joiners at
256
+ wrong positions — latent for ALL Tier-0 archs since v0.0.9. **Fixed
257
+ same day**: `UniversalRope.applyDynamic` (+ `ops.ropeScaledDynamic`)
258
+ routes the wrapper's per-row `ropeOffsetArr`; gated **token-exact vs
259
+ mlx-lm B=2 on Llama-3.2-3B** — static uneven rows AND the dynamic
260
+ join/leave protocol (tests/batched-decode-parity.test.ts "Llama 3B
261
+ Tier-0"). Plain full-attention universal archs now batch; maskArray
262
+ (gemma2-family, pad-blind causal mask in forwardLayers) and
263
+ sliding-window universal archs remain serial (unvalidated cells).
264
+ Lesson, per [[per-model-quant-specialization]]: the gateway's
265
+ capability gate admitted a model family no batched-parity suite ever
266
+ validated.
267
+
268
+ ### Phase D completion (2026-07-04)
269
+
270
+ - **KV-budget admission** — `--kv-budget <GB>`: aggregate projected-KV gate
271
+ in the scheduler (queue-don't-OOM, oversized-alone rejects,
272
+ `/stats.batch.{pending_rows,kv_bytes,kv_budget_bytes}`;
273
+ tests/batch-kv-budget.test.ts).
274
+ - **Vectorized homogeneous sampling** — all-plain-greedy batches sample via
275
+ ONE log-softmax+argmax over [B,V]; gateway flags `plainGreedy`; BIT-equal
276
+ A/B vs the per-row path (`MLX_BUN_BATCH_VEC_SAMPLE=0` kill switch;
277
+ tests/batch-vec-sample.test.ts).
278
+ - **Extend-join** — `extendKVRows` (mlx-lm `BatchKVCache.extend` semantics:
279
+ one pad + one B-axis concat, running rows untouched, pads grow) replaces
280
+ the O(B·S) re-merge for full-attention layers in `#mergeJoiner`
281
+ (`MLX_BUN_BATCH_EXTEND=0` = re-merge). **Own oracle** (extend's pad
282
+ layout ≠ re-merge's): scripts/gen-batched-extend-golden.py →
283
+ token-for-token on CPM AND Llama-3B (tests/batched-decode-parity.test.ts
284
+ "EXTEND-JOIN ORACLE"); full scheduler suite incl. the grammar-churn join
285
+ test green through the extend path AND the kill-switch path.
286
+ Rotating-layer extend (Gemma joins re-merge their sliding layers, extend
287
+ their full layers) is a follow-up.
288
+
289
+ ## Risks
290
+
291
+ - **Grammar+spec has no oracle** — mitigated by the three-gate stack
292
+ (equivalence to grammar-only greedy, 100% validity, regression fence)
293
+ and by keeping the drafter out of the grammar state in v1.
294
+ - **Multi-token bursts through stop-sequence/detokenizer state** — the
295
+ known §7 risky part, now load-bearing for two phases; land the burst
296
+ audit with Phase B tests, not after.
297
+ - **Memory at 12B+drafter / B=4 on 24 GB** — Phase D's admission slice
298
+ is the backstop; benchmark cells must go through it, not around it.
299
+ - **Scope creep via the matrix** — the benchmark measures what exists;
300
+ it must not become a reason to build P1/P2/drafter-masking
301
+ speculatively. Each parked item has its trigger written above.
@@ -15,8 +15,9 @@ The inference forward composes per-op MLX nodes (`quantized_matmul`, `sdpa`, …
15
15
  We tried to insert our own fused `fast.metal_kernel`s (steel `BlockMMA` GEMM) into
16
16
  that forward to fuse+speed it up. **Confirmed dead end:** MLX's lazy buffer planner
17
17
  corrupts *chained* `BlockMMA` custom kernels (nondeterministic NaN/garbage/crash) —
18
- the pointwise `fused-gelu` chains fine, ours do not, and ≥2 of ours in one lazy
19
- graph corrupt. The only robust fixes are (a) a per-kernel eval boundary (24 syncs/
18
+ the pointwise `fused-gelu` kernel chained fine (that kernel has since been deleted,
19
+ 2026-07-05, in favor of the compiled activations — unified-engine-frontier-plan.md
20
+ §6), ours did not, and ≥2 of ours in one lazy graph corrupt. The only robust fixes are (a) a per-kernel eval boundary (24 syncs/
20
21
  forward → net **slower**, 0.5–0.8×), or (b) **don't give the planner anything to
21
22
  chain: one resident kernel for the whole forward.** This plan builds (b).
22
23
 
@@ -48,9 +49,11 @@ miracle. The *architectural* win (no corruption, fully ours) is the point.
48
49
  entire MiniCPM5 decode forward in **one** `MetalKernel.apply` dispatch.
49
50
  2. **Correctness gate (L1, bf16 KV):** teacher-forced greedy trajectory matches
50
51
  `goldens/minicpm5-parity.json` — argmax agreement **≥ 98/100**, mean per-step
51
- logit maxDiff small (this is an L3 path: ~1–2 bf16 ULP/op accumulating; it will
52
- NOT be bit-exact, gate by teacher-forced agreement + KL like
53
- `tests/perf-kernel-oracle.test.ts`, threshold ≥ 56/64-style). DETERMINISTIC
52
+ logit maxDiff small (this is a no-oracle Lab-gated path: ~1–2 bf16 ULP/op
53
+ accumulating; it will NOT be bit-exact, gate by teacher-forced agreement + KL —
54
+ the frozen-trajectory envelope method of the old `tests/perf-kernel-oracle.test.ts`,
55
+ threshold ≥ 56/64-style; that test was deleted 2026-07-05 with its kernel, but
56
+ the methodology stands on its own). DETERMINISTIC
54
57
  across runs (no nondeterminism — that was the corruption signature).
55
58
  3. **Perf gate:** decode tok/s ≥ the serial baseline on a paired same-process A/B
56
59
  (`scripts/experiments/` style). Not slower. (Stretch: measurably faster.)
@@ -383,7 +386,7 @@ q-level / group-min-max boundary and flips a level → a full quant-step dequant
383
386
  amplified 9.7e-4 → 1.38e-2. **NOT a quant bug — the megakernel's intrinsic ~1-ULP GEMV
384
387
  difference amplified by quant discontinuity. 93/100 is the L2 ceiling (as 97/100 is L1's);
385
388
  bit-exact L2 would need a bit-exact GEMV, defeating qmv4.** Gate L2 by KL + agreement
386
- (L3 class), not the bit-exact golden. Scripts: `megakernel-kv-{cmpkv,cmpl1,perstep}.ts`.
389
+ (no-oracle, Lab class), not the bit-exact golden. Scripts: `megakernel-kv-{cmpkv,cmpl1,perstep}.ts`.
387
390
  **Increment 2b — MEMORY win (later):** bit-pack `q` into uint32 (flat KPACK/VPACK,
388
391
  per-layer literal offsets) to actually shrink 4-bit KV. Same logits as 2a. Deferred.
389
392
  **Build on the GENERATED kernel** (`genSourceMt`, `MLX_BUN_MEGAKERNEL_GEN`): the
@@ -404,8 +407,10 @@ teacher-forced agreement + KL. Defer until L1 solid (L1 IS solid — parity, Pha
404
407
  per-step `forwardHidden→logitsFromHidden` at decode (M=1) only; prefill unchanged.
405
408
  Guard: bf16 KV (L1) only, no adapters, no vision. Add `--megakernel` CLI flag.
406
409
  **Gate:** `bun test tests/minicpm5-*.test.ts` green (off); a new
407
- `tests/minicpm5-megakernel.test.ts` (teacher-forced gate, env-gated like
408
- `tests/perf-kernel-oracle.test.ts`); paired decode tok/s A/B logged. Update
410
+ `tests/minicpm5-megakernel.test.ts` (teacher-forced gate, env-gated in the pattern
411
+ of the old `tests/perf-kernel-oracle.test.ts` — deleted 2026-07-05 with its kernel,
412
+ unified-engine-frontier-plan.md §6; the envelope-gate methodology stands on its
413
+ own); paired decode tok/s A/B logged. Update
409
414
  `STATUS.md` + `benchmarks/RESULTS.md` (preflight-gated `benchmark.sh` for the
410
415
  quotable number — `[[dirty-machine-numbers-are-garbage]]`).
411
416
 
@@ -425,7 +430,7 @@ catch any divergence the moment it's introduced, not 24 layers + a quant step do
425
430
  Steps: (1) copy mlx `quantized.h` GEMV kernels INCLUDING the per-(bits,M,N,K) dispatch
426
431
  (`qmv_fast`/`qmv`/`qmv_quad`), `rms_norm`, `rope`, `sdpa`; (2) per-op maxDiff-0 harness
427
432
  before fusing; (3) analyze the single fused file; (4) optimize with the harness as a
428
- regression gate. This megakernel was built L3-first (KL-gated, NOT bit-exact by design),
433
+ regression gate. This megakernel was built no-oracle-first (Lab-style, KL-gated, NOT bit-exact by design),
429
434
  so its 1-ULP/op drift is within contract — bit-exact is a focused REBUILD, not a patch
430
435
  (and may cost qmv4's speed if mlx's real 4-bit kernel is the slower qmv_quad).
431
436
 
@@ -438,8 +443,8 @@ so its 1-ULP/op drift is within contract — bit-exact is a focused REBUILD, not
438
443
  param. Bake dims as `templateInts` / read `x_shape[0]`, don't pass a runtime
439
444
  `shape` input array (extra lazy nodes).
440
445
  - **Match the reference's bf16 rounding op-for-op** (round to `T` at each boundary) —
441
- `metal::precise::exp`/`tanh` are the only residual vs the reference; that's L3 and
442
- fine. Do NOT compute everything in f32 then round once (drifts).
446
+ `metal::precise::exp`/`tanh` are the only residual vs the reference; that's
447
+ no-oracle (Lab-gated) territory and fine. Do NOT compute everything in f32 then round once (drifts).
443
448
  - **`atomicOutputs:true` + `initValue:0`** for any atomic-accumulated output.
444
449
  - **GQA:** 16 q-heads, 2 kv-heads, `nRep=8` → q-head `h` reads kv-head `h/8`.
445
450
  - **`rope_theta=5e6`, not 10000.** scale `=128^-0.5`. No MiniCPM residual scaling.
@@ -324,7 +324,17 @@ follow-on if we build the shared capture instrumentation anyway.
324
324
 
325
325
  ## 7. `serve --draft-model` / `--num-draft-tokens` (speculative decoding at serve time)
326
326
 
327
- This is the concrete, hand-to-an-implementer plan.
327
+ > **BUILT 2026-07-03** as grammar-spec-batching-integration.md Phase B —
328
+ > `src/spec/{source,two-model,serve-loop}.ts`, flags in cli.ts, `hasDraft`
329
+ > routing, fit admission, `usage.speculation` telemetry. **L1 gate passed:
330
+ > 48/48 token-for-token vs mlx_lm's speculative path** (Llama 3B+1B,
331
+ > `scripts/oracle-spec-two-model.py`, `tests/spec-serve.test.ts`). Grammar
332
+ > composes (Phase C constrained verify walk). v1 deviations: prompt-cache
333
+ > reuse bypassed on the spec path; ring-wrap degrades to plain decode
334
+ > (pre-pollution gate) instead of raising. §7.9's assistant/DSpark sources
335
+ > remain open behind the same `DraftSource` seam.
336
+
337
+ This was the concrete, hand-to-an-implementer plan (kept for the record).
328
338
 
329
339
  ### What mlx_lm.server does (read: `server.py`, `generate.py`)
330
340
 
@@ -0,0 +1,98 @@
1
+ # oMLX adoption map — every feature, where it slots, status
2
+
3
+ Status: LIVING (started 2026-07-02). oMLX (github.com/jundot/omlx, Apache
4
+ 2.0) is the systematic port source: read its actual implementation before
5
+ building (full source ships locally in
6
+ `/Applications/oMLX.app/Contents/Resources/omlx/`), port the IDEA into our
7
+ architecture, and keep it only if OUR benchmarks improve (the burst-decode
8
+ refutation below is the cautionary tale). Attribution: note derived code in
9
+ THIRD_PARTY_LICENSES.md; idea ports get a source comment.
10
+
11
+ ## Where oMLX sits in the fidelity-tier model
12
+
13
+ It doesn't slot at L1 or L2 — it decomposes. The tiers are NUMERICS
14
+ contracts (parity-tier-dag.md); oMLX is a Python appliance ON mlx-lm:
15
+
16
+ - Its stock forwards are mlx-lm's numerics = our existing **L1 oracle**
17
+ (no new oracle value; mlx-lm itself stays the reference).
18
+ - Its own numeric inventions (oQ, custom kernels, DFlash, MTP) are
19
+ oracle-less = **L3-class** here, and get our L3 gating (KL/eval/envelope
20
+ + kill switch) that they don't have upstream.
21
+ - Its product surface (SSD cache, EnginePool, menu bar, MCP, grammar) is
22
+ **tier-agnostic serving layer** — keyed by the effective tier/scheme,
23
+ never part of the decode-numerics DAG.
24
+
25
+ ## Scoreboard
26
+
27
+ ### Ported, kept (benchmarked wins)
28
+ | Feature | Theirs | Ours | Result |
29
+ | --- | --- | --- | --- |
30
+ | Continuous batching parity | scheduler on mlx-lm BatchGenerator | `--batch N` lane + P5 SSM port + per-row logits processors (2026-07-02); since: grammar under batching, `--kv-budget` admission, Tier-0 universal (Llama) per-row RoPE (2026-07-03) | cpm5 345 vs 339 (win), e4b −3%, Qwen3.5 −1%, TTFT 2–3× better (batching-perf-path.md) |
31
+ | SSD KV cold tier | `cache/paged_ssd_cache.py` (content-hashed blocks) | `src/ssd-cache.ts` + kv-store v2 (whole-entry spill, zero-copy mmap) | restart TTFT 12.1s→0.24s vs their 1–3s; 0% decode overhead vs their ~20% (ssd-kv-cold-tier.md) |
32
+
33
+ ### Ported, refuted (do not re-add without new evidence)
34
+ | Feature | Why it doesn't transfer |
35
+ | --- | --- |
36
+ | Adaptive burst decode (`engine_core.py _step_burst`) | Amortizes Python GIL/asyncio ping-pong (~1ms/token). Bun has no GIL; faithful port REGRESSED cpm5 B=4 345→289 + TTFT +100ms. Reverted with breadcrumb (batching-perf-path.md P4). |
37
+
38
+ ### Queue (roughly by leverage; ★ = Josh explicitly wants)
39
+ 1. **Structured output / JSON-schema constrained decoding** (`api/grammar.py`)
40
+ — serving layer. **LANDED 2026-07-02, MERGED to main 2026-07-03** (branch
41
+ `feat/structured-output` deleted after merge):
42
+ `@mlc-ai/web-xgrammar` (WASM) on `/v1/chat/completions` + `/v1/completions`;
43
+ full `response_format`/`guided_*` surface, serial AND batched lanes;
44
+ L2-verified vs oMLX (byte-identical
45
+ content). See [docs/design/structured-output.md](structured-output.md);
46
+ follow-up debt sequenced in
47
+ [grammar-spec-batching-integration.md](grammar-spec-batching-integration.md).
48
+ 2. **★ Menu bar app** (`apps/omlx-mac/`, native SwiftUI + Sparkle) — product
49
+ layer. Their repo is the structural reference; our signed/notarized
50
+ single binary is the sidecar. Supersedes tauri-desktop-app.md's Electron
51
+ ambivalence: native SwiftUI is the received-well shape.
52
+ 3. **Multi-model serving / EnginePool** (`engine_pool.py`: LRU + pinning +
53
+ load/unload API) — serving layer. Fits our single-USER framing as model
54
+ SWITCHING (one active, LRU-evict the rest), not concurrent tenants.
55
+ 4. **oQ-style sensitivity-driven quantization** (`oq.py`,
56
+ docs/oQ_Quantization.md) — L3-class, lands in `convert` beside
57
+ `--target-bpw`: calibration-measured per-layer sensitivity, boosts on
58
+ non-expert tensors only, batched expert GPTQ. Gate: perplexity + 6-task
59
+ eval vs our knapsack at equal bpw. (arXiv-lens candidate.)
60
+ 5. **DFlash serving wiring** (`engine/dflash.py` + their published -DFlash
61
+ drafts) — L3-class; unblocks DSpark (docs/design/dspark-speculative-decoding.md).
62
+ Their integration pattern: separate engine class, ctx-length fallback
63
+ routing, tape-replay cache rollback. *Note: serve-time speculative
64
+ decoding itself LANDED 2026-07-03 (`serve --draft-model`, two-model
65
+ serial lane, 48/48 token-for-token vs mlx-lm's spec path —
66
+ grammar-spec-batching-integration.md Phase B); DFlash/DSpark would slot
67
+ behind the same `DraftSource` seam.*
68
+ 6. **Vision feature cache** (`cache/vision_feature_cache.py`) — serving
69
+ layer; encoder features for repeated-image agent turns. Natural sibling
70
+ of the SSD tier (same keying discipline).
71
+ 7. **MCP tool execution in serve** (`--mcp-config`, `mcp_routes.py`) —
72
+ serving layer. Note overlap: Pi already owns tool orchestration for our
73
+ UIs — scope this to bare-API consumers; don't duplicate Pi.
74
+ 8. **Prefill progress observability** (`prefill_progress.py`) — /stats +
75
+ web UI surfacing for long prompts. Pairs with the serial-lane hop fix.
76
+ 9. **Rerank endpoint** (`/v1/rerank`) — needs a reranker model family
77
+ first; park until a Qwen3-Reranker-class port is wanted.
78
+ 10. **Document ingestion (MarkItDown)** — product layer; ours would go
79
+ through the chat UI / Pi attachments, not the server.
80
+ 11. **Admin one-click benchmark with prefix-cache-hit testing** — fold into
81
+ bench-serving-load.ts as a `--cache-hit-ratio` mode instead of a UI.
82
+
83
+ ### Explicitly not porting
84
+ - **Audio (STT/TTS/STS)** — audio tower deferred (project scope).
85
+ - **Their SSE burst streaming** — side effect of the GIL workaround;
86
+ per-token streaming is strictly better UX at zero cost for us.
87
+ - **Python-side scheduler details** (GIL executors, collector reaping) —
88
+ runtime-specific.
89
+
90
+ ## Porting discipline (learned this week)
91
+ 1. Read their real source first (local app bundle) — the README lies less
92
+ than the code.
93
+ 2. Port the idea into OUR architecture; never transliterate Python.
94
+ 3. Benchmark before/after on THIS machine; keep only wins (burst decode
95
+ died here). Wall-clock metrics only — their own logs over-report.
96
+ 4. Numeric features get L3 gates + kill switches; serving features get
97
+ the effective-scheme keying; nothing touches L1/L2 contracts.
98
+ 5. Verify the served model id (/v1/models) before trusting any bench.
@@ -23,11 +23,11 @@ runs half the forwards per step.
23
23
 
24
24
  > Oracle note: neither `mlx_lm` nor `optiq` ships ORPO, so — unlike DPO
25
25
  > ([`loss.ts`](../../src/train/loss.ts) ported `optiq/lora/dpo.py`) — ORPO is
26
- > an **L3 mlx-bun original**. Correctness is anchored to a standalone
26
+ > a **no-oracle mlx-bun original (Lab-class)**. Correctness is anchored to a standalone
27
27
  > torch/numpy reference of the paper/TRL math (see [Parity](#parity)), not a
28
28
  > bit-exact upstream port.
29
29
 
30
- > **L3 verification (2026-06-21).** The flash-CCE head has no oracle either, so
30
+ > **No-oracle (Lab-gated) verification (2026-06-21).** The flash-CCE head has no oracle either, so
31
31
  > the "0.28% dh" figure is **fp-reassociation vs the full-logits *proxy*, not an
32
32
  > error** (two correct reductions in different orders can't be bit-identical).
33
33
  > Correctness is proven by **finite-difference** (`flash-fd-check.ts`) + the math
@@ -35,7 +35,7 @@ runs half the forwards per step.
35
35
  > (0.66→2.7%, from `flash-cce-parity.ts`'s random hidden) is its **worst case**:
36
36
  > random logits give a flat softmax, but real outputs are sharply peaked, so
37
37
  > filtering the ≈0-softmax tail is near-free — **measure teacher-forced on real
38
- > hiddens before judging** (it's plausibly a free speedup to enable). Standing L3
38
+ > hiddens before judging** (it's plausibly a free speedup to enable). Standing Lab
39
39
  > gates (filter-on-real-data, teacher-forced grad fidelity, end-to-end quality)
40
40
  > + the host-buffer pin-leak post-mortem:
41
41
  > [orpo-flash-cce-pin-leak.md](../investigations/orpo-flash-cce-pin-leak.md).
@@ -68,11 +68,13 @@ else validates against):**
68
68
  [`scripts/experiments/parity-orpo.ts`](../../scripts/experiments/parity-orpo.ts):
69
69
  10/10 checks pass — scalar loss, log1mexp stability, gradient checks (finite
70
70
  differences vs autograd) at B=1 and B=3, edge cases.
71
- - ✅ `fusedGegluDifferentiable` in
72
- [`src/model/fused-geglu-kernel.ts`](../../src/model/fused-geglu-kernel.ts):
73
- `CustomVjp`-backed version with hand-derived backward (grad\_b = dc·gelu(a),
74
- grad\_a = dc·b·gelu'(a)); tested in
75
- [`tests/fused-geglu-vjp.test.ts`](../../tests/fused-geglu-vjp.test.ts).
71
+ - ✅ `fusedGegluDifferentiable` in `src/model/fused-geglu-kernel.ts`
72
+ (**kernel DELETED 2026-07-05** in the Phase-1 deletion pass —
73
+ [unified-engine-frontier-plan.md §6](unified-engine-frontier-plan.md);
74
+ the compiled activations are the only geglu now, bit-exact, and training
75
+ needs no flag sanitization): `CustomVjp`-backed version with hand-derived
76
+ backward (grad\_b = dc·gelu(a), grad\_a = dc·b·gelu'(a)); was tested in
77
+ `tests/fused-geglu-vjp.test.ts` (deleted with it).
76
78
  - ✅ **Fused GeGLU wired into the e4b training path.** A training-mode flag
77
79
  (`setFusedGeluTraining`, set by the trainer around the loop, cleared in its
78
80
  finally) makes BOTH Gemma GeGLU sites — the MLP (`gelu(gate)·up`) and the
@@ -92,10 +94,11 @@ else validates against):**
92
94
  to the closed form at tol 0.05 in the vjp test). Composes with the segmented
93
95
  path (the fused `CustomVjp` nests inside the segmented `mlx_vjp`: segmented ORPO
94
96
  still PASSES with fused on, peak 11.25 GB full → 7.58 GB segmented). Unit guard
95
- ([`tests/fused-geglu-vjp.test.ts`](../../tests/fused-geglu-vjp.test.ts)): the
97
+ (`tests/fused-geglu-vjp.test.ts`, deleted with the kernel): the
96
98
  flag toggles and the differentiable forward bit-matches the plain kernel.
97
- Training still needs `MLX_BUN_PERF_KERNEL=0` (a separate no-vjp perf kernel —
98
- its own follow-on).
99
+ (Historical: training at the time also needed `MLX_BUN_PERF_KERNEL=0` — that
100
+ perf kernel was likewise deleted 2026-07-05, so no training flag sanitization
101
+ remains.)
99
102
  - ✅ `responseOnlyLogpMean` helper in `loss.ts` (post-finalNorm h → mean response
100
103
  logp; used by the ORPO head VJP).
101
104
  - ✅ `SegmentedBackwardOrpo` in
@@ -520,7 +523,7 @@ tier below it, bottoming out at the math reference above:
520
523
 
521
524
  ## Memory & speed optimization
522
525
 
523
- ORPO has no upstream bit-exact oracle (L3 original), which gives us latitude to
526
+ ORPO has no upstream bit-exact oracle (a no-oracle original, Lab-class), which gives us latitude to
524
527
  optimize the runtime aggressively as long as we stay self-consistent with a
525
528
  naïve reference (see [Parity](#parity)). This section is the result of a
526
529
  dedicated research spike across three areas — activation/segmentation memory,
@@ -568,7 +571,7 @@ GB** at this vocab scale. This **subsumes our current `responseOnlyCe` trick**
568
571
  LoRA-factored head backward (`∇A`, `∇B`) falls out per chunk — Apple's CCE
569
572
  has no LoRA path, so even adopting it would mean deriving this ourselves.
570
573
  Exact in forward and backward; no Metal kernel required. Reserve a
571
- `mx.fast.metal_kernel` flash-CCE as an **L3 speed tier** only if per-chunk
574
+ `mx.fast.metal_kernel` flash-CCE as a **Lab speed experiment** only if per-chunk
572
575
  logits are still too large in practice.
573
576
 
574
577
  > ✅ **LANDED + validated (2026-06-18) — `fusedLogpMeanB1` in
@@ -640,7 +643,7 @@ GB** at this vocab scale. This **subsumes our current `responseOnlyCe` trick**
640
643
  > token-chunks (which free fine). Forcing an eval mid-chain breaks autodiff (a
641
644
  > 35 GB blowup). **Conclusion:** the CCE residency bound (`[chunk,Vblock]` peak) is
642
645
  > unreachable in pure MLX; it requires a `mx.fast.metal_kernel` with the vocab loop
643
- > INSIDE the kernel (the L3 flash-CCE tier). The pure-MLX implementation is kept as
646
+ > INSIDE the kernel (the flash-CCE Lab kernel). The pure-MLX implementation is kept as
644
647
  > a **validated correctness reference** for that kernel and is **gated off** (not
645
648
  > wired to a trainer knob — it would only raise peak). So: token-chunking
646
649
  > (`orpo_chunk_size`) + the analytic fused head (`orpo_fused_ce`) are the usable
@@ -649,8 +652,8 @@ GB** at this vocab scale. This **subsumes our current `responseOnlyCe` trick**
649
652
  > 🔬 **flash-CCE Metal kernel — PROTOTYPED + working, scaling-limited at largest
650
653
  > vocab (2026-06-18).** [`src/train/flash-cce.ts`](../../src/train/flash-cce.ts):
651
654
  > `mx.fast.metal_kernel` forward (`logp` + `lse`) and backward (`dh`) that compute
652
- > logits in-kernel from the quantized head (in-Metal 4/8-bit affine dequant via the
653
- > qdot pattern from [`fused-decode-kernel.ts`](../../src/model/fused-decode-kernel.ts)),
655
+ > logits in-kernel from the quantized head (in-Metal 4/8-bit affine dequant via
656
+ > mlx's `quantized.h` qdot pattern — the deleted fused-decode kernel used the same),
654
657
  > online softmax across vocab, target capture, softcap + sech², so the vocab loop
655
658
  > lives INSIDE the kernel — neither `[M,V]` nor a dequantized `[V,hidden]` head
656
659
  > touches HBM. **Validated** ([`scripts/experiments/flash-cce-parity.ts`](../../scripts/experiments/flash-cce-parity.ts)):
@@ -710,8 +713,8 @@ GB** at this vocab scale. This **subsumes our current `responseOnlyCe` trick**
710
713
  > knob (the existing `orpo_chunk_size` stays the token-axis tier; the two
711
714
  > compose — token-chunk the M response rows, vocab-chunk each row's head). It
712
715
  > replaces the per-chunk `logitsFromHidden` (which materializes `[Cc, V]`) with
713
- > the tiled engine below, wrapped in a single `CustomVjp` (our
714
- > [`fused-geglu-kernel.ts`](../../src/model/fused-geglu-kernel.ts) pattern: a
716
+ > the tiled engine below, wrapped in a single `CustomVjp` (the pattern the
717
+ > since-deleted `fused-geglu-kernel.ts` established: a
715
718
  > `mx.custom_function` with a hand `.vjp`, validated against `mx.vjp`/autograd —
716
719
  > never a hand formula, per the standing rule).
717
720
  > - **Explicit VJP inputs** `(hC [M,hidden], baseW, scales, biases, [loraA, loraB
@@ -952,7 +955,7 @@ for custom fused Metal kernels. Both apply to **forward and backward**.
952
955
  already fused both directions. (Note: `sdpa` forward only — its backward is
953
956
  the custom-kernel target in Tier 1.)
954
957
 
955
- **Tier 1 — custom fused fwd+bwd Metal kernels (the cutting-edge; L3 originals):**
958
+ **Tier 1 — custom fused fwd+bwd Metal kernels (the cutting-edge; no-oracle originals, Lab-gated):**
956
959
  3. **Fused linear + log-softmax + gather loss (CCE/Liger-style) — THE moat.**
957
960
  Lever 1 of the memory plan as a single kernel: fuse the (quantized,
958
961
  vocab-chunked) head matmul + `logsumexp` + target gather so the `[T, 262k]`
@@ -968,14 +971,15 @@ for custom fused Metal kernels. Both apply to **forward and backward**.
968
971
  memory instead of today's O(L²). High value because it removes the term
969
972
  segmentation is currently compensating for. (FA3's extra tricks are
970
973
  Hopper-specific; mirror FA2 on Metal.)
971
- 5. **Fused GeGLU/SwiGLU with a vjp.** We already have a fused-GeGLU *forward*
972
- kernel ([`fused-geglu-kernel.ts`](../../src/model/fused-geglu-kernel.ts))
973
- **disabled in training because it has no gradient** (the
974
- `MLX_BUN_FUSED_GELU=0` constraint). The backward math is known (Liger): for
974
+ 5. **Fused GeGLU/SwiGLU with a vjp.** (The custom fused-GeGLU/SwiGLU kernels
975
+ were **DELETED 2026-07-05** — compiled activations own the fusion win
976
+ bit-exactly; see
977
+ [unified-engine-frontier-plan.md §6](unified-engine-frontier-plan.md). Kept
978
+ here as the backward-math record.) The backward math is known (Liger): for
975
979
  `c = act(a)⊙b`, save `a`,`b`, recompute the activation — `db = dc·act(a)`,
976
- `da = dc·b·act'(a)`. Writing this `.vjp` re-enables the kernel in training —
977
- doubly valuable since the MLP intermediate is our **dominant resident tensor**
978
- (the intra-layer split target, lever 4).
980
+ `da = dc·b·act'(a)`. This `.vjp` was written and validated before the
981
+ deletion; the memory angle still matters since the MLP intermediate is our
982
+ **dominant resident tensor** (the intra-layer split target, lever 4).
979
983
  6. **Fused dequant-GEMM + LoRA epilogue (+ manual backward).** MLX's
980
984
  `quantized_matmul` already fuses dequant into the *base* GEMM; the missing
981
985
  piece is folding the LoRA epilogue `scale·(x@A)@B` into that kernel and a
@@ -1021,9 +1025,9 @@ Metal path**. Two openings stand out:
1021
1025
  checkpointing / segmented backward. Other gaps nobody fills: cached DPO
1022
1026
  reference log-probs, a concatenated/prefix-shared chosen+rejected forward.
1023
1027
 
1024
- These fused kernels are **L3 originals** (mlx-bun's own tier) — validated
1028
+ These fused kernels are **no-oracle originals (Lab-gated)** — validated
1025
1029
  against the naïve path via the [oracle ladder](#parity), consistent with the
1026
- three-level fidelity model.
1030
+ fidelity model (external-oracle tiers L1/L2 plus the no-oracle Lab class).
1027
1031
 
1028
1032
  ## Model coverage, quantization, batching, dropout
1029
1033
 
@@ -1181,8 +1185,8 @@ Runbook (the generate/train steps are yours to run — they're long / download):
1181
1185
  2. **Baseline:** `MODEL=<e4b> bun scripts/run-ifeval.ts ifeval.jsonl` → record.
1182
1186
  3. **Fit check:** `MODEL=<e4b> CHUNK=512 bun scripts/bench-orpo.ts` to confirm
1183
1187
  the config fits 24 GB (e4b's 262k vocab makes the chunked head matter).
1184
- 4. **Train ORPO** (e4b is Gemma → set the no-vjp kernels off):
1185
- `MLX_BUN_FUSED_GELU=0 MLX_BUN_PERF_KERNEL=0` + `method:"orpo"`,
1188
+ 4. **Train ORPO** (no env-flag sanitization needed — the no-vjp kernels were
1189
+ deleted 2026-07-05): `method:"orpo"`,
1186
1190
  `learning_rate 1e-5`, `orpo_lambda 0.1`, `orpo_chunk_size 512`,
1187
1191
  `max_seq_length ~1024`, `rank 16`. ⚠️ Segmented backward for ORPO is
1188
1192
  **MiniCPM5-only** today (`SegmentedBackwardOrpoGemma4` is a follow-on), so