Phase C · mechanistic interpretability

Activation patching / causal mediation

Vig et al. 2020; ROME, Meng et al. 2022

← back to the method catalogue

What it does?

Activation patching, also called causal mediation, is a core mechanistic-interpretability method. It runs the system once, records an internal value, and then, on a second run, overwrites, or 'patches', that internal value with the one recorded from a different input. The change in the output measures how much that internal value causally mediates the behaviour. In language-model work this is how researchers localise where a fact or a computation lives inside the network. The idea is genuinely causal: it does not correlate, it substitutes a value and observes the effect. We apply it to the VCS by patching a recorded state value, a RAM cell or register, from one run into another and measuring the causal effect on the output. On this machine the operation is special. Clamping a cell to a donor value and re-running the real ROM is literally the same operation the intervention oracle performs. So activation patching cannot disagree with the oracle: it recovers the exact causal-effect table by construction. This makes it the clearest demonstration that where a method's operation is itself a real intervention on a fully observable machine, faithfulness is automatic. The catch, taken up elsewhere, is that recovering the exact effect table is still not the same as recovering what the patched variables mean.

Activation patching / causal mediation result

Each point is one intervention site: its exact causal effect from the oracle (x) against the method's recovered/approximate effect (y). Points on the dashed diagonal mean the method recovered the true effect. max_abs_recovered_minus_exact = 0.000 — this example only (pong, state gameplay(seed=0,prefix=90)+15); the audit aggregate is below.

How it's scored

The score is the maximum absolute difference between the recovered patch effect and the exact patch effect from the oracle, taken across every site and every core game. The grading is always against the intervention oracle. This method is exact by construction, so the difference is zero. Clamping a cell to a donor value and re-running the real ROM is literally the same operation the intervention oracle performs, so the recovered effect table cannot disagree with the oracle, and site precision and recall are both perfect. Faithfulness for this method is therefore at the ceiling, level with the oracle-as-method positive control. There is no sprite-position hard-case here: because the method intervenes on the real machine rather than taking a derivative, it works on position outputs exactly as it works on content outputs. The important qualification, which the score does not by itself capture, is that recovering the exact effect table is not the same as recovering what the patched variables mean or how they combine into the computed function. The audit box reports the measured triad. This method is the clearest demonstration that when a method's operation is itself a real intervention on a fully observable machine, faithfulness is automatic.

The score is measured against the §1 intervention oracle — never against another interpretability method. F (faithful) is always vs the oracle; see the execution stack. How each of F / S / M is actually computed for this method (and whether it matches the paper) is in the box just below; the numbers are in the In the audit box under it.

How F, S, M are computed here

The exact formula this method uses for each score, read from its runner, and whether it matches the paper's §3 (F ∧ S ∧ M triad) definition. From the committed audit fsm_math_phaseC.json.

AxisFormula (as computed) What it measuresMatches §3?
F
faithfulness
\(F = 1 - \max_u|\,\hat{\Delta y}(u)-\Delta y(u)\,| = 1\)The patch literally re-runs the exact single-site intervention, so recovered effect equals the oracle effect and the causal-effect agreement is 1.✓ matches activation_patching.jl main record (metric max_abs_recovered_minus_exact=0 -> F=1.0); jl:693 position record F = _pearson(rec_px, ex_px). Both are the paper's Phase-C patching F = causal-effect-agreement correlation vs Delta_y(u); here exact by construction. Record: extra.triad.F=1.0 (battery mean F=1.0, n=84).
how it's measured — call stack:
  1. main record: max_abs_recovered_minus_exact = 0 ⇒ F = 1 — activation_patching.jl:582
  2. position record: F = Pearson(recovered_px, exact_px) — activation_patching.jl:693
  3. true causal effect Δy(u) by bit-exact re-run — oracle_intervene.jl:242
S
sufficiency
\(S = \dfrac{\#\{\,u_{\mathrm{held}}:\ |\hat{y}-y|\le\varepsilon\,\}}{|\mathrm{held\text{-}out}|}\in[0,1]\)Held-out predictive sufficiency: fit the attribution->Δy map on a calibration half of the causes, predict the disjoint held-out half, count hits within an ε band.✓ matches common/triad_sm.jl sufficiency_score, called via triad_extra_dict (activation_patching.jl). Exactly the paper's held-out predictive estimator on the oracle's per-cause bit-exact re-runs. Record: extra.triad.S (activation_patching_pong.json S=1.0; battery mean S=0.996, n=84).
how it's measured — call stack:
  1. sufficiency_score: fit Δy=a·attr+b on a calib half, predict held-out within ε — triad_sm.jl:111
  2. assemble the triad via triad_extra_dict — activation_patching.jl:597
  3. true causal effect Δy(u) by bit-exact re-run — oracle_intervene.jl:242
M
minimality
\(M = |U^{\star}| / |\hat{U}| \in (0,1]\quad U^{\star}=\{u:\Delta y(u)>0\},\ \hat{U}=\{u:|\mathrm{attr}(u)|>\tau\}\)True minimal cause set size over the method-named set size.✓ matches common/triad_sm.jl minimality_score. Exactly the paper's M. Record: extra.triad.M=1.0, M_true_minimal_size / M_named_size (pong |U*|=6/|U_hat|=6; battery mean M=0.973, n=84).
how it's measured — call stack:
  1. minimality_score: |U*| oracle movers / |U_hat| named cells — triad_sm.jl:57
  2. assemble the triad via triad_extra_dict — activation_patching.jl:597
  3. true causal effect Δy(u) by bit-exact re-run — oracle_intervene.jl:242

✓ matches = the same quantity as §3; ◐ partial = the same kind of estimator but a differing detail; ✗ does not match = a different quantity (see the note). Definitions: F = agreement with the oracle's true causal effects Δy(u); S = held-out predictive score in [−1, 1]; M = |U*|/|Û| (true-minimal-set size / named-set size).

In the audit

This is the method's entry in the actual cross-method audit — scored on the paper's correctness triad, each axis a mean over all 42 scored games (84 committed §R records), not the single example shown above. Tradition: causal. The example figure (Pong) is one of those records.

1.000F — faithfulness vs oracle (mean over 42 games, ±0.000 CI95)
0.996S — sufficiency: held-out predictive (n/a where the paper does not define this axis)
0.973M — minimality: true-minimal-set / named-set (n/a otherwise)
84committed records aggregated
0.00human-plausibility proxy

F faithfulness (scored vs the oracle for every method) · S sufficiency (held-out predictive score in [−1, 1]; a negative value means the explanation predicts held-out interventions worse than the unperturbed baseline; reported for the predictive methods across all three phases where the calibration/held-out split is defined) · M minimality (true-minimal-set / named-set; where the method names a cause set) — n/a otherwise, per the paper's F ∧ S ∧ M triad.

Source: leaderboard.json · the whole leaderboard is on the methods page and the Paper 2 audit.

Results per game

This method's faithfulness on each of the 42 scored games (all-regime F, and the content vs position split). Click a header to sort. Every number is read from site_data.json (methods.activation_patching.per_game).

GameF (all)content-Fposition-FSMNote
Air Raid1.000n/a1.0001.0001.000recovers the true causes
Alien1.000n/a1.0000.8331.000recovers the true causes
Assault1.000n/a1.0001.0001.000recovers the true causes
Atlantis1.000n/a1.0001.0000.944recovers the true causes
Bank Heist1.000n/a1.0001.0001.000recovers the true causes
Beam Rider1.000n/a1.0001.0001.000recovers the true causes
Berzerk1.000n/a1.0001.0000.944recovers the true causes
Bowling1.000n/a1.0001.0000.955recovers the true causes
Boxing1.000n/a1.0001.0001.000recovers the true causes
Breakout1.000n/a1.0001.0001.000recovers the true causes
Carnival1.000n/a1.0001.0000.975recovers the true causes
Centipede1.000n/a1.0001.0000.958recovers the true causes
Chopper Command1.000n/a1.0001.0000.964recovers the true causes
Demon Attack1.000n/a1.0001.0000.929recovers the true causes
Double Dunk1.000n/a1.0001.0000.971recovers the true causes
Fishing Derby1.000n/a1.0001.0001.000recovers the true causes
Freeway1.000n/a1.0001.0001.000recovers the true causes
Frostbite1.000n/a1.0001.0000.900recovers the true causes
Gopher1.000n/a1.0001.0001.000recovers the true causes
Hero1.000n/a1.0001.0000.955recovers the true causes
Ice Hockey1.000n/a1.0001.0001.000recovers the true causes
Jamesbond1.000n/a1.0001.0000.909recovers the true causes
Kangaroo1.000n/a1.0001.0000.909recovers the true causes
Krull1.000n/a1.0001.0000.923recovers the true causes
Kung-Fu Master1.000n/a1.0001.0001.000recovers the true causes
Montezuma's Revenge1.000n/a1.0001.0000.938recovers the true causes
Ms. Pac-Man1.000n/a1.0001.0001.000recovers the true causes
Name This Game1.000n/a1.0001.0001.000recovers the true causes
Pacman1.000n/a1.0001.0001.000recovers the true causes
Phoenix1.000n/a1.0001.0001.000recovers the true causes
Pitfall1.000n/a1.0001.0001.000recovers the true causes
Pong1.000n/a1.0001.0001.000recovers the true causes
Private Eye1.000n/a1.0001.0001.000recovers the true causes
Q*bert1.000n/a1.0001.0000.944recovers the true causes
Riverraid1.000n/a1.0001.0000.940recovers the true causes
Road Runner1.000n/a1.0001.0000.938recovers the true causes
Seaquest1.000n/a1.0001.0001.000recovers the true causes
Space Invaders1.000n/a1.0001.0000.958recovers the true causes
Tennis1.000n/a1.0001.0000.929recovers the true causes
Venture1.000n/a1.0001.0000.978recovers the true causes
Video Pinball1.000n/a1.0001.0001.000recovers the true causes
Yars' Revenge1.000n/a1.0001.0001.000recovers the true causes

Call stack — how it runs on jutari / jaxtari

The path from this method's runner (run_game) into the bit-exact VCS substrate, and the computation it involves. Every step links to the exact source on main. From callstack_phaseC.json.

Applied on the substrate

  1. Boot the real ROM with the xitari-parity reset (60 NOOP + 4 RESET) — env_reset!(env; boot_noop_steps = 60, boot_reset_steps = 4) jutari
  2. Deterministically step the emulator one action per env-frame — env_step!(env::StellaEnvironment, action::Integer) jutari
  3. Assemble the shared gameplay-state testbed (checkpoint at f*, causes, shared screen output cell, cause-density gate) — build_shared_testbed(game; ... intervene_ram = intervene_ram!, run_intervention = run_intervention, soft_ram_peek = soft_ram_peek, ...) runner
  4. Freeze byte-exact RAM + screen out of the env at a frame — snapshot(env::StellaEnvironment, frame::Integer) jutari

The computation

  1. Single-site activation patch: write the patched value into one RAM cell / TIA register, re-run the tail, snapshot (the TRUE patch) — run_patch(checkpoint, tail, kind, site, value) -> intervene_ram!/intervene_tia! runner intervene_ram! @ activation_patching.jl:285, intervene_tia! @ :287
  2. Patch loop: recovered (rec_snap) vs exact-clean and corrupted-fresh (ex_snap) re-runs per patch site, both via run_patch — rec_snap = run_patch(clean_ckpt, ...); ex_snap = run_patch(fresh_ckpt, ...) inside _run_game_body runner rec_snap @ :460, ex_snap @ :466

Scored against the exact intervention oracle Δy(u): run_intervention(checkpoint, actions, target_frame, horizon, cause).

Implementation
tools/xai_study/phaseC_mechanistic/activation_patching.jl
Reference
Vig et al. 2020; ROME, Meng et al. 2022
Record
activation_patching_pong.json
All records
phaseC_mechanistic/out

The figure is generated from the committed record by docs/gen_method_figures.py; the game frame and each RAM cell's screen footprint are produced by render_scenes.jl / cell_footprints.jl.