mlx-bun 0.0.10 → 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 +58 -35
- 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 +14 -6
- 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 +3 -2
- package/docs/design/ssd-kv-cold-tier.md +130 -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/dspark-handoff.md +182 -16
- 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.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 +250 -121
- package/docs/reference/training.md +14 -15
- 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 +356 -105
- 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 -24
- package/src/grammar.ts +585 -0
- package/src/hub-rest.ts +242 -0
- package/src/kv-store.ts +442 -88
- 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 +106 -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 +153 -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 +363 -39
- 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 +76 -15
- package/src/model/qwen3-moe.ts +404 -0
- package/src/model/qwen3.ts +9 -8
- package/src/model/qwen3_5.ts +88 -23
- 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 +293 -33
- package/src/quantize/quantizer.ts +16 -1
- package/src/registry.ts +57 -2
- package/src/serve/batch-scheduler.ts +965 -72
- package/src/serve/generation-gateway.ts +214 -11
- 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 +1380 -135
- 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 +107 -35
- 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 +2 -2
- package/src/train/job.ts +0 -2
- package/src/train/trainer.ts +1 -4
- 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
|
@@ -78,7 +78,55 @@ Also confirmed: width is NOT the ceiling (`dDraft=1024` and `2560` both plateau
|
|
|
78
78
|
~0.17 pre-scale); the parity-safe `hiddenTap` doesn't change target numerics;
|
|
79
79
|
long-context spec rollback works via the `trim(n, bypass)` physical-slice.
|
|
80
80
|
|
|
81
|
+
## Serve integration (Phase 1, 2026-07-06)
|
|
82
|
+
|
|
83
|
+
DSpark is now serve-loadable behind the same `--draft-model` seam as every
|
|
84
|
+
other drafter — one verify/accept executor, no lane routing. The seam
|
|
85
|
+
(`src/spec/source.ts`) was extended from its two-model-only shape to carry the
|
|
86
|
+
target state KV-borrowing drafters need (it previously could NOT host these,
|
|
87
|
+
despite the header claiming so):
|
|
88
|
+
|
|
89
|
+
- `DraftProvider.open({sampler, target:{model,caches}})` — the source gets the
|
|
90
|
+
target model + its live caches.
|
|
91
|
+
- `DraftSource.tapLayers?` — when set (DSpark), the serve loop taps the
|
|
92
|
+
target's prefill + every verify forward and hands the captured `[1,L,m*H]`
|
|
93
|
+
context to `prefill(ids, ctxML)` / `commit(n, kAccept, vCtxML)` (ownership
|
|
94
|
+
transfers). `draft(feed, n, stepBase, anchorHidden?)` — the assistant borrows
|
|
95
|
+
the target anchor hidden; DSpark drafts from its growing `H_ctx`.
|
|
96
|
+
|
|
97
|
+
Provider selection is by artifact kind (`src/server.ts` `detectDraftKind`):
|
|
98
|
+
`dspark.json` → **DflashProvider** (`src/spec/dflash-source.ts`), a `*_assistant`
|
|
99
|
+
config → **AssistantProvider** (`src/spec/assistant-source.ts`, the optiq
|
|
100
|
+
KV-borrowing Gemma drafter — ships a real ~1.09× γ=1 decode win with no
|
|
101
|
+
training), else **TwoModelProvider**. `--draft-kind` overrides. The server pins
|
|
102
|
+
`--num-draft-tokens` to the DSpark checkpoint's trained block width
|
|
103
|
+
(`cfg.gamma`) so the serve loop never asks for more positions than were trained.
|
|
104
|
+
|
|
105
|
+
**Unified-engine alignment (per-slot readiness).** This is the
|
|
106
|
+
`DraftSource` seam the unified-engine plan names as the substrate for its
|
|
107
|
+
frontier "per-slot drafting behind the `DraftSource` seam" row
|
|
108
|
+
([docs/design/unified-engine-frontier-plan.md](unified-engine-frontier-plan.md)).
|
|
109
|
+
Spec still forces the serial lane today (upstream `is_batchable = draft is
|
|
110
|
+
None`); Phase 1 did NOT change that — it made the seam *carry per-source target
|
|
111
|
+
state* (each source binds its own `TargetView{model,caches}`, anchor hidden,
|
|
112
|
+
and tapped H_ctx), which is exactly what a batched executor would need to open
|
|
113
|
+
one `DraftSource` per row. So the seam is forward-compatible with per-slot
|
|
114
|
+
drafting; the hard part that remains is the executor, not the seam — variable
|
|
115
|
+
accept-length per slot breaks a uniform-B step (see
|
|
116
|
+
[spec-decode-larger-targets.md](spec-decode-larger-targets.md) caveats), so
|
|
117
|
+
per-slot spec is a real batched-scheduler project, not free composition.
|
|
118
|
+
|
|
119
|
+
**v1 acceptance caveat:** the serve loop verifies with TOKEN-MATCH acceptance
|
|
120
|
+
(mlx-lm/optiq style, lossless at any temperature), NOT the paper's
|
|
121
|
+
distribution-level rejection-sampling verify. Greedy is identical; temp>0 is
|
|
122
|
+
still lossless but lower-acceptance. The richer rejection-sampling verify stays
|
|
123
|
+
in the standalone `generate-dflash.ts` (the measure script). See
|
|
124
|
+
[[dspark-seam-kv-borrowing]].
|
|
125
|
+
|
|
81
126
|
## Files
|
|
127
|
+
- `src/spec/source.ts` — the extended DraftSource/DraftProvider/TargetView seam.
|
|
128
|
+
- `src/spec/assistant-source.ts` / `dflash-source.ts` — the two KV-borrowing providers.
|
|
129
|
+
- `src/spec/serve-loop.ts` — the shared verify/accept executor (now tap-aware).
|
|
82
130
|
- `src/model/gemma4.ts` — `hiddenTap`/`captureLayer` (parity-safe multi-layer tap).
|
|
83
131
|
- `src/model/gemma4-base.ts` — `trim(n, bypass)` on rotating caches (spec rollback past the window).
|
|
84
132
|
- `src/spec/dspark/module-dflash.ts` — faithful DFlash+Markov+confidence module.
|
|
@@ -96,11 +144,125 @@ bun scripts/dspark-train-dflash.ts --data <data> --out <ckpt> --iters 8000 --bat
|
|
|
96
144
|
bun scripts/dspark-measure-dflash.ts --drafter <ckpt> --data <prompts.jsonl> # τ + tok/s vs vanilla
|
|
97
145
|
```
|
|
98
146
|
|
|
147
|
+
## Paper components — Phase 2 (2026-07-06): the paper is CODE-COMPLETE
|
|
148
|
+
|
|
149
|
+
All remaining components landed the same day (multi-agent build, adversarially
|
|
150
|
+
reviewed; smoke 21/21, dspark test files 17/17, real-weights serve gate 3/3):
|
|
151
|
+
|
|
152
|
+
- **Alg 1, single-user form — confidence-scheduled draft-length pruning.**
|
|
153
|
+
`forwardInfer` drops position k (and truncates the block) when
|
|
154
|
+
`c_k < thresholds[k] ?? minConf`; position 0 always survives. Activation is
|
|
155
|
+
checkpoint-driven: STS thresholds in `dspark.json` (`cfg.sts`) or the
|
|
156
|
+
`MLX_BUN_DSPARK_MINCONF` env override — **uncalibrated checkpoints draft
|
|
157
|
+
fixed-γ exactly as before** (zero behavior change). Losslessness invariant:
|
|
158
|
+
pruning changes how many positions the target verifies, never what's emitted
|
|
159
|
+
(gated: truncation-never-redraw, prefix-identical to the unpruned block).
|
|
160
|
+
- **Variable-length draft contract.** `DraftSource.draft()` returns 1..n —
|
|
161
|
+
the RETURN LENGTH is authoritative; the serve loop and `dflashGenerate`
|
|
162
|
+
verify/trim/re-feed over `d = drafts.length`. Two-model/assistant return
|
|
163
|
+
exactly n (unchanged).
|
|
164
|
+
- **STS calibration (§3.2.1).** `src/spec/dspark/calibration.ts`
|
|
165
|
+
(`fitStsThresholds`: per position, smallest τ whose Laplace-smoothed
|
|
166
|
+
`P(accepted | conf ≥ τ)` ≥ target; pos 0 → 0, under-sampled → 0 (don't
|
|
167
|
+
prune on thin evidence), unreachable target → 1.0) +
|
|
168
|
+
`scripts/dspark-calibrate.ts` (GPU: greedy unpruned rounds via
|
|
169
|
+
`dflashGenerate`'s `onRound` hook → fit → patch `config.sts` into
|
|
170
|
+
dspark.json in place). ⚠ estimator shape is our reading — paper PDF absent.
|
|
171
|
+
- **RNN sequential head (Eq 6 — ⚠ design-doc-faithful shape, paper PDF
|
|
172
|
+
absent, flagged in code).** `cfg.seqHead: "markov"|"rnn"`; Elman recurrence
|
|
173
|
+
`s_k = tanh(s_{k-1}·wH + E[x_{k-1}] + bH)`, `B_k = s_k·wO`, `wO` zero-init
|
|
174
|
+
(starts as pure DFlash, like markov.w2); shares `markov.w1` as the token
|
|
175
|
+
embedding (also feeds the confidence head). Same loss, autograd-gated;
|
|
176
|
+
init-equivalence gate: rnn ≡ markov token-for-token at init.
|
|
177
|
+
Train with `scripts/dspark-train-dflash.ts --seq-head rnn`.
|
|
178
|
+
- **Draft loop TIGHTENED** (handoff item 3): greedy tokens chain ON-DEVICE
|
|
179
|
+
(one concat + one host read after the loop, was γ syncs); confidence reads
|
|
180
|
+
deferred+batched when pruning is inactive (per-position only when Alg 1
|
|
181
|
+
needs the answer mid-loop — inherent); `collectLogits: false` on the serve
|
|
182
|
+
path skips the per-position draft-logits materialization entirely
|
|
183
|
+
(`draftLogits` now optional). Gate: bit-identical to the pre-tightening
|
|
184
|
+
loop (pinned reference tokens, tests/dspark-infer-loop.test.ts).
|
|
185
|
+
- **Rename + central loader.** Canonical variant is now `"dspark"`
|
|
186
|
+
(`save()` stamps it; `load()` accepts legacy `"dflash"`);
|
|
187
|
+
`src/spec/dspark/loader.ts` `loadDsparkDrafter()` dispatches by variant and
|
|
188
|
+
refuses v1 single-vector checkpoints with a pointer at the v2 trainer. The
|
|
189
|
+
serve path and the bench (`bench-feature-matrix.ts` via the server's
|
|
190
|
+
exported `detectDraftKind`) both go through it.
|
|
191
|
+
|
|
192
|
+
## DeepSpec ground truth (2026-07-06 audit — paper arXiv:2607.05147 + source)
|
|
193
|
+
|
|
194
|
+
DeepSeek open-sourced the reference implementation (**DeepSpec**, MIT,
|
|
195
|
+
github.com/deepseek-ai/DeepSpec) + trained drafters incl.
|
|
196
|
+
**`deepseek-ai/dspark_gemma4_12b_block7`** (for google/gemma-4-12B-it — our
|
|
197
|
+
primary target; 6.86 GB bf16 single safetensors, γ=7). Two audit agents
|
|
198
|
+
verified our build against the paper AND the source. Verdicts:
|
|
199
|
+
|
|
200
|
+
- **MATCH the paper exactly:** Eq 2-3 (context + KV injection), the
|
|
201
|
+
anchor++masks block, Eq 5 Markov (r=256), Eq 7 confidence (we add a
|
|
202
|
+
zero-init bias — superset), **Eq 8-12 loss to the letter** (0.1/0.9/1.0,
|
|
203
|
+
w_k=exp(−(k−1)/γ), analytic c*). Our defaults = their production config
|
|
204
|
+
(γ=5, 5 layers, Markov head).
|
|
205
|
+
- **Eq 6 RNN differs:** paper is a gated cell over
|
|
206
|
+
`z_k=[s_{k−1}; W₁[x_{k−1}]; h_k]`, fused (2r+d)→3r projection,
|
|
207
|
+
`B_k=W₂ᵀtanh(W_o z_k)` (conditioned on the backbone hidden). Ours is
|
|
208
|
+
ungated Elman without h_k — kept as variant `"rnn"` (paper calls the gains
|
|
209
|
+
"marginal"; production uses Markov); DeepSpec also ships an RNNHead of the
|
|
210
|
+
gated form if we ever want the faithful one.
|
|
211
|
+
- **§3.2.1 STS + Alg 1: the paper's machinery is NOT in the released code.**
|
|
212
|
+
STS = Sequential Temperature Scaling (per-position temperatures by ECE grid
|
|
213
|
+
search on cumulative survival products); Alg 1 = expected-throughput
|
|
214
|
+
maximization over survival products with a profiled SPS(B) cost table —
|
|
215
|
+
both live in their UNRELEASED production serving layer. The released
|
|
216
|
+
reference ships **per-position threshold truncation on sigmoid confidence
|
|
217
|
+
(default 0 = off), ℓ=0 allowed** — i.e. the released reference is shaped
|
|
218
|
+
like OUR scheduler. Our threshold fit (`calibration.ts`) is therefore a
|
|
219
|
+
reasonable calibrator for the reference-shaped scheduler, NOT the paper's
|
|
220
|
+
STS; renamed framing in code comments. Paper-faithful STS/Alg-1 = a future
|
|
221
|
+
Lab item if the survival-product scheduler ever earns a default.
|
|
222
|
+
- **The reference drafter is a DIFFERENT module than ours:** a full
|
|
223
|
+
Gemma4-shaped 5-layer transformer — hidden 3840, 16 Q heads × head_dim 512,
|
|
224
|
+
**1 KV head with K≡V (`attention_k_eq_v`, no v_proj tensor; scale-less
|
|
225
|
+
v_norm)**, attention scale 1.0 (QK-norm), RoPE partial_rotary 0.25 θ=1e6,
|
|
226
|
+
per-layer learned `layer_scalar`, scaled embed (×√3840, not stored), UNTIED
|
|
227
|
+
lm_head, final softcap 30, and an **incremental context-KV cache** (stores
|
|
228
|
+
projected target-context K/V; crops the block's noise K/V each round).
|
|
229
|
+
Sequential Markov sampling + confidence head (input [h; w1[prev]] = 4096)
|
|
230
|
+
as in the paper. Checkpoint: 74 tensors, bf16, **no key prefix**;
|
|
231
|
+
config.json `architectures: ["Gemma4DSparkModel"]` (loader detection key).
|
|
232
|
+
**12B tap layers: `target_layer_ids [5,17,29,41,46]`** (their layer_id+1
|
|
233
|
+
output convention == our tapLayers index; no final-norm sentinel).
|
|
234
|
+
- **THE ORACLE:** their verify is leaky rejection sampling that at
|
|
235
|
+
temperature 0 **degenerates to exact argmax token-match — RNG-free,
|
|
236
|
+
token-for-token deterministic**. Their eval emits per-round
|
|
237
|
+
proposal/accepted/next-token traces. Protocol: run their eval at temp 0
|
|
238
|
+
(+ a threshold arm) on fixed prompts → JSONL round fixtures → our port
|
|
239
|
+
must match round-for-round. Same discipline as the D2 diffusion oracle,
|
|
240
|
+
minus RNG parity entirely.
|
|
241
|
+
|
|
242
|
+
**Consequence:** DSpark is no longer oracle-less L3. The port of their module
|
|
243
|
+
(the `deepspec` variant) + their checkpoint gives a bit-exact-gateable path to
|
|
244
|
+
a REAL 12B speedup with zero training. Our own trainable module remains the
|
|
245
|
+
research path for custom targets. Training recipe reference: open-perfectblend
|
|
246
|
+
regenerated by the target, 10 epochs, batch 512, lr 6e-4, position decay
|
|
247
|
+
exp(−pos/4), ~38 TB cached hiddens — i.e. our "thousands of generations" data
|
|
248
|
+
estimate was directionally right and their scale is far beyond a laptop run.
|
|
249
|
+
|
|
99
250
|
## Open items / next
|
|
100
|
-
|
|
101
|
-
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
251
|
+
|
|
252
|
+
- [x] Serve integration (Phase 1) · [x] Alg-1-shaped scheduler (matches the
|
|
253
|
+
RELEASED reference; paper Alg-1 unreleased) · [x] threshold calibration ·
|
|
254
|
+
[x] RNN head (Elman variant; paper's gated cell differs — documented above)
|
|
255
|
+
· [x] loop tightening · [x] `dspark` rename + loader —
|
|
256
|
+
**everything buildable without a GPU is DONE.**
|
|
257
|
+
- [ ] **DeepSpec-variant port** (`Gemma4DSparkModel`) + loader detection +
|
|
258
|
+
oracle scripts — the no-training path to the 12B speedup (in progress).
|
|
259
|
+
- [ ] **Josh-gated GPU (the payoff):** real data scale (thousands of
|
|
260
|
+
on-distribution generations) + **retarget to 12B** (`--model` +
|
|
261
|
+
`--tap-layers 5,17,29,41,46` (DeepSeek's trained taps); 27B memory-infeasible to train on 24 GB, kept
|
|
262
|
+
dim-generic) + train (`--seq-head` A/B optional) + **calibrate**
|
|
263
|
+
(`scripts/dspark-calibrate.ts`) + live-τ measure. Recipe:
|
|
264
|
+
[docs/investigations/dspark-handoff.md](../investigations/dspark-handoff.md).
|
|
265
|
+
- [ ] Verify the Eq 6 / §3.2.1 shapes against the actual paper when the PDF
|
|
266
|
+
is available (both flagged in code).
|
|
105
267
|
|
|
106
268
|
Full session handoff: `docs/investigations/dspark-handoff.md`.
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
# Faithful → L1: consolidating the "match mlx-lm" path
|
|
2
|
+
|
|
3
|
+
> **2026-07-05 addendum:** the Phase-1 deletion pass executed —
|
|
4
|
+
> fused-gelu/fused-swiglu/perf-kernel/fused-decode/CPM5_FAITHFUL deleted, `--l3`
|
|
5
|
+
> removed ([unified-engine-frontier-plan.md §6](unified-engine-frontier-plan.md)).
|
|
6
|
+
> The flag tables below are the historical decision record.
|
|
7
|
+
|
|
8
|
+
**Status:** PLANNED (2026-07-04). Written after the 2026-07-04 audit of the
|
|
9
|
+
`MLX_BUN_FAITHFUL` work (commit `fd8ff5f`). The faithful kernels are correct and
|
|
10
|
+
proven byte-identical to mlx-lm — but they were wired as a *parallel base-preset
|
|
11
|
+
axis* rather than folded into the L1 parity regime, which (a) abandons the
|
|
12
|
+
model-specific optimized files and (b) leaves the preset incomplete. This doc is
|
|
13
|
+
the contract for fixing that. Companion memory:
|
|
14
|
+
`faithful-is-the-l1-implementation`. Framing: [parity-tier-dag.md](parity-tier-dag.md),
|
|
15
|
+
`three-level-fidelity-tree-model` (memory).
|
|
16
|
+
|
|
17
|
+
## The governing framing (two orthogonal axes)
|
|
18
|
+
|
|
19
|
+
1. **Parity** — a *guarantee about which oracle you reproduce bit-for-bit*, full
|
|
20
|
+
logprobs (not just greedy argmax). **L1 = mlx-lm, L2 = mlx-optiq, L3 = no
|
|
21
|
+
oracle.** A config *is* L1 by definition iff its output matches mlx-lm bit-for-bit.
|
|
22
|
+
Nothing about parity concerns speed or methodology.
|
|
23
|
+
2. **Performance / implementation** — orthogonal. Within a regime there are many
|
|
24
|
+
valid implementations (faster/slower, more/less memory). For a given regime we
|
|
25
|
+
**default-serve the fastest implementation that still holds the guarantee**; a
|
|
26
|
+
user overrides individual kernels with extra flags (`--l1 --fused-geglu`).
|
|
27
|
+
|
|
28
|
+
**Where "faithful" fits:** it is *the fastest known implementation of L1* — it runs
|
|
29
|
+
mlx-lm's own exact kernels, so it's bit-exact, and per the 2026-07-03 h2h it is
|
|
30
|
+
faster than our previous L1 path (the old "perf" path was net-SLOWER than mlx-lm:
|
|
31
|
+
12B 0.88×, cpm5 0.95×, perf-kernel HURT e4b 0.62×). So faithful should become
|
|
32
|
+
**`--l1`'s default implementation**, not a new naked default and not a third
|
|
33
|
+
selection system.
|
|
34
|
+
|
|
35
|
+
**Naked `mlx-bun` (no flags) is unchanged:** best experience for local AI on a Mac —
|
|
36
|
+
fastest / most-intelligent / most-memory-efficient, no parity worries. The user who
|
|
37
|
+
needs the mlx-lm drop-in guarantee reads the docs and passes `--l1`.
|
|
38
|
+
|
|
39
|
+
> **SUPERSEDED 2026-07-05 — naked default is now `--l1`.** The full h2h pass
|
|
40
|
+
> (benchmarks-h2h-2026-07-05, M1 Max 32GB) showed the L1 faithful kernel set at
|
|
41
|
+
> exact speed parity with mlx-lm (1.00×) on cpm5/e4b/12B while every
|
|
42
|
+
> output-changing lever failed to beat it in a paired A/B: fused-decode 1.00×,
|
|
43
|
+
> fused-gelu +0–1%, the perf arm 0.62–0.93× on e4b (its one win, 12B @16k +6%,
|
|
44
|
+
> carried a KL WARN), and quantized KV 5–20% slower decode than bf16 at ≤16k on
|
|
45
|
+
> BOTH stacks (mlx-lm's own kv8 trails its bf16). "Best experience" and "L1"
|
|
46
|
+
> turned out to be the same thing today. Decision: naked = the l1 preset
|
|
47
|
+
> (`applyDecodeRoute` defaults the tier), quantized KV and every non-faithful
|
|
48
|
+
> kernel are opt-in (`--l2`/`--l3`/per-fork flags), and the L1 baseline is the
|
|
49
|
+
> base all future optimization must beat in a paired A/B to earn a default.
|
|
50
|
+
|
|
51
|
+
**`--l1/--l2/--l3` are pure ALIASES, not modes.** Each expands to a fixed set of the
|
|
52
|
+
individual per-fork flags — [`applyDecodeRoute`, cli.ts:752](../../src/cli.ts) just
|
|
53
|
+
`set()`s env vars, and a per-fork flag overrides the alias. Two invariants follow,
|
|
54
|
+
and they are the real design bar:
|
|
55
|
+
|
|
56
|
+
- **A user can reproduce any tier byte-for-byte by passing the individual flags.**
|
|
57
|
+
`--l1` is nothing but "the fastest parity-holding value of each flag."
|
|
58
|
+
- **No preset may set anything that isn't also an individual flag.** There is exactly
|
|
59
|
+
one bundle per tier; nothing bypasses the flag layer.
|
|
60
|
+
|
|
61
|
+
`MLX_BUN_FAITHFUL` violates both — it is a *second bundle* that sets choices
|
|
62
|
+
(compiled-geglu, `compiledLogitSoftcap`) which are **not exposed as flags at all**, so
|
|
63
|
+
`--l1` can neither reproduce nor reach them. Corollary: bit-exact-to-mlx-lm actually
|
|
64
|
+
*wants* compiled activations (mlx-lm itself `@mx.compile`s geglu/swiglu), so the
|
|
65
|
+
current `--l1` "unfused" description is backwards — **compiled is the L1 default**
|
|
66
|
+
(bit-exact + faster); unfused is a slower, same-parity opt-in.
|
|
67
|
+
|
|
68
|
+
## What is actually wrong today
|
|
69
|
+
|
|
70
|
+
Three overlapping "match mlx-lm" mechanisms exist and disagree:
|
|
71
|
+
|
|
72
|
+
- **Tier system** `--l1/--l2/--l3` ([`applyDecodeRoute`, cli.ts:752](../../src/cli.ts))
|
|
73
|
+
— the correct axis, but `--l1` keeps `compiled-decode` on, leaves
|
|
74
|
+
`MLX_BUN_FUSED_GELU` on (custom metal geglu, divergent), and does **not** route to
|
|
75
|
+
the faithful compiled-geglu. So today `--l1` is not actually bit-exact for gemma.
|
|
76
|
+
- **`MLX_BUN_FAITHFUL` preset** ([faithful.ts](../../src/faithful.ts)) — flips a
|
|
77
|
+
*different, smaller* set of defaults and **forces the gemma monolith**
|
|
78
|
+
([factory.ts:51](../../src/model/factory.ts)), abandoning the generated
|
|
79
|
+
`gemma4-12b/e4b/26b` specialization. It also leaves `MLX_BUN_PERF_KERNEL` on
|
|
80
|
+
(fires on every quantized-KV/OptiQ model), leaves kv-quant on, and never sets
|
|
81
|
+
`MLX_BUN_CPM5_FAITHFUL` — so `=1` is neither a real L1 guarantee on OptiQ models
|
|
82
|
+
nor faithful for MiniCPM5.
|
|
83
|
+
- **`Faithful*` subclasses** — `FaithfulMiniCPM5` (wired), and `FaithfulGemma4` /
|
|
84
|
+
`FaithfulQwen3` / `FaithfulQwen35` / `FaithfulUniversalDense` (**unwired** — their
|
|
85
|
+
headers claim factory dispatch that does not exist; `MLX_BUN_UNIVERSAL_FAITHFUL`
|
|
86
|
+
lives only in a comment).
|
|
87
|
+
|
|
88
|
+
Two of these violate stated principles: **forcing the monolith** runs an optimized
|
|
89
|
+
model through the generic path (rejected), and the **preset is incomplete**.
|
|
90
|
+
|
|
91
|
+
Only `qwen3_moe` did it right: the production class **is** the faithful port, so it's
|
|
92
|
+
bit-exact-by-construction with no flag. That is the pattern to generalize.
|
|
93
|
+
|
|
94
|
+
### Current-state flag reference
|
|
95
|
+
|
|
96
|
+
| Flag | Default | Diverges from mlx-lm? | Notes |
|
|
97
|
+
|---|---|---|---|
|
|
98
|
+
| `MLX_BUN_PERF_KERNEL` | **ON** | **yes** — original kernel, not bit-exact | fires only on quantized KV; output-changing → L3-only |
|
|
99
|
+
| kv-quant | **config** for OptiQ | uniform=mlx-lm has it; mixed=optiq-only | off (bf16)=L1; uniform 4\|8=L1 in the L1 set (unfused SDPA == mlx-lm base.py); config (mixed)=L2 |
|
|
100
|
+
| `MLX_BUN_COMPILED_DECODE` | ON | no (whole-decode fusion, bit-exact) | structural; off in faithful preset |
|
|
101
|
+
| `MLX_BUN_FUSED_GELU` | ON | **yes** — custom metal geglu ≠ compiled geglu | output-changing vs mlx-lm dispatch |
|
|
102
|
+
| `MLX_BUN_FUSED_DECODE` | off | yes | L>1 only |
|
|
103
|
+
| `MLX_BUN_NO_FUSED_SDPA` | fused **on** | no — `ops.sdpa` is mlx's own kernel | faithful-aligned |
|
|
104
|
+
| `MLX_BUN_COMPILED_SWIGLU` | ON | no — matches mlx-lm `@mx.compile` | faithful-aligned |
|
|
105
|
+
|
|
106
|
+
The genuinely output-changing, must-be-off-for-L1 levers: **perf-kernel, kv-quant,
|
|
107
|
+
custom fused-gelu, fused-decode.** Compiled activations and `ops.sdpa` are
|
|
108
|
+
faithful-aligned and should stay on.
|
|
109
|
+
|
|
110
|
+
**Fork coverage gap.** `applyDecodeRoute` exposes only four forks —
|
|
111
|
+
`--compiled-decode`, `--perf-kernel`, `--fused-decode`, `--fused-sdpa`. The
|
|
112
|
+
activation-fusion choice (compiled-geglu vs custom fused-gelu; compiled-swiglu /
|
|
113
|
+
`useCompiledActivation`) has **no CLI fork** and is not in the TIERS presets. So the
|
|
114
|
+
alias invariant is broken: `--l1` cannot select the faithful compiled activation, and
|
|
115
|
+
you cannot reproduce the faithful setup with individual flags — only the out-of-band
|
|
116
|
+
`MLX_BUN_FAITHFUL` reaches it. Closing this gap is the crux of the fix.
|
|
117
|
+
|
|
118
|
+
## The decisive rule: "compiled" = L1, custom "fused" = L3
|
|
119
|
+
|
|
120
|
+
The whole flag pile collapses to one distinction, verified in-source 2026-07-04:
|
|
121
|
+
|
|
122
|
+
- **"compiled" kernels** (`@mx.compile` geglu/swiglu, `compiled-decode`) go through
|
|
123
|
+
the **same libmlx** as mlx-lm → same graph → same kernel → **bit-exact**. These are
|
|
124
|
+
the faithful kernels. L1-valid AND faster (they remove per-op host tax).
|
|
125
|
+
- **custom "fused" Metal kernels** are mlx-bun ORIGINALS, each with a proven residual
|
|
126
|
+
vs mlx-lm → **NOT bit-exact → L3 only**:
|
|
127
|
+
- `fused-gelu-kernel.ts` header: *"Still a perf kernel (not bit-exact)… the only
|
|
128
|
+
residual is the pow/tanh math-lib difference"* (bit-exact vs our OWN unfused
|
|
129
|
+
path, kl=0 — but not vs mlx-lm's math lib).
|
|
130
|
+
- `fused-swiglu-kernel.ts`: MATCH variant *"Only residual: the metal exp() vs MLX's
|
|
131
|
+
math lib"*; F32 variant *"NOT bit-exact → L3"*. Default off.
|
|
132
|
+
- `fused-decode-kernel.ts` (`PERF_KERNEL`): online-softmax, envelope-gated, quant-KV
|
|
133
|
+
only. Not bit-exact by construction.
|
|
134
|
+
- **kv-quant** is a cache-precision axis, not a kernel, and it is NOT a clean
|
|
135
|
+
L1↔L2 boundary. **off (bf16) → L1**. **uniform `4|8` → L1** in the L1 kernel set
|
|
136
|
+
(`--l1 --kv-quant 8` = fused-sdpa off + perf off): mlx-bun's `quantizedSdpaUnfused`
|
|
137
|
+
(gemma4-base.ts) is **op-for-op identical** to mlx-lm's
|
|
138
|
+
`quantized_scaled_dot_product_attention` (`mlx_lm/models/base.py` — same
|
|
139
|
+
`mx.quantized_matmul` ×2 + `mx.softmax(precise=True)` + `where(…, finfo.min)` +
|
|
140
|
+
`queries *= scale`), and mlx-lm exposes exactly this scheme via `--kv-bits`. So
|
|
141
|
+
uniform-quant is **bit-exact L1 by construction** (a uniform-kv logit golden would
|
|
142
|
+
confirm the measured number; `bench.ts --baseline-kv 8` runs the stock-mlx-lm side
|
|
143
|
+
for the perf comparison). The FUSED / N-tiled prefill path (fused-sdpa ON — the
|
|
144
|
+
naked default and `--l2`) is the **optiq-aligned** one, not mlx-lm. **config
|
|
145
|
+
(per-layer mixed-precision) → L2** (optiq-only; mlx-lm has no per-layer analog).
|
|
146
|
+
|
|
147
|
+
**Decision table (serve decode flags):**
|
|
148
|
+
|
|
149
|
+
| Flag | Breaks mlx-lm parity? | L1 | L3 / naked | Relation to faithful |
|
|
150
|
+
|---|---|---|---|---|
|
|
151
|
+
| `MLX_BUN_COMPILED_SWIGLU` | no | ON | on | **is** faithful swiglu |
|
|
152
|
+
| compiled-geglu *(needs a fork)* | no | ON | on | **is** faithful geglu |
|
|
153
|
+
| `MLX_BUN_COMPILED_DECODE` | no (on==off) | ON | on | orthogonal free speed |
|
|
154
|
+
| `MLX_BUN_FUSED_SDPA` | no (`ops.sdpa`=mlx kernel) | on (bf16 no-op) | on | faithful-aligned; L2 lever |
|
|
155
|
+
| `MLX_BUN_FORCE_WIRE` / `EXPERT_OFFLOAD` | no (memory only) | user | user | orthogonal |
|
|
156
|
+
| `MLX_BUN_FUSED_GELU` | **yes** (pow/tanh) | **OFF** | if faster, KL-gated | competes w/ faithful geglu |
|
|
157
|
+
| `MLX_BUN_FUSED_SWIGLU`(`_F32`) | **yes** (exp/f32) | **OFF** | off (unproven) | competes w/ faithful swiglu |
|
|
158
|
+
| `MLX_BUN_PERF_KERNEL` | **yes** (online softmax) | **OFF** | on w/ kv-quant | unrelated original |
|
|
159
|
+
| `MLX_BUN_FUSED_DECODE` | **yes** | **OFF** | off | unrelated experiment |
|
|
160
|
+
| `--kv-quant` | off=L1; uniform `4\|8`=L1 in the L1 set (unfused SDPA==mlx-lm); config=L2 | **off (bf16)**, uniform ok | config | cache axis, not kernel (see kv note above) |
|
|
161
|
+
| `MLX_BUN_FAITHFUL` | — | **delete** | delete | illegal 2nd bundle → flags |
|
|
162
|
+
| `MLX_BUN_CPM5_FAITHFUL` | no | fold into `--l1` | — | A/B tool; likely retire |
|
|
163
|
+
|
|
164
|
+
So **L1 = all "compiled" flags on, all custom-"fused" flags off, kv-quant off** —
|
|
165
|
+
which is exactly what the `--l1` alias should expand to. After Phase 1 gives
|
|
166
|
+
compiled-geglu a fork, "faithful" stops being a separate concept: it's just the L1
|
|
167
|
+
flag values.
|
|
168
|
+
|
|
169
|
+
## Per-model target (the 13 supported models)
|
|
170
|
+
|
|
171
|
+
"Targeted" = a dedicated optimized file exists and MUST be used in every regime.
|
|
172
|
+
"Generic" = universal-dense fallback.
|
|
173
|
+
|
|
174
|
+
| Model | Dedicated file | Faithful-L1 today | Gap |
|
|
175
|
+
|---|---|---|---|
|
|
176
|
+
| MiniCPM5-1B-OptiQ | `minicpm5.ts` (+`-faithful`, `CPM5_FAITHFUL`) | ~yes (compiled-swiglu default) | `--l1` ⇒ kv off + perf off; confirm default == faithful (may retire `-faithful`) |
|
|
177
|
+
| Qwen3-Embedding-4B | `qwen3.ts` | ❌ unfused swiglu | make compiled-swiglu default; retire `FaithfulQwen3` |
|
|
178
|
+
| Qwen3.5-4B / 9B-OptiQ | `qwen3_5.ts` | ❌ `useCompiledActivation=false` | default it true; retire `FaithfulQwen35` |
|
|
179
|
+
| Qwen3.6-27B-OptiQ | `qwen3_5.ts` | ❌ same | same |
|
|
180
|
+
| Qwen3-30B-A3B | `qwen3-moe.ts` | ✅ by default | gate perf/kv by regime only |
|
|
181
|
+
| gemma-4-e4b/12B/26B-OptiQ | `generated/gemma4-*.ts` | ❌ faithful forces monolith | drop force-monolith; make compiled-geglu/softcap default |
|
|
182
|
+
| Qwen2.5-0.5B, Llama-3.2-1B/3B | `universal/dense.ts` | ❌ unfused swiglu | default compiled-swiglu; retire `FaithfulUniversalDense` |
|
|
183
|
+
| gemma-2-2b-it | `universal/dense.ts` | ✅ (gemma2 geglu not `@mx.compile`d upstream) | none |
|
|
184
|
+
|
|
185
|
+
## Plan
|
|
186
|
+
|
|
187
|
+
### Phase 1 — Global bit-exact wins (no output change) ✅ LANDED 2026-07-04
|
|
188
|
+
|
|
189
|
+
Landed on branch `faithful-decode-parity` (M1 Max, 32 GB). Changes: qwen3-dense
|
|
190
|
+
(`qwen3.ts`) and universal-dense (`universal/dense.ts` `swigluFn` default) now use
|
|
191
|
+
the shared `compiledSwiglu`; gemma `faithfulGegluActive` defaults ON via
|
|
192
|
+
`MLX_BUN_COMPILED_GEGLU` (kill-switch `=0`); custom `MLX_BUN_FUSED_GELU` metal geglu
|
|
193
|
+
demoted to explicit opt-in (`fusedGeluEnabled` default off); factory force-monolith
|
|
194
|
+
branch removed (gemma keeps its generated specialization). qwen3.5 MLP already
|
|
195
|
+
compiled (`useCompiledActivation` fields were vestigial). **Verified bit-exact
|
|
196
|
+
(`maxDiff===0`) vs mlx-lm:** `universal-parity` (Qwen2.5-0.5B, Llama-3.2-1B/3B,
|
|
197
|
+
gemma-2-2b) 6/6; `generated-parity` (12B + e4b generated vs monolith) 5/5;
|
|
198
|
+
`parity.test` (gemma-4-12B monolith greedy+logits vs mlx-lm goldens) 1/1. tsc 0,
|
|
199
|
+
hygiene green. qwen3-dense (Qwen3-Embedding) not on this box → verified by analogy
|
|
200
|
+
to the identical qwen3.5 MLP pattern. The already-global micro-fixes (native
|
|
201
|
+
`mlx_arange`, host-side bf16 scalars) need no change — they were never
|
|
202
|
+
`faithfulMode`-gated.
|
|
203
|
+
|
|
204
|
+
These change *dispatched kernels only*, not output, so they apply in **every**
|
|
205
|
+
regime (naked default included) and need no regime gate. Each is bit-exact + faster.
|
|
206
|
+
|
|
207
|
+
1. **Compiled activation default-on** for the dedicated classes that still run it
|
|
208
|
+
unfused: `qwen3.ts`, `qwen3_5.ts` (`useCompiledActivation` → default true), and
|
|
209
|
+
`universal/dense.ts` swiglu kinds. (qwen3_moe, minicpm5 already default-on.)
|
|
210
|
+
2. **Decouple compiled-geglu + `compiledLogitSoftcap` from `faithfulMode()`** in
|
|
211
|
+
`gemma4.ts` (`faithfulGegluActive`) → make them the default; retire the custom
|
|
212
|
+
`MLX_BUN_FUSED_GELU` metal geglu to explicit opt-in. This makes gemma bit-exact on
|
|
213
|
+
the activation axis under `--l1` *and* naked default, in the generated files (which
|
|
214
|
+
inherit the base MLP) without regenerating them.
|
|
215
|
+
3. **Drop the "force monolith under faithfulMode" branch** in `factory.ts` — the
|
|
216
|
+
generated `gemma4-*` specialization keeps running; it already gates
|
|
217
|
+
perf-kernel/fused-decode on flags, so `--l1` (perf/fused off) yields the L1 path
|
|
218
|
+
while staying model-specific.
|
|
219
|
+
4. Confirm the already-global bit-exact micro-fixes (native `mlx_arange`, host-side
|
|
220
|
+
bf16 scalars) apply on all paths.
|
|
221
|
+
|
|
222
|
+
**Verify per model:** bit-exact tokens **and** logprobs vs the mlx-lm oracle venv
|
|
223
|
+
(`/Users/joshrossi/Code/mlx-lm/.venv`), plus an xctrace shader-list diff where a
|
|
224
|
+
kernel-set claim is made (`.gputrace` isn't parseable — use the shader-list method).
|
|
225
|
+
|
|
226
|
+
### Phase 2 — Make `--l1` a pure alias over complete forks ✅ LANDED 2026-07-04
|
|
227
|
+
|
|
228
|
+
Landed: added `--compiled-activations on|off` (drives `MLX_BUN_COMPILED_GEGLU` +
|
|
229
|
+
`MLX_BUN_COMPILED_SWIGLU`) and `--fused-gelu on|off` (`MLX_BUN_FUSED_GELU`, the
|
|
230
|
+
custom L3 kernel) as per-fork flags in `applyDecodeRoute`; the `TIERS` presets now
|
|
231
|
+
set `compiledAct: true, fusedGelu: false` for l1/l2/l3, so `--l1` = { bf16 KV,
|
|
232
|
+
perf-kernel off, fused-decode off, **compiled activations on, custom fused-gelu
|
|
233
|
+
off** } and is byte-reproducible from individual flags. Fixed the `--l1` help text
|
|
234
|
+
(compiled activations, not "unfused"); documented both forks in `serve --help`, the
|
|
235
|
+
ready-card lever summary, `server-config.md`, and `cli.md`. Verified: tsc 0, help
|
|
236
|
+
renders the flags, and gemma `parity.test` is bit-exact vs mlx-lm under BOTH
|
|
237
|
+
`MLX_BUN_COMPILED_GEGLU=1` (default) and `=0` (uncompiled) — proving
|
|
238
|
+
`--compiled-activations off` is a valid same-parity opt-in and the env plumbing name
|
|
239
|
+
is honored. (qwen3/qwen3.5/universal compile swiglu unconditionally, so
|
|
240
|
+
`--compiled-activations off` only reverts gemma geglu + MiniCPM5 swiglu — documented.)
|
|
241
|
+
|
|
242
|
+
Original plan for reference:
|
|
243
|
+
|
|
244
|
+
First **close the fork-coverage gap**: expose the activation-fusion choice as an
|
|
245
|
+
individual per-fork flag (e.g. `--geglu compiled|fused|unfused` and the swiglu
|
|
246
|
+
equivalent, or booleans `--compiled-geglu` / `--fused-gelu` / `--compiled-swiglu`),
|
|
247
|
+
reading the same env vars the model files already check. Then wire it into the
|
|
248
|
+
`TIERS` presets in `applyDecodeRoute` so the `l1` alias sets it to the compiled
|
|
249
|
+
(bit-exact-fastest) value.
|
|
250
|
+
|
|
251
|
+
Result: `--l1` ⇒ { kv-quant off (bf16), perf-kernel off, fused-decode off,
|
|
252
|
+
custom-fused-gelu off, **compiled activations on**, model-specific file retained } —
|
|
253
|
+
and every one of those is an individual flag, so `--l1` is byte-reproducible by hand
|
|
254
|
+
and any single choice is overridable (`--l1 --fused-gelu on`). For MiniCPM5, `--l1`
|
|
255
|
+
implies whatever makes its decode bit-exact (its default may already qualify once
|
|
256
|
+
kv/perf are off — decide whether `FaithfulMiniCPM5` is still needed or retire it).
|
|
257
|
+
|
|
258
|
+
Fix the `--l1` help text: it is compiled activations + bf16 KV (not "unfused").
|
|
259
|
+
|
|
260
|
+
### Phase 3 — Retire the parallel axis ✅ LANDED 2026-07-04
|
|
261
|
+
|
|
262
|
+
Deleted `MLX_BUN_FAITHFUL`/`faithfulMode`: `src/faithful.ts` → `src/flags.ts` (keeps
|
|
263
|
+
only the generic `flagOn` helper); `generate.ts` compiled-decode default is now a
|
|
264
|
+
plain `flagOn(…, true)`. Deleted the four unwired subclasses (`gemma4-faithful`,
|
|
265
|
+
`qwen3-faithful`, `qwen3_5-faithful`, `universal/dense-faithful`) and their exclusive
|
|
266
|
+
hooks: `setFaithfulGeglu`/`_faithfulGeglu` gone (`faithfulGegluActive` → the simpler
|
|
267
|
+
`compiledGegluActive` = `flagOn("MLX_BUN_COMPILED_GEGLU", true)`), the dead
|
|
268
|
+
`useCompiledActivation` fields gone (qwen3.5 already compiles all three activation
|
|
269
|
+
sites unconditionally). **Kept `FaithfulMiniCPM5`** (`MLX_BUN_CPM5_FAITHFUL`) as the
|
|
270
|
+
one wired, tested, complete op-for-op A/B reference. Verified: tsc 0, repo-wide sweep
|
|
271
|
+
for the deleted concepts is clean, gemma `parity.test` + `minicpm5-faithful-parity`
|
|
272
|
+
green. Net: "faithful" is no longer a concept in the code — it's just the default
|
|
273
|
+
kernels, selected (or not) by the individual flags.
|
|
274
|
+
|
|
275
|
+
### Phase 4 — Docs + benchmark ✅ LANDED 2026-07-04 (numbers Josh-gated)
|
|
276
|
+
|
|
277
|
+
Docs updated: `features-matrix.md` (new `--compiled-activations` + `--fused-gelu`
|
|
278
|
+
rows), `server-config.md` + `cli.md` (Phase 2). Reworked `scripts/bench-faithful-matrix.ts`
|
|
279
|
+
+ `benchmark.sh` comparison 0 for the new model — since faithful is now the default,
|
|
280
|
+
the matrix measures what **removing** each faithful kernel costs and what the L3
|
|
281
|
+
custom kernels add on top (`− compiled-decode`, `− compiled activations`,
|
|
282
|
+
`+ custom fused-gelu`), all vs mlx-lm; tsc 0. **The actual numbers are Josh-gated**
|
|
283
|
+
(clean/rebooted machine — `./benchmark.sh --redo`, or
|
|
284
|
+
`bun scripts/bench-faithful-matrix.ts`); loaded-machine absolutes are noise. Promote
|
|
285
|
+
quotable rows to `benchmarks/RESULTS.md` after the clean run.
|
|
286
|
+
|
|
287
|
+
## Status: consolidation complete
|
|
288
|
+
|
|
289
|
+
Phases 1–4 landed on `faithful-decode-parity`. The three overlapping "match mlx-lm"
|
|
290
|
+
mechanisms are now one: the individual flags, with `--l1/l2/l3` as pure aliases.
|
|
291
|
+
Faithful kernels are the default; `--l1` is the bit-exact-mlx-lm implementation;
|
|
292
|
+
naked default is best-experience. Only follow-ups: (a) live qwen3-dense parity on a
|
|
293
|
+
box that has Qwen3-Embedding; (b) the clean-machine bench numbers.
|
|
294
|
+
|
|
295
|
+
## Open decisions
|
|
296
|
+
|
|
297
|
+
- **`FaithfulMiniCPM5`**: retire (if `MiniCPM5Model` default is already bit-exact
|
|
298
|
+
mlx-lm under `--l1`) or keep as the A/B harness?
|
|
299
|
+
- **Keep one A/B backend** per family for regression proofing, or delete all and
|
|
300
|
+
lean on the oracle parity tests?
|
|
301
|
+
- **`MLX_BUN_FAITHFUL`**: delete, or alias to `--l1`?
|
|
302
|
+
|
|
303
|
+
## Risks
|
|
304
|
+
|
|
305
|
+
- Compiled activation must be **autograd-safe** on the training path (mlx-lm's
|
|
306
|
+
`@mx.compile` threads the VJP; verified for cpm5/dense earlier — re-verify per
|
|
307
|
+
family before defaulting on).
|
|
308
|
+
- Making compiled-geglu the naked default changes gemma's *default* dispatched
|
|
309
|
+
kernels; confirm no downstream (vision, batching, generated specializations)
|
|
310
|
+
assumes the unfused/custom path.
|
|
@@ -22,10 +22,10 @@ This doc generalizes that shape to the whole mlx-lm zoo:
|
|
|
22
22
|
- **Tier-0 GENERIC** — one config-driven "universal" module per architecture
|
|
23
23
|
*family* (dense llama-family first, MoE second). Runs any covered checkpoint
|
|
24
24
|
at **L1 parity** (bit-exact vs mlx-lm on this machine's GPU). Monolith path
|
|
25
|
-
only: no
|
|
25
|
+
only: no compiled decode, no kv-quant. Slow, never broken.
|
|
26
26
|
- **Tier-1 TARGETED** — a model *graduates*: dedicated/generated forward,
|
|
27
|
-
optiq-quant **L2** parity,
|
|
28
|
-
Exactly what gemma4 / minicpm5 / qwen3_5 already are.
|
|
27
|
+
optiq-quant **L2** parity, Lab experiments where warranted, benchmark
|
|
28
|
+
entries. Exactly what gemma4 / minicpm5 / qwen3_5 already are.
|
|
29
29
|
|
|
30
30
|
The fidelity contract is unchanged (three-level tree): **L1 = mlx-lm
|
|
31
31
|
bit-exact is the gate for ANY new model**, generic or targeted. A model the
|
|
@@ -337,8 +337,11 @@ vs a dedicated path that's worth the maintenance (number in
|
|
|
337
337
|
`Gemma4Model` specifically — generalizing it to emit from
|
|
338
338
|
`UniversalDense` is Phase 4 work, and *optional*: hand ports are proven
|
|
339
339
|
and cheap ("faithful port took ~1 hr, not the huge effort claimed").
|
|
340
|
-
3. **
|
|
341
|
-
|
|
340
|
+
3. **Perf levers**: compiled decode / kv-quant / fused-sdpa, each behind its
|
|
341
|
+
existing flag. (The former "L3 kernels" step — fused decode / flash paths
|
|
342
|
+
with frozen-oracle gates — was deleted 2026-07-05; output-changing
|
|
343
|
+
experiments are now Lab items, see
|
|
344
|
+
[unified-engine-frontier-plan.md §6](unified-engine-frontier-plan.md).)
|
|
342
345
|
4. RESULTS.md + docs entries; `mlx-bun models` label flips to `targeted`.
|
|
343
346
|
|
|
344
347
|
Nothing about graduation removes the generic path — it stays as the
|