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.
- package/README.md +66 -21
- package/THIRD_PARTY_LICENSES.md +1 -0
- package/docs/archive/mlx-bun-lab-report.html +275 -0
- package/docs/archive/wwdc-mlx-bun.html +848 -0
- package/docs/design/audio-input-plan.md +356 -0
- package/docs/design/batching-perf-path.md +255 -0
- package/docs/design/batching-v2-plan.md +23 -9
- package/docs/design/bucketing-stage.md +2 -1
- package/docs/design/compat-cli-surface-design.md +4 -3
- package/docs/design/decode-speed-program.md +172 -0
- package/docs/design/docs-reorg-plan.md +1 -1
- package/docs/design/dspark-serving-program.md +371 -0
- package/docs/design/dspark-speculative-decoding.md +167 -5
- package/docs/design/faithful-l1-consolidation.md +310 -0
- package/docs/design/generic-model-support.md +8 -5
- package/docs/design/grammar-spec-batching-integration.md +301 -0
- package/docs/design/minicpm5-decode-megakernel.md +16 -11
- package/docs/design/mlx-lm-tool-parity-plan.md +11 -1
- package/docs/design/omlx-adoption-map.md +98 -0
- package/docs/design/orpo-training.md +34 -30
- package/docs/design/paged-kv-cache.md +214 -0
- package/docs/design/parallel-slots.md +82 -49
- package/docs/design/parity-tier-dag.md +43 -33
- package/docs/design/repo-cleanup-plan.md +189 -0
- package/docs/design/runtime-isolation.md +19 -3
- package/docs/design/segmented-backward-training.md +18 -2
- package/docs/design/ssd-kv-cold-tier.md +288 -0
- package/docs/design/structured-output.md +484 -0
- package/docs/design/the-dreaming-master-plan.md +2 -3
- package/docs/design/turboquant-kv.md +235 -0
- package/docs/design/unified-engine-frontier-plan.md +774 -0
- package/docs/design/web-chat-beat-matrix.md +315 -0
- package/docs/design/web-chat-redesign.md +1112 -0
- package/docs/investigations/decode-roofline-lookagain.md +92 -6
- package/docs/investigations/dspark-handoff.md +182 -16
- package/docs/investigations/kernel-perf-review-2026-07.md +105 -26
- package/docs/investigations/lab-build-journal.md +2 -2
- package/docs/investigations/openwiki-evaluation.md +249 -0
- package/docs/planning/IDEAS.md +14 -0
- package/docs/planning/PRODUCT_ROADMAP.md +2 -3
- package/docs/planning/chat-ui-vision-memory-first.md +199 -0
- package/docs/planning/chat-ui-vision.md +50 -0
- package/docs/planning/memory-docs-and-dag-plan.md +15 -6
- package/docs/planning/release-notes-v0.0.10.md +57 -0
- package/docs/planning/release-notes-v0.0.11.md +239 -0
- package/docs/reference/cli.md +35 -16
- package/docs/reference/distribution.md +13 -6
- package/docs/reference/embedding.md +35 -14
- package/docs/reference/features-matrix.md +97 -0
- package/docs/reference/library-api.md +30 -9
- package/docs/reference/memory.md +26 -6
- package/docs/reference/models.md +11 -1
- package/docs/reference/orpo-quickstart.md +3 -4
- package/docs/reference/server-api.md +634 -22
- package/docs/reference/server-config.md +251 -119
- package/docs/reference/training.md +20 -16
- package/package.json +5 -3
- package/src/anthropic.ts +13 -0
- package/src/audio/conformer.ts +647 -0
- package/src/audio/decode.ts +133 -0
- package/src/audio/features.ts +202 -0
- package/src/audio/transcode.ts +67 -0
- package/src/cli.ts +402 -107
- package/src/config.ts +49 -2
- package/src/download.ts +40 -0
- package/src/eval/README.md +7 -6
- package/src/eval/kl.ts +59 -3
- package/src/eval/quality-db.ts +2 -2
- package/src/eval/runner.ts +3 -3
- package/src/expert-offload.ts +15 -2
- package/src/flags.ts +13 -0
- package/src/generate.ts +330 -16
- package/src/grammar.ts +585 -0
- package/src/hub-rest.ts +242 -0
- package/src/kv-store.ts +618 -122
- package/src/lora.ts +8 -0
- package/src/md-modules.d.ts +42 -0
- package/src/media-fetch.ts +284 -0
- package/src/memory/rest.ts +245 -0
- package/src/memory/vault.ts +74 -0
- package/src/mlx/array.ts +127 -19
- package/src/mlx/autograd.ts +2 -2
- package/src/mlx/ffi.ts +27 -3
- package/src/mlx/metal-kernel.ts +7 -4
- package/src/mlx/ops.ts +179 -23
- package/src/mlx/turboquant-ops.ts +831 -0
- package/src/mlx/turboquant-tables.ts +237 -0
- package/src/model/batched-mask.ts +62 -1
- package/src/model/batched-quant.ts +206 -0
- package/src/model/batched-rotating-quant.ts +346 -0
- package/src/model/batched-rotating.ts +46 -1
- package/src/model/compiled-decode.ts +4 -15
- package/src/model/factory.ts +10 -4
- package/src/model/gemma4-base.ts +391 -34
- package/src/model/gemma4.ts +122 -33
- package/src/model/generated/gemma4-12b.ts +9 -13
- package/src/model/generated/gemma4-26b.ts +10 -5
- package/src/model/generated/gemma4-e4b.ts +14 -9
- package/src/model/minicpm5.ts +58 -102
- package/src/model/paged-kv.ts +253 -0
- package/src/model/qwen3-delta.ts +123 -15
- package/src/model/qwen3-moe.ts +404 -0
- package/src/model/qwen3.ts +9 -8
- package/src/model/qwen3_5.ts +99 -25
- package/src/model/support.ts +12 -1
- package/src/model/universal/dense.ts +40 -5
- package/src/model/universal/rope.ts +36 -21
- package/src/pi-web.ts +1299 -82
- package/src/preflight.ts +27 -8
- package/src/prompt-cache.ts +338 -32
- package/src/quantize/quantizer.ts +16 -1
- package/src/registry.ts +61 -3
- package/src/serve/batch-scheduler.ts +1001 -76
- package/src/serve/generation-gateway.ts +292 -30
- package/src/serve/isolate.ts +417 -0
- package/src/serve/lane-registry.ts +44 -0
- package/src/serve/session-search.ts +326 -0
- package/src/server.ts +1460 -105
- package/src/spec/assistant-source.ts +150 -0
- package/src/spec/deepspec-source.ts +143 -0
- package/src/spec/dflash-source.ts +124 -0
- package/src/spec/dspark/ab-stats.ts +136 -0
- package/src/spec/dspark/calibration.ts +112 -0
- package/src/spec/dspark/deepspec-module.ts +801 -0
- package/src/spec/dspark/generate-dflash.ts +72 -20
- package/src/spec/dspark/loader.ts +23 -0
- package/src/spec/dspark/module-dflash.ts +278 -36
- package/src/spec/dspark/quantize-drafter.ts +91 -0
- package/src/spec/generate.ts +59 -25
- package/src/spec/ngram-source.ts +132 -0
- package/src/spec/serve-loop.ts +536 -0
- package/src/spec/source.ts +93 -0
- package/src/spec/two-model.ts +171 -0
- package/src/ssd-cache.ts +285 -0
- package/src/tokenizer.ts +288 -2
- package/src/tool-approvals.ts +131 -0
- package/src/tool-call.ts +408 -20
- package/src/train/flash-cce.ts +29 -26
- package/src/train/job.ts +0 -2
- package/src/train/loss.ts +53 -11
- package/src/train/prefix-shared.ts +30 -7
- package/src/train/segmented.ts +150 -46
- package/src/train/trainer.ts +22 -15
- package/src/vision/prompt.ts +217 -48
- package/src/web/app.html +1184 -1789
- package/src/web/app.js +5667 -0
- package/src/web/icon.svg +13 -0
- package/src/web/manifest.webmanifest +24 -0
- package/src/web/src/adapters-panel.ts +288 -0
- package/src/web/src/api.ts +57 -0
- package/src/web/src/assistant.ts +485 -0
- package/src/web/src/chat.ts +1050 -0
- package/src/web/src/composer.ts +959 -0
- package/src/web/src/dataset.ts +132 -0
- package/src/web/src/finetune.ts +267 -0
- package/src/web/src/hub.ts +428 -0
- package/src/web/src/main.ts +98 -0
- package/src/web/src/markdown.ts +0 -0
- package/src/web/src/memory-panel.ts +572 -0
- package/src/web/src/model-picker.ts +159 -0
- package/src/web/src/palette.ts +346 -0
- package/src/web/src/protocol.ts +62 -0
- package/src/web/src/quantize.ts +167 -0
- package/src/web/src/rag.ts +274 -0
- package/src/web/src/sessions.ts +469 -0
- package/src/web/src/shell.ts +804 -0
- package/src/web/src/status.ts +252 -0
- package/src/web/src/ui-catalog.ts +92 -0
- package/src/web/sw.js +71 -0
- package/src/web/vendor/README.md +61 -0
- package/src/web/vendor/hljs-LICENSE +29 -0
- package/src/web/vendor/hljs-theme.css +59 -0
- package/src/web/vendor/hljs.js +26 -0
- package/src/model/fused-decode-kernel.ts +0 -312
- package/src/model/fused-geglu-kernel.ts +0 -198
- package/src/model/fused-mlp-kernel.ts +0 -169
- package/src/model/fused-swiglu-kernel.ts +0 -240
- package/src/model/steel-linear-kernel.ts +0 -96
- 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`
|
|
19
|
-
|
|
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
|
|
52
|
-
NOT be bit-exact, gate by teacher-forced agreement + KL
|
|
53
|
-
`tests/perf-kernel-oracle.test.ts`,
|
|
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
|
-
(
|
|
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
|
|
408
|
-
`tests/perf-kernel-oracle.test.ts`
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
>
|
|
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
|
-
> **
|
|
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
|
|
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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
-
(
|
|
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
|
-
|
|
98
|
-
|
|
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 (
|
|
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
|
|
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
|
|
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
|
|
653
|
-
> qdot pattern
|
|
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` (
|
|
714
|
-
>
|
|
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;
|
|
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.**
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
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)`.
|
|
977
|
-
|
|
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 **
|
|
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
|
-
|
|
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** (
|
|
1185
|
-
|
|
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
|