Fix TransformerBridge adapter traversal coverage - #1671
Conversation
jlarson4
left a comment
There was a problem hiding this comment.
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": |
There was a problem hiding this comment.
_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") |
There was a problem hiding this comment.
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.
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_embedhas now landed ondev-4.xvia #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_biasbuffer 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()andload_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
Validation
basetempsetup error (5219 + 51passed;55 skipped,54 deselected,10 xfailed)82 passedmypy .: success across 431 source filesgit diff --check: cleanChecklist: