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,774 @@
|
|
|
1
|
+
# Unified engine + the frontier program — the reorganization plan
|
|
2
|
+
|
|
3
|
+
Status: **PLAN** (2026-07-05, from the flag-architecture discussion with Josh
|
|
4
|
+
after the naked-=-L1 decision). This is the design that makes the library
|
|
5
|
+
"make sense": one engine, one honest flag surface, per-scheme oracles, and a
|
|
6
|
+
Lab where optimization experiments live until they earn defaults.
|
|
7
|
+
|
|
8
|
+
## 0. The goals (Josh, 2026-07-05 — the two-part product)
|
|
9
|
+
|
|
10
|
+
1. **A drop-in replacement for mlx-lm: support EVERYTHING they do, with
|
|
11
|
+
bit-exact parity.** This is the default. Parity is only DEFINED over
|
|
12
|
+
outputs mlx-lm can produce — you cannot ask for "mlx-lm's bits under
|
|
13
|
+
mixed-precision KV" because mlx-lm cannot output anything under it.
|
|
14
|
+
2. **Beyond mlx-lm — features you opt into by deliberately trading the
|
|
15
|
+
parity guarantee for something you value MORE**, each answering to its
|
|
16
|
+
own oracle (whoever already ships it):
|
|
17
|
+
- **multi-model switching** (oracle: optiq per-request switching / oMLX
|
|
18
|
+
EnginePool) — ❌ missing today; the newest backlog item (layer 4).
|
|
19
|
+
- **mixed-precision KV cache** (oracle: optiq) — ✅ serial, bit-exact;
|
|
20
|
+
✅ **under batching for all-full-attention configs (LANDED 2026-07-05
|
|
21
|
+
— Phase 3.1 P1: cpm5 end-to-end, per-row bit-exact vs the optiq
|
|
22
|
+
composition; no other stack ships this)**; rotating-layer configs
|
|
23
|
+
(gemma) = milestone 2.
|
|
24
|
+
- **prefix sharing** (oracle: vLLM/SGLang) — ⚠️ whole-prefix reuse only;
|
|
25
|
+
true cross-request sharing (one system prompt's KV, N rows) missing.
|
|
26
|
+
- **prompt caching** (oracle: mlx-lm's LRUPromptCache) — ✅ byte-capped
|
|
27
|
+
+ SSD cold tier beyond anyone; ❌ batched rows → Phase 3.2.
|
|
28
|
+
- **structured outputs** (oracle: oMLX/xgrammar) — ✅ serial + batched,
|
|
29
|
+
byte-identical vs oMLX; composes with spec decode.
|
|
30
|
+
|
|
31
|
+
Supersedes: batching-v2-plan.md's standing decision that `--batch N` is a
|
|
32
|
+
mode switch (reversal documented in §4, with the new rationale — the old
|
|
33
|
+
rejection's determinism argument is answered, not ignored). Builds on:
|
|
34
|
+
faithful-l1-consolidation.md (the L1 baseline, now the naked default),
|
|
35
|
+
parity-tier-dag.md (per-scheme oracles), omlx-adoption-map.md (survey),
|
|
36
|
+
batching-perf-path.md (the B=1 gap numbers this plan must close).
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## 1. North star: beyond the Pareto frontier
|
|
41
|
+
|
|
42
|
+
The claim we are building toward: **"the best way to run local AI on a Mac."**
|
|
43
|
+
Formally: every (model, workload) point we ship should sit on or beyond the
|
|
44
|
+
current local-inference Pareto frontier in this space:
|
|
45
|
+
|
|
46
|
+
| axis | direction | measured by |
|
|
47
|
+
|---|---|---|
|
|
48
|
+
| memory | less is better | generation peak (Metal), fit-model predictions |
|
|
49
|
+
| decode tok/s | more | bench-h2h (stable cells only) |
|
|
50
|
+
| prefill / TTFT | faster | bench-h2h, server TTFT |
|
|
51
|
+
| throughput | higher | batched aggregate tok/s (bench-serving-load) |
|
|
52
|
+
| intelligence | higher | frozen eval suite (MMLU/GSM8K/IFEval/HumanEval/BFCL/HashHop, evals.sqlite) |
|
|
53
|
+
|
|
54
|
+
**THE CONTRACT (Josh, 2026-07-05):** two rules, and they compose:
|
|
55
|
+
|
|
56
|
+
1. **The drop-in promise: we must produce the SAME BITS as mlx-lm** — the
|
|
57
|
+
original oracle. How the bits are produced (storage, kernels,
|
|
58
|
+
scheduling, engine, language) is the space we compete in.
|
|
59
|
+
2. **The oracle for ANYTHING is whoever already does that thing.** Want
|
|
60
|
+
mlx-lm's decode → mlx-lm is the oracle. Want optiq's mixed KV → optiq.
|
|
61
|
+
Want vLLM's paged attention → **vLLM is the oracle**: read their
|
|
62
|
+
implementation, copy what they did, verify against them, then optimize.
|
|
63
|
+
This is the standing house method (FaithfulMiniCPM5 = copy mlx-lm;
|
|
64
|
+
mixed KV = copy optiq; batching = copy BatchGenerator; SSD tier = copy
|
|
65
|
+
oMLX). Never frame a capability someone ships as oracle-less. "No
|
|
66
|
+
oracle" means NOBODY does the thing — genuine research — and that, and
|
|
67
|
+
only that, is what the Lab is for.
|
|
68
|
+
|
|
69
|
+
Verification form follows the oracle's framework: same-framework oracles
|
|
70
|
+
(mlx-lm, optiq — same libmlx) admit bit-exact gates; cross-framework
|
|
71
|
+
oracles (vLLM — CUDA/PyTorch) anchor the ALGORITHM and behavior (block
|
|
72
|
+
semantics, sharing correctness, utilization), while rule 1 still binds the
|
|
73
|
+
final output bits in the default regime. "Faithful" kernel-set identity is
|
|
74
|
+
the easiest route to same-bits, not the contract; the one constraint
|
|
75
|
+
bit-exactness puts on implementations is math/reduction order (the perf
|
|
76
|
+
kernel died exactly there).
|
|
77
|
+
|
|
78
|
+
Two consequences drive everything below:
|
|
79
|
+
|
|
80
|
+
- **The baseline must be even and proven before optimizing.** We now have it:
|
|
81
|
+
the L1 faithful path is bit-exact with mlx-lm at 1.00× speed on every
|
|
82
|
+
model. Anything that claims to beat it proves it with a paired A/B on a
|
|
83
|
+
stable pass — otherwise it is noise or a regression (2026-07-05 evidence:
|
|
84
|
+
every output-changing lever failed this bar).
|
|
85
|
+
- **Frontier-shifting work is usually a *quality-per-byte* play, not a raw
|
|
86
|
+
kernel play.** Mixed-precision quantization (weights AND KV) moves memory
|
|
87
|
+
down while holding or raising intelligence — a genuine beyond-the-frontier
|
|
88
|
+
move, unlike micro-kernel wins that trade along it. This is why quantized
|
|
89
|
+
KV stays first-class even though it lost the speed A/B: it buys context
|
|
90
|
+
headroom (12B@16k: −1.3 GB, growing linearly) and mixed schemes buy
|
|
91
|
+
intelligence-per-byte.
|
|
92
|
+
|
|
93
|
+
## 2. Survey — what the other engines got right (and wrong for us)
|
|
94
|
+
|
|
95
|
+
- **mlx-lm** (`server.py`/`generate.py`): the original oracle and THE
|
|
96
|
+
drop-in target — and the project most similar to ours: it spans layers
|
|
97
|
+
1+2+3(+4), shipping the reference graphs, native bf16 + uniform 4/8-bit
|
|
98
|
+
quantized KV (`QuantizedKVCache`/`--kv-bits` — layer 2 is theirs too),
|
|
99
|
+
the batched engine, and the server. Its server is **concurrency-driven
|
|
100
|
+
by default** — it auto-creates a `BatchGenerator` (decode-concurrency
|
|
101
|
+
cap 32, prefill concurrency, chunked `prefill_step_size`) and routes any
|
|
102
|
+
batchable request into it; only non-batchable requests (draft models
|
|
103
|
+
etc.) serve sequentially. There is no "batching mode" flag — the cap is
|
|
104
|
+
the only knob. Count-capped prompt cache (the OOM footgun we already
|
|
105
|
+
fixed by byte-capping); no quantization on the batched path.
|
|
106
|
+
→ **Adopt: the concurrency-driven default is our drop-in-parity behavior.**
|
|
107
|
+
- **vLLM**: continuous batching + PagedAttention — the KV cache is block-paged
|
|
108
|
+
with a block table per sequence, which is what makes batching, long context,
|
|
109
|
+
and prefix sharing *compose* instead of conflict. No batch flag; `max_num_seqs`
|
|
110
|
+
caps. Chunked prefill interleaves with decode so TTFT doesn't starve running
|
|
111
|
+
streams. → **Adopt (long-term): block-paged KV as the structure that unifies
|
|
112
|
+
batch + quantized KV + prompt cache + SSD tier. Adopt (near-term): the
|
|
113
|
+
no-mode-flag scheduler and prefill/decode interleaving policy.**
|
|
114
|
+
- **llama.cpp** (server): slot-based (`-np` = slot count), one shared KV
|
|
115
|
+
buffer partitioned by sequence — concurrency-driven, no mode flag. KV cache
|
|
116
|
+
default is **f16; quantized KV (q8_0/q4_0) is opt-in** — independent
|
|
117
|
+
confirmation of our bf16-default decision. Its imatrix (importance-matrix)
|
|
118
|
+
quantization is the ecosystem's calibration-driven intelligence-per-byte
|
|
119
|
+
play — the same family as oQ and our knapsack `--target-bpw`.
|
|
120
|
+
→ **Adopt: nothing structural we don't have; validates KV defaults; imatrix
|
|
121
|
+
is prior art for the §7 quant program.**
|
|
122
|
+
- **oMLX**: scheduler on mlx-lm's BatchGenerator + product appliances. Already
|
|
123
|
+
systematically mapped (omlx-adoption-map.md): batching parity + SSD tier
|
|
124
|
+
ported and won; burst decode ported and REFUTED (Python-GIL medicine, our
|
|
125
|
+
disease doesn't exist); oQ quant + DFlash queued. → **Keep mining the queue;
|
|
126
|
+
its engine architecture is not ahead of ours.**
|
|
127
|
+
- **mlx-optiq**: the L2 oracle — per-layer mixed KV (`kv_config.json`), fused
|
|
128
|
+
quantized SDPA, vision sidecar. Not a batching engine. → **Its role is the
|
|
129
|
+
oracle for mixed-precision schemes (§5) and the source of the next quant
|
|
130
|
+
ideas (TurboQuant per Josh).**
|
|
131
|
+
|
|
132
|
+
## 3. The architecture (four layers, each with one job)
|
|
133
|
+
|
|
134
|
+
```mermaid
|
|
135
|
+
flowchart TB
|
|
136
|
+
subgraph clients["CLIENTS"]
|
|
137
|
+
direction LR
|
|
138
|
+
PI["Pi agent<br/>chat UI + terminal"]
|
|
139
|
+
CLI["mlx-bun CLI<br/>generate · eval · train"]
|
|
140
|
+
API["any OpenAI client"]
|
|
141
|
+
LIB["TS/Bun library import"]
|
|
142
|
+
end
|
|
143
|
+
subgraph surface["SERVING SURFACE — src/server.ts"]
|
|
144
|
+
V1["OpenAI /v1 API"]
|
|
145
|
+
ADMIT["admission control<br/>--memory-budget · --kv-budget"]
|
|
146
|
+
end
|
|
147
|
+
subgraph engine["ENGINE — one loop (Phase 2: concurrency IS the batch size)"]
|
|
148
|
+
SCHED["continuous-batching scheduler<br/>--batch N = cap (default 32 after B=1 gate)"]
|
|
149
|
+
B1["B=1 fast path<br/>pipelined + compiled step (today's serial loop)"]
|
|
150
|
+
BN["B>=2 batched step<br/>extend-join · bit-exact vs mlx-lm at same composition"]
|
|
151
|
+
FEAT["shared, implemented ONCE: prompt cache ·<br/>SSD KV tier · spec decode · grammar"]
|
|
152
|
+
end
|
|
153
|
+
subgraph models["MODEL LAYER — fingerprint-routed, NEVER flag-routed"]
|
|
154
|
+
OPT["optimized-verified graphs<br/>gemma4 12B/e4b/26B · MiniCPM5 · Qwen3/3.5"]
|
|
155
|
+
UNI["universal graph — any HF model, best effort"]
|
|
156
|
+
end
|
|
157
|
+
subgraph numerics["NUMERICS / KV SCHEMES — per-scheme oracles"]
|
|
158
|
+
BF["bf16 (default) — oracle: mlx-lm"]
|
|
159
|
+
UKV["uniform kv 4/8 — oracle: mlx-lm --kv-bits"]
|
|
160
|
+
MKV["mixed kv_config — oracle: mlx-optiq"]
|
|
161
|
+
LAB["LAB: TurboQuant · mixed-bpw weights ·<br/>new kernels — KL + eval gates"]
|
|
162
|
+
end
|
|
163
|
+
HW["libmlx (Metal) via bun:ffi — the only runtime link"]
|
|
164
|
+
PI --> V1
|
|
165
|
+
CLI --> V1
|
|
166
|
+
API --> V1
|
|
167
|
+
LIB --> SCHED
|
|
168
|
+
V1 --> ADMIT --> SCHED
|
|
169
|
+
SCHED -->|1 active| B1
|
|
170
|
+
SCHED -->|2+ active| BN
|
|
171
|
+
SCHED -.-> FEAT
|
|
172
|
+
B1 --> OPT
|
|
173
|
+
BN --> OPT
|
|
174
|
+
B1 --> UNI
|
|
175
|
+
BN --> UNI
|
|
176
|
+
OPT --> BF
|
|
177
|
+
OPT --> UKV
|
|
178
|
+
OPT --> MKV
|
|
179
|
+
UNI --> BF
|
|
180
|
+
MKV -.-> LAB
|
|
181
|
+
BF --> HW
|
|
182
|
+
UKV --> HW
|
|
183
|
+
MKV --> HW
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
How a model becomes servable, and the promotion path:
|
|
187
|
+
|
|
188
|
+
```mermaid
|
|
189
|
+
flowchart LR
|
|
190
|
+
Q["mlx-bun serve QUERY"] --> REG["registry (local HF cache)"]
|
|
191
|
+
REG -->|not downloaded| GET["mlx-bun get"]
|
|
192
|
+
GET --> REG
|
|
193
|
+
REG --> FP["config.json fingerprint"]
|
|
194
|
+
FP -->|optimized-verified| OPTF["custom graph for THAT model"]
|
|
195
|
+
FP -->|anything else| UNIF["universal graph"]
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
```mermaid
|
|
199
|
+
flowchart LR
|
|
200
|
+
A["runs on universal"] --> B["custom per-model graph<br/>(port the oracle's exact ops)"]
|
|
201
|
+
B --> C["bit-exact goldens vs scheme oracle"]
|
|
202
|
+
C --> D["h2h bench >= 1.00x mlx-lm, stable pass"]
|
|
203
|
+
D --> E["OPTIMIZED-VERIFIED badge"]
|
|
204
|
+
E -.-> F["Lab experiments on top<br/>paired A/B + expiry"]
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
**The model-layer philosophy (Josh's, verbatim intent):** the universal layer
|
|
208
|
+
is the promise that anything from Hugging Face *runs*; the optimized-verified
|
|
209
|
+
layer is the promise that the models we care about run *better than anywhere
|
|
210
|
+
else*. Promotion from universal → optimized is a defined checklist: custom
|
|
211
|
+
graph file, bit-exact parity goldens vs the scheme oracle, h2h bench entry at
|
|
212
|
+
≥1.00× vs mlx-lm, eval-suite scores recorded. "Optimized and verified" is a
|
|
213
|
+
badge with teeth, and the badge list is the marketing claim.
|
|
214
|
+
|
|
215
|
+
### Layer 0 — the SSD spill substrate (Josh, 2026-07-05)
|
|
216
|
+
|
|
217
|
+
Every stage that tried it found the same result: **it is almost always
|
|
218
|
+
faster to cache to SSD and mmap it back than to regenerate, and spilling
|
|
219
|
+
lets RAM be used as a cache instead of the only tier.** Three independent
|
|
220
|
+
implementations already prove the pattern: the SSD KV cold tier (restart
|
|
221
|
+
TTFT 12.1 s → 0.24 s, 0% decode overhead), MoE expert offload (page-aligned
|
|
222
|
+
mmap, phys footprint ≈ active params), and the byte-capped prompt cache.
|
|
223
|
+
Decision: extract the pattern into ONE generic substrate — content-addressed
|
|
224
|
+
keys (model fingerprint + scheme + adapter + producer version), zero-copy
|
|
225
|
+
mmap restore, byte-capped LRU eviction, corruption self-quarantine — that
|
|
226
|
+
layers 1/2/4 consume. Future clients: vision encoder features (oMLX
|
|
227
|
+
adoption-map #6), compiled xgrammar grammars, quantized-conversion results,
|
|
228
|
+
TurboQuant artifacts. `src/ssd-cache.ts` is the seed to generalize.
|
|
229
|
+
|
|
230
|
+
### The batching workload (Josh, 2026-07-05)
|
|
231
|
+
|
|
232
|
+
The motivating case is THE USER'S AGENT HARNESS: someone points pi / a
|
|
233
|
+
Claude-Code-style coding agent at their local server and spins up
|
|
234
|
+
SUB-AGENTS to work different parts of a project. Serial, each agent queues
|
|
235
|
+
behind all the others — the Nth agent's first token waits for N−1 full
|
|
236
|
+
generations and every agent's loop stalls. Batched, 4–8 agents decode
|
|
237
|
+
simultaneously: each stream is somewhat slower, but nobody waits, every
|
|
238
|
+
agent starts instantly, and the fleet's wall-clock collapses (cpm5: 345
|
|
239
|
+
aggregate at B=4 vs 267 solo → four jobs finish in ~3.1×T with zero queue
|
|
240
|
+
latency, vs 4×T serial with the last agent idle through three
|
|
241
|
+
generations). That is the entire reason a single-person laptop runtime
|
|
242
|
+
carries a continuous-batching engine. Consequences: concurrency-driven
|
|
243
|
+
batching (sub-agents don't coordinate arrivals); **prefix sharing gains
|
|
244
|
+
priority** (sub-agents share long system prompts — one shared KV prefix ×
|
|
245
|
+
N rows); per-slot adapters (different agents, different specializations);
|
|
246
|
+
and batching × quantized KV first (agent fleets want long contexts).
|
|
247
|
+
"Single-user" in this project means single-user, many-agents.
|
|
248
|
+
|
|
249
|
+
### Layer responsibilities (the contract, 2026-07-05)
|
|
250
|
+
|
|
251
|
+
1. **Model graph** — one forward step given hidden + caches; attention
|
|
252
|
+
dispatches on the CACHE TYPE it is handed (bf16 → ops.sdpa, quantized →
|
|
253
|
+
quantizedSdpa); shape-generic in B; selected by fingerprint, never flags.
|
|
254
|
+
*Responsibility: numerics of a step, for any B, for whatever cache it's
|
|
255
|
+
given.*
|
|
256
|
+
2. **KV scheme (cache classes)** — the memory format and its semantics:
|
|
257
|
+
bf16 / uniform / mixed per-layer; conversion timing (bf16 prefill →
|
|
258
|
+
quantize populated rows — the optiq hook semantics); in batched form the
|
|
259
|
+
multi-row layout (padding, per-row rope, merge/extract/extend/filter).
|
|
260
|
+
*Responsibility: the scheme PER ROW — a row's bytes and math identical
|
|
261
|
+
solo or batched. This is where each scheme's oracle binds.*
|
|
262
|
+
3. **Engine (scheduler)** — rows: admission, solo prefill, join/merge, the
|
|
263
|
+
step loop, per-row sampling, eviction, pipelining, the concurrency cap.
|
|
264
|
+
Never implements scheme math; calls layer-2 operations.
|
|
265
|
+
*Responsibility: how many rows and when — never what a row's bytes mean.*
|
|
266
|
+
4. **Serving surface** — endpoints, per-request features (adapters,
|
|
267
|
+
grammar, sampling, spec policy), prompt cache (keyed fingerprint +
|
|
268
|
+
effective scheme), admission budgets. *Responsibility: requests, not
|
|
269
|
+
tensors.*
|
|
270
|
+
|
|
271
|
+
**Features decompose ALONG the layers (that's the model working, not
|
|
272
|
+
leaking).** Per-request LoRA is the worked example: layer 1 = the math (a
|
|
273
|
+
parameterization — `Wx + scale·B(Ax)` — oracle-gated vs mlx-lm's tuner);
|
|
274
|
+
layer 3 = the policy (which rows run which adapter; today one active
|
|
275
|
+
adapter per batch, per-slot adapters are the matrix item); layer 4 = the
|
|
276
|
+
surface (`adapter` field, mount/hot-swap, discovery); plus one sideways
|
|
277
|
+
obligation — KV under adapter A ≠ base KV, so the prompt cache keys by
|
|
278
|
+
adapter (the SSD tier already does). When placing a feature, name its
|
|
279
|
+
component at each layer and each component's gate.
|
|
280
|
+
|
|
281
|
+
**Sampling's placement (and the debranching invariant).** The model
|
|
282
|
+
graph's contract ENDS AT LOGITS. Sampling is *specified* at layer 4 (the
|
|
283
|
+
request's params become a per-row sampler closure) and *executed* at
|
|
284
|
+
layer 3 (each step, on each row's [1,V] slice; vectorized when all-greedy)
|
|
285
|
+
— it never enters layer 1. This is the load-bearing placement behind the
|
|
286
|
+
layer-1 debranching goal (Josh: the generated file pre-decides all ops and
|
|
287
|
+
shapes; the path is a serial line with no branchable checks): everything
|
|
288
|
+
per-request-variable is either POST-GRAPH (sampling, grammar masks, stop
|
|
289
|
+
logic) or DATA the fixed graph consumes (adapter weights, caches). Mixed
|
|
290
|
+
adapter/no-adapter batches stay branch-free via vLLM's Punica/SGMV pattern
|
|
291
|
+
— every row runs the adapter math, non-adapter rows at scale 0; data, not
|
|
292
|
+
control flow (vLLM = the oracle for per-slot LoRA batching). Debranching
|
|
293
|
+
backlog: the per-token runtime checks still in generated files (`L > 1`,
|
|
294
|
+
`fusedSdpaRuntimeOk`) are per-GENERATION-decidable → hoist them out of the
|
|
295
|
+
token path; compiled decode already achieves the fully-debranched line
|
|
296
|
+
dynamically (trace once, replay).
|
|
297
|
+
|
|
298
|
+
**The option surface IS the four layers.** Users adjust: (1) how the model
|
|
299
|
+
graph works — universal vs optimized file, compiled decode, future
|
|
300
|
+
unrolled/fused variants; (2) how the quantization works — weights bpw,
|
|
301
|
+
KV scheme, future TurboQuant; (3) how the scheduler works — concurrency
|
|
302
|
+
cap, KV budget, prefill chunking, spec policy; (4) the serving surface —
|
|
303
|
+
endpoints, adapters, structured output, sampling defaults, caches,
|
|
304
|
+
multi-model. Flags, help text, and reference docs should be ARRANGED in
|
|
305
|
+
these four groups.
|
|
306
|
+
|
|
307
|
+
**Composition truth (2026-07-05, source-verified to the line AND
|
|
308
|
+
empirically proven live):** "does mixed-precision KV work with batched
|
|
309
|
+
requests?" — optiq serve as shipped: **NO** (install_mixed_kv hooks
|
|
310
|
+
stream_generate, the serial path; mlx_lm.server 0.31.3 contains zero
|
|
311
|
+
quantization in its 1,904 lines and BatchKVCache has no to_quantized —
|
|
312
|
+
batchable requests decode bf16). **Live 2×2 proof** (optiq serve, cpm5,
|
|
313
|
+
9,132-token prompt, 128-tok decode, fresh server per arm; seeded requests
|
|
314
|
+
route serial per mlx-lm's `_is_batchable`): batched path decode is
|
|
315
|
+
IDENTICAL with and without --kv-config (120.5 vs 120.6 tok/s — the config
|
|
316
|
+
is inert there), while the serial path drops 148.7 → 32.7 tok/s when the
|
|
317
|
+
mixed hook engages (4.5× slower at this context in the shipped python
|
|
318
|
+
pipeline). So optiq's composed reality today: batched requests silently
|
|
319
|
+
bf16; quantized requests pay a heavy serial penalty. Phase 3.1's batched
|
|
320
|
+
mixed-KV at full engine decode speed is an uncontested frontier point. mlx-bun
|
|
321
|
+
today: **NO** (willBatch routes shape.kvQuant serial). mlx-bun after Phase
|
|
322
|
+
3.1: **YES — the build**, and a first on this stack. In layer terms the
|
|
323
|
+
limitation is purely layer 2: the quantized schemes only have single-row
|
|
324
|
+
implementations, so layer 3 has nothing to call — not an engine limitation,
|
|
325
|
+
not a model limitation. Phase 3.1 = give layer 2 batched quantized caches
|
|
326
|
+
(copy the serial per-row ops that already bit-match optiq), teach layer 3 to
|
|
327
|
+
call them at join/evict, hand layer 1 what it already dispatches on, then
|
|
328
|
+
delete `shape.kvQuant` from willBatch — that deletion IS the feature.
|
|
329
|
+
Bonus finding from the same review: optiq's Mac-safe concurrency default is
|
|
330
|
+
**8** (they warn mlx-lm's 32 OOMs unified memory under burst) — adopt 8 as
|
|
331
|
+
the `--batch` default, superseding the earlier 32.
|
|
332
|
+
|
|
333
|
+
## 4. The engine: concurrency IS the batch size
|
|
334
|
+
|
|
335
|
+
**Decision (Josh, 2026-07-05): batching is determined by how many concurrent
|
|
336
|
+
requests are in flight, not by a flag.** One request = batch of 1 at full
|
|
337
|
+
serial speed; N requests = continuous batching; requests join/leave running
|
|
338
|
+
batches (the extend-join machinery already landed and is token-exact vs
|
|
339
|
+
mlx-lm B=2).
|
|
340
|
+
|
|
341
|
+
This **reverses** batching-v2-plan.md's standing decision (`--batch N` as a
|
|
342
|
+
mode switch, auto-batching rejected for determinism). The old objection was
|
|
343
|
+
"an idle-vs-loaded server produces different numerics for the same request."
|
|
344
|
+
It gets a real answer, not a shrug:
|
|
345
|
+
|
|
346
|
+
1. **The L1 oracle itself behaves this way.** mlx_lm.server auto-batches by
|
|
347
|
+
default; "drop-in for mlx-lm" now *requires* concurrency-driven batching.
|
|
348
|
+
Our parity contract becomes: **bit-exact to mlx-lm at the same batch
|
|
349
|
+
composition** — already golden-verified at B=1 and B=2.
|
|
350
|
+
2. Anyone who needs load-independent numerics pins the cap to 1 (see below).
|
|
351
|
+
3. **Batch-invariant kernels** (identical numerics regardless of B — the
|
|
352
|
+
Thinking-Machines-style program) go to the Lab as a research item; if that
|
|
353
|
+
ever lands, determinism-under-load comes back for free and it's an
|
|
354
|
+
arXiv-lens result on Apple Silicon.
|
|
355
|
+
|
|
356
|
+
**Flag decision (Josh, 2026-07-05): the flag stays `--batch` — no rename.**
|
|
357
|
+
Its *semantics* change from mode switch to cap: `--batch N` = maximum
|
|
358
|
+
concurrent rows (mlx-lm's `--decode-concurrency` twin), **default 32**, and
|
|
359
|
+
`--batch 1` is the force-serial / determinism pin. TIMING: the 32 default
|
|
360
|
+
lands **with Phase 2, not before** — flipping it while the batched lane is
|
|
361
|
+
still 1.8× slower at B=1 would regress every single-request user ~44%. Until
|
|
362
|
+
GATE-B1-SPEED passes, `--batch` keeps today's default (1 = the serial lane).
|
|
363
|
+
|
|
364
|
+
**The crux gate — the B=1 gap.** Today the batched lane at B=1 runs cpm5 at
|
|
365
|
+
~149 tok/s vs 267 serial (batching-perf-path.md): the unified engine is
|
|
366
|
+
**1.8× off** at the composition that matters most for a single-user runtime.
|
|
367
|
+
|
|
368
|
+
**Measured 2026-07-05 (paired in-process A/B, cpm5, M1 Max): mlx-lm's own
|
|
369
|
+
`BatchGenerator` at B=1 runs 256.5 tok/s vs 264.6 for its `stream_generate`
|
|
370
|
+
— a 3.3% tax on the most host-tax-sensitive model we have.** Two
|
|
371
|
+
consequences: (1) the unified design is *proven achievable* — a continuous-
|
|
372
|
+
batching loop can serve a lone request at ~0.97× of the best serial loop,
|
|
373
|
+
so our lane's 1.8× gap is an implementation artifact, not an inherent cost
|
|
374
|
+
of the architecture; (2) our h2h 1.00× parity numbers were measured against
|
|
375
|
+
`stream_generate` (mlx-lm's FASTEST single-stream path), so the baseline we
|
|
376
|
+
match is not batched-handicapped — and `mlx_lm.server` actually serves a
|
|
377
|
+
single request ~3% below the number we already match. The serial lane
|
|
378
|
+
cannot be deleted until:
|
|
379
|
+
|
|
380
|
+
- `GATE-B1-SPEED`: unified engine at B=1 ≥ 99% of today's serial decode tok/s
|
|
381
|
+
and TTFT on cpm5 + e4b + 12B (stable bench pass), and
|
|
382
|
+
- `GATE-B1-PARITY`: unified engine at B=1 bit-exact vs the L1 goldens (full
|
|
383
|
+
logprobs, not just greedy).
|
|
384
|
+
|
|
385
|
+
How to close 1.8×: the serial lane's wins are known and portable — the
|
|
386
|
+
pipelined decode loop (async-eval overlap), compiled per-step graph replay,
|
|
387
|
+
and no per-token scheduler hops. The likely landing: **the scheduler owns
|
|
388
|
+
admission and join/leave; when exactly one sequence is active it executes the
|
|
389
|
+
same pipelined/compiled step the serial path runs today** (B=1 specialization
|
|
390
|
+
inside one engine — a fast path, not a second lane), falling back to the
|
|
391
|
+
general batched step at B≥2. Same public machinery, no gateway routing rules,
|
|
392
|
+
no feature×lane matrix.
|
|
393
|
+
|
|
394
|
+
**COMPOSITION IS THE PRODUCT (Josh, 2026-07-05).** The goal state, verbatim
|
|
395
|
+
intent: *"you start with the server, it serves an optimized version of the
|
|
396
|
+
model, you add on top of that mixed-precision quantized KV cache, as well as
|
|
397
|
+
LoRA adapters, and you add on top of that DSpark speculative decoding, and
|
|
398
|
+
structured output potentially, and then you also can switch the sampling
|
|
399
|
+
method and values."* Every serving capability is a STACKABLE layer on one
|
|
400
|
+
engine — never a lane-routing condition, never mutually exclusive. The
|
|
401
|
+
current gateway is the anti-pattern: `willBatch` routes vision / adapters /
|
|
402
|
+
logprobs / seeds / kv-quant / drafts to a different lane, which is exactly
|
|
403
|
+
the "batching OR performance" choice being killed.
|
|
404
|
+
|
|
405
|
+
The composition matrix the unified engine must satisfy (each row = a layer;
|
|
406
|
+
any subset of rows must stack):
|
|
407
|
+
|
|
408
|
+
| layer | today | end state |
|
|
409
|
+
|---|---|---|
|
|
410
|
+
| optimized per-model graph | ✅ both lanes | ✅ (fingerprint-routed, unchanged) |
|
|
411
|
+
| mixed-precision quantized KV | serial only | one active scheme per server, applies at any B |
|
|
412
|
+
| LoRA adapters (hot-swap, per-request select) | serial only (compiled decode also skips them) | per-slot adapter state in the batched step |
|
|
413
|
+
| speculative decoding (two-model + assistant + DSpark, 2026-07-06) | serial only, forces ALL requests serial; the `DraftSource` seam is now KV-borrowing-ready (target donor-KV / anchor-hidden / tapped H_ctx flow through it — the per-slot substrate is in place) | per-slot drafting behind the `DraftSource` seam |
|
|
414
|
+
| structured output (grammar) | ✅ both lanes (B1 per-row matchers) | ✅ (already composes; keep the conformance gate) |
|
|
415
|
+
| sampling method + values (temp/top-p/top-k/min-p/XTC/penalties/HLG/seed) | per-row samplers batch; seeds force serial | fully per-row, seeds included |
|
|
416
|
+
| prompt cache / SSD tier | serial only | prefix reuse for batched rows |
|
|
417
|
+
|
|
418
|
+
Ordering (by value): quantized KV under batching first (the
|
|
419
|
+
agent-fan-out-with-long-context composition), then prompt cache, then
|
|
420
|
+
adapters, then spec decode per slot, then chunked prefill interleaving
|
|
421
|
+
(vLLM policy) so a 16k prefill doesn't stall running streams.
|
|
422
|
+
`scripts/bench-modes.ts` is already the composition scoreboard (its cell
|
|
423
|
+
matrix spans kv × batch × grammar × cache × spec) — every layer added to
|
|
424
|
+
the batched engine gets a cell there and a parity gate.
|
|
425
|
+
|
|
426
|
+
## 5. Fidelity: per-SCHEME oracles (fixes "L1 can't oracle mixed")
|
|
427
|
+
|
|
428
|
+
The tier ladder stops being a product mode and becomes what it always really
|
|
429
|
+
was — a **map from numeric scheme to its verification oracle**:
|
|
430
|
+
|
|
431
|
+
| scheme | oracle | verification |
|
|
432
|
+
|---|---|---|
|
|
433
|
+
| bf16 (default), any batch B | **mlx-lm** at same composition | bit-exact goldens (serial + B=2 landed) |
|
|
434
|
+
| uniform kv 4/8 | **mlx-lm** `--kv-bits` (+ optiq's rotating cache class where upstream is NYI) | bit-exact (kv-quant.test) |
|
|
435
|
+
| mixed per-layer kv (`kv_config.json`) | **mlx-optiq** `install_mixed_kv` | bit-exact (mixed-kv-parity.test, landed 2026-07-05, maxDiff 0) |
|
|
436
|
+
| mixed-precision weights (knapsack/oQ/TurboQuant), original kernels, batch-invariant kernels | **none exists** → Lab gates | KL vs our own reference path + frozen eval suite (intelligence axis) + envelope tests + kill switch |
|
|
437
|
+
|
|
438
|
+
So: mixed-precision quantization is NOT demoted by the naked-=-L1 decision —
|
|
439
|
+
it is the flagship of the row that mlx-lm cannot oracle, verified against
|
|
440
|
+
optiq where optiq reaches (KV) and against the eval suite where no oracle
|
|
441
|
+
exists (weights). `--l1/--l2` survive as *bench/test vocabulary* for the
|
|
442
|
+
first three rows; they stop being user-facing product modes.
|
|
443
|
+
|
|
444
|
+
**Composition rule (Josh, 2026-07-05): a composition inherits its scheme's
|
|
445
|
+
oracle.** Batching, prompt cache, spec decode, adapters — none of them
|
|
446
|
+
change a scheme's numerics, so composing them with a scheme verifies
|
|
447
|
+
against THAT SCHEME'S oracle, per row. Batched bf16 anchored to mlx-lm
|
|
448
|
+
(B=N token-exact goldens); batched mixed-KV anchors to the L2 oracle the
|
|
449
|
+
same way: copy optiq's per-row quantization mechanics (`install_mixed_kv`'s
|
|
450
|
+
per-layer map + streaming `to_quantized` + RotatingQuantizedKVCache — our
|
|
451
|
+
serial `maybeQuantizeKv` is already the verified op-for-op copy), then
|
|
452
|
+
gate on per-row bit-exactness against it (row cache contents; B=1 through
|
|
453
|
+
the engine vs the mixed-KV golden; B>1 trajectories at the standing
|
|
454
|
+
long-prefix bar). Do NOT invent Lab-style KL/teacher-forced gates for a
|
|
455
|
+
composition of an oracle-backed scheme — the Lab is only for schemes with
|
|
456
|
+
no oracle at all (new quant methods, original kernels).
|
|
457
|
+
|
|
458
|
+
## 6. The flag surface (end state — every flag with its reason)
|
|
459
|
+
|
|
460
|
+
User-facing serve/generate flags after the deletion pass:
|
|
461
|
+
|
|
462
|
+
| flag | why it exists |
|
|
463
|
+
|---|---|
|
|
464
|
+
| `--kv-quant config\|4\|8\|off` | the ONE performance trade-off a user makes: decode speed vs context headroom (+ intelligence-per-byte with mixed). Default off (bf16). Composition (fused vs unfused quantized SDPA) is DERIVED from the scheme's oracle, not chosen. |
|
|
465
|
+
| `--batch N` | cap on concurrent rows (mlx-lm's `--decode-concurrency` twin). Default 32 once Phase 2's B=1 gate passes; `--batch 1` = force-serial / determinism pin. Name kept (Josh 2026-07-05) — semantics change from mode to cap. |
|
|
466
|
+
| `--draft-model`, `--num-draft-tokens` | speculative decoding (mlx-lm parity) |
|
|
467
|
+
| `--adapter`, `--memory-budget`, `--kv-budget`, `--prompt-cache`, `--ssd-cache`, `--expert-offload`, `--force-wire` | capacity/serving features, not numerics levers |
|
|
468
|
+
| sampling flags (`--temperature` etc., HLG family) | request-default sampling |
|
|
469
|
+
|
|
470
|
+
Everything else becomes either a **kill switch** (env-only, documented for
|
|
471
|
+
debugging: `MLX_BUN_COMPILED_DECODE=0`, `MLX_BUN_COMPILED_GEGLU=0`,
|
|
472
|
+
`MLX_BUN_NO_FUSED_SDPA=1`) or a **Lab flag** (env-only, lives with its bench
|
|
473
|
+
script and expiry — §7). Kill switches are bit-exact by definition (they
|
|
474
|
+
select a slower same-parity path); anything output-changing is Lab.
|
|
475
|
+
|
|
476
|
+
**Deleted — EXECUTED 2026-07-05** (one funeral each; ~50 files touched, 23
|
|
477
|
+
deleted, suite green):
|
|
478
|
+
- `--fused-decode` / `MLX_BUN_FUSED_DECODE` — 1.00×, forces uncompiled,
|
|
479
|
+
silent-wrongness footgun. Delete kernel path + flag + backstop throw.
|
|
480
|
+
- `--fused-gelu` / `MLX_BUN_FUSED_GELU` + `MLX_BUN_FUSED_SWIGLU` — +0–1%;
|
|
481
|
+
the compiled closures already own the fusion win bit-exactly. Delete both
|
|
482
|
+
custom Metal kernels + flags (git history keeps the source).
|
|
483
|
+
- `--perf-kernel` / `MLX_BUN_PERF_KERNEL` — **DELETE** (Josh 2026-07-05:
|
|
484
|
+
"we are going to start a full optimization program anyway… we will end up
|
|
485
|
+
redoing all the work we have currently done given that we now have a
|
|
486
|
+
different starting point"). The kernel, its frozen-oracle test scaffolding
|
|
487
|
+
(perf-kernel-oracle.test.ts, freeze-perf-oracle.ts), and the flag all go;
|
|
488
|
+
git history + the h2h evidence (+6% 12B@16k, −38% e4b@16k, KL WARN) are
|
|
489
|
+
the breadcrumb. If a flash-decode kernel returns, it's re-derived from the
|
|
490
|
+
L1 baseline in the Lab under the §7.4 program, not resurrected.
|
|
491
|
+
- `MLX_BUN_CPM5_FAITHFUL` + `FaithfulMiniCPM5` — the default IS the faithful
|
|
492
|
+
path now; the A/B reference served its purpose.
|
|
493
|
+
- `--l3` as a product mode — the Lab replaces it; the flag now hard-errors
|
|
494
|
+
with a pointer here. `--l1`/`--l2` stay as documented aliases.
|
|
495
|
+
- Also deleted with the fused-swiglu family: `fused-mlp-kernel.ts` and
|
|
496
|
+
`steel-linear-kernel.ts` (only reachable through the deleted gates) and
|
|
497
|
+
the ~17 `scripts/experiments/swiglu-*`/geglu/steel one-off scripts.
|
|
498
|
+
- Training flag sanitization (`MLX_BUN_PERF_KERNEL=0` / `MLX_BUN_FUSED_GELU=0`
|
|
499
|
+
in trainers/launchers/recipes) — obsolete; the no-vjp kernels it guarded
|
|
500
|
+
against no longer exist.
|
|
501
|
+
|
|
502
|
+
**Lab lifecycle rule** (the anti-flag-pile law): every Lab experiment ships
|
|
503
|
+
with (a) a hypothesis stated in frontier-axis terms, (b) a paired A/B bench
|
|
504
|
+
script, (c) an expiry review date. Outcomes: promote to default (with the
|
|
505
|
+
stable-pass numbers) or delete (with a breadcrumb in the design doc). No
|
|
506
|
+
third state. "Off in every regime with no promotion path" — the fused-decode
|
|
507
|
+
condition — is a deletion that hasn't happened yet.
|
|
508
|
+
|
|
509
|
+
## 7. The frontier program (AFTER stability — the order matters)
|
|
510
|
+
|
|
511
|
+
Phase gate: none of this starts until §8 phases 0–2 are done. "Now we
|
|
512
|
+
finally have a baseline that is even and proven; investigate THAT."
|
|
513
|
+
|
|
514
|
+
Ranked by expected frontier shift (memory ↓ / intelligence ↑ first, then
|
|
515
|
+
speed):
|
|
516
|
+
|
|
517
|
+
1. **Mixed-precision weights** — knapsack `--target-bpw` (ours) vs oQ
|
|
518
|
+
(calibration-driven sensitivity, omlx-adoption-map §4) vs llama.cpp
|
|
519
|
+
imatrix as prior art. Gate: perplexity + frozen 6-task eval at equal bpw.
|
|
520
|
+
The purest beyond-the-frontier play we know: same memory, more
|
|
521
|
+
intelligence. (arXiv-lens candidate.)
|
|
522
|
+
2. **TurboQuant KV** (Josh) — ORTHOGONAL to mixed precision and composes
|
|
523
|
+
with it: mixed precision is the ALLOCATION axis (how many bits per
|
|
524
|
+
transformer layer, optiq's sensitivity map), TurboQuant is the
|
|
525
|
+
QUANTIZER axis (how a given bit budget encodes each K/V vector, within
|
|
526
|
+
a layer — rotation-based, online, calibration-free). Composition =
|
|
527
|
+
sensitivity allocation × better quantizer: more fidelity at the same
|
|
528
|
+
KV bytes. Architecture: a layer-2 cache format + a layer-1 attention
|
|
529
|
+
kernel keyed by cache type (same shape as optiq's scheme). Oracle: the
|
|
530
|
+
paper + its reference implementation anchor the ALGORITHM (read them
|
|
531
|
+
first, per the oracle rule); the output is genuinely novel (no serving
|
|
532
|
+
stack ships it) → eval-suite gates. Composes with Phase 3.1's batched
|
|
533
|
+
quantized buffers.
|
|
534
|
+
3. **Speculative decoding depth** — DFlash/DSpark behind the existing
|
|
535
|
+
`DraftSource` seam; decode tok/s at zero quality cost when drafts land.
|
|
536
|
+
4. **Per-model graph work from the baseline** — the optimized-verified
|
|
537
|
+
layer's whole point: unroll a model's flat DAG, find fusion the compiler
|
|
538
|
+
misses, prove with kernel-trace diffs, promote per model. The perf-kernel
|
|
539
|
+
root-cause lives here.
|
|
540
|
+
5. **Batch-invariant kernels** (research) — restores determinism-under-load;
|
|
541
|
+
novel on Apple Silicon.
|
|
542
|
+
|
|
543
|
+
## 8. Migration phases (each with a hard gate)
|
|
544
|
+
|
|
545
|
+
- **Phase 0 — measure the gap** — **DONE 2026-07-05** (M1 Max, same server
|
|
546
|
+
harness both lanes, median-of-5, bf16 KV):
|
|
547
|
+
|
|
548
|
+
| model | serial B=1 | batch-lane B=1 | ratio | extra host ms/token | TTFT serial→batch |
|
|
549
|
+
|---|---|---|---|---|---|
|
|
550
|
+
| cpm5 | 281.4 tok/s | 128.8 | 0.46× | +4.2 ms | 44 → 55 ms |
|
|
551
|
+
| e4b | 62.1 | 44.9 | 0.72× | +6.2 ms | 54 → 121 ms |
|
|
552
|
+
| 12B | 29.8 | 25.6 | 0.86× | +5.5 ms | 82 → 251 ms |
|
|
553
|
+
|
|
554
|
+
The overhead is a roughly CONSTANT ~4–6 ms per decode step, independent of
|
|
555
|
+
model size → per-step host tax, not GPU work. cpm5 (which never uses
|
|
556
|
+
compiled decode) pays it too, so compiled-decode's absence is not the
|
|
557
|
+
story. Suspects (in dispatch-site order): the `setImmediate` macrotask hop
|
|
558
|
+
per drive-loop iteration, per-step per-layer `BatchedDecodeMaskCache`
|
|
559
|
+
construction + mask materialization at B=1 (serial passes caches bare with
|
|
560
|
+
an empty mask), and the per-token emit path. TTFT gap = solo-prefill +
|
|
561
|
+
merge + admission hops. Closure worklist = profile these three, in order.
|
|
562
|
+
- **Phase 1 — the deletion pass** (§6): dead kernels, flag surface, docs,
|
|
563
|
+
Lab scaffolding (move perf-kernel + its bench + oracle tests). Gate: full
|
|
564
|
+
suite green; naked defaults byte-identical to today's L1 route.
|
|
565
|
+
- **Phase 2 — engine unification** — **DECODE GAP CLOSED 2026-07-05.** The
|
|
566
|
+
~4–6 ms/step host tax decomposed into exactly two bugs, both fixed:
|
|
567
|
+
1. **The pipelined token read enqueued an `astype` kernel** —
|
|
568
|
+
`prev.toFloat32()` on the int token array created a NEW cast op that
|
|
569
|
+
queued BEHIND the entire just-dispatched next step on the GPU stream,
|
|
570
|
+
stalling the "overlapped" read for a FULL step of GPU work every
|
|
571
|
+
token (this alone was the bulk of the gap; zero host/GPU overlap,
|
|
572
|
+
proven by NO_PIPELINE ≈ pipelined). Fix: `MlxArray.toIntTokens()` —
|
|
573
|
+
eval + raw uint32/int32 buffer read, no cast (mirrors serial's
|
|
574
|
+
`itemUint32`).
|
|
575
|
+
2. **Per-layer per-step mask/rope wrapper churn**: `#step` rebuilt a
|
|
576
|
+
`BatchedDecodeMaskCache` + array mask for every full layer every
|
|
577
|
+
token even when no row had left padding. Fix: the unpadded fast path —
|
|
578
|
+
bare caches (KVCache.makeMask(1) = the empty mask, scalar rope) →
|
|
579
|
+
the batch step dispatches the SAME graph the serial loop builds.
|
|
580
|
+
|
|
581
|
+
Measured after (B=1 through the batch lane, same SSE harness as Phase 0):
|
|
582
|
+
cpm5 129→**264** (serial 281 SSE / in-process ratio **0.994**), e4b
|
|
583
|
+
45→**57.6** (0.93 — remainder = compiled decode, serial-only today),
|
|
584
|
+
12B 25.6→**29.7** (**1.00**). Batched parity oracles green (11/11
|
|
585
|
+
applicable; the CPM extend-join golden failure PRE-EXISTS this work —
|
|
586
|
+
proven by stash/rerun — and is logged as an open item).
|
|
587
|
+
**Remaining before the `--batch` 32 default flip**: compiled decode
|
|
588
|
+
inside the B=1 step (e4b's last 7%), prompt cache for batched rows
|
|
589
|
+
(TTFT: serial hits the cache on repeat prompts, batch re-prefills —
|
|
590
|
+
e4b 126 vs 54 ms, 12B 246 vs 82 ms), then GATE-B1-SPEED ≥99% on all
|
|
591
|
+
three + GATE-B1-PARITY (bit-exact vs L1 goldens) + B=2 goldens intact.
|
|
592
|
+
- **Phase 3 — composition**: quantized KV under batching — **P1 LANDED
|
|
593
|
+
2026-07-05**: src/model/batched-quant.ts (merge/extend/filter over
|
|
594
|
+
triples + BatchedQuantDecodeMaskCache), scheduler solo-conversion at the
|
|
595
|
+
serial chunk boundaries, gateway kv-batchability memo (all-full-attention
|
|
596
|
+
kvConfig batches; uniform/rotating stays serial; scheme-less gateway
|
|
597
|
+
refuses — never silently drop quantization, the optiq bug class). Gates
|
|
598
|
+
green: B=1 through the scheduler BIT-EXACT vs the cpm5 optiq golden
|
|
599
|
+
(decode steps, maxDiff 0); B=2 dynamic join — unpadded row BIT-EXACT vs
|
|
600
|
+
solo at every step, padded row within the calibrated envelope (5e-2;
|
|
601
|
+
bf16 same-harness shows ~9e-3 — the pre-existing reduction-order noise,
|
|
602
|
+
amplified by grid snapping; scripts/experiments/batched-quant-kl-profile
|
|
603
|
+
.ts). E2E: --batch 2 --kv-quant config on cpm5 = /stats active_rows 2,
|
|
604
|
+
400 tok @ 240 tok/s aggregate, coherent output. NEXT: milestone 2
|
|
605
|
+
(batched rotating-quant for gemma configs), prompt-cache reuse for
|
|
606
|
+
batched rows, chunked prefill interleaving. Gate per feature: parity +
|
|
607
|
+
no aggregate-throughput regression at B=4.
|
|
608
|
+
- **Phase 3.2 — lone-request = serial (LANDED 2026-07-05)**, three moves:
|
|
609
|
+
1. **Adopt-don't-copy**: a row joining an EMPTY batch keeps its solo
|
|
610
|
+
caches as the inners (pointer handoff; the merge copy now runs only
|
|
611
|
+
when a second row actually joins — incl. a new merge branch that
|
|
612
|
+
treats an adopted serial RotatingKVCache as the first row). The
|
|
613
|
+
lone row's caches stay SERIAL-CLASS, which unlocks the next two.
|
|
614
|
+
2. **Compiled decode at B=1**: the scheduler replays the serial
|
|
615
|
+
engine's CompiledDecode step when B=1 ∧ unpadded ∧
|
|
616
|
+
supports(inners) ∧ uint32 pipeline register (same runner, same
|
|
617
|
+
traces, same MLX_BUN_COMPILED_DECODE kill switch; falls back +
|
|
618
|
+
disables on error like generate()). Gate: free-running greedy B=1
|
|
619
|
+
== serial generate() token-for-token on 12B with
|
|
620
|
+
CompiledDecode.stepsExecuted advancing (tests/batch-scheduler).
|
|
621
|
+
3. **Prompt cache on the batch lane**: joiners take() the longest
|
|
622
|
+
usable prefix at admission (suffix-only prefill — multi-turn chat
|
|
623
|
+
TTFT); rows finishing NEVER-MERGED put() back (exact prompt+fed
|
|
624
|
+
accounting, offset-checked; reject/batch-drop paths poisoned; SSD
|
|
625
|
+
retain hooks ride every disposal path). Gate: second identical
|
|
626
|
+
request reports cached_tokens=prompt-1, step-0 argmax anchored,
|
|
627
|
+
KL≤1e-2 (tests/batch-scheduler "prompt-cache reuse").
|
|
628
|
+
**Measured (apple-m1-max/32GB, paired in-process A/B, best-of):
|
|
629
|
+
B=1-through-batch-lane / serial = cpm5 0.996, e4b 0.992 (was 0.93 —
|
|
630
|
+
compiled closed the last 7%), 12B 0.993 → GATE-B1-SPEED decode met on
|
|
631
|
+
all three.** All gated suites green (scheduler CPM+12B, quant gates 1+2,
|
|
632
|
+
full per-file model-free suite 0 fail).
|
|
633
|
+
Findings: (a) gate 2's padded-row KL is JOIN-STEP dependent (grid-snap
|
|
634
|
+
bin flips compound: K=5→4.5e-2, 6→3.5e-2, 7→1.5e-1, 8→1.3e-1; bf16
|
|
635
|
+
≤9e-3 at every K; identical on pre-3.2 main; extend vs re-merge
|
|
636
|
+
byte-identical) — the gate now PINS the join step and calibrates there;
|
|
637
|
+
padded-row KL is an envelope, the contract is the unpadded row's
|
|
638
|
+
bit-exactness + model-free triple-surgery byte-identity. (b) v1 gap:
|
|
639
|
+
an entry take()n by a row that later MERGES is consumed, not re-put —
|
|
640
|
+
fix is a zero-copy view snapshot at merge (serial's boundary-snapshot
|
|
641
|
+
trick); noted for milestone 2. (c) monolithic `bun test` can jetsam on
|
|
642
|
+
a busy 32GB box (largestProcess=bun) — per-file loop is the fallback
|
|
643
|
+
gate; not a repo defect.
|
|
644
|
+
**Remaining before the `--batch` default flip (Josh's call)**:
|
|
645
|
+
milestone 2 batched rotating-quant, then flip.
|
|
646
|
+
- **Phase 3 milestone 2 — batched ROTATING-quantized KV (LANDED
|
|
647
|
+
2026-07-05)**: src/model/batched-rotating-quant.ts —
|
|
648
|
+
BatchedRotatingQuantCache subclasses RotatingQuantizedKVCache (attention
|
|
649
|
+
dispatch untouched) and runs the mlx-lm BatchRotatingKVCache ring
|
|
650
|
+
mechanics over (packed, scales, biases) triples; RotatingQuantizedKVCache
|
|
651
|
+
gains temporalView(); scheduler #quantizeSolo converts rotating layers
|
|
652
|
+
(maybeQuantizeKv dispatch) and #mergeJoiner grows a rot-quant twin
|
|
653
|
+
branch; gateway #kvBatchable accepts configs naming rotating layers →
|
|
654
|
+
**gemma's whole kv_config batches** (every shipped kv_config now does).
|
|
655
|
+
Gates: model-free byte-identity per row vs the serial oracle through
|
|
656
|
+
ring wrap, B=1+B=2+filter (tests/batched-rotating-quant.test.ts); gemma
|
|
657
|
+
12B B=2 dynamic join through the real scheduler — unpadded row KL-0 at
|
|
658
|
+
EVERY step, padded row ≤4e-3 (tests/batched-kv-quant-parity.test.ts).
|
|
659
|
+
**Findings (hard-won, 2026-07-05)**: (a) THE ROPE-ARRAY STEP-STABILITY
|
|
660
|
+
CONTRACT — a batched cache's ropeOffsetArr must be STABLE within a
|
|
661
|
+
decode step and refresh only at releaseRopeArr(): the monolith CAPTURES
|
|
662
|
+
it pre-update and ropes Q post-update (in-update dispose = use-after-
|
|
663
|
+
dispose), while the GENERATED specializations RE-READ it for Q
|
|
664
|
+
post-update (in-update refresh = K and Q roped one position apart →
|
|
665
|
+
O(10) KL garbage). (b) GENERATED-FILE GUARDS ACCEPT BATCHED SUBCLASSES:
|
|
666
|
+
the generated forwards' per-layer `instanceof` cache guards pass any
|
|
667
|
+
batched cache that subclasses a serial class — an all-quant gemma batch
|
|
668
|
+
therefore decodes through the GENERATED fast path (a feature — B=1
|
|
669
|
+
twin proved BIT-EXACT through it — but a contract: batched caches must
|
|
670
|
+
behave serially under re-reads). This is why the bug only appeared when
|
|
671
|
+
EVERY layer was configured: one bf16 layer anywhere fails the guard and
|
|
672
|
+
drops to the monolith. Diagnosis artifacts:
|
|
673
|
+
scripts/experiments/gemma-rotquant-kl-profile.ts (CONFIG_FILTER/
|
|
674
|
+
ROT_KEEP/ROT_DROP/ROT_IDX bisection) and gemma-twin-b1-repro.ts
|
|
675
|
+
(TWIN_MASK/TWIN_ROPE toggles — rope isolated in two runs once the
|
|
676
|
+
B=1 repro existed).
|
|
677
|
+
- **Layer 0 — the SSD tier becomes a property of the store (LANDED
|
|
678
|
+
2026-07-05)**: PromptCache gains a structural ColdTier and runs the
|
|
679
|
+
two-tier take (RAM peek vs cold find → zero-copy restore + trim)
|
|
680
|
+
INSIDE take(), so the batch scheduler restores prefixes from disk at
|
|
681
|
+
admission with zero scheduler changes (gated E2E on cpm5: demote →
|
|
682
|
+
disk → batch-lane restore, cached=prompt−1, KL 1.5e-3); onPut drives
|
|
683
|
+
the write-behind snapshot for both lanes; NEW idle demotion
|
|
684
|
+
(--ssd-demote-idle, default 300 s with the tier) spills idle entries
|
|
685
|
+
and FREES their GPU memory — RAM drains between agent bursts, every
|
|
686
|
+
prefix stays reachable. Also fixes a latent flaw: the old serial flow
|
|
687
|
+
trimmed the RAM entry before deciding the SSD copy won. The economics
|
|
688
|
+
(Josh): the SSD competes with RECOMPUTE, not RAM — 12B prefill 168
|
|
689
|
+
tok/s ⇒ a 30k agent context is ~3 min of prefill vs ~1 s of NVMe
|
|
690
|
+
restore, and the ratio grows with context. Follow-ups noted: extract
|
|
691
|
+
the generic blob-store mechanics (fingerprint dirs, atomic writes,
|
|
692
|
+
scan/recovery, LRU cap) for a second client when one lands (adapters,
|
|
693
|
+
expert offload); memory-pressure-triggered demotion (vs idle-only).
|
|
694
|
+
- **Prefix sharing v1 (LANDED 2026-07-05)**: PromptCache.take() is
|
|
695
|
+
NON-CONSUMING — a hit serves zero-copy CLONES (cloneKvCaches views,
|
|
696
|
+
trimmed to the matched prefix, retain ref-counted so a demoted/evicted
|
|
697
|
+
donor can never unmap pages a clone still reads) and the donor entry
|
|
698
|
+
stays put. Safe by mlx's functional cache updates (updateAndFetch
|
|
699
|
+
reassigns fresh arrays; donation needs refcount 1 and the donor holds a
|
|
700
|
+
ref), so clones can never mutate donors. put() supersedes same-ns
|
|
701
|
+
prefix-ancestors AND exact duplicates when the new entry is trimmable
|
|
702
|
+
(untrimmable extensions preserve their prompt-boundary snapshots — the
|
|
703
|
+
drift-proof server). This kills the CANNIBALIZATION flaw: agent B
|
|
704
|
+
borrowing agent A's 2k system prompt used to consume-and-trim A's 10k
|
|
705
|
+
entry to serve it. Gates: 22 unit tests (donor persistence, clone
|
|
706
|
+
independence, supersede rules, ref-count ordering) + real-model
|
|
707
|
+
scheduler gate: B served from A's entry (cached=|SYS|, KL 1.8e-3), A's
|
|
708
|
+
NEXT TURN still hits its full entry. New-session and restart sharing
|
|
709
|
+
fall out of the (already non-consuming) SSD tier. **Scope, honestly**:
|
|
710
|
+
v1 shares the COMPUTE (one prefill serves N agents) and the durability;
|
|
711
|
+
concurrent batch rows still hold separate physical KV copies — one
|
|
712
|
+
shared physical prefix across rows is the paged/block-KV frontier item
|
|
713
|
+
(§9.4), and per §9.5 the disk tier's whole-entry duplication of shared
|
|
714
|
+
prefixes is the block-granularity revisit trigger.
|
|
715
|
+
- **Runtime isolation P1 (LANDED 2026-07-05, opt-in `--isolate`)**: the
|
|
716
|
+
engine child = the ENTIRE existing server on a unix domain socket; the
|
|
717
|
+
parent = a thin reverse proxy (src/serve/isolate.ts) — zero MLX calls,
|
|
718
|
+
crash isolation (502 + auto-respawn with crash-loop backoff), abort
|
|
719
|
+
propagation, GET-retry across respawn. Decision recorded in
|
|
720
|
+
runtime-isolation.md: proxy-the-whole-server instead of the original
|
|
721
|
+
gateway-as-IPC-client sketch (grammar WASM/vision arrays/sampler
|
|
722
|
+
closures don't serialize; the inter-process API is the /v1 surface
|
|
723
|
+
itself — no second protocol). Measured paired (M1 Max, cpm5): proxied
|
|
724
|
+
197.2 vs direct 197.9 tok/s (−0.4%, noise), TTFT +2 ms, SSE per-token
|
|
725
|
+
granularity preserved (96 reads/96 tokens), parent 0.6 ms mid-decode
|
|
726
|
+
(criterion <50 ms). Gates: tests/isolate-proxy.test.ts (model-free fake
|
|
727
|
+
engine: passthrough, granularity, abort, crash→respawn) +
|
|
728
|
+
tests/isolate-e2e.test.ts (real cpm5 child). Prefill chunk loops also
|
|
729
|
+
gained macrotask yields (the last isolation-Phase-1 item). Not proxied
|
|
730
|
+
v1: /ws/chat (501). NEXT: P2 = child-per-model pool — multi-model
|
|
731
|
+
switching by SPAWN-OVERLAP (new child loads while the old one serves;
|
|
732
|
+
drain → demote-to-SSD → park; route by the request `model` field);
|
|
733
|
+
P3 = GPU-lease unification with quantize/finetune jobs.
|
|
734
|
+
- **Runtime isolation P2 — CHILD-PER-MODEL POOL (LANDED 2026-07-05; task
|
|
735
|
+
#14 multi-model switching)**: ModelPool in src/serve/isolate.ts — LRU
|
|
736
|
+
residency over engine children (--model-pool, default 1), request
|
|
737
|
+
routing by the JSON `model` field on the generation endpoints (STRICT
|
|
738
|
+
exact-/v1/models-id resolution: fuzzy strings like "gpt-4" keep
|
|
739
|
+
mlx-lm's ignored-field drop-in semantics), spawn-overlap switching (the
|
|
740
|
+
new model health-gates while the old serves), and graceful lossless
|
|
741
|
+
eviction: POST /admin/drain (engine-mode-only endpoint: gateway
|
|
742
|
+
quiesce + demoteIdle(0) → the whole prompt cache to that model's SSD
|
|
743
|
+
fingerprint dir) then exit. engineArgvForModel pins --model per child.
|
|
744
|
+
**Measured (M1 Max, cpm5⇄qwen0.8b, pool 1): switch 1.5 s · switch-back
|
|
745
|
+
1.2 s · cached_tokens 103/104 on return** — the conversation's KV
|
|
746
|
+
survived full model eviction and restored from disk instead of
|
|
747
|
+
re-prefilling (tests/isolate-switch-e2e.test.ts). Model-free pool gates
|
|
748
|
+
(routing, drop-in ignore, cap-1 drain→demote→evict→respawn):
|
|
749
|
+
tests/isolate-proxy.test.ts. Defaults unaffected: without --isolate no
|
|
750
|
+
pool code exists in the request path.
|
|
751
|
+
- **Phase 4 — the frontier program opens** (§7 order).
|
|
752
|
+
|
|
753
|
+
## 9. Decisions log (was: open questions)
|
|
754
|
+
|
|
755
|
+
1. **Cap default 8 — FLIPPED 2026-07-05** (Josh; earlier "32" note
|
|
756
|
+
superseded by the sub-agents rationale, 4–8 agents + optiq's Mac-safe
|
|
757
|
+
8). Landed after Phase 3 met every gate: lone-request = serial engine
|
|
758
|
+
(bits + speed + TTFT + prompt/SSD cache), quantized compositions
|
|
759
|
+
batch. `--batch 1` pins strict arrival-independent numerics;
|
|
760
|
+
kv-budget admission keeps 8 safe on small boxes.
|
|
761
|
+
2. **Flag stays `--batch`** — no rename; semantics become the cap;
|
|
762
|
+
`--batch 1` = force-serial pin.
|
|
763
|
+
3. **Perf kernel: deleted in Phase 1** (§6) — the optimization program
|
|
764
|
+
re-derives from the L1 baseline; no resurrection.
|
|
765
|
+
4. Still open: PagedAttention-style block KV — Phase 3 follow-up or its own
|
|
766
|
+
design doc when prefix sharing under batching becomes the bottleneck.
|
|
767
|
+
5. **Prefix sharing scope (Josh 2026-07-05)**: sharing must extend to the
|
|
768
|
+
DISK tier too — the whole-entry cold store duplicates a shared system
|
|
769
|
+
prompt across N agent conversations (blocks would dedupe; that is the
|
|
770
|
+
revisit trigger for block granularity). First-class use cases beyond
|
|
771
|
+
concurrent rows: SPINNING UP A NEW SESSION (fresh conversation, same
|
|
772
|
+
system prompt → instant prefix) and SERVER RESTART (re-attach without
|
|
773
|
+
re-prefill) — the shared prefix should be a durable object, not a
|
|
774
|
+
property of any one conversation's entry.
|