Repository navigation
[Bug Report] PositionEmbeddingsAttentionBridge replaces eager_attention_forward in transformers' gemma-2 and gemma-3 modules for the rest of the process #1795
Description
Human Preface (@speediedan)
We've contributed and thanked you before, but thanks again for TransformerLens! I'm sure you're aware how important it remains to the open-source AI interp community but it's integral to the AI world model analysis and tuning framework we're building (interpretune.org) so thanks again!
We've reviewed and human-validated the rest of this description below but it is largely claude-opus-5 written. Let us know if you want more human clarification/feedback/revision of it. Thanks!
Describe the bug
transformer_lens/model_bridge/generalized_components/position_embeddings_attention.py::_setup_eager_attention_hook_wrapper
assigns a hook-firing wrapper over the module-level eager_attention_forward of
transformers.models.gemma2.modeling_gemma2 and transformers.models.gemma3.modeling_gemma3 the first time any
PositionEmbeddingsAttentionBridge is constructed. That component backs llama, qwen2/3, gemma 1/2/3, olmo, phi,
mixtral and about twenty other architectures, so building a bridge for a llama model patches both gemma modules.
The wrapper is installed once per process, is never removed (deleting the bridge does not restore it), and carries
no functools.wraps metadata, so nothing downstream can see through it.
Two consequences outside TransformerLens:
- Any code that inspects the function bound at gemma's
attention_interfacecall site sees TransformerLens's wrapper
instead of transformers' function. nnsight's recursive.sourcetracing parses that function's source, and the
wrapper's body has nonn.functional.dropout(...)call, so the operation node
...self_attn.source.attention_interface_0.source.nn_functional_dropout_0that circuit-tracer uses as the attention
pattern location for gemma models does not exist once any such bridge has been built. circuit-tracer's nnsight
backend on gemma-3 then fails withAttributeError: 'SourceEnvoy' object has no attribute 'nn_functional_dropout_0'
for the rest of the process, and only in processes where a bridge was built first, which makes it look like a
flaky test in every downstream suite that mixes the two. - The wrapper runs for every gemma-2/3 forward in the process, bridged or not (it looks the bridge up by module id and
passes through when none is registered), so plain HF gemma models pay the lookup on every attention call after any
bridge has existed.
Code example
Measured with transformer-lens 3.5.1 (the patch is unchanged on main as of v3.8.1), transformers 5.16.1, CPU only:
import inspect
import transformers.models.gemma3.modeling_gemma3 as g3
import transformers.models.gemma2.modeling_gemma2 as g2
from transformer_lens.model_bridge import TransformerBridge
def state(tag):
for name, mod in (("gemma3", g3), ("gemma2", g2)):
f = mod.eager_attention_forward
print(tag, name, f"{f.__module__}.{f.__qualname__}", "wrapped" if hasattr(f, "__wrapped__") else "no __wrapped__",
"dropout calls in source:", inspect.getsource(f).count("dropout("))
state("before")
bridge = TransformerBridge.boot_transformers("google/gemma-3-270m", device="cpu")
state("after bridge")
del bridge
state("after del")Output:
before gemma3 transformers.models.gemma3.modeling_gemma3.eager_attention_forward no __wrapped__ dropout calls in source: 1
before gemma2 transformers.models.gemma2.modeling_gemma2.eager_attention_forward no __wrapped__ dropout calls in source: 1
after bridge gemma3 transformer_lens.model_bridge.generalized_components.position_embeddings_attention._setup_eager_attention_hook_wrapper.<locals>.hooked_eager_attention_forward no __wrapped__ dropout calls in source: 0
after bridge gemma2 transformer_lens.model_bridge.generalized_components.position_embeddings_attention._setup_eager_attention_hook_wrapper.<locals>.hooked_eager_attention_forward no __wrapped__ dropout calls in source: 0
after del gemma3 transformer_lens.model_bridge.generalized_components.position_embeddings_attention._setup_eager_attention_hook_wrapper.<locals>.hooked_eager_attention_forward no __wrapped__ dropout calls in source: 0
after del gemma2 transformer_lens.model_bridge.generalized_components.position_embeddings_attention._setup_eager_attention_hook_wrapper.<locals>.hooked_eager_attention_forward no __wrapped__ dropout calls in source: 0
A llama bridge gives the same result for the two gemma modules (the patch is unconditional on the bridged
architecture); modeling_llama.eager_attention_forward itself is untouched.
Expected behavior
Building a bridge should not change what other libraries see when they inspect transformers' gemma modules, and
should leave no trace once the bridge is gone. Options, in rough order of how much they change:
- Register the hooked function as an attention implementation (
AttentionInterface.register("tl_hooked_eager", ...))
and select it on the bridged model's config, so the patch is per-model rather than per-process, and ordinary
_attn_implementation="eager"models keep transformers' function. - If the module-level assignment must stay, make it reversible (restore in the bridge's teardown / a context manager,
reference-counted across live bridges), apply it only to the modeling module of the architecture being bridged,
and decorate the wrapper withfunctools.wraps(original)soinspect.unwrapreaches the original.
System Info
- transformer-lens 3.5.1 (checked
mainat v3.8.1:position_embeddings_attention.pystill assigns
gemma2_module.eager_attention_forward/gemma3_module.eager_attention_forward) - transformers 5.16.1, torch 2.x, Python 3.13, Linux, CPU reproduction
Additional context
Found through circuit-tracer's nnsight backend, whose attention-pattern location for gemma models is resolved
through nnsight's recursive source tracing; the failure appears only in processes where a bridge was built first.
Activity
MdSadiqMd commentedon Sep 21, 2026
hey @speediedan are you working on this, if not can i take this up after confirmation from the maintainers?
jlarson4 commentedon Sep 22, 2026
Thanks for the report @speediedan, confirmed on
dev.@MdSadiqMd it's yours to tackle if @speediedan does not plan to take it on. The best solution for this will be deleting the Gemma patch functionality. The patch is dead code, safer to just remove it than to try to continue supporting something that is no longer useful.
Why it can go.
_setup_eager_attention_hook_wrapperwas added in #1113 so thathook_rot_q/hook_rot_kwould fire when HF's eager attention ran. Since #1217,PositionEmbeddingsAttentionBridge.forwardreimplements attention and fires those hooks itself, and it never calls the HF attention module.This is my recommended solution:
transformer_lens/model_bridge/generalized_components/position_embeddings_attention.py.tests/unit/model_bridge/generalized_components/. Build a tiny Gemma3 from aGemma3TextConfig(...), wrap it withbuild_bridge_from_module(hf_model, "Gemma3ForCausalLM", hf_config=cfg)(the_tiny_gemma3_bridgehelper intests/integration/model_bridge/test_gemma3_adapter.pyis the pattern), callrun_with_cacheon a few tokens, then assert two things:blocks.0.attn.hook_rot_qis in the cache, andeager_attention_forward.__module__on bothmodeling_gemma2andmodeling_gemma3still starts withtransformers.. This test must fail on currentdevand pass with your change.Whoever ends up taking this on, please let me know if you have any questions!
speediedan commentedon Sep 22, 2026
It's all yours @MdSadiqMd , thanks for your contribution!
jlarson4 commentedon Sep 23, 2026
Merged into
devand will be included in this week's bug fix release