Coverage for transformer_lens/model_bridge/supported_architectures/_remote_code_compat.py: 99%
55 statements
« prev ^ index » next coverage.py v7.10.1, created at 2026-09-21 19:27 +0000
« prev ^ index » next coverage.py v7.10.1, created at 2026-09-21 19:27 +0000
1"""Shared transformers-v5 compatibility helpers for remote-code adapters.
3Modeling files loaded via ``trust_remote_code`` were mostly written against
4transformers 4.x and break under v5 in recurring ways: the meta-device
5load-then-materialise flow re-runs ``_init_weights`` over already-loaded
6modules, ``tie_weights`` calls ``.keys()`` on list-form ``_tied_weights_keys``,
7``all_tied_weights_keys`` lookups hit broken remote ``__getattr__``s, and the
8``ROPE_INIT_FUNCTIONS["default"]`` entry the remote code looks up was removed.
9The patch mechanics live here; each adapter's ``prepare_loading`` keeps the
10WHY — what its remote code gets wrong and which classes need patching.
12These paths only fire on real ``trust_remote_code`` loads (CI-invisible), so
13the helpers are unit-tested directly in
14``tests/unit/model_bridge/test_remote_code_compat.py``.
15"""
17import sys
18from collections.abc import Iterator
19from types import ModuleType
20from typing import Any
22import torch
23from transformers import PreTrainedModel
26def iter_remote_modeling_modules(*name_fragments: str) -> Iterator[ModuleType]:
27 """Yield imported modules whose name contains ``modeling`` and any fragment.
29 Each checkpoint revision of a remote-code repo imports its own copy of the
30 modeling file (distinct ``transformers_modules.*`` entries), so class-level
31 patches must be applied to every one. Matching is case-insensitive.
32 """
33 fragments = tuple(fragment.lower() for fragment in name_fragments)
34 for key in list(sys.modules.keys()):
35 key_lower = key.lower()
36 if "modeling" not in key_lower:
37 continue
38 if not any(fragment in key_lower for fragment in fragments):
39 continue
40 module = sys.modules.get(key)
41 if module is not None: 41 ↛ 34line 41 didn't jump to line 34 because the condition on line 41 was always true
42 yield module
45def force_import_remote_class(model_name: str, dotted_ref: str, **kwargs: Any) -> type | None:
46 """Import a remote-code class so its module lands in ``sys.modules`` to patch.
48 ``dotted_ref`` is ``"<module_file>.<ClassName>"`` as understood by
49 ``get_class_from_dynamic_module``; extra kwargs are forwarded to it.
50 Returns the class, or None when the dynamic module is unavailable
51 (offline, renamed, not a remote-code repo) — callers treat that as
52 "nothing to patch".
53 """
54 try:
55 from transformers.dynamic_module_utils import get_class_from_dynamic_module
57 cls = get_class_from_dynamic_module(dotted_ref, model_name, **kwargs)
58 assert isinstance(cls, type)
59 return cls
60 except Exception:
61 return None
64def patch_init_weights_skip_loaded(cls: type[Any]) -> None:
65 """Wrap ``cls._init_weights`` to skip modules already loaded from checkpoint.
67 v5's meta-device flow calls ``_init_weights`` on modules that already hold
68 checkpoint weights (remote-code modules lack ``_is_hf_initialized``),
69 re-randomising them; the wrapper only lets initialisation through for
70 modules still on the meta device. Idempotent via the ``_tl_patched``
71 sentinel.
73 Raises:
74 ValueError: for ``transformers.PreTrainedModel`` itself and for classes
75 without their own ``_init_weights``. Remote modules re-export the
76 HF base under local names, and patching it would disable weight
77 init — including HF's rotary-buffer restoration — for every model
78 loaded later in the process.
79 """
80 if getattr(cls, "_tl_patched", False):
81 return
82 if cls is PreTrainedModel:
83 raise ValueError(
84 "Refusing to patch transformers.PreTrainedModel itself: that would "
85 "disable weight init for every model loaded later in this process. "
86 "Pass the remote code's own PreTrainedModel subclass."
87 )
88 if "_init_weights" not in cls.__dict__:
89 raise ValueError(
90 f"Refusing to patch {cls.__name__}: it defines no _init_weights of "
91 "its own, so the wrap would capture an inherited (possibly HF base) "
92 "implementation."
93 )
95 original_init_weights = cls._init_weights
97 def safe_init_weights(self: Any, mod: Any, _original: Any = original_init_weights) -> None:
98 # Only initialise modules still on meta device (pre-loading); never
99 # re-randomise weights already read from the checkpoint.
100 first_param = next(mod.parameters(), None)
101 if first_param is not None and first_param.device.type != "meta":
102 return
103 _original(self, mod)
105 cls._init_weights = safe_init_weights
106 cls._tl_patched = True
109def retie_weights_keys_v5(cls: Any, mapping: dict[str, str]) -> None:
110 """Rewrite a 4.x list-form ``_tied_weights_keys`` to the v5 dict form.
112 v5's ``tie_weights`` -> ``get_expanded_tied_weights_keys`` calls ``.keys()``
113 on the attribute and raises ``AttributeError`` on the legacy list. No-op
114 when ``cls`` is None or the attribute is already a dict (or absent).
115 """
116 if cls is not None and isinstance(getattr(cls, "_tied_weights_keys", None), list):
117 cls._tied_weights_keys = mapping
120def disable_tied_weights_lookup(cls: type[Any]) -> None:
121 """Give ``cls`` an empty ``all_tied_weights_keys``.
123 Some remote ``__getattr__`` implementations fail to delegate v5's
124 ``all_tied_weights_keys`` lookup back to ``PreTrainedModel`` and raise;
125 the affected checkpoints are untied anyway.
126 """
127 setattr(cls, "all_tied_weights_keys", {})
130def compute_default_rope_inv_freq(
131 config: Any = None,
132 device: Any = None,
133 seq_len: Any = None,
134 **rope_kwargs: Any,
135) -> tuple[torch.Tensor, float]:
136 """transformers-v4 ``_compute_default_rope_parameters``, removed in v5.
138 Standard (unscaled) RoPE inverse frequencies with the v4 call contract the
139 remote code targets — ``(config, device) -> (inv_freq, attention_scaling)``
140 — plus v4's kwargs-only fallback (``base``/``dim`` passed directly).
141 Registration strategy stays with each adapter: dream re-registers the
142 global ``ROPE_INIT_FUNCTIONS["default"]``, ouro deliberately patches only
143 its own modeling module's copy.
144 """
145 if config is not None:
146 base = config.rope_theta
147 partial_rotary_factor = getattr(config, "partial_rotary_factor", None) or 1.0
148 head_dim = getattr(config, "head_dim", None) or (
149 config.hidden_size // config.num_attention_heads
150 )
151 dim = int(head_dim * partial_rotary_factor)
152 else:
153 base = rope_kwargs["base"]
154 dim = rope_kwargs["dim"]
155 inv_freq = 1.0 / (
156 base
157 ** (torch.arange(0, dim, 2, dtype=torch.int64).to(device=device, dtype=torch.float) / dim)
158 )
159 return inv_freq, 1.0