strata metal opts v2 (opus partner): resident-first experts, read-ahead precision, prewarm, and v1's attribution #
patch: /tmp/opus_metal_probes/opus_v2.diff, against the current vendor tree (v1 already in), 255 changed lines, git apply --check clean.
stream.cpp and window.cpp pass clang++ -fsyntax-only. the Metal library compiles, and every new pipeline specialization builds
(mcheck.mm, CPU only). the new plan was checked on the GPU (plantest.mm, a few ms of GPU; 200 random windows, T = 1..8, 60% resident,
λ = 0.85; ids, weights and every (token, k) → entry → expert/weight mapping identical to the unsplit plan; resident groups strictly first;
0/200 bad). the decode itself is not built or run: that's yours.
0. first, what v1's numbers do and don't say #
| arm (ops_1024, 7.0 GB unless noted) | ms/tok | CB "GPU" | host / waiting | pagein / decomp over the run |
|---|---|---|---|---|
| 0 stock, unwired | 58.72 | 58.32 | 7.94 / 7.26 | 1.7 / 7.4 MiB |
| a old code, wired, 6.5 / 7.0 | 67.52 / 71.66 | 66.3 / 71.1 | 7.75 / 7.09 | 7.3 / 12.8 MiB |
| b v1 defaults, wired, 6.5 / 7.0 | 49.83 / 49.92 | 48.72 / 48.76 | 7.14 / 7.04 | 1.5 / 4.4 MiB |
| c b + PREFETCH=16 | 48.46 | 47.23 | 5.37 / 5.29 | 0.6 / 3.6 MiB |
| unpert K=6 / 7 | 47.89 / 48.05 | 46.75 / 46.85 | 4.99 / 4.91, 4.57 / 4.49 | — |
- i was wrong about paging. 1.7 MiB of page-ins over a whole stock run can't cost ~8 ms/token, and wiring alone made the old code slower (+9…+13 ms), not faster. retracting it. MLOCK/WIRE stay a stability choice, not a speed lever.
- so the −8.9 ms between arm 0 and arm b is v1's code, or run-to-run state, and i can't account for most of it: KPROF puts the kernels at 38.3 ms; b's 48.7 ≈ 38.3 + 7.0 wait + 0.1 host + ~3.3 handshakes, which closes. stock's 58.3 ≈ 38.3 + 7.3 + 0.7 + 3.3 + ~1.7 (shexp not hidden, F32 read-ahead router) ≈ 51, leaving ~7 ms in the stock arm that nothing i changed explains. the arms ran once each, in sequence, on a fanless machine.
- needed before anything gets a number in PLAN.md: 2×2 interleaved, 3 reps (v1 off/on × wired off/on: ABCD ABCD ABCD, same arena, same PREFETCH), the stream line + profile line per run. if v1-on/unwired lands at ~49 ms the patch is the win and wiring is noise; if v1-off lands at ~51 the stock arm was a bad run.
1. v2 patch #
1.1 resident-first experts (the big one: handshakes + part of the SSD wait hidden) #
the router already reads smap (for MoCE). now plan_body (function constant 26, decode only) also orders the plan's groups
resident-at-routing-time first, missing after, with two more pairs of indirect grids (P_SPLIT: count, then IGU/IDN for each part).
moe_gu/moe_down take function constant 27: phase 1 = the resident groups, phase 2 = the missing ones.
window, per layer: [… router/route] → signal → [shared expert, moe_gu ph1, moe_down ph1] → wait → [moe_gu ph2, moe_down ph2, combine].
- what moves off the critical path: the resident experts' gate/up + down. KPROF has 3.06 + 2.28 = 5.34 ms for 48 layers × 10, so ~96% resident ≈ 105 µs a layer, plus the shared expert (~30–45 µs). that's more than the 57–73 µs event round trip, so every handshake should hide completely (~3 ms). on layers with misses, ~140 µs of their SSD wait now overlaps GPU work too (expected −0.5…−1.5 ms at ~10 misses/tok). estimate −3…−4.5 ms/tok.
- exactness: entries keep their (token, k) → entry mapping (
P_TOKENT), so the combine sums the same values in the same order; every number is bitwise the old one. without fc 26 the plan is the old plan bit for bit (no-miss arithmetic path, tested). - why phase 1 may read the arena during the host's turn:
- a chosen resident expert is pinned (
at_layerpass 1) before anyvictim()call in that turn. - claims and finished reads only ever publish
smap(~0 → slot), never unpublish a chosen expert. - the one writer that could race is the previous turn's
after_layer→ahead(l), which runs beside the GPU. its victims now skip layerln(victim(cls, avoid_layer)). - this also fixes a v1 determinism hole: an
ahead(l+1)eviction could land just before or just afterroute(l+1)readsmap, which changed MoCE routing. that was rare but real; it can't happen now.
- a chosen resident expert is pinned (
- switch:
STRATA_METAL_EXPERT_SPLIT=0.
1.2 read-ahead hygiene (lateness went 49 → 118 with v1, 397 with K=16) #
- v1 moved the cancel of queued, unwanted read-aheads to
after_layer, so duringat_layer's wait they could still start and hold I/O threads ahead of the urgent misses. that's my regression. nowat_layerretires them before it looks up a single expert (retire_unused_pre(l, l, &limbo_new_, used), no waiting), andafter_layerreleases the previous turn's limbo (two generations, still deterministic). STRATA_METAL_IO_URGENT=2(default): the first 2 I/O threads take urgent reads only while decoding, so a miss never queues behind in-flight read-aheads. prompts keep all threads.
1.3 why unpert K=6–7 wins, and where to stop #
closed-loop replay (7.0 GB = 5027 slots, stock MoCE, no read-ahead), for non-resident experts by their rank in the raw (unperturbed)
guess W(l+1)·XF(l): P(that expert is then actually selected):
| raw rank | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10–15 | 16+ |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| P(selected) | .58 | .46 | .34 | .23 | .17 | .12 | .08 | .06 | .05 | .03 | .01–.03 | ≤ .01 |
so the k-th read-ahead is useful 58% … 8% of the time down to rank 6, then below ~6%, and every useless read costs SSD time and an LRU-tail expert. K=6–7 sits right at the knee, which is what you measured (20.88 tok/s). the MoCE-ranked version wastes its slots on residents the bonus already favours. leave unpert as the default and don't go past K=8 without the urgent-thread reservation.
1.4 prewarm (the 282 ms T=3 stall) #
prewarm_pipelines() builds, and throws away, the dispatch lists for T = 2..max_t once, right before the first window is built. that
compiles every #t specialization, the QSA selection's (built at pos 4096), the PLE GEMVs and the head. no side effects: rec_,
cuts_, sigs_, cur_inj_ and nb_ are restored, nothing is encoded. switch: STRATA_METAL_PREWARM=0.
- on
--suffix-draft: ops_1024 accepted only 3 T=3 windows in 61, so the drafter barely fires on this prompt, and a T=3 window's CB was 86.5 ms (≈ 1.8× T=1: ~21 distinct experts a layer, so more misses). with resident-first experts the extra misses partly hide. re-measure on a repetitive tool-output prompt before deciding anything.
1.5 kernel sweep hooks (no default changes) #
STRATA_METAL_MOE_GU_TILE / STRATA_METAL_MOE_DN_TILE = "LPR,NR,SG" select any instantiated moe_gu_/moe_down_ tile.
moe_down_142_8_1_4moves 48 × 10 × 0.46 MB = 221 MB in 2.28 ms = 97 GB/s, whilemoe_gudoes 144 GB/s and the dense GEMVs do 122–135. that's the one kernel clearly below the bandwidth line: ~0.7 ms on the table.- instantiated candidates:
8,2,4,8,4,4,8,1,8,8,2,8,4,1,4,4,2,4,16,1,4,16,2,4,2,2,4. run KPROF per tile and keep the best.
2. what's left in the 38 ms of kernels #
| item | ms | GB/s | lever |
|---|---|---|---|
HC gr_up_q8 + gr_down_q8 (192 Q8_0 matrices, 668 MB) |
5.45 | 122 | bytes only: Q8_0 → a 5–6-bit format with a gr kernel for it (−1.9 ms at 0.69 B/w), KLD-gated |
output.weight Q6_K (521 MB) |
3.93 | 133 | bytes only: two-stage head (Q2/Q3 copy over 248K rows → exact Q6_K on the top 64): −2.5 ms, +~180 MB of memory; needs a top-20 recall check |
| attn_qkv + ssm_out + attn_gate + attn_out (Q6_K/Q5_K) | ~10 | 125–135 | at bandwidth; requant is a quality decision |
moe_down |
2.28 | 97 | tile sweep (1.5) |
ffn_gate_inp F32 ×95 |
1.77 | 141 | that KPROF run still shows 95 F32 GEMVs: was it built with PRED_Q8=1? with v1 it should be 48 F32 + 47 Q8_0 (−0.6 ms). a BF16 router would take another −0.45 ms but moves routing slightly: KLD first |
gdn_step ×36 |
2.01 | ~114 (F32 state r+w) | none cheap |
3. asks for the next M5 round #
- the 2×2 interleaved A/B from §0, so v1 gets an honest number.
- v2 defaults + unpert K=6/7 (stream + profile lines; watch
late,waiting, and CB vs 38.3 + waiting). STRATA_METAL_EXPERT_SPLIT=0vs 1, interleaved, same config (isolates §1.1).- KPROF once with v2, to confirm the 47 Q8_0 read-ahead GEMVs and the phase split.
- MOE_DN_TILE sweep under KPROF.