Paper · 2025-07-11 · cs.LG
The Non-Linear Representation Dilemma: Is Causal Abstraction Enough for Mechanistic Interpretability?
Shows that if the alignment map between a network and an algorithm may be any invertible function, any network can be aligned with any algorithm, so causal abstraction becomes vacuous. Experiments: non-linear RevNet maps reach near-perfect interchange-intervention accuracy even on randomly initialised MLPs and Pythia models; linear rotations do worse.
Causal abstractionMechanistic interpretabilityActivation patchingLinear probingmechanistic-interpretabilitycausal-abstractiondistributed-alignment-searchinterchange-interventionlinear-representation-hypothesis
arXivPDFCodeMarkdown15 min read
AI-compiled by claude-haiku-5-5 on 2026-11-08, not yet reviewed by a human. Every claim cites its source; check it when a detail matters.
In plain words
Researchers want to know what a neural network is really doing inside. One approach is causal abstraction: find a simpler, step-by-step program that seems to do the same job, then match parts of the network to the program's intermediate steps. The test is interventional. If you change one step in the program, the matched part of the network should change in the same way.
Most published work makes the matching step a straight-line map, because many people assume that the concepts a network uses sit along straight directions in its internal activations. This paper asks what happens if that restriction is removed and the matching can be any invertible function. The authors show that, under mild conditions, a match of this kind can be found for any network and any program, so the test passes even when it tells you nothing.
They back this up with experiments on a small network trained on an equality puzzle and on language models that solve a name-tracking task. Flexible matches fit even networks that were never trained, while straight-line matches fit less well. The lesson is that causal abstraction only says something useful when you also commit to how information is encoded.
TL;DR
The paper argues that causal abstraction, used with unrestricted alignment maps, is trivial: under countability, layerwise injectivity and output surjectivity, any network can be aligned with any algorithm. Experiments show that non-linear RevNet alignment maps reach near-perfect interchange intervention accuracy even on randomly initialised models, while linear rotations are weaker. The authors conclude that causal abstraction needs an explicit assumption about information encoding before its results mean anything. Results are author-reported.
Problem
Causal abstraction asks whether a neural network implements an algorithm: a directed graph of named intermediate variables that computes the task output §2.1. The network is abstracted by an algorithm if a map from hidden states to the algorithm's variables makes interventions on the network correspond to interventions on the algorithm §3. Distributed alignment search (DAS) estimates such a map from data, by training it so that the intervened network produces the output the algorithm would produce §3.3.
Most work on causal abstraction implicitly assumes the linear representation hypothesis, so the map is restricted to linear or orthogonal transformations §1, §5. The definition of causal abstraction does not require linearity §1, §3. The paper's concern is that once the map is unrestricted, the notion becomes vacuous. The authors argue that the usual evidence (a low interchange-intervention loss) then no longer separates a real implementation from a coincidence §1, §4.
Method
Definitions
- Algorithm. A deterministic causal model: a directed acyclic graph with input nodes, inner nodes and output nodes. Each node's value is a function of its parents' values, and an intervention overwrites a node with a constant §2.1.
- Network. A stack of layer functions with real-valued hidden states and a softmax output. An intervention fixes a set of neurons to given values §2.2.
- Abstraction map τ. Maps the network's hidden states to the algorithm's node values. The intervention map ω_τ sends a network intervention to an algorithm intervention when both induce the same set of states §3.
- τ-abstraction. The algorithm is a τ-abstraction of the network if τ is surjective, the algorithm's allowed interventions are exactly those induced by the network, and τ commutes with the interventions for all inputs [§3, definition after Beckers et al.]. A strong abstraction allows every intervention.
- Constructive abstraction. τ is block-wise: each node reads its own disjoint set of neurons, and some neurons are unused §3.
- Distributed abstraction. Before the block-wise step, a bijective layerwise alignment map φ rotates the hidden states into latent coordinates. Each node then reads its own disjoint set of latent coordinates §3.2. Interventions are applied in latent space: map to latent, overwrite the chosen coordinates, map back §3.2, app. B.
- Input-restricted abstraction. Interventions are limited to values that other input-restricted interventions can produce. This is the notion used in practice, and it is the one the paper's theorem is about §3.2.
Main theorem and its assumptions §4, app. F
The theorem: given a DNN and an algorithm that satisfy the five assumptions below, the algorithm is an input-restricted distributed abstraction of the DNN §4.
- Countable inputs. The input set is countable. This holds for language (finite strings) and, approximately, for images at a fixed resolution [§4, app. F].
- Input-injectivity. Every prefix of the network, up to each layer, is injective §4, app. F.
- Strict output-surjectivity. For every layer, the suffix of the network followed by the output map is strictly surjective onto the outputs. The softmax bottleneck may break this in theory §4, app. F.
- Matchable partial orders. Some assignment of single neurons to algorithm nodes respects the algorithm's order. Each layer keeps at least one neuron unused app. F.
- The DNN solves the task. For every input, the network's argmax equals the task output §4, app. F.
Proof idea (for orientation, not a full derivation): build the alignment map layer by layer by induction. Three invariants are kept: each node's value can be read from the layer's latent coordinates, the output still matches the algorithm, and earlier node information survives in the unused coordinates app. F. Because the input set is countable and the reals are uncountable, injective per-coordinate maps exist, and uncountably many hidden states share any given output class, so the output can be kept app. F. The final map is made a bijection by a separate construction on the unused coordinates app. F. The existence proof does not say the map is learnable from finite data §7.
Alignment maps used in experiments §5
- Identity. φ is the identity, and the neuron partition is found greedily, adding one neuron at a time to each node's set §5.
- Linear. φ(h) = Q h with Q orthogonal §5. The code parametrises an orthogonal matrix, and its inverse is the transpose [code: das/Rotation_Model.py#L6-L50].
- Non-linear (RevNet). φ is a reversible residual network with K blocks and hidden size d_h §5. Each block splits the vector in half, adds a one-hidden-layer ReLU MLP F to the second half and a second MLP G to the first, and has an exact inverse RevNet.py.
Evaluation
The metric is interchange intervention accuracy (IIA): on held-out interventions, how often the intervened network's argmax equals the algorithm's output §5. Algorithm outputs on the intervention set are computed by running the algorithm with the same intervention §3.3.
Pseudocode: interchange intervention app. B, §3.2
Descriptive pseudocode of the intervention, mirroring the code's hook logic DAS_MLP.py:
# Interchange intervention on one layer [§3.2] [app. B]
# base_hidden, source_hidden: (B, d) activations at the chosen layer
# mask: (B, d) bool, True where the latent coordinate comes from the source
def interchange(phi, phi_inverse, base_hidden, source_hidden, mask):
# Map both activations into the latent space defined by phi
base_latent = phi(base_hidden) # (B, d)
source_latent = phi(source_hidden) # (B, d)
# Copy the chosen latent coordinates from the source
latent = where(mask, source_latent, base_latent) # (B, d)
# Return to the neuron basis; the network continues from here
return phi_inverse(latent) # (B, d)Pseudocode: DAS training step §3.3, app. I.1
The algorithm sets the target. The network is frozen and only φ is trained. The loss is cross-entropy between the intervened network output and the algorithm's output §3.3, the DAS loss.
# One DAS training step; only phi receives gradients [§3.3]
# [code: das/DAS_MLP.py#L84-L117]
def das_step(dnn, layer, phi, phi_inverse, batch):
# Hidden states at the chosen layer for base and source inputs
base_hidden = hidden_at(dnn, layer, batch.base) # (B, d)
# Each intervened variable takes its columns from its own source
source_hidden = hidden_at(dnn, layer, batch.sources) # (B, d)
patched = interchange(phi, phi_inverse, base_hidden,
source_hidden, batch.mask)
# Finish the frozen forward pass from the patched layer [§2.2]
logits = forward_from(dnn, layer, patched) # (B, classes)
# Target: the algorithm's output under the same intervention
return cross_entropy(logits, batch.algorithm_label)Pseudocode: RevNet alignment map §5
# One RevNet block (additive coupling) [§5] [code: das/RevNet.py#L63-L81]
# x: (B, d); F and G are one-hidden-layer ReLU MLPs on d/2 inputs
def revnet_block(x):
# Split the vector into two halves
x1, x2 = split_half(x) # (B, d/2) each
y1 = x1 + F(x2) # (B, d/2)
y2 = x2 + G(y1) # (B, d/2)
return concat(y1, y2) # (B, d)
# Exact inverse, used to map patched latents back to neurons
def revnet_block_inverse(y):
y1, y2 = split_half(y) # (B, d/2) each
x2 = y2 - G(y1) # (B, d/2)
x1 = y1 - F(x2) # (B, d/2)
return concat(x1, x2) # (B, d)Pseudocode: fitting loop with early stopping app. I.1, code
This follows train_test in the code, including how the best map is kept
DAS.py. The code's exact
bookkeeping is in Pitfalls.
# Fit the alignment map with the network frozen [app. I.1]
def fit_alignment(dnn, layer, phi, train_set, eval_set):
best_phi = copy_of(phi)
best_eval = inf # lowest eval loss seen (keeps the phi)
counted_eval = inf # eval loss counted for patience
epochs_without_gain = 0
for epoch in range(50): # at most 50 epochs [app. I.1]
train_losses = []
for batch in batches(train_set, size=6400): # [app. I.1]
train_losses.append(das_step(dnn, layer, phi,
phi.inverse, batch))
# The learning-rate schedule sees the mean training loss
scheduler_step(mean(train_losses)) # [code: das/DAS.py#L291]
eval_loss = mean_loss(dnn, layer, phi, eval_set)
if eval_loss < best_eval:
best_eval = eval_loss
best_phi = copy_of(phi)
if eval_loss + 0.001 < counted_eval: # [app. I.1]
counted_eval = eval_loss
epochs_without_gain = 0
else:
epochs_without_gain += 1
else:
epochs_without_gain += 1
if epochs_without_gain >= 5: # patience 5 [app. I.1]
break
return best_phiHyperparameters
Hierarchical equality task and MLP (paper values first; code values are noted where they differ or add detail).
| Name | Value | Where | Notes |
|---|---|---|---|
| input space | 16-d, uniform on [-0.5, 0.5] | §5 app. I.1 | four 4-d blocks |
| DNN architecture | 3 hidden ReLU layers, width 16, softmax output | §5 app. E Classification_Model.py | |
| DNN optimiser | Adam, lr 1e-3, batch 1024 | app. I.1 Classification_Model.py | |
| DNN training data | 1,048,576 samples; 10,000 eval; 10,000 test | app. I.1 Standard_DAS.py | |
| DNN epochs | max 20 | app. I.1 Standard_DAS.py | |
| DNN early stopping | paper: 3 epochs without improvement | app. I.1 | code differs, see Paper vs code |
| DNN seeds | 10 | fig. 2 Standard_DAS.py | |
| DAS training set | 1,280,000 interventions | app. I.1 Standard_DAS.py | |
| DAS eval and test sets | 10,000 each | app. I.1 Standard_DAS.py | |
| intervened variables | one of {first}, {second}, {both}, each with probability 1/3 | app. I.1 Dataset_Generation.py | |
| base and source equality | each pair equal with probability 1/2 | app. I.1 Dataset_Generation.py | |
| intervention size (both-equality, left-equality) | 8, then 2, then 1 neurons per variable | Standard_DAS.py | paper sweeps intervention size fig. 2 fig. 3 |
| DAS optimiser | Adam, lr 1e-3 | app. I.1 Standard_DAS.py | |
| DAS batch size | 6400 | app. I.1 Standard_DAS.py | |
| DAS max epochs | 50 | app. I.1 Standard_DAS.py | |
| DAS early stopping | 5 epochs, min improvement 0.001 | app. I.1 Standard_DAS.py | |
| LR schedule | ReduceLROnPlateau, factor 0.5, patience 10 | Standard_DAS.py | paper: not stated |
| RevNet blocks K | 10 (heq main); 10 (distributive law) | fig. 2 app. I.3 Standard_DAS.py | |
| RevNet hidden size | 16 (heq); 24 (distributive law) | fig. 2 app. I.3 Standard_DAS.py | |
| RevNet F and G depth | one hidden layer | RevNet.py | paper: not stated |
| Rotation | orthogonal matrix, orthogonal init | §5 Rotation_Model.py | |
| Interventions for identity-of-first-argument | one variable, first 8, then 2, then 1 neurons | Standard_DAS.py | |
| IIA metric | argmax agreement on held-out interventions | §5 |
Distributive law task (app. I.3): input 24-d, MLP width 24, three layers, Adam 1e-3, batch 1024, 1,048,576 training samples, 20 max epochs, early stopping after 3 epochs app. I.3. Alignment maps: Adam 1e-3, batch 6400, up to 50 epochs, early stopping after 5 epochs app. I.3.
Indirect object identification with Pythia (app. I.2; code values differ, see Paper vs code):
| Name | Value | Where | Notes |
|---|---|---|---|
| models | Pythia 31M, 70M, 160M, 410M, several checkpoints | app. I.2 pythia_size.sh | randomly initialised checkpoint "step0" also used |
| intervention layer | middle layer (410M: layer 12) | fig. 4 pythia410m.sh | |
| intervention size | half of the hidden dimension | app. I.2 das_llm.py | |
| train interventions | 1,000,000, for 2 epochs | app. I.2 das_llm.py | |
| eval and test interventions | not stated in paper | das_llm.py | 2,048 eval; 16,384 test defaults |
| batch size | 256 | app. I.2 das_llm.py | |
| learning rate | 1e-4 for the models | app. I.2 | code scripts use other values, see Paper vs code |
| float precision | float64 with lr 1e-3 for the smaller models | app. I.2 pythia_size.sh | |
| RevNet hidden size | 64 | fig. 4 app. I.2 pythia410m.sh | |
| RevNet blocks | 1, 2, 4, 8, 16 swept on 410M | §6 pythia410m.sh | |
| warmup | not stated in paper | pythia410m.sh | 500 linear warmup steps |
| validation | every 100 steps | das_llm.py | |
| early stopping | patience 15 to 100 validation checks, depending on script | pythia410m.sh pythia410m_name_split.sh | paper: not stated |
| ABAB/ABBA intervention | intervene on whether the sentence is ABAB | app. I.2 | |
| compute | Pythia runs ~30 GPU hours on one A100 80 GB, incl. tuning | app. J |
Results
Author-reported. Setups are the hierarchical equality task with a 3-layer MLP of width 16 (dataset and task in §5), and indirect object identification (IOI) with Pythia models §5, §6.
| Setting | Metric | Baseline | This paper | Source |
|---|---|---|---|---|
| IOI, randomly initialised Pythia LMs, RevNet alignment | IIA | not stated | 100% reported for randomly initialised LMs | §1 (abstract) §6 |
| Equality MLP, layer 1, randomly initialised, most complex RevNet | IIA | not stated | above 80% | §6 |
| Equality MLP, identity-of-first-argument algorithm | IIA | identity, rotation | about 50% for identity, rotation and RevNet | §6 |
| Equality MLP, RevNet, train-set IIA, identity-of-first-argument | IIA | not stated | below 0.55 on 1.28M samples; above 0.99 with 1,000 samples | app. H |
| Pythia-410m, five seeds, RevNet | IIA | rotation | perfect alignment for seeds 1, 2 and 5; seed 3 poor; seed 4 does not learn IOI | app. I.2 |
| Pythia, name-disjoint train and eval, randomly initialised | IIA | rotation | no alignment found even with complex maps | app. I.2 |
| Pythia, name-disjoint train and eval, fully trained | IIA | rotation | perfect with RevNet; reasonably high with rotation | app. I.2 |
| Distributive-law task, And-Or-And and And-Or | IIA | rotation, identity | RevNet near perfect everywhere; linear dynamics vanish | app. I.3 |
Qualitative results, from figures only: for the both-equality and left-equality algorithms, rotation IIA drops in the third layer of the equality MLP, and RevNet removes this drop fig. 2 §6. For Pythia-410m across training, 8-layer RevNet is near perfect at all checkpoints, including random initialisation, while rotation tracks the network's own accuracy more closely fig. 4 §6. Smaller fully trained Pythia models show slightly lower RevNet alignment §6.
Injectivity check: no collisions among 1,280,000 hidden states, repeated over 10 seeds app. H. The minimal distances are smaller for pairs that share equality-variable values, which the authors read as harder separation tab. 1 app. H.
Paper vs code
Official repository: densutter/non-linear-representation-dilemma, linked
from the paper §1, app. A. The repository has no LICENSE file; the code is
cited by path and line only. Commit checked: 7a8d538. Results.zip is
committed and was not inspected.
- IOI learning rate. Paper: 1e-4 app. I.2. The argparse default is 1e-4 das_llm.py. The run scripts override it: 5e-4 for the 410M main and name-split runs pythia410m.sh [code: run/pythia410m_name_split.sh#L8], and 1e-3 for the size sweep and seed runs pythia_size.sh pythia410m_seeds.sh.
- IOI epochs. Paper: 2 epochs app. I.2. The size sweep runs 4 epochs pythia_size.sh; the seed runs run 3 [code: run/pythia410m_seeds.sh#L9].
- IOI precision. Paper: float64 and lr 1e-3 for the 31M and 70M models only app. I.2. The size script uses float64 and lr 1e-3 for all three sizes it loops over, including 160M pythia_size.sh.
- IOI warmup and early stopping. The paper states neither. The code uses a 500-step linear warmup pythia410m.sh. Early stopping counts validation checks run every 100 steps, not epochs, with patience 15 to 100 depending on the script das_llm.py [code: das/DAS.py#L239-L267]. The default improvement threshold is 0.01, not 0.001 das_llm.py.
- MLP early stopping. Paper: stop after 3 epochs without improvement app. I.1. The code does not pass a threshold to the MLP training function, so the default of 1024 applies. The counter increments on each batch step, not each epoch, and the model kept is the one with the best eval accuracy Classification_Model.py [code: das/Classification_Model.py#L120-L129] [code: das/Classification_Model.py#L144-L149]. With batch 1024 and 1,048,576 samples, one epoch is 1024 steps, so the effective patience is about one epoch.
- Which alignment map is kept. Paper: silent. The code returns the map with the lowest eval loss, not the last one DAS.py [code: das/DAS.py#L304-L306] DAS.py.
- LR schedule input. Paper: silent. The schedule steps on the mean training loss, not the eval loss DAS.py.
- LM loss. Paper: cross-entropy of the intervened output, with the output set given as the two name tokens §3.3 app. I.2. The default loss is cross-entropy over the full vocabulary at the final position [code: das/LLM_Model.py#L81-L85] DAS_LLM.py. Two alternative losses exist behind flags das_llm.py. IIA is a two-way comparison of the two name-token logits [code: das/DAS_LLM.py#L106].
- LM intervention position. Paper: intervene at the middle layer fig. 4. The code intervenes only at the final token position of that layer's output, for base and source DAS_LLM.py [code: das/DAS_LLM.py#L91-L96].
- Launch script.
run/pythia410m.sh#L12joins two flags into one token (--model_init_mode 0--layer_index 12). As written, argparse should reject the merged token as a non-integer, so the layer flag is never read. I did not run it. - Script name. The README points to
run/pythia_seeds.sh; the file isrun/pythia410m_seeds.sh. - Equality MLP setup. The code matches the paper on width, batch size, learning rate, sample counts, intervention variables and RevNet blocks and hidden size Standard_DAS.py [code: notebooks/Standard_DAS.py#L124-L126]. No discrepancy found there.
Pitfalls
- The theorem is existential. It proves that some alignment map exists. It does not say the map is learnable from finite data or that it generalises §7. Its proof uses uncountably many hidden states per output class to keep the output correct app. F, which is a form of extreme fitting §7.
- Assumptions that can fail. Injectivity holds almost surely for transformers at initialisation with real weights app. G. Finite precision and neural collapse may break it in practice §4. Output-surjectivity may fail because of the softmax bottleneck §4 §8.
- Low IIA can be capacity, not absence. For identity-of-first-argument on a trained MLP, RevNet reaches over 0.99 train IIA with 1,000 samples but under 0.55 with 1.28M samples. The authors read this as RevNet lacking the capacity to fit the required function app. H.
- Generalisation depends on names. Randomly initialised Pythia models reach high IIA only when train and eval share names; with disjoint names, no alignment was found even with complex maps app. I.2.
- Seed variance. Two of five Pythia-410m seeds failed, one with poor IIA and one that did not learn the task. The authors attribute the failures to suboptimal alignment training and did not tune further app. I.2.
- Plateaus can trigger early stopping. For randomly initialised Pythia-31M, the loss plateaus between about 4k and 6k steps before dropping app. I.2. The code's patience of 15 validation checks is 1,500 steps das_llm.py pythia410m.sh, so a plateau this long can stop the run before it converges.
- Linear maps can exceed network accuracy. The rotation map sometimes reaches higher IIA than the network's own accuracy. The authors note this may mean the map is learning the task itself §7.
- Label construction for equality data. For each sample, the label reuses the equality flag of the intervened variable, so the label reflects the source, not the base Dataset_Generation.py. This matches the paper's intent, but a re-implementation must do the same.
- Possible label inversion in the IOI data (unverified). In
Preprocess_IOI_Data, when the final name appears before the subject, the code takes the subject as the true answer [code: das/Dataset_Generation.py#L257-L263]. That looks inverted for ABBA sentences, where the indirect object is the first introduced name. I did not fetch the dataset (fahamu/ioi), so I could not check the sentence format. Verify before using the LM labels. - Compute. The equality MLP experiments took about 3 weeks on 10 CPU computers, because DAS on small MLPs ran faster on CPU app. J. The Pythia runs took about 30 GPU hours on one A100 80 GB, including tuning app. J.
Lineage
- Builds on: causal abstraction [arXiv:2301.04709] [arXiv:2303.02536] (DAS, Geiger et al.), and RevNet (Gomez et al., 2017) for the non-linear map. The paper cites them in its bibliography.
- Compared against: Boundless DAS and HyperDAS (named in app. C; no card here) and probing-based analyses §7.
- Superseded by: not known from this wiki.
Source and license
Adapted from “The Non-Linear Representation Dilemma: Is Causal Abstraction Enough for Mechanistic Interpretability?” by Denis Sutter, Julian Minder, Thomas Hofmann, Tiago Pimentel (arXiv:2507.08802), used under CC BY 4.0. Changes: condensed and restructured for implementation, with tables, equations and algorithms taken from the paper and its code, each cited to its source. Not affiliated with or endorsed by the authors. Code citations refer to https://github.com/densutter/non-linear-representation-dilemma at commit 7a8d538bd301 (none (no LICENSE file in repository)). Any quoted code is shown under that license.