Skip to content

Fix TransformerBridge adapter traversal coverage - #1671

Open
emerardd wants to merge 1 commit into
TransformerLensOrg:dev-4.xfrom
emerardd:fix/bridge-adapter-coverage
Open

Fix TransformerBridge adapter traversal coverage#1671
emerardd wants to merge 1 commit into
TransformerLensOrg:dev-4.xfrom
emerardd:fix/bridge-adapter-coverage

Conversation

@emerardd

Copy link
Copy Markdown
Contributor

Description

This is the adapter-coverage half of #1655, following the two-PR, compatibility-first split agreed in the maintainer discussion. PR #1661 addressed recursive state-dict composition; this PR completes parent parameter and buffer traversal without changing the existing direct bridge.state_dict() TransformerLens-key contract.

The original BERT reproduction exposed adapter gaps that left live forward parameters outside the registered Bridge tree. Since token_type_embed has now landed on dev-4.x via #1664, this PR adds the remaining BERT embedding LayerNorm and MLM transform dense mappings, and covers pooler plus NSP-only and combined MLM/NSP variants using HookedEncoder-aligned names. A broader download-free structural sweep also found and maps the AST classifier LayerNorm and the bare ViT pooler.

Some Hugging Face models also own state directly on container modules rather than on mapped leaf modules; BART's root final_logits_bias buffer is one example. The Bridge now registers non-owning state-owner views only for parameter and buffer identities not already owned by the adapter tree. These views synchronize replacements made by _apply() and load_state_dict(assign=True) back to the original containers, so parent .to(), freezing, optimizers, and recursive loading continue to operate on the objects used by forward without registering the complete source model twice.

Regression coverage constructs 15 tiny random models entirely from config, spanning joint QKV, split QKV/RoPE, GQA, BERT MLM/NSP task heads, encoder-decoder, MoE, vision, and audio. It asserts that the source model, direct Bridge traversal, and parent traversal expose identical parameter and buffer identity sets, and separately covers container-buffer dtype conversion and assign-loading.

Fixes #1655

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • This change requires a documentation update

Validation

  • Full non-slow unit surface: all collected tests passed across the original run and an exact retry of 51 tests initially blocked by a local basetemp setup error (5219 + 51 passed; 55 skipped, 54 deselected, 10 xfailed)
  • Focused adapter and parent-traversal surface: 82 passed
  • mypy .: success across 431 source files
  • pycln, isort, Black, and git diff --check: clean

Checklist:

  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • I have not rewritten tests relating to key interfaces which would affect backward compatibility

@emerardd
emerardd marked this pull request as ready for review August 14, 2026 09:00

@jlarson4 jlarson4 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for putting this together! The traversal invariant you added is great. A couple quick comments:

original_container._parameters.update(self._parameters)
original_container._buffers.update(self._buffers)

def _apply(self, fn: Any, recurse: bool = True) -> "_ContainerStateOwner":

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_sync_original_container pushes the owner to the original container using tensors captured at registration, but TransformerBridge.load_state_dict(..., assign=True) replaces the tensor on the container and bypasses the bridge tree, leaving self stale. The next _apply writes the pre-load tensor back over the loaded one, so loaded weights have zero effect on forward. This hits BART's final_logits_bias and Hubert's masked_spec_embed.

Can you make the original container the single source of truth? Add a _refresh_from_original_container() that re-reads the container's _parameters/_buffers for the names it owns and call it at the top of _apply, and resync the owners after the assign load in TransformerBridge.load_state_dict (transformer_bridge.py:3971) so traversal isn't stale in between? Please add a guard test covering bridge.load_state_dict(..., assign=True) followed by .cpu().

if hasattr(hf_model, "cls") and hasattr(hf_model.cls, "seq_relationship"):
# NSP model — swap head components
if getattr(getattr(hf_model, "bert", None), "pooler", None) is not None:
self.components["pooler"] = LinearBridge(name="bert.pooler.dense")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bridge.state_dict() now returns bert.bert.pooler.dense.dense.weight where we previously returned bert.pooler.dense.weight. The anti-doubling guard in _normalize_bridge_key_to_hf only skips when the TL name is the last segment of its HF path, and pooler sits mid-path in bert.pooler.dense, so the segment gets re-expanded. This is the same issue you worked around on the ViT side with vision_pooler.

Can you widen the guard at transformer_bridge.py:3795 to skip whenever the TL name is any segment of its own HF name (tl_name in component.name.split("."))? That yields a clean pooler.weight, keeps the HookedEncoder-aligned name, and would let ViT drop the vision_pooler workaround. A test pinning the emitted key names for BERT/ViT/AST would stop this recurring.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants