Steps — Path Patch
PathPatch step: isolate the direct sender-to-receiver effect.
Activation patching (:class:~murano.steps.patch.Patch) replaces one site and lets
the change propagate through every downstream route, so it measures a component’s
total effect. Path patching measures the direct effect: it injects the sender’s
activation from a second run but freezes every other component at its base value, so
the perturbation can only reach the receiver along the direct residual path. This is
the primitive behind circuit localization (name movers, s-inhibition, backup name
movers in Wang et al.’s IOI work); the algorithm is the one from Goldowsky-Dill et
al., “Localizing Model Behavior with Path Patching”.
For senders S and receiver R, with base = the run to measure and
source = the run supplying the sender activations:
- Run
source; cache each sender’s activation. - Run
base; cache every head’s per-head output and every MLP output. - Run
baseagain with every attention head frozen to its base output, MLPs frozen too whenfreeze_mlps(otherwise they recompute), and each sender’s output overwritten with itssourcevalue atpositions. CaptureR. - When
Ris the final residual, step 3’s logits are the result. Otherwise runbaseonce more withRpatched to its step-3 value and everything downstream ofRfree, and read the logits.
R is a residual-stream node, or a specific head’s query/key/value input (a
Node(layer, "self_attn", head=h, side=Q|K|V)): the latter isolates edges into a
head, such as the S-inhibition heads writing to a name mover’s query in IOI. For a
Q/K/V receiver, step 3 captures the head’s projection output and step 4 patches it,
so the model’s own rotary, softmax, and downstream layers recompute from it.
Freezing head outputs (the attention output projection’s per-head input) plus MLP
outputs is equivalent to freezing the reference’s q/k/v and matches what murano can
write. The default direction (base = clean prompts, source = corrupt
corrupt_prompts) injects the corrupt sender into the clean run, so a metric step
reads how much of the behaviour the direct sender path carries.
nnsight’s own per-token trace idiom differs across versions, so the freeze, inject,
and capture are done with plain torch forward hooks on the underlying modules, the
same mechanism :meth:~murano.model.MuranoModel._register_generation_intervention
uses; it is independent of the nnsight tracing layer.
PathPatch
Section titled “PathPatch”Path-patch senders into a receiver and write the resulting logits.
Runs the base prompts while injecting the source prompts’ sender activations
along only the direct path to the receiver (every other component frozen), then
stores the logits so a metric step can score the direct effect. With the
defaults (base = clean prompts, source = corrupt corrupt_prompts) this
reads how much of the behaviour the direct sender path carries.
Reads from results:
results[base_key]: PromptBatch (defaultprompts), the run to measure.results[source_key]: PromptBatch (defaultcorrupt_prompts), the run- supplying the sender activations.
Writes to results:
results[logits_key]: Tensor [B, S, vocab] of path-patched logits.results[mask_key]: Tensor [B, S] marking real (1) vs padding (0) for the- base batch, so a downstream metric step can locate the answer position.
Args:
model: Model backend to run.senders: The sending sites, as a :class:~murano.nodes.NodeSet, anaddress, an iterable of addresses, or an: class:~murano.nodes.Edge- (its source is the sender, its dest the receiver). Each sender is an
- attention head (
Node(layer, "self_attn", head=h)) or an MLP - (
Node(layer, "mlp")). Pass this orsenders_key, not both. senders_key: Results key holding a- “: class:
~murano.artifacts.ComponentSelectiona discovery step wrote, - read at run time so attribute-then-path-patch composes in one pipeline.
- The
receiveris still fixed at construction. Pass this or senders, not both.receiver: The receiving site. A residual-stream “Node(layer,"resid_post")`` (default: the final residual, the last layer), or a- head’s query/key/value input “Node(layer, “self_attn”, head=h,
- side=Q|K|V)“ to path-patch into that head’s Q/K/V (e.g. an S-inhibition
- head into a name mover’s query). The output side (O) is a head’s output,
- addressable only as a sender; an MLP or side-less head receiver raises.
base_key: Results key of the batch to measure (defaultprompts).source_key: Results key of the batch supplying sender activations (defaultcorrupt_prompts).positions: Token position(s) to inject the senders at, in the- **
**: func:`~murano.steps.metrics._answer_positions` form;None“ injects - at every position.
freeze_mlps: If True, freeze MLP outputs to their base values too, so only- the attention-mediated direct path survives; if False (default), MLPs
- recompute and mediate the effect.
logits_key: Results key to write the path-patched logits under.mask_key: Results key to write the base attention mask under.
Raises:
ValueError: If neither or both ofsendersandsenders_keyare- given,
sendersis empty or malformed, a sender/receiver layer or - sender/receiver head is out of range, a per-node receiver position is
- given, both an
Edgeand areceiverare given, anEdgeis - combined with
senders_key, or the base and source prompts are not - token-length-matched per pair.
NotImplementedError: Ifreceiveris neither a residual-stream site nor a- head’s Q/K/V input, or if a head’s Q/K/V receiver is requested on an
- architecture with interleaved fused q/k/v (e.g. GPT-NeoX).
__init__
Section titled “__init__”def __init__(self, model: ModelBackend, senders: NodeSet | AddressLike | Iterable[AddressLike] | Edge | None = None, receiver: AddressLike | None = None, senders_key: str | None = None, base_key: str = keys.PROMPTS, source_key: str = keys.CORRUPT_PROMPTS, positions: int | Sequence[int] | Tensor | None = None, freeze_mlps: bool = False, logits_key: str = keys.PATH_PATCHED_LOGITS, mask_key: str = keys.PATH_PATCHED_MASK):