feat: resample ablation for self-repair (#35) - #43
Conversation
Adapter hooks for resample ablation, mirroring identity_forward: - residual_of — pick the residual stream(s) in raw layer-output coords (no token-slice, no norm); the coordinate δ is captured/applied in. Default single stream; Mitra returns both (support, query). - resample_forward — input residual + donor_δ, in the layer's own output shape (LimiX 3-tuple, Mitra both streams). - label_token_index — token-axis label position for label←label bucketing (LimiX -1, Mitra 0, None for 3D TabICL/TabFM). Toy 4D + double-stream adapters get matching overrides; TestResampleHooks pins the round-trip invariant (re-applying a layer's own δ reproduces its output). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- core/donor.py: donor_deltas (one table's per-layer δ = residual_of(out) − residual_of(in)) + build_donor_delta (target-shaped δ, each position filled by a random draw from the donor's same-role positions). Buckets: row-role context/query always; token-role label←label / feature←feature for 4D. Draw with replacement (donor/target row & feature counts differ). - core/interventions.py: resample_layer — layer returns input + donor_δ for the context (skip_layer is the special case δ:=0). Role integrity tested by tagging (each bucket a constant), incl. the hard constraint that a feature δ never lands on the label token. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- ablation_sweep(ablation={zero,resample}, donor_tables, n_donors=8, seed):
resample draws n_donors leave-one-out tables without replacement, swaps each
layer's δ for a role-matched donor δ, and averages the METRIC over donors (not
the δ — averaging δ = mean ablation, off-manifold). Keeps {metric}_p25/_p75 for
the IQR band; "skip" key unchanged so the plots read either mode.
- run_self_repair_sweep.py: --ablation/--donor/--n-donors; loads all tasks up
front so the others serve as each target's donor pool (--donor table only).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
#35) Smoke on real LimiX hit IndexError when a donor table has fewer features than the target: _fill_block indexed the donor block with the target's token indices. Resolve label/feature indices per block (target cells from target tokens, donor pool from donor tokens) so cross-table draws survive any feature-count mismatch. Regression tests: donor narrower / wider than target on the token axis. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
validate_resample_v1.py — is resample on-manifold + norm-preserving? Off the decoded position (label token 4D / row 3D), zero as the reference: - ③ residual-norm preservation (money plot): ‖resid_ablated‖/‖resid_clean‖ per layer, zero vs resample. - ① ‖donor_δ‖/‖native_δ‖ ratio. - ② cos(donor_δ, native_δ) vs within-table native pairs. Cheap: post-ablation residual at layer m = clean input + intervention (upstream frozen) → one clean forward + one per donor, no per-layer re-forward, no decoders. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
V1 — donor
|
| model | zero's worst-hit layers | ||
|---|---|---|---|
| LimiX | 1.00 | 0.14 | first layer |
| Mitra | 1.00 | 0.52 | first / last |
| TabICL | 0.96 | 0.64 | mid–late |
| TabFM | 0.99 | 0.66 | early |
Takeaway: resample hugs 1 (blue), zero collapses (red); the gap = the magnitude distortion zero creates and resample removes. Tight bands → holds across all 15 datasets, not just on average.
LimiX caveat: decoded position = query label token; its norm is near-constant across tables (layer 0 dominates + per-token normalization) → band ~0, and L1–11 can't drop the norm regardless of ablation.
- Informative layer = L0 (zero 0.14 → resample 1.0); elsewhere ③ is structurally trivial here.
Claim 2 · Direction — the donor $\delta$ points like a normal $\delta$
Why:
- Matched size (Claim 1) ≠ on-manifold — a right-sized
$\delta$ in a direction the model never uses is still a shock. - Direction is the other half.
- Test: does the donor
$\delta$ point like a real$\delta$ , not orthogonal noise?
Cross-table alignment
Reads as: direction only. (
-
$c_{\text{within}}$ = cosine of two real$\delta$ 's (different rows, same table) — the yardstick for "how aligned genuine$\delta$ 's already are". -
$c_{\text{cross}}$ = cosine of the donor$\delta$ vs the native$\delta$ it replaces. -
pass:
$c_{\text{cross}}$ tracks$c_{\text{within}}$ , both$\gg 0$ → the donor$\delta$ is just "another normal$\delta$ ". -
$c_{\text{cross}}\to 0$ = orthogonal → donor$\delta$ points where native$\delta$ 's never do → off-manifold noise (would fail). We don't see this.
| model | ||
|---|---|---|
| LimiX | 0.90 | 0.81 |
| Mitra | 0.93 | 0.84 |
| TabICL | 0.71 | 0.56 |
| TabFM | 0.90 | 0.84 |
Caveat: TabICL is 3D (one vector per row) →
Verdict
- Magnitude (③): resample preserves the norm (blue ≈ 1, tight bands); zero collapses it at high-$\delta$ layers (LimiX L0 0.14; others 0.52–0.66) — the off-distribution distortion Ablation method: resample (cross-table) instead of zero/skip #35 targets, confirmed on real models.
-
Direction (②): donor
$\delta$ points like a normal$\delta$ ($c_{\text{cross}}\approx c_{\text{within}}\gg0$ ) → on-manifold. -
No donor
$\delta$ rescale needed — resample already preserves the norm (design item D dropped). - → resample validated. Greenlights V2 (skip-vs-resample cross-check).
Method note: post-ablation residual at layer scripts/validate_resample_v1.py.
…and (#35) _model_stats now aggregates ③ per table (8 donors averaged within a table, per the N=8 design) and keeps every table's per-layer value (ratio_*_tbl) so the money plot can show a median line + IQR/min-max band (cross-dataset spread). Drops the ① δ-norm scalar from the outputs (subsumed by ③; not plotted). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
V2 — self-repair under a faithful ablation (cross-check)V1: resample is healthy (on-manifold + norm-preserving) — the better ablation. V2: redo the self-repair sweep with it. Does self-repair still show up, or was it a zero-only off-manifold artifact? Answer: on the decision margin, the faithful ablation independently shows self-repair (dip → recover) — and more stably. Zero is shown side-by-side for reference, not as ground truth.
Setup:
Claim 1 · Self-repair under the faithful ablationresample (right) is the faithful method; zero (left) is shown for reference. Reads as: under resample, each ablated layer dips to a red x (immediate hit) → climbs back to the black baseline (downstream repair) → self-repair, on resample's own terms. Zero shows the same shape, more violently. Takeaway: a better ablation, redone from scratch, lands on the same self-repair → a property of the model, not a zero off-manifold artifact. (Zero for reference, not ground truth.) Why margin, not gt_logit
Claim 2 · StabilityA faithful ablation should perturb consistently, not sometimes catastrophically. Zero, off-manifold, occasionally sends the readout off — big swings + overshoots (ablation makes margin better than baseline = nonsensical).
Takeaway: resample = moderate, layer-consistent, near-artifact-free. Smoothness partly from 8-donor averaging; no wild overshoot is the on-manifold property (V1). Verdict
Method: one clean forward + |
Address review comments on #43/#39: separate policy-agnostic δ-transport from the resample policy that supplies the δ. Transport (policy-agnostic): - resample_forward -> inject_delta_forward (base/mitra/limix adapters) - resample_layer -> inject_delta (interventions; skip = special case δ:=0) Policy (genuinely resample): - core/donor.py -> core/resample_ablation.py - scripts/validate_resample_v1.py -> validate_resample_donor_health.py - tests/test_donor.py -> test_resample_ablation.py Also: comment [-1]=final layer in layerwise GT-logit (#39). Kept build_donor_delta/donor_deltas/donor_delta — those are about donors. Tests: 36 passed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…figures (#35) The V2 PR comment's figures (margin zero|resample pairs, gt_logit pairs, stability bars) were produced by an ad-hoc inline script — not reproducible. Add validate_resample_self_repair.py to regenerate them from the sweep JSONs (run_self_repair_sweep.py --ablation zero|resample). Reproduces the published 1000/500 numbers exactly: resample std(imm) 0.10-0.16 vs zero 0.24-0.71; TabFM zero overshoot 3.50 -> resample 0.01; overshoot layers zero 12 -> resample 0. Drops the deprecated "resample reproduces zero" framing (Spearman/Pearson y=x agreement) — resample is the better ablation that lands on the same self-repair, not a reproduction of zero. Emits v2_pair_{model}.png, v2_pair_gtlogit_{model}.png, v2_stability.png. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>









Implements #35 — resample ablation. Full design discussion: issue #35.
What & why
zerokept as a second mode for the skip-vs-resample cross-check.Method
Contribution$\delta$ (what gets swapped).
residual_of(layer_output)picks the stream (no token-slice, no norm).Role matching (where a donor$\delta$ may come from). Same-role positions only:
Donor source = leave-one-out real tables. Pool = the other loaded TabArena tasks (target excluded). Model-agnostic + matches the eval distribution → cleanest norm match, no new data pipeline.
Changes (module by module)
adapters/—residual_of(pick stream, no slice/norm),resample_forward(input + donor_δ in the layer's tuple shape),label_token_index(LimiX -1, Mitra 0, None for 3D).core/donor.py—donor_deltas(one table's per-layer δ) +build_donor_delta(target-shaped δ by role-matched random draw).core/interventions.py—resample_layer(mirrorsskip_layer; δ:=0 == skip).evaluation/self_repair.py—ablation_sweep(ablation={zero,resample}, donor_tables, n_donors, seed);"skip"key unchanged so the plots read either mode.scripts/run_self_repair_sweep.py—--ablation {zero,resample}--donor {table,row,feature}(table only)--n-donors; loads all tasks up front for the LOO donor pool.Tests
Validation (done)
Full redundant-vs-repair still needs DE (#37); this swaps the ablation method only.
🤖 Generated with Claude Code