phase 1a v2 / 1b replayer audit (read-only) #
scope: scripts/assemble_corpus.py, scripts/patch_imatrix.py, scripts/select_and_eval_prunes.py, scripts/replay_moe_cache.py, results/reap_saliency_v2.json, results/keep_maps/ (9 files), against audits/opus_audit_phase1_methodology.md.
run audited (parent transcript seq 173661): llama-imatrix ... -f calibration_mix_v2.txt --parse-special -c 2048 -b 512 -ngl 99 --chunks 64 --output-frequency 5 --no-ppl.
verdict #
- JSON invariants: all pass on all 48 layers.
- v1 findings: the corpus/leak ones (A2, A4–A8) are fixed. A1/A3 are only partly fixed, B2/B4 partly, B3 half, C3 still open.
- new bug:
reap_shrink's prior keeps every dead expert. all 8 zero-hit experts in the model are kept at every K, and the method replaces 40% of top-10 slots at K=224. - replayer math: correct. lazy EMA matches an eager reference exactly. there are 3 edge cases (below).
- for 1c, prioritize
reap_l2_sumandreap_hybrid. don't slicereap_shrinkuntil its prior is fixed.
1. v1 findings vs v2 #
| id | status | evidence |
|---|---|---|
| A1 prefix only | partial | --chunks 64 -c 2048 = 131,072 tok of a 3.78 MB file (~1M tok), so ~12%, about the first ~35–40 of ~330 rounds. the round-robin (assemble_corpus.py L219–229) makes the prefix mixed, but only ~40/300 ukisai, ~10/24 TB, ~40/260 whittle and ~40 JA docs are seen. the tail of the file is JA-only rounds (ja list ≈ 330 > 300). |
| A2 parse-special | fixed | flag is in the command. ukisai and hotchpotch are still hand-wrapped (L64, L184), not apply_chat_template. minor. |
| A3 char vs token | partial | the per-round budget is a char heuristic (comment at L208–213). it was never tokenized, so the shares aren't verified. |
| A4 TB leak | fixed | xhigh only, one prompt per task, task-disjoint assert (L80–91) |
| A5 ChiTako leak | fixed | eval JA now comes only from hotchpotch. ChiTako is shingle-deduped (L156–163), and hp is url-unique plus shingle-filtered against everything before it (L168–192). |
| A6 ukisai leak/preamble | fixed | last episode per trial, one trial per base task, disjoint assert, preamble kept in 2 docs only (L29–71). nit: the regex at L26 strips only one suffix, so X_run2__dsv4… → X_run2. |
| A7 whittle | fixed | prompt+response, kind quotas, left-truncation (L115–142). only shard 0 is used. |
| A8 hotchpotch split | fixed | explicit test-00000-of-00001.parquet (L167) |
| B2 hook ids | partial | ids are still read from down_t->src[2] (patch L224–230), not ffn_moe_topk. the non-MUL_MAT_ID case now aborts (L225, good), but --moe-cache-mib would still silently mis-attribute experts. not triggered by this run. |
| B3 trace | half | header rewritten on every flush (L107–111) ✓. no (chunk,pos,token_id) per row ✗ |
| B4 router imatrix | open | return true at L220. minor. |
| nonfinite / empty-JSON guard | fixed | L149–155, L163. JSON nonfinite_drops = 0 |
| C1 metric | partial | shrink and hybrid were added, but the shrink prior is wrong (§3). no per-domain REAP. |
| C2 substitution screen | added | score_trace_substitution (select L73–107) |
| C3 vision | open | known. keep maps aren't exportable. |
2. reap_saliency_v2.json invariants (direct check, all 48 layers) #
Σcount == 1,310,720on every layer ✓.Σprob_sum == 131,072.000on every layer ✓.trace_tokens_written = 16384✓,nonfinite_drops = 0✓.count==0 ⇔ reap_l2_sum==0on every layer ✓. all values finite ✓. Cauchy–Schwarzsq_sum ≥ sum²/countholds everywhere, apart from one rounding tie at L30 e87 (count 1, %.6f).- zero-hit experts: 8 total (L0: 181, 193, 244, 271, 413, 424; L1: 116; L47: 371).
- the 9 keep maps match a re-run of
compute_layer_scoresexactly. they are sorted, unique, size K and nested across K (224 ⊂ 256 ⊂ 320).
per-layer view at K=224 (share of count kept / share of reap_l2_sum kept / smallest kept count):
| L | l2_sum | hybrid | shrink | overlap sum∩hyb, sum∩shr, hyb∩shr | μ_layer vs median expert mean |
|---|---|---|---|---|---|
| 0 | .59/.78/927 | .56/.77/927 | .53/.74/0 (6 dead kept) | 204,190,210 | .159 vs .126 |
| 10 | .73/.76/1552 | .67/.72/393 | .54/.61/177 | 186,140,177 | .118 vs .110 |
| 13 | .71/.75/1597 | .64/.70/333 | .55/.62/227 | 177,143,187 | .105 vs .097 |
| 22 | .67/.74/1599 | .61/.71/287 | .55/.66/287 | 184,162,201 | .169 vs .151 |
| 47 | .88/.95/856 | .74/.89/0 | .59/.83/0 | 165,142,201 | .616 vs .308 (expert 143: mean 5.56, expert 445: E[x²]=726) |
whole-model top-10 slot replacement (exactly 1 − kept count share, over all 131K tokens):
| K | l2_sum mean/worst | hybrid | shrink |
|---|---|---|---|
| 320 | .116 / .226 (L0) | .169 / .258 | .231 / .295 |
| 256 | .194 / .343 | .258 / .372 | .342 / .408 |
| 224 | .243 / .409 | .308 / .440 | .402 / .470 |
3. shrink prior bug (select_and_eval_prunes.py L47–54) #
mu_layer = Σreap/Σcount is the count-weighted mean. heavy hitters push it above most experts' own means: 63% (L0) to 85% (L47) of experts have a mean below μ. a zero-hit expert scores exactly μ, so it lands in the top ~15–37%.
result: reap_shrink keeps all 8 dead experts at K=224, 256 and 320, plus 26–40 experts with fewer than 100 hits in L47. reap_hybrid inherits this at K=320 (8 dead) and in L47 at every K.
fixes, pick one:
- use the median of the per-expert means as the prior;
- use a lower confidence bound,
mean − z·sd/√n(sd fromreap_l2_sq_sum, which is already in the JSON); - or set
count ≥ c_min.
the hybrid's max-normalization (L66–69) is also fragile: one L47 outlier compresses the shrink term to p50 = 0.06. rank-averaging would be more robust.
4. replay_moe_cache.py #
- lazy decay (L137–211): stored S = Σ credit·d^−s, true score = S·d^t. eviction compares within one step, so the shared factor cancels. the rescale at 1e100 happens before the current credit is added, which is correct. checked against an eager decay-every-expert reference on a 3,000-token synthetic trace with decay 0.99 and 0.9 (0.9 triggers the rescale): miss counts match exactly for ema, near_pick_ema and inverse_router_ema. self-test passes.
- Belady pointer advance (L147–151) is correct when λ=0, and λ>0 is skipped (L288).
- edge 1, keep-map filtering (L58–68): after filtering, fewer than
k_usedsurvivors may remain in the top-64.select_moce_top_kthen returns fewer than 10 experts, and the real pruned model would fetch rank >64 experts that aren't in the trace, so misses are undercounted. count these tokens and treat the missing slots as forced misses. - edge 2:
if actual_tokens < n_tokens(L33) means a header with ntok=0 parses 0 tokens. useactual_tokenswhen the header is 0. this is moot for v2 traces, since the header is written on every flush. - edge 3:
ema_decayandnear_pick_nhave no CLI flags, so 0.99 and 48 are hard-coded. - validity: the v2 trace is the first 16,384 calibration tokens (≈ first 5 rounds). replaying keep maps on it is in-sample, because the maps were fit on those tokens. it's also teacher-forced prefill, not decode.
- perf: tuple parsing is ~100M Python objects for 16K × 48 × 128 (several GB). numpy would fix it.
5. what to slice in 1c #
reap_l2_sumat K=320 and 256. it has the lowest slot replacement by a wide margin, and kept experts all have ≥ 856 hits.reap_hybridat K=320 and 256, as the rare-specialist hedge. it shares 263–302 of 320 experts with l2_sum, so the KL gap is an informative ablation.- K=224: only
reap_l2_sum(24% slot replacement on average, 41% at L0). reap_shrink: don't slice as-is. regenerate with a median or LCB prior first.
the 8 dead experts may be vision or rare-path experts (C3). that's an argument for adding image calibration, not for keeping them by accident.