Coverage for transformer_lens/model_bridge/supported_architectures/apertus.py: 92%
58 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"""Apertus architecture adapter."""
3import logging
4from typing import Any
6from transformer_lens.model_bridge.architecture_adapter import ArchitectureAdapter
7from transformer_lens.model_bridge.generalized_components import (
8 BlockBridge,
9 EmbeddingBridge,
10 LinearBridge,
11 RMSNormalizationBridge,
12 RotaryEmbeddingBridge,
13 UnembeddingBridge,
14)
15from transformer_lens.model_bridge.generalized_components.position_embeddings_attention import (
16 PositionEmbeddingsAttentionBridge,
17)
19logger = logging.getLogger(__name__)
22class ApertusArchitectureAdapter(ArchitectureAdapter):
23 """Architecture adapter for Apertus models.
25 Apertus uses a pre-norm architecture with RMSNorm, Q/K normalization in attention,
26 rotary position embeddings (RoPE with LLaMA-3 scaling), grouped query attention (GQA),
27 non-gated MLP (XiELU activation), and no biases on any projections.
29 Similar to Qwen3 (pre-norm RMSNorm, QK-norm, GQA, RoPE) but uses a non-gated MLP
30 (up_proj -> XiELU -> down_proj) instead of gated MLP.
32 Note: Apertus uses different layer norm names than most Llama-family models:
33 - attention_layernorm (instead of input_layernorm)
34 - feedforward_layernorm (instead of post_attention_layernorm)
35 """
37 def __init__(self, cfg: Any) -> None:
38 """Initialize the Apertus architecture adapter."""
39 super().__init__(cfg)
41 self._set_rms_rotary_defaults(gated=False)
43 # Use eager attention to support output_attentions for hook_attn_scores and hook_pattern
44 # SDPA doesn't support output_attentions, which is required for HookedTransformer compatibility
45 self.cfg.attn_implementation = "eager"
47 self.weight_processing_conversions = {
48 # Q/K/V weight conversions - handle GQA (Grouped Query Attention)
49 **self._qkvo_weight_conversions(),
50 }
52 # Set up component mapping
53 # Apertus uses attention_layernorm / feedforward_layernorm instead of the
54 # typical input_layernorm / post_attention_layernorm names.
55 self.component_mapping = {
56 "embed": EmbeddingBridge(name="model.embed_tokens"),
57 "rotary_emb": RotaryEmbeddingBridge(name="model.rotary_emb", config=self.cfg),
58 "blocks": BlockBridge(
59 name="model.layers",
60 submodules={
61 "ln1": RMSNormalizationBridge(name="attention_layernorm", config=self.cfg),
62 "ln2": RMSNormalizationBridge(name="feedforward_layernorm", config=self.cfg),
63 "attn": PositionEmbeddingsAttentionBridge(
64 name="self_attn",
65 config=self.cfg,
66 submodules={
67 "q": LinearBridge(name="q_proj"),
68 "k": LinearBridge(name="k_proj"),
69 "v": LinearBridge(name="v_proj"),
70 "o": LinearBridge(name="o_proj"),
71 "q_norm": RMSNormalizationBridge(name="q_norm", config=self.cfg),
72 "k_norm": RMSNormalizationBridge(name="k_norm", config=self.cfg),
73 },
74 ),
75 "mlp": self._ungated_mlp(),
76 },
77 ),
78 "ln_final": RMSNormalizationBridge(name="model.norm", config=self.cfg),
79 "unembed": UnembeddingBridge(name="lm_head"),
80 }
82 def prepare_loading(self, model_name: str, model_kwargs: dict) -> None:
83 """Patch XIELUActivation to defer eager .item() calls for meta tensor compat.
85 Transformers v5 uses meta tensors during from_pretrained, but
86 XIELUActivation.__init__ eagerly calls .item() on beta/eps buffers to
87 precompute _beta_scalar/_eps_scalar for the CUDA kernel path. This fails
88 on meta device. Once upstream fixes this (transformers PR #43473), this
89 patch can be removed.
91 Instead of reimplementing __init__, we wrap it to catch the meta tensor
92 failure and defer scalar computation to forward() time.
93 """
94 try:
95 from transformers.activations import XIELUActivation
96 except ImportError:
97 return
99 if getattr(XIELUActivation, "_apertus_patched", False):
100 return
102 # Check if upstream already defers scalar computation (fix landed)
103 if not self._xielu_needs_patch(XIELUActivation): 103 ↛ 104line 103 didn't jump to line 104 because the condition on line 103 was never true
104 return
106 _orig_init = XIELUActivation.__init__
107 _orig_forward = XIELUActivation.forward
109 def _patched_init(self, *args, **kwargs):
110 try:
111 _orig_init(self, *args, **kwargs)
112 except NotImplementedError:
113 # Meta device — re-run without the .item() calls
114 _orig_init.__wrapped_meta = True # type: ignore[attr-defined]
115 # Call nn.Module.__init__ and replicate only the tensor setup
116 import torch
118 torch.nn.Module.__init__(self)
119 alpha_p_init = kwargs.get("alpha_p_init", 0.8)
120 alpha_n_init = kwargs.get("alpha_n_init", 0.8)
121 beta = kwargs.get("beta", 0.5)
122 eps = kwargs.get("eps", -1e-6)
123 dtype = kwargs.get("dtype", torch.bfloat16)
124 self.with_vector_loads = kwargs.get("with_vector_loads", False)
125 self.alpha_p = torch.nn.Parameter(
126 torch.log(torch.expm1(torch.tensor(alpha_p_init, dtype=dtype))).unsqueeze(0)
127 )
128 self.alpha_n = torch.nn.Parameter(
129 torch.log(
130 torch.expm1(torch.tensor(alpha_n_init - beta, dtype=dtype))
131 ).unsqueeze(0)
132 )
133 self.register_buffer("beta", torch.tensor(beta, dtype=dtype))
134 self.register_buffer("eps", torch.tensor(eps, dtype=dtype))
135 self._beta_scalar = None
136 self._eps_scalar = None
137 self._xielu_cuda_obj = None
139 def _patched_forward(self, x):
140 """Lazily compute scalars on first real forward pass."""
141 if self._beta_scalar is None: 141 ↛ 144line 141 didn't jump to line 144 because the condition on line 141 was always true
142 self._beta_scalar = float(self.beta.detach().cpu().float().item())
143 self._eps_scalar = float(self.eps.detach().cpu().float().item())
144 return _orig_forward(self, x)
146 XIELUActivation.__init__ = _patched_init # type: ignore[method-assign]
147 XIELUActivation.forward = _patched_forward # type: ignore[method-assign]
148 XIELUActivation._apertus_patched = True # type: ignore[attr-defined]
149 logger.debug("Patched XIELUActivation for meta tensor compatibility")
151 @staticmethod
152 def _xielu_needs_patch(cls: type) -> bool:
153 """Check whether XIELUActivation still eagerly calls .item() in __init__."""
154 import inspect
156 src = inspect.getsource(cls.__init__) # type: ignore[misc]
157 # If __init__ still has the eager .item() / float() pattern, patch needed
158 return "_beta_scalar" in src and ".item()" in src