Describe the bug
TransformerBridge.boot_transformers("EleutherAI/pythia-70m") (and every other GPT-NeoX-family checkpoint) raised AttributeError during component setup on the pinned transformers version. The NeoX architecture adapter mapped the model's unembedding to the HF module named embed_out (transformer_lens/model_bridge/supported_architectures/neox.py:190), but transformers renamed GPTNeoXForCausalLM.embed_out to lm_head in the 5.13 layout. The repository pinned transformers==5.13.0 in uv.lock and required transformers>=5.9.0 in pyproject.toml, so the locked CI environment could not boot any NeoX/Pythia model through the bridge.
Suggested labels: bug, model-bridge, architecture-adapter
Root cause
transformers GPT-NeoX exposes its output projection at the top level. In the ≥ 5.13 layout that module is lm_head; older releases exposed embed_out. Verified on 5.15.1: GPTNeoXForCausalLM top-level children are ['gpt_neox', 'lm_head']; hasattr(model, "embed_out") is False, hasattr(model, "lm_head") is True.
- The NeoX adapter hard-coded the old name:
"unembed": UnembeddingBridge(name="embed_out") (neox.py:190).
ArchitectureAdapter.get_remote_component resolves each dotted path segment with a plain getattr and has no fallback / alias mechanism (transformer_lens/model_bridge/architecture_adapter.py:388), so a stale name raised immediately rather than degrading.
Every other component the NeoX adapter references still resolves on transformers 5.15.1, so the fix was isolated to the unembedding mapping:
| Adapter mapping (neox.py) |
HF module |
Resolves on 5.13+? |
embed → gpt_neox.embed_in (L151) |
gpt_neox.embed_in |
✅ still present |
block MLP in/out → dense_h_to_4h / dense_4h_to_h (L179–180) |
present |
✅ |
block attn qkv/o → query_key_value / dense (L172–173) |
present |
✅ |
ln_final → gpt_neox.final_layer_norm (L186) |
gpt_neox.final_layer_norm |
✅ |
unembed → embed_out (L190) |
renamed to lm_head |
❌ AttributeError |
The sibling GPT-Neo adapter (transformer_lens/model_bridge/supported_architectures/neo.py:160)
already used UnembeddingBridge(name="lm_head") and booted cleanly. EleutherAI/gpt-neo-125M (a dense torch.nn.Linear / out_in MLP model, the same layout family as Pythia) was booted end-to-end and used by the Backward Lens capture with exact reconstruction (abs_err = 0.0, rel_err = 0.0 across layers 0/6/11). This
confirmed the correct name is lm_head and that the NeoX-family MLP capture would work once the adapter booted. An in-repo comment already documented the rename boundary: qwen2_audio.py refers to "lm_head (the transformers >= 5.13 layout)".
Code example
import torch
from transformer_lens.model_bridge import TransformerBridge
TransformerBridge.boot_transformers("EleutherAI/pythia-70m", device="cpu", dtype=torch.float32)
Observed traceback (abridged, pre-fix):
File ".../model_bridge/component_setup.py", line 372, in setup_components
original_component = architecture_adapter.get_remote_component(...)
File ".../model_bridge/architecture_adapter.py", line 388, in get_remote_component
current = getattr(current, part)
File ".../torch/nn/modules/module.py", line 1967, in __getattr__
raise AttributeError(...)
AttributeError: 'GPTNeoXForCausalLM' object has no attribute 'embed_out'
System Info
transformer_lens installed from source (repo checkout), editable dev environment.
transformers pinned in uv.lock: 5.13.0 (also reproduced on 5.15.1).
transformers floor in pyproject.toml: >= 5.9.0 (no upper bound; no CI version matrix — CI runs the locked version).
- Affects the
TransformerBridge path only. (The HookedTransformer legacy path is a separate code path, not affected.)
- OS: Linux.
Additional context
Scope of affected models: every checkpoint routed to NeoxArchitectureAdapter, including the Pythia suite (EleutherAI/pythia-70m, -160m, -410m, …) and EleutherAI/gpt-neox-20b.
Impact: no GPT-NeoX / Pythia model could be loaded through TransformerBridge.boot_transformers on the locked/supported transformers versions. This directly blocked the Backward Lens dense-MLP generalization, whose acceptance criterion requires Pythia-70m as a required out_in integration family. The Backward Lens code itself is architecture-neutral and was already verified on a real out_in model (gpt-neo-125M) — only the NeoX adapter was broken.
Design question weighed in the fix: pyproject.toml allows transformers>=5.9.0, but the lockfile and CI use 5.13.0. If releases in the [5.9, 5.13) window still expose embed_out, a hard rename to lm_head would regress them. The implementation plan weighed a straight rename (matches the lock + the GPT-Neo adapter) against a version/hasattr-aware resolution that accepts either name. Resolution: the landed fix (11912677) uses the straight rename to lm_head, matching the locked transformers==5.13.0 and the GPT-Neo adapter precedent.
Acceptance criteria (tracked in the implementation plan):
TransformerBridge.boot_transformers("EleutherAI/pythia-70m") boots on the locked transformers version and exposes working blocks, ln_final, and unembed.
- A regression test boots a small NeoX/Pythia checkpoint through the bridge and asserts the unembedding resolves and produces logits of shape
[..., d_vocab] (using a cached small model only; no new large download in CI). — covered by 9665e075.
- No other NeoX adapter behavior changes; existing bridge tests stay green.
Checklist
Describe the bug
TransformerBridge.boot_transformers("EleutherAI/pythia-70m")(and every other GPT-NeoX-family checkpoint) raisedAttributeErrorduring component setup on the pinnedtransformersversion. The NeoX architecture adapter mapped the model's unembedding to the HF module namedembed_out(transformer_lens/model_bridge/supported_architectures/neox.py:190), buttransformersrenamedGPTNeoXForCausalLM.embed_outtolm_headin the 5.13 layout. The repository pinnedtransformers==5.13.0inuv.lockand requiredtransformers>=5.9.0inpyproject.toml, so the locked CI environment could not boot any NeoX/Pythia model through the bridge.Suggested labels:
bug,model-bridge,architecture-adapterRoot cause
transformersGPT-NeoX exposes its output projection at the top level. In the ≥ 5.13 layout that module islm_head; older releases exposedembed_out. Verified on 5.15.1:GPTNeoXForCausalLMtop-level children are['gpt_neox', 'lm_head'];hasattr(model, "embed_out")isFalse,hasattr(model, "lm_head")isTrue."unembed": UnembeddingBridge(name="embed_out")(neox.py:190).ArchitectureAdapter.get_remote_componentresolves each dotted path segment with a plaingetattrand has no fallback / alias mechanism (transformer_lens/model_bridge/architecture_adapter.py:388), so a stale name raised immediately rather than degrading.Every other component the NeoX adapter references still resolves on transformers 5.15.1, so the fix was isolated to the unembedding mapping:
embed→gpt_neox.embed_in(L151)gpt_neox.embed_inin/out→dense_h_to_4h/dense_4h_to_h(L179–180)qkv/o→query_key_value/dense(L172–173)ln_final→gpt_neox.final_layer_norm(L186)gpt_neox.final_layer_normunembed→embed_out(L190)lm_headThe sibling GPT-Neo adapter (
transformer_lens/model_bridge/supported_architectures/neo.py:160)already used
UnembeddingBridge(name="lm_head")and booted cleanly.EleutherAI/gpt-neo-125M(a densetorch.nn.Linear/out_inMLP model, the same layout family as Pythia) was booted end-to-end and used by the Backward Lens capture with exact reconstruction (abs_err = 0.0,rel_err = 0.0across layers 0/6/11). Thisconfirmed the correct name is
lm_headand that the NeoX-family MLP capture would work once the adapter booted. An in-repo comment already documented the rename boundary:qwen2_audio.pyrefers to "lm_head(the transformers >= 5.13 layout)".Code example
Observed traceback (abridged, pre-fix):
System Info
transformer_lensinstalled from source (repo checkout), editable dev environment.transformerspinned inuv.lock: 5.13.0 (also reproduced on 5.15.1).transformersfloor inpyproject.toml: >= 5.9.0 (no upper bound; no CI version matrix — CI runs the locked version).TransformerBridgepath only. (TheHookedTransformerlegacy path is a separate code path, not affected.)Additional context
Scope of affected models: every checkpoint routed to
NeoxArchitectureAdapter, including the Pythia suite (EleutherAI/pythia-70m,-160m,-410m, …) andEleutherAI/gpt-neox-20b.Impact: no GPT-NeoX / Pythia model could be loaded through
TransformerBridge.boot_transformerson the locked/supportedtransformersversions. This directly blocked the Backward Lens dense-MLP generalization, whose acceptance criterion requires Pythia-70m as a requiredout_inintegration family. The Backward Lens code itself is architecture-neutral and was already verified on a realout_inmodel (gpt-neo-125M) — only the NeoX adapter was broken.Design question weighed in the fix:
pyproject.tomlallowstransformers>=5.9.0, but the lockfile and CI use 5.13.0. If releases in the[5.9, 5.13)window still exposeembed_out, a hard rename tolm_headwould regress them. The implementation plan weighed a straight rename (matches the lock + the GPT-Neo adapter) against a version/hasattr-aware resolution that accepts either name. Resolution: the landed fix (11912677) uses the straight rename tolm_head, matching the lockedtransformers==5.13.0and the GPT-Neo adapter precedent.Acceptance criteria (tracked in the implementation plan):
TransformerBridge.boot_transformers("EleutherAI/pythia-70m")boots on the lockedtransformersversion and exposes workingblocks,ln_final, andunembed.[..., d_vocab](using a cached small model only; no new large download in CI). — covered by9665e075.Checklist