opus audit — Phase 3 local healing v2 verification #

scope: read-only verification of the v2 changes (scripts/heal_k256_q2_0.cpp, scripts/run_phase3_local_healing.py, scripts/patch_strata_metal.py, results/phase3_local_healing_validation.json, PLAN.md §2.7.A #31 and §7.1 item 6) against audits/opus_audit_phase3_local_v1.md. i re-ran the full 22-window / 2,686-token unseen KLD suite on the v2 model (the v2 report only ran 4 of the 22 windows), regenerated the v1 held-out 512-token MoE dump (bit-identical, ‖y_ref‖² = 36288.5374 both times) to compare v1 and v2 routed residuals, re-ran the ridge cross-validation probe on the new 1,022-token trace, and re-ran the 9-prompt speed suite on v2. no repo files edited; artifacts are in /tmp/p3audit/ (kld_results_v2.json, v2_eval_cal1024.tsv(.cv), v2_eval_heldout.tsv, speed_results_v2.json).

0. tl;dr #

  • v2 is a real but small improvement over v1, and it's good vs unhealed. v2 vs unhealed on all 22 windows: −28.6% pooled KLD [−33.5, −23.2] at MoCE 0 (wins 20/22), −27.0% [−32.0, −22.1] at MoCE 0.85 (wins 21/22), top-1 +2.3 / +1.5 pt. v2 vs v1: −5.1% [−10.8, −0.1] at MoCE 0 (11/22 windows), −3.3% [−7.6, +0.4] at MoCE 0.85, and top-1 −0.3 / −0.5 pt (n.s.). on the 18 windows whose context shares no 16-gram with the new calibration trace, v2 vs v1 is −3.5% [−8.8, +1.5] → not distinguishable from v1.
  • the routed-MoE local metrics improved cleanly and transfer: on the regenerated unseen 512-token dump, routed residual drops 24.13% (v1 shipped) → 23.37% (v2 shipped), with v2's real-trace α within 0.01 pt of the oracle and only 2/48 layers where α is worse than α=1 (was 22/48).
  • japanese got worse vs v1 (ja KLD 0.172 → 0.180 at MoCE 0, top-1 85.0 → 83.7; 3 of 5 ja windows regress), while ops / chat-json improved. the canary 192-token set says v2 is worse than v1 (−23% vs −35%), PLAN.md reports v2's canary numbers without saying so.
  • the "fixed tool-calling regression" is partly circular: the new calibration trace contains the same <tools> system block as the windows cj20_tool_5turn_t3 (117 shared 16-grams in its context), cj20_t3_answer_tool (117) and cj03_toolcall (43), and a different turn of the same persona conversation as persona3_t4 (85). the scored spans are clean (the self-test is right about that), but fixing cj20 by calibrating on cj20's boilerplate is in-distribution, not generalization. and the 4-window v2 benchmark in the results JSON includes exactly that window.
  • code: the α, cache-fingerprint and I/O changes are correct. two real defects: (1) the yr_q2 fingerprint doesn't cover the gate/up experts, so a gain_gu sweep still silently reuses a stale cache; (2) "full-rank 640/640" is false for L0 (rank 430) and L2 (617). plus a doc/code mismatch on the W_down importance exponent. the stream.cpp guard is correct; the multi-chunk dump is untested (the 1,023-token dump is single-chunk) and holds whole files in RAM.
  • stream.cpp / speed on v2: see §4.

1. code verification #

1.1 --quantize #

  • W_gate/W_up importance: now w_d = sqrt(E[mixed_d²]) from the 1,022-token dump, used as the per-element weight on squared error in the LS/score. correct plumbing. note that the diag-Hessian-optimal weight on squared weight error is E[x_d²] itself (β=1); β=0.5 is a tempering choice, so call it that, not "AWQ". it's untested against β=1 or β=0. fine as a default.
  • W_down importance: s_down[c] = (vg·vu)^(1/4), vg = Σ_d g_cd² E[x_d²], i.e. sqrt(σ_g σ_u). the header comment and PLAN say "SwiGLU RMS sqrt(E[h_c²])", but E[h²]^(1/2) ≈ σ_g σ_u, so the code uses β=0.25 on W_down vs β=0.5 on gate/up: an undocumented inconsistency. it also assumes diagonal, uncorrelated, zero-mean inputs and ignores the SiLU. the exact per-expert E[h_c²] is computable from the routed tokens in the same dump (≈20 tokens per expert on average, which is thin but unbiased; you could blend toward the analytic estimate). low priority, but fix the docs either way.
  • the hc_ffn_norm fallback writes max(ms2·0.25, 1e-6) (a variance) into layer_s_in, while the real path writes an RMS. so the two code paths aren't on the same scale. harmless since the runner always passes cal_pfx, but the fallback should be sqrt(...) or deleted.
  • for layers whose gate/up are already Q2_0 in the source, gate/up are still dequantized for the down importance only when td != Q2_0. that's correct.
  • verified that stage 1 actually re-ran with the new binary: d_bak.bin 21:03 > heal binary 20:56 > .mixed 20:23.

1.2 --calibrate-and-heal #

  • α (Part A): γ=0 → α = cos/ratio = ⟨y_ref,y_q⟩/‖y_q‖², computed over all tokens and all routed experts (via the routed yr_q2). my probe reproduces the shipped α to within 5.8e-5 of the in-sample oracle. the cos ≥ 0.1 clamp is inert. idempotent (rewrites from d_bak). ✓

  • cache fingerprint: hashes d_bak (down scales) + layer-0 .mixed + t_cal. two holes:

    1. yr_q2 depends on the gate/up Q2 weights too, and those aren't hashed. a --quantize re-run that changes only gain_gu (or the gate/up importance β) leaves d_bak byte-identical (down importance is computed from the source gate/up, not the Q2 ones), so the stale cache is accepted. this is exactly the sweep scenario the fingerprint was meant to catch. fix: hash the healed .experts gate/up slices (or just its mtime+size), plus gain_gu.
    2. layer-0 mixed is a function of the embeddings/dense weights only, so a dump from a different expert quantization (e.g. re-dumping from Q2 instead of IQ3_S) hashes identical. hash a late layer too (e.g. L47 .mixed or .yr).
  • ridge (Part B): math unchanged. my probe on the 1,022-token trace: rank(Z) = 640 for 46/48 layers, but L0 = 430 and L2 = 617, so "full-rank (640/640)" in PLAN.md is wrong for 2 layers (λ>0 keeps it well-posed anyway). effective dof at ρ=6 is 5–70 of 640. cross-validation (float ΔW):

    split ρ=0.25 ρ=1 ρ=6 (shipped, η=.15) ρ=24
    in-sample −10.7% −6.7% −2.9% −1.4%
    leave-one-block-out (4 contiguous) −1.6% −1.4% −0.9% −0.6%
    even/odd −3.9% −3.1% −1.7% −1.0%

    doubling T didn't change the picture: in-sample is still ~3× LOBO, and the realized gain after IQ4_NL requant is −1.10% in-sample (25 multipliers; v1 with 7 got −1.68% on its own trace) and −0.46% on the unseen dump (v1: −0.36%). still safe, still ~noise. the PLAN line "MoE MSE 19,061 → 18,665 → 18,461 (−3.15%)" is in-sample and mostly α (−2.08%), the ridge is the last −1.10%.

  • writers file now holds only the 48 changed ffn_down_shexp tensors, transplant count 48 / 1175 untouched. ✓ error-checked I/O ✓. t_cal inferred from file size with divisibility check ✓ (but .ids/.wts/.yr sizes aren't cross-checked against it).

1.3 patch_strata_metal.py #

  • stream.cpp guard if (moce_bonus_ > 0.0f) { wait + evict }: correct and matches the v1 recommendation. at MoCE 0 routing can't see smap_, so skipping is safe. source and binary are consistent (stream.cpp 20:27:45, CLI 20:28:12).
  • multi-chunk dump_moe_layer: logic reads the previous whole file into a static std::map<ext, vector> at l=0 and rewrites [old rows | new rows] per layer. as far as i can trace it's correct for in-order layers, with these caveats: (a) it's never exercised, since the 1,023-token dump with PROMPT_CHUNK=1024 is a single chunk (T=1022); (b) per chunk it holds a full previous copy of .mixed, .yr and .hh in RAM (≈0.5+0.5+0.03 GB at T=1022, growing linearly) inside the inference process, and rewrites everything per chunk (O(chunks²) I/O); (c) first_chunk keys on last_T_ == 0 && s_pfx != pfx with static state, so a second dump in the same process with the same prefix appends instead of resetting (fine for the one-shot CLI, wrong for a server); (d) fseek with long is fine on macOS LP64 only. add a 2-chunk test (e.g. PROMPT_CHUNK=512 on the 1,024 file, compare against the single-chunk dump byte-for-byte) before relying on it.

1.4 run_phase3_local_healing.py #

  • stage 1 is still skipped if .experts + d_bak exist, with no fingerprint, so changing gains or the cal trace requires manual deletion. same class of hazard as §1.2.
  • verify_disjoint_calibration_prompts checks 16-grams of scored spans only. it passes, correctly. but it doesn't check contexts or doc identity, and the new trace shares context with 4 windows (§2.2). extend it to report context overlap and doc-level reuse, and fail on doc reuse.
  • the results JSON's unseen_22window_2686tok_audit block is v1 numbers (copied from my audit file), not v2. that's labeled healed_v1_* in the keys, so it's honest, but PLAN.md mixes the v1 22-window and v2 4-window numbers in one bullet.

2. falsifier: full 22-window unseen KLD on the v2 model #

same 22 windows, same flags as v1 (§3.1 of the v1 audit). the IQ3_S reference was re-run and reproduced every window's PPL exactly (deterministic). v1/unhealed numbers are from the v1 audit runs, same binary family (the MoCE-0 path now skips the eviction wait, which can't change logits at MoCE 0).

2.1 pooled (95% CI = window bootstrap) #

comparison KLD A → B Δ KLD windows B better Δ top-1
unhealed → v2, MoCE 0 0.2029 → 0.145 -28.55% (-33.5, -23.2) 20/22 +2.27 (1.29, 3.4)
unhealed → v2, MoCE .85 0.2123 → 0.155 -26.98% (-32.0, -22.1) 21/22 +1.53 (0.41, 2.7)
v1 → v2, MoCE 0 0.1527 → 0.145 -5.08% (-10.8, -0.1) 11/22 -0.26 (-1.15, 0.51)
v1 → v2, MoCE .85 0.1602 → 0.155 -3.26% (-7.6, 0.4) 13/22 -0.52 (-1.39, 0.31)
v1 → v2, MoCE 0, 18 clean windows 0.1522 → 0.1469 -3.51% (-8.8, 1.5) 8/18 -0.37 (-1.35, 0.49)
v1 → v2, MoCE .85, 18 clean 0.1591 → 0.1568 -1.45% (-5.5, 2.0) 10/18 -0.51 (-1.56, 0.54)
isolated MoCE (m.85 vs own m0), v1 → v2 0.0389 → 0.0325 -16.53% (-28.3, -4.4) 16/22 +1.34

"clean" = the 18 windows whose full token file (context + scored) shares no 16-gram with cal_disjoint_1024tok.txt.

reading: v2 clearly beats unhealed (−28.6%, a bit more than v1's −24.7%) and the falsifier isn't triggered. v2 vs v1 is a small, marginal gain at MoCE 0 that loses significance once the 4 context-overlapping windows are dropped, and top-1 doesn't move. the clearest v2 win is isolated MoCE robustness (−16.5% vs v1).

2.2 per stratum, KLD / top-1 (token-pooled) #

model ops chat_json ja persona pooled
unhealed, MoCE 0 0.1650 / 89.7 0.1554 / 89.5 0.2374 / 80.2 0.3576 / 72.1 0.2029 / 85.22
v1, MoCE 0 0.1199 / 90.9 0.1243 / 90.4 0.1723 / 85.0 0.2637 / 78.4 0.1527 / 87.75
v2, MoCE 0 0.1077 / 90.6 0.1130 / 90.8 0.1796 / 83.7 0.2527 / 77.9 0.1450 / 87.49
unhealed, MoCE .85 0.1589 / 91.4 0.1702 / 89.5 0.2513 / 79.6 0.3702 / 72.7 0.2123 / 85.55
v1, MoCE .85 0.1175 / 90.0 0.1341 / 90.2 0.1887 / 84.1 0.2683 / 80.7 0.1602 / 87.60
v2, MoCE .85 0.1070 / 90.6 0.1267 / 90.1 0.1929 / 82.2 0.2652 / 79.2 0.1550 / 87.08

ops and chat-json improve vs v1. ja regresses vs v1 (MoCE 0: 0.172 → 0.180, top-1 85.0 → 83.7; ja05, ja12, ja30 each 4–8% worse). persona is flat (−4% KLD, −0.5 pt top-1). so the new calibration mix traded some japanese for tool/ops. its only ja content is one doc (ja[2], ~100 tokens), versus a big block of tool calls.

2.3 the 4 context-overlap windows (†) #

the scored spans are clean, but the calibration trace contains the identical <tools> system block, and for persona3 a different turn of the same conversation:

window shared 16-grams with cal (whole window) v1 → v2 KLD (MoCE 0)
cj20_tool_5turn_t3 117 / 701 0.1783 → 0.1444 (−19%)
cj20_t3_answer_tool 117 / 742 0.0927 → 0.0430 (−54%)
cj03_toolcall 43 / 1064 0.0631 → 0.0621
persona3_t4 85 / 1974 (same doc, calibration_corpus conversation 3) 0.2856 → 0.2983 (+4%)

the cj20 "regression fix" is real but in-distribution. note that cj20 at MoCE 0 is now tied with unhealed (0.1444 vs 0.1440), not better. the results JSON's 4-window benchmark (ops12, cj20, ja22, persona0) was chosen after seeing v1's per-window losses and includes cj20, so its −29.7% headline is optimistic; the full-suite figure is −28.6%.

2.4 per window #

† = context shares 16-grams with the calibration trace.

window unhealed m0 v1 m0 v2 m0 v2 vs v1 unhealed m.85 v1 m.85 v2 m.85 top1 m0 unh/v1/v2
ops14_csharp_bug 0.0797 0.0767 0.0775 +1.1% 0.0776 0.0834 0.0791 89.8/92.2/90.6
ops17_term_multiturn 0.0627 0.0331 0.0344 +3.9% 0.0595 0.0437 0.0374 95.3/95.3/95.3
ops19_term 0.0581 0.0513 0.0583 +13.7% 0.0603 0.0332 0.0456 93.0/91.4/93.0
ops12_java_turn1 0.3805 0.2778 0.2489 -10.4% 0.3384 0.2758 0.2567 79.7/82.8/82.0
cj03_toolcall † 0.0978 0.0631 0.0621 -1.6% 0.1349 0.0780 0.0731 96.1/98.4/99.2
cj20_tool_5turn_t3 † 0.1440 0.1783 0.1444 -19.0% 0.1534 0.1363 0.1417 91.4/89.8/91.4
cj15_php_review 0.0947 0.0703 0.0654 -6.9% 0.0959 0.0908 0.0838 85.9/89.1/87.5
cj19_italian 0.1779 0.1493 0.1497 +0.3% 0.1982 0.1839 0.1735 82.8/81.2/84.4
cj06_dostoevsky 0.4949 0.3406 0.3320 -2.5% 0.5289 0.3677 0.3618 78.9/82.0/79.7
cj07_json_extract_near 0.0420 0.0348 0.0349 +0.0% 0.0442 0.0322 0.0410 95.3/95.3/95.3
cj17_math 0.2147 0.1663 0.1645 -1.0% 0.2379 0.1839 0.1805 82.0/83.6/83.6
ja05 0.2284 0.1627 0.1691 +3.9% 0.2378 0.1666 0.1744 79.7/82.8/82.8
ja12_table 0.2305 0.1847 0.1982 +7.3% 0.2354 0.2060 0.2181 77.3/85.9/79.7
ja22_pedal 0.2965 0.1856 0.1845 -0.6% 0.3175 0.2114 0.2162 80.5/85.2/85.2
ja30_climate 0.2173 0.1750 0.1859 +6.3% 0.2397 0.1880 0.1833 82.0/85.2/86.7
persona0_t2 0.4100 0.2858 0.2878 +0.7% 0.4163 0.2945 0.3020 68.8/76.6/77.3
persona3_t4 † 0.3872 0.2856 0.2983 +4.4% 0.4340 0.3357 0.3125 73.4/76.6/73.4
cj07_answer_json 0.0006 0.0001 0.0001 -1.1% 0.0019 0.0001 0.0002 100.0/100.0/100.0
cj20_t3_answer_tool † 0.1030 0.0927 0.0430 -53.6% 0.1053 0.1090 0.0615 95.3/96.1/97.7
ja05_answer 0.1030 0.0625 0.0665 +6.5% 0.1058 0.0885 0.0753 86.4/90.9/86.4
persona0_t2_answer 0.2755 0.2197 0.1721 -21.7% 0.2603 0.1746 0.1811 74.2/82.0/82.8
ops19_answer_json 0.2441 0.1606 0.1194 -25.6% 0.2587 0.1515 0.1160 90.6/93.0/92.2

2.5 the 192-token canary #

v2 canary macro KLD 0.1563 vs v1 0.1320 (MoCE 0), i.e. v2 is 18% worse than v1 on the canary (−23.1% vs −35.1% vs unhealed). given §2.1, that's mostly the canary's 192-token noise, the same thing that made v1 look better than it was. but PLAN.md reports the v2 canary numbers as gains without the v1 comparison, which reads as if nothing got worse.

3. local MoE metrics on the regenerated unseen dump (512 tok, bit-identical to v1's) #

α=1 (AWQ only) shipped α oracle α (on this data) shipped α + ridge layers α worse than 1
v1 24.31% 24.21% 23.81% 24.13% 22/48
v2 23.92% 23.48% 23.47% 23.37% 2/48

so all three v2 local changes transfer: the importance-weighted quantizer is −1.6% relative (α=1 column), the real-trace α now captures essentially all of the oracle gain (vs 20% for v1's Gaussian α), and the ridge adds −0.46%. caveat: this dump contains ~100 tokens of calibration_corpus conversation 1, which the new trace also samples (different region, 0 shared 16-grams, 4 shared 8-grams), plus the cj20 tool boilerplate.

4. speed / hit rate on v2 (9-prompt suite, MoCE .85 × 2 reps + MoCE 0) #

prompt v1 hit (m.85) v2 hit (m.85) v2 misses/tok v2 decode tok/s r0/r1 v2 det. r0==r1 v1 hit (m0) v2 hit (m0)
ja_pedal_398 0.9276 0.9253 35.9 9.5 / 11.19 yes 0.8422 0.8471
json_extract_163 0.9560 0.9560 21.1 10.68 / 10.07 yes 0.8663 0.8699
tool_multiturn_588 0.9098 0.9088 43.8 13.17 / 11.71 yes 0.8290 0.8390
persona_458 0.9304 0.9126 41.9 11.14 / 11.04 yes 0.8453 0.8469
math_43 0.9443 0.9455 26.2 9.48 / 10.46 yes 0.8454 0.8469
ops_csharp_713 0.9249 0.9272 34.9 8.95 / 11.1 yes 0.8409 0.8422
coldshift_ja2rust 0.9394 0.9364 30.5 10.7 / 10.43 yes 0.8690 0.8661
prompt_43tok_wal 0.9313 0.9295 33.8 10.83 / 9.45 yes 0.8494 0.8551
prompt_1024tok_ops 0.9309 0.9248 36.1 11.71 / 9.14 yes 0.8448 0.8473
  • mean combined hit at MoCE .85: v1 0.9327 → v2 0.9296 (−0.3 pt); MoCE 0: 0.8480 → 0.8512. persona_458 drops 1.8 pt (misses/tok 33.4 → 41.9) and ops1024 drops 0.6 pt (matching the results JSON's own 0.931 → 0.921). free-decode trajectories differ between models, so a single prompt is a single sample, but persona is the target workload: re-check it with more persona prompts before calling it noise.
  • determinism: 9/9 bit-identical across reps at MoCE .85 on v2 ✓. MoCE 0 path (guard skips the wait) ran clean on all 9.
  • PLAN.md §7.1 item 6 quotes the 9-prompt hit rates (93.27%, 90.98–95.60%) and 9/9 determinism as if they were v2. they are v1-model numbers. update them to v2: mean 92.96%, min 90.88% (tool), max 95.60% (json).

5. PLAN.md corrections #

  1. "full-rank (640/640)" → 46/48 layers full rank; L0 rank 430, L2 rank 617.
  2. "W_down by per-expert analytical SwiGLU RMS (σ_g σ_u)^(1/2)": that's E[h²]^(1/4), not the RMS. state the exponent honestly (β=0.25 vs 0.5 on gate/up).
  3. the "4-stratum unseen benchmark −29.67%" headline → replace with the full 22-window v2 result: −28.6% [−33.5, −23.2] at MoCE 0, −27.0% at MoCE .85, +2.3 / +1.5 pt top-1, v2 vs v1 −5.1% [−10.8, −0.1] (n.s. on clean windows), ja regressed vs v1.
  4. "eliminating the cj20 regression" → note the tool boilerplate in the calibration trace (in-distribution), and that cj20 is tied with unhealed, not better.
  5. canary: add v1 → v2 (0.1320 → 0.1563, worse) next to the v2 numbers.
  6. 9-prompt speed/determinism → v2 numbers (§4), including the persona hit-rate drop.
  7. "0 16-gram overlap against all 22 unseen windows": true for scored spans only. add "4 windows share context (tools block / same persona conversation)".

6. recommendations #

  1. ship v2 (it's at least as good as v1 and clearly better than unhealed), but quote −28.6% / +2.3 pt on 22 windows, not the 4-window number.
  2. rebalance the calibration trace: more japanese (ja regressed), and swap persona conversation 3 for one that no eval window uses. then the tool/persona gains can be claimed as out-of-sample. the clean move is to freeze a doc-level split file (cal docs vs eval docs) and make the self-test enforce it.
  3. fix the fingerprint (gate/up + late-layer hash) before any gain_gu / β sweep.
  4. test the multi-chunk dump against a single-chunk dump byte-for-byte before using T > 1,022.
  5. ridge: still ~0.5% out of sample. keep or drop; don't spend more on it.
  6. next real lever: per-expert exact E[h_c²] for W_down and a β ∈ (0.5, 1) ablation for gate/up, both evaluated on the 22 windows plus a held-out ja/persona expansion. persona remains the worst stratum (KLD 0.25, top-1 78%).

v3 verification addendum #

re-checked PLAN.md (§2.7.A #31, §7.1 item 6), scripts/heal_k256_q2_0.cpp (identical to vendor/strata/metal/src/heal_k256_q2_0.cpp, binary rebuilt after the source), scripts/run_phase3_local_healing.py (--self-test PASS, re-run by me) and results/phase3_local_healing_validation.json. nothing re-measured; all numbers cross-checked against my v2 runs.

closed:

  • PLAN.md: all 7 §5 corrections are present in both #31 and item 6:

    • rank 46/48 full, with L0 = 430 and L2 = 617;
    • the β = 0.25 vs 0.5 exponents;
    • the 22-window v2 numbers (−28.55% / −26.98%, +2.27 / +1.53 pt), plus v2 vs v1 −5.08% and the clean-18 −3.51% not significant;
    • the japanese regression;
    • cj20 being in-distribution and tied with unhealed;
    • the canary v1 → v2 got worse;
    • the v2 speed numbers (92.96%, persona 93.04 → 91.26%);
    • the 4 context-overlapping windows.

    no stale headline is left (no −29.67%, no "eliminating the cj20 regression", 93.27% only as the v1 reference).

  • results JSON: the v2 22-window block and the v1/v2 9-prompt block match kld_results_v2.json / speed_results_v2.json exactly.

  • yr_q2 fingerprint: now 4 × uint64: d_bak, L0 + L47 .mixed, gate/up of expert 0 at L0/L24/L47 from the healed experts, and t_cal. a global gain_gu / β change rewrites expert 0's gate/up, so it's now caught, and the L47 .mixed catches a teacher swap.

  • cross-checks: .yr/.ids/.wts/.hh sizes are now checked against t_cal.

  • fallback: the hc fallback now writes an RMS.

  • self-test: it now asserts 0 scored-span overlap plus exactly 18/22 context-clean windows, and names the 4 overlapping ones.

still open (minor, none affects the shipped model):

  1. run_phase3_local_healing.py skips Stage 1 if .quant.meta merely exists; it doesn't compare the stored (gain_gu, gain_d, cal_pfx, t_cal) against the values it's about to request. change the "0.50", "0.30" args and Stage 1 is still skipped. read the 3 uint64s and compare them.

  2. .quant.meta hashes the cal_pfx path string, not the dump contents. the dump step likewise only checks that .mixed/.yr exist, so a re-made cal_disjoint_1024tok.txt under the same prefix wouldn't invalidate anything. hash the token file into the meta.

  3. provenance: .quant.meta (22:43:19) and cal_teacher_yr_q2_1024.bin.meta (22:43:20) are newer than the data they vouch for: d_bak is 21:03 and the yr_q2 bin is 21:04. so they were written without re-running --quantize or recomputing yr_q2.

    • the d_bak + .mixed hashes would match regardless.
    • the gate/up sample is the one input nothing re-read: .experts was rewritten at 22:44 (α re-applied from d_bak, deterministic), but no --quantize ran.
    • stored quant.meta values: gain_gu 0.5, gain_d 0.3, t_cal 1022, which is consistent.

    fine for now. just don't treat these metas as having validated the cache. the next real run will recompute or confirm them.

  4. the gate/up hash samples 1 expert × 3 layers. that's adequate for global knob changes, but it would miss a per-layer or per-expert-only edit. acceptable; document it.

verdict: v2 corrections verified. the remaining items are about cache/skip hygiene for future sweeps, not about the current K256-q2_0-healed artifact or its reported numbers.