Module neuralops.utils.replace_attention
Functions
def replace_attention(model, attention_key, layer_indices=None, **kwargs) ‑> Any-
Expand source code
def replace_attention(model, attention_key, layer_indices=None, **kwargs) -> Any: """ Replace attention modules in a model with a registered attention block. Args: model: A PyTorch model (typically a Hugging Face transformer) attention_key: String key registered in ATTENTION_REGISTRY layer_indices: Optional list of layer indices to replace (0-indexed). If None, replace all attention layers. **kwargs: Additional arguments passed to the attention block constructor. Returns: Model with attention layers replaced. """ attn_fn = ATTENTION_REGISTRY.get(attention_key) if attn_fn is None: raise ValueError( f"Attention key '{attention_key}' not found in registry. " f"Available: {list(ATTENTION_REGISTRY.keys())}" ) if not hasattr(model, "config"): raise AttributeError("Model does not have a 'config' attribute.") config = model.config total_layers = getattr(config, "num_hidden_layers", 12) # Collect top-level attention module targets (avoiding submodules) targets: list[tuple[str, nn.Module, int]] = [] for name, module in model.named_modules(): parts = name.split(".") if not parts or not parts[-1]: continue leaf = parts[-1].lower() if leaf in [ "attention", "self_attn", "self", "attn", "selfattention", "encdecattention", ] or isinstance(module, nn.MultiheadAttention): if not any(name.startswith(t[0] + ".") for t in targets): layer_idx = 0 for part in parts: if part.isdigit(): layer_idx = int(part) break if layer_indices is None or layer_idx in layer_indices: targets.append((name, module, layer_idx)) replaced_count = 0 failed_count = 0 for name, old_module, layer_idx in targets: try: new_attn = _try_instantiate(attn_fn, config, layer_idx, total_layers, old_module) except Exception as e: print(f"[WARN] Could not replace {name} with '{attention_key}': {e}") failed_count += 1 continue # Copy weights if supported if hasattr(new_attn, "copy_weights") and callable(new_attn.copy_weights): try: new_attn.copy_weights(old_module) except ( AttributeError, KeyError, RuntimeError, TypeError, ValueError, OSError, ImportError, ): pass # Match device and dtype of old_module try: old_param = next(old_module.parameters()) new_attn.to(dtype=old_param.dtype, device=old_param.device) except (StopIteration, RuntimeError): pass # Wrap in universal adapter wrapped_attn = UniversalAttentionWrapper(new_attn) # Pass T5 metadata to new_attn and wrapper from neuralops.core.t5_compat import is_t5_model if is_t5_model(config): for attr in [ "has_relative_attention_bias", "relative_attention_bias", "relative_attention_num_buckets", "relative_attention_max_distance", "is_decoder", "layer_idx", ]: if hasattr(old_module, attr): val = getattr(old_module, attr) setattr(new_attn, attr, val) setattr(wrapped_attn, attr, val) if hasattr(old_module, "compute_bias"): new_attn.compute_bias = old_module.compute_bias wrapped_attn.compute_bias = old_module.compute_bias parts = name.split(".") parent_name = ".".join(parts[:-1]) child_name = parts[-1] parent = model.get_submodule(parent_name) if parent_name else model setattr(parent, child_name, wrapped_attn) replaced_count += 1 if replaced_count == 0 and failed_count == 0: print(f"[WARN] No attention layers found to replace for key='{attention_key}'.") elif failed_count > 0: print( f"Replaced {replaced_count}/{replaced_count + failed_count} layers with '{attention_key}' ({failed_count} skipped)." ) else: print(f"Successfully replaced {replaced_count} attention layers with '{attention_key}'.") return modelReplace attention modules in a model with a registered attention block.
- Args
- -----=
model- A PyTorch model (typically a Hugging Face transformer)
attention_key- String key registered in ATTENTION_REGISTRY
layer_indices- Optional list of layer indices to replace (0-indexed). If None, replace all attention layers.
**kwargs- Additional arguments passed to the attention block constructor.
Returns -----= Model with attention layers replaced.
Classes
class UniversalAttentionWrapper (inner_attn: torch.nn.modules.module.Module,
layer_name: str = '',
return_tuple: bool | None = None)-
Expand source code
class UniversalAttentionWrapper(nn.Module): """ Universal forward adapter wrapping any custom attention module. Seamlessly adapts across all HuggingFace architectures: - DistilBERT: forward(query=x, key=x, value=x, mask=mask, head_mask=head_mask, output_attentions=...) -> tuple (out, weights) - BERT / RoBERTa: forward(hidden_states, attention_mask, ...) -> tuple (out, weights) - LLaMA / Mistral: forward(hidden_states, attention_mask, position_ids, ...) -> tuple (out, weights, ...) - GPT-2: forward(x, layer_past, ...) -> tuple (out, present) """ def __init__( self, inner_attn: nn.Module, layer_name: str = "", return_tuple: bool | None = None, ) -> None: super().__init__() self._wrapped_attention = inner_attn self.layer_name = layer_name self.return_tuple = return_tuple @property def inner(self) -> nn.Module: return self._wrapped_attention @inner.setter def inner(self, module: nn.Module) -> None: self._wrapped_attention = module def forward(self, *args: Any, **kwargs: Any) -> tuple[torch.Tensor, torch.Tensor | None]: # 1. Extract hidden states / input tensor hidden_states: torch.Tensor | None = None if len(args) > 0 and isinstance(args[0], tuple): # Unwrap tuple-wrapped hidden_states (e.g. BERT layer output passed through). # The first element of the tuple is always the hidden tensor. args = (args[0][0],) + args[1:] if len(args) > 0 and isinstance(args[0], torch.Tensor): hidden_states = args[0] args = args[1:] elif "hidden_states" in kwargs: hidden_states = kwargs.pop("hidden_states") elif "query" in kwargs: hidden_states = kwargs.pop("query") elif "x" in kwargs: hidden_states = kwargs.pop("x") # 2. Extract attention mask attention_mask: torch.Tensor | None = None if len(args) > 0 and (isinstance(args[0], torch.Tensor) or args[0] is None): attention_mask = args[0] args = args[1:] elif "attention_mask" in kwargs: attention_mask = kwargs.pop("attention_mask") elif "mask" in kwargs: attention_mask = kwargs.pop("mask") elif "attn_mask" in kwargs: attention_mask = kwargs.pop("attn_mask") # Qwen supplies a prepared 4-D causal mask and rotary position # embeddings. Legacy replacements generally implement their own # masking and cannot consume that representation. The base Qwen model # is untouched; this applies only to an injected replacement. if "position_embeddings" in kwargs: # Native Qwen attention owns RoPE. A legacy replacement may opt # out explicitly; positional tensors are then removed only from # this injected call, never from the base model. if not getattr(self._wrapped_attention, "supports_rope", False): kwargs = bypass_qwen_rope(kwargs, self._wrapped_attention) kwargs.pop("qwen_rope_bypassed", None) if isinstance(attention_mask, torch.Tensor) and attention_mask.dim() == 4: attention_mask = qwen_attention_mask( attention_mask, causal=True, expects_bidirectional=bool( getattr(self._wrapped_attention, "expects_bidirectional", False) ), ) key_value_states = kwargs.pop("key_value_states", None) position_bias = kwargs.pop("position_bias", None) from neuralops.core.t5_compat import is_t5_model cfg = getattr(self._wrapped_attention, "config", getattr(self, "config", None)) is_t5 = is_t5_model(cfg) or key_value_states is not None or position_bias is not None if is_t5: out, ret_pos_bias = self._forward_t5( hidden_states=hidden_states, attention_mask=attention_mask, key_value_states=key_value_states, position_bias=position_bias, **kwargs, ) return out, ret_pos_bias # Clean redundant or mismatched args kwargs.pop("key", None) kwargs.pop("value", None) head_mask = kwargs.pop("head_mask", None) output_attentions = kwargs.pop("output_attentions", False) # 3. Dynamic call dispatch out = None call_attempts = [ lambda: self._wrapped_attention( hidden_states, attention_mask=attention_mask, head_mask=head_mask, output_attentions=output_attentions, **kwargs, ), lambda: self._wrapped_attention(hidden_states, attention_mask=attention_mask, **kwargs), lambda: self._wrapped_attention(hidden_states, mask=attention_mask, **kwargs), lambda: self._wrapped_attention( query=hidden_states, key=hidden_states, value=hidden_states, mask=attention_mask, **kwargs, ), lambda: self._wrapped_attention(hidden_states, **kwargs), lambda: self._wrapped_attention(hidden_states), ] last_err = None for attempt in call_attempts: try: out = attempt() break except TypeError as e: last_err = e continue if out is None: if last_err is not None: raise last_err raise RuntimeError( f"Failed to execute forward pass on {type(self._wrapped_attention).__name__}" ) # 4. Standardize output format from neuralops.utils.compat import is_decoder_family from neuralops.utils.model_family import detect_family cfg = getattr(self._wrapped_attention, "config", getattr(self, "config", None)) fam = detect_family(cfg) if cfg is not None else "unknown" is_decoder_family(fam) or fam in { "qwen3_5", "qwen", "gpt", "llama", "ssm", } if isinstance(out, tuple): first = out[0] second = out[1] if len(out) > 1 else None else: first = out second = None if self.return_tuple is not None: return (first, second) if self.return_tuple else first if "linear_attn" in self.layer_name: return first return (first, second) def _forward_t5( self, hidden_states: torch.Tensor, attention_mask: torch.Tensor | None = None, key_value_states: torch.Tensor | None = None, position_bias: torch.Tensor | None = None, **kwargs: Any, ) -> tuple[torch.Tensor, torch.Tensor]: """T5 forward pass executing relative position bias and cross/self attention correctly.""" # 1. If inner module implements custom forward_t5, call it if hasattr(self._wrapped_attention, "forward_t5") and callable( self._wrapped_attention.forward_t5 ): res = self._wrapped_attention.forward_t5( hidden_states, attention_mask=attention_mask, key_value_states=key_value_states, position_bias=position_bias, **kwargs, ) if isinstance(res, tuple): return res[0], (res[1] if len(res) > 1 else position_bias) return res, position_bias # 2. Determine cross-attention vs self-attention is_cross_attention = key_value_states is not None kv_input = key_value_states if is_cross_attention else hidden_states B, N_q, D = hidden_states.shape N_k = kv_input.shape[1] H = getattr(self._wrapped_attention, "num_heads", getattr(self, "num_heads", 8)) d = getattr(self._wrapped_attention, "head_dim", getattr(self, "head_dim", 64)) # 3. Position bias computation and propagation if position_bias is None: compute_bias_fn = getattr( self._wrapped_attention, "compute_bias", getattr(self, "compute_bias", None), ) has_rel = getattr( self._wrapped_attention, "has_relative_attention_bias", getattr(self, "has_relative_attention_bias", False), ) rel_emb = getattr( self._wrapped_attention, "relative_attention_bias", getattr(self, "relative_attention_bias", None), ) if has_rel and compute_bias_fn is not None and rel_emb is not None: position_bias = compute_bias_fn(N_q, N_k, device=hidden_states.device) else: position_bias = torch.zeros( (1, H, N_q, N_k), device=hidden_states.device, dtype=hidden_states.dtype, ) if attention_mask is not None: causal_mask = attention_mask[:, :, :, :N_k] if attention_mask.dim() == 4 else None if causal_mask is not None: position_bias = position_bias + causal_mask.to(position_bias.dtype) # 4. Extract projection layers from self._wrapped_attention q_mod = getattr( self._wrapped_attention, "q_proj", getattr( self._wrapped_attention, "q", getattr(self._wrapped_attention, "query", None), ), ) k_mod = getattr( self._wrapped_attention, "k_proj", getattr( self._wrapped_attention, "k", getattr(self._wrapped_attention, "key", None), ), ) v_mod = getattr( self._wrapped_attention, "v_proj", getattr( self._wrapped_attention, "v", getattr(self._wrapped_attention, "value", None), ), ) o_mod = getattr( self._wrapped_attention, "out_proj", getattr( self._wrapped_attention, "o", getattr(self._wrapped_attention, "dense", None), ), ) if q_mod is not None and k_mod is not None and v_mod is not None: Q = q_mod(hidden_states).view(B, N_q, H, d).transpose(1, 2) k_out = k_mod(kv_input) H_kv = k_out.shape[-1] // d if d > 0 else H K = k_out.view(B, N_k, H_kv, d).transpose(1, 2) if H_kv < H: K = K.repeat_interleave(H // H_kv, dim=1) v_out = v_mod(kv_input) H_v = v_out.shape[-1] // d if d > 0 else H V = v_out.view(B, N_k, H_v, d).transpose(1, 2) if H_v < H: V = V.repeat_interleave(H // H_v, dim=1) # T5 computes Q @ K.T without sqrt(d) division scores = torch.matmul(Q, K.transpose(-2, -1)) if position_bias is not None: scores = scores + position_bias.to(scores.dtype) if attention_mask is not None and attention_mask.dim() == 2: min_val = torch.finfo(scores.dtype).min if scores.dtype.is_floating_point else -1e4 extended_mask = attention_mask.unsqueeze(1).unsqueeze(2) == 0 scores = scores.masked_fill(extended_mask, min_val) # Float32 Softmax to guarantee finite FP16 import torch.nn.functional as F probs = F.softmax(scores.float(), dim=-1).to(scores.dtype) probs = torch.nan_to_num(probs, nan=0.0) dropout = getattr(self._wrapped_attention, "dropout", None) if dropout is not None and callable(dropout): probs = dropout(probs) context = torch.matmul(probs, V).transpose(1, 2).contiguous().view(B, N_q, -1) output = o_mod(context) if o_mod is not None else context return output, position_bias # Fallback to direct inner call try: res = self._wrapped_attention( hidden_states, key_value_states=key_value_states, encoder_hidden_states=key_value_states, **kwargs, ) except TypeError: res = self._wrapped_attention(hidden_states, **kwargs) if isinstance(res, tuple): return res[0], (res[1] if len(res) > 1 and res[1] is not None else position_bias) return res, position_bias def __getattr__(self, name: str) -> Any: try: return super().__getattr__(name) except AttributeError: if name in ("_wrapped_attention", "_modules", "_parameters", "_buffers"): raise return getattr(self._wrapped_attention, name)Universal forward adapter wrapping any custom attention module. Seamlessly adapts across all HuggingFace architectures: - DistilBERT: forward(query=x, key=x, value=x, mask=mask, head_mask=head_mask, output_attentions=…) -> tuple (out, weights) - BERT / RoBERTa: forward(hidden_states, attention_mask, …) -> tuple (out, weights) - LLaMA / Mistral: forward(hidden_states, attention_mask, position_ids, …) -> tuple (out, weights, …) - GPT-2: forward(x, layer_past, …) -> tuple (out, present)
Initialize internal Module state, shared by both nn.Module and ScriptModule.
Ancestors
- torch.nn.modules.module.Module
Instance variables
prop inner : torch.nn.modules.module.Module-
Expand source code
@property def inner(self) -> nn.Module: return self._wrapped_attention
Methods
def forward(self, *args: Any, **kwargs: Any) ‑> tuple[torch.Tensor, torch.Tensor | None]-
Expand source code
def forward(self, *args: Any, **kwargs: Any) -> tuple[torch.Tensor, torch.Tensor | None]: # 1. Extract hidden states / input tensor hidden_states: torch.Tensor | None = None if len(args) > 0 and isinstance(args[0], tuple): # Unwrap tuple-wrapped hidden_states (e.g. BERT layer output passed through). # The first element of the tuple is always the hidden tensor. args = (args[0][0],) + args[1:] if len(args) > 0 and isinstance(args[0], torch.Tensor): hidden_states = args[0] args = args[1:] elif "hidden_states" in kwargs: hidden_states = kwargs.pop("hidden_states") elif "query" in kwargs: hidden_states = kwargs.pop("query") elif "x" in kwargs: hidden_states = kwargs.pop("x") # 2. Extract attention mask attention_mask: torch.Tensor | None = None if len(args) > 0 and (isinstance(args[0], torch.Tensor) or args[0] is None): attention_mask = args[0] args = args[1:] elif "attention_mask" in kwargs: attention_mask = kwargs.pop("attention_mask") elif "mask" in kwargs: attention_mask = kwargs.pop("mask") elif "attn_mask" in kwargs: attention_mask = kwargs.pop("attn_mask") # Qwen supplies a prepared 4-D causal mask and rotary position # embeddings. Legacy replacements generally implement their own # masking and cannot consume that representation. The base Qwen model # is untouched; this applies only to an injected replacement. if "position_embeddings" in kwargs: # Native Qwen attention owns RoPE. A legacy replacement may opt # out explicitly; positional tensors are then removed only from # this injected call, never from the base model. if not getattr(self._wrapped_attention, "supports_rope", False): kwargs = bypass_qwen_rope(kwargs, self._wrapped_attention) kwargs.pop("qwen_rope_bypassed", None) if isinstance(attention_mask, torch.Tensor) and attention_mask.dim() == 4: attention_mask = qwen_attention_mask( attention_mask, causal=True, expects_bidirectional=bool( getattr(self._wrapped_attention, "expects_bidirectional", False) ), ) key_value_states = kwargs.pop("key_value_states", None) position_bias = kwargs.pop("position_bias", None) from neuralops.core.t5_compat import is_t5_model cfg = getattr(self._wrapped_attention, "config", getattr(self, "config", None)) is_t5 = is_t5_model(cfg) or key_value_states is not None or position_bias is not None if is_t5: out, ret_pos_bias = self._forward_t5( hidden_states=hidden_states, attention_mask=attention_mask, key_value_states=key_value_states, position_bias=position_bias, **kwargs, ) return out, ret_pos_bias # Clean redundant or mismatched args kwargs.pop("key", None) kwargs.pop("value", None) head_mask = kwargs.pop("head_mask", None) output_attentions = kwargs.pop("output_attentions", False) # 3. Dynamic call dispatch out = None call_attempts = [ lambda: self._wrapped_attention( hidden_states, attention_mask=attention_mask, head_mask=head_mask, output_attentions=output_attentions, **kwargs, ), lambda: self._wrapped_attention(hidden_states, attention_mask=attention_mask, **kwargs), lambda: self._wrapped_attention(hidden_states, mask=attention_mask, **kwargs), lambda: self._wrapped_attention( query=hidden_states, key=hidden_states, value=hidden_states, mask=attention_mask, **kwargs, ), lambda: self._wrapped_attention(hidden_states, **kwargs), lambda: self._wrapped_attention(hidden_states), ] last_err = None for attempt in call_attempts: try: out = attempt() break except TypeError as e: last_err = e continue if out is None: if last_err is not None: raise last_err raise RuntimeError( f"Failed to execute forward pass on {type(self._wrapped_attention).__name__}" ) # 4. Standardize output format from neuralops.utils.compat import is_decoder_family from neuralops.utils.model_family import detect_family cfg = getattr(self._wrapped_attention, "config", getattr(self, "config", None)) fam = detect_family(cfg) if cfg is not None else "unknown" is_decoder_family(fam) or fam in { "qwen3_5", "qwen", "gpt", "llama", "ssm", } if isinstance(out, tuple): first = out[0] second = out[1] if len(out) > 1 else None else: first = out second = None if self.return_tuple is not None: return (first, second) if self.return_tuple else first if "linear_attn" in self.layer_name: return first return (first, second)Define the computation performed at every call.
Should be overridden by all subclasses.
Note
Although the recipe for forward pass needs to be defined within this function, one should call the :class:
Moduleinstance afterwards instead of this since the former takes care of running the registered hooks while the latter silently ignores them.