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_layer pass 1) before any victim() 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 layer ln (victim(cls, avoid_layer)).
    • this also fixes a v1 determinism hole: an ahead(l+1) eviction could land just before or just after route(l+1) read smap, which changed MoCE routing. that was rare but real; it can't happen now.
  • 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 during at_layer's wait they could still start and hold I/O threads ahead of the urgent misses. that's my regression. now at_layer retires them before it looks up a single expert (retire_unused_pre(l, l, &limbo_new_, used), no waiting), and after_layer releases 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_4 moves 48 × 10 × 0.46 MB = 221 MB in 2.28 ms = 97 GB/s, while moe_gu does 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 #

  1. the 2×2 interleaved A/B from §0, so v1 gets an honest number.
  2. v2 defaults + unpert K=6/7 (stream + profile lines; watch late, waiting, and CB vs 38.3 + waiting).
  3. STRATA_METAL_EXPERT_SPLIT=0 vs 1, interleaved, same config (isolates §1.1).
  4. KPROF once with v2, to confirm the 47 Q8_0 read-ahead GEMVs and the phase split.
  5. MOE_DN_TILE sweep under KPROF.