TransformerBridge Compatibility Mode¶
TransformerBridge.boot_transformers(...) returns a bridge whose default numerics match HuggingFace — raw weights, no folding, no centering. Calling bridge.enable_compatibility_mode() afterwards puts the bridge into HookedTransformer-equivalent numerics — weights folded and centered. The legacy hook names already resolve on every bridge (see Model Structure); compatibility mode changes the numerics, not the names.
Most research code that was written against HookedTransformer.from_pretrained(...) assumes compatibility mode. Most new code that needs HF-faithful logits does not.
When to enable it¶
Use case |
Compatibility mode? |
Why |
|---|---|---|
Logit lens / direct logit attribution |
Yes |
These analyses reason in the post-fold-LN coordinate system; raw HF weights produce different (wrong) attributions. |
Residual-stream norm analysis |
Yes |
Centered weights give the residual a meaningful zero. |
Circuit analysis using HT-style hook names ( |
Yes |
Legacy hook names resolve on every bridge (see model structure); compat mode makes the cached values match HookedTransformer’s processed-weight numerics. |
Logit parity against HuggingFace |
No |
Folding changes weights; logits will not match HF. |
Generation / inference vs HF baseline |
No |
Same reason. |
Verifying a new adapter’s forward pass |
No (initially) |
Use |
What each flag does¶
bridge.enable_compatibility_mode(
disable_warnings: bool = False,
no_processing: bool = False,
fold_ln: bool = True,
center_writing_weights: bool = True,
center_unembed: bool = True,
fold_value_biases: bool = True,
refactor_factored_attn_matrices: bool = False,
)
Flag |
Default |
Effect |
|---|---|---|
|
|
If |
|
|
Folds LayerNorm scale + bias into the subsequent linear weights so the LayerNorm modules become pure normalization. Changes weights; mathematically equivalent. |
|
|
Subtracts the mean from each “writing” weight ( |
|
|
Subtracts the mean from the unembedding matrix. Logits become mean-zero — affects logit-lens output but not argmax. |
|
|
Folds attention value biases into the output bias. Same numerics, fewer parameters. |
|
|
Refactors |
|
|
Suppresses warnings emitted by legacy component aliases when accessed. |
After processing, the bridge also:
Re-initializes the hook registry.
Re-runs
_setup_hook_compatibility()(it already ran at boot) so per-attention hook conversions such as thehook_zreshape to[batch, seq, n_heads, d_head]are rebuilt on the fresh registry.Re-registers aliases recursively so the property aliases (
W_Q,b_O, …) point at the processed weights. The hook aliases already exist from boot.
compatibility_mode is then True on the bridge and on every component, so subsequent operations behave as if the bridge were loaded by HookedTransformer.from_pretrained().
Hook semantic parity¶
With their config flag set, these HT hook names fire on the pre-norm residual in either mode (matching HookedTransformer semantics):
blocks.{i}.attn.hook_q_input,hook_k_input,hook_v_inputblocks.{i}.hook_attn_inblocks.{i}.hook_mlp_in(gated oncfg.use_hook_mlp_in; toggle viabridge.set_use_hook_mlp_in(True))
Carve-outs (issue #1317):
Post-norm architectures (OLMo 2, BERT-style) read the post-attention residual instead, because the norm semantically lives elsewhere in the block.
MLA blocks (DeepSeek V2 / V3 / R1) do not expose the split-qkv aliases — MLA’s compressed K/V doesn’t have a clean split.
On post-norm architectures (Gemma 2/3’s ln1_post/ln2_post, OLMo 2/3’s ln1/ln2),
blocks.{i}.hook_attn_out / hook_mlp_out fire after the post-sublayer norm, so
that they capture the tensor added to the residual stream and the identities
resid_pre + attn_out == resid_mid and resid_mid + mlp_out == resid_post hold
(issue #1648).
One consequence for head-level direct logit attribution: per-head contributions from
attn.hook_result no longer sum to hook_attn_out, because the norm sits between
them. This is inherent to post-norm — decompose heads on the pre-norm side
(attn.hook_out for the raw module output) or attribute through the norm explicitly.
The Granite family has the same raw-vs-contribution split for a different reason:
HF scales each sublayer output by residual_multiplier before the residual add, so
hook_attn_out / hook_mlp_out fire on the scaled contribution while
attn.hook_out / mlp.hook_out stay raw — attn.hook_result sums to the raw
output, off from the contribution by the multiplier.
An adapter author for a new post-norm or MLA-style architecture must handle these carve-outs in setup_hook_compatibility. The Gemma1/Gemma2 adapters are exemplars of when not to override setup_hook_compatibility — GemmaTextScaledWordEmbedding already scales internally, so any added hook_conversion would double-scale embed.hook_out.
The four-quadrant test matrix¶
The shared conftest at tests/conftest.py provides three bridge variants plus frozen reference goldens:
Variant |
|
|
Tests… |
|---|---|---|---|
|
off |
n/a |
HF-faithful numerics |
|
on |
|
HT-equivalent numerics |
|
on |
|
Compatibility-mode forward without weight processing — used to bisect numerical bugs |
(reference) |
n/a |
n/a |
Frozen HookedTransformer outputs with/without weight processing |
New integration tests should use the variant that matches the property they’re testing. Tests of HF parity → gpt2_bridge. Tests of HT-API behaviour → gpt2_bridge_compat. Tests of hook semantics regardless of weights → gpt2_bridge_compat_no_processing.
Cost¶
enable_compatibility_mode() mutates the bridge’s weights in-place. It is:
One-shot: calling it twice re-runs the centering subtractions. Don’t.
Not reversible from within the bridge — re-boot for raw weights.
_setup_hook_compatibilityis idempotent; onlyprocess_weightsmutates weights.Incompatible with a CPU/disk-offloaded
device_mapunlessno_processing=True. Weight processing (fold_lnetc.) reads and rewrites parameters directly across many components at once, not through a single component’s ownforward()call, so it isn’t covered byGeneralizedComponent’s per-call materialization and raises immediately rather than crashing mid-fold on a rawmetatensor. The default (non-compat-mode) forward pass works normally under offload — this restriction is compat mode’s weight processing specifically.
See also¶
Creating Architecture Adapters in contributing.md — adapter contract and the four-place registration; adapter authors override
setup_hook_compatibilityonly when an architecture has the post-norm / MLA carve-outs mentioned above.Debugging Numerical Divergence — uses
no_processing=Trueas a key bisection tool.Migrating to TransformerLens 3 — when porting HT code, you almost always want
enable_compatibility_mode().