Module neuralops.attentions.replace
Model Attention Replacement and Dynamic Graph Surgery Utilities — v2.
Functions
def _cfg_get(cfg: Any, key: str, default: Any = None) ‑> Any-
Expand source code
def _cfg_get(cfg: Any, key: str, default: Any = None) -> Any: if cfg is None: return default if isinstance(cfg, dict): return cfg.get(key, default) return getattr(cfg, key, default) def _get_valid_mask(attention_mask: torch.Tensor | None,
batch_size: int,
seq_len: int,
device: torch.device) ‑> torch.Tensor-
Expand source code
def _get_valid_mask( attention_mask: torch.Tensor | None, batch_size: int, seq_len: int, device: torch.device, ) -> torch.Tensor: if attention_mask is None: return torch.ones(batch_size, seq_len, device=device, dtype=torch.bool) if attention_mask.dim() == 2: if attention_mask.dtype == torch.bool: return attention_mask if attention_mask.dtype.is_floating_point: return attention_mask > -1e4 return attention_mask > 0 if attention_mask.dim() == 3: v = ( attention_mask if attention_mask.dtype == torch.bool else ( attention_mask > -1e4 if attention_mask.dtype.is_floating_point else attention_mask > 0 ) ) return v.any(dim=1) if attention_mask.dim() == 4: v = ( attention_mask if attention_mask.dtype == torch.bool else ( attention_mask > -1e4 if attention_mask.dtype.is_floating_point else attention_mask > 0 ) ) v = v.any(dim=-2) # (B, H, N) if v.size(1) == 1: v = v.squeeze(1) else: v = v.any(dim=1) return v return torch.ones(batch_size, seq_len, device=device, dtype=torch.bool) def _replace_bert_attention(layer: torch.nn.modules.module.Module,
inject_fn: Any,
model_config: Any = None,
**kwargs: Any) ‑> None-
Expand source code
def _replace_bert_attention( layer: nn.Module, inject_fn: Any, model_config: Any = None, **kwargs: Any ) -> None: _replace_layers([layer], "attention", inject_fn, model_config, kwargs, "bert") def _replace_distilbert_attention(layer: torch.nn.modules.module.Module,
inject_fn: Any,
model_config: Any = None,
**kwargs: Any) ‑> None-
Expand source code
def _replace_distilbert_attention( layer: nn.Module, inject_fn: Any, model_config: Any = None, **kwargs: Any ) -> None: _replace_layers([layer], "attention", inject_fn, model_config, kwargs, "distilbert") def _replace_gpt_attention(layer: torch.nn.modules.module.Module,
inject_fn: Any,
model_config: Any = None,
**kwargs: Any) ‑> None-
Expand source code
def _replace_gpt_attention( layer: nn.Module, inject_fn: Any, model_config: Any = None, **kwargs: Any ) -> None: _replace_layers([layer], "attn", inject_fn, model_config, kwargs, "gpt2") def build_attention(attention_name: str, hidden_size: int, num_heads: int, **kwargs: Any) ‑> torch.nn.modules.module.Module-
Expand source code
def build_attention( attention_name: str, hidden_size: int, num_heads: int, **kwargs: Any ) -> nn.Module: return _build_attention_from_registry(attention_name, hidden_size, num_heads, **kwargs) def inject_replace(layer: Any = None,
model: Any = None,
model_config: Any = None,
comp_config: Any = None,
layer_idx: int = 0,
total_layers: int = 12,
old_attn: torch.nn.modules.module.Module | None = None,
**kwargs: Any) ‑> torch.nn.modules.module.Module | None-
Expand source code
@ATTENTION_REGISTRY.register("replace_inject") @ATTENTION_REGISTRY.register("replace_attention_inject") @ATTENTION_REGISTRY.register("inject_replace") @ATTENTION_REGISTRY.register("inject_replace_attention") @ATTENTION_REGISTRY.register("replace") @ATTENTION_REGISTRY.register("replace_attention") def inject_replace( layer: Any = None, model: Any = None, model_config: Any = None, comp_config: Any = None, layer_idx: int = 0, total_layers: int = 12, old_attn: nn.Module | None = None, _depth: int = 0, **kwargs: Any, ) -> nn.Module | None: """Safe injection of ReplaceAttention with recursion guard.""" if _depth > 8: logger.warning("inject_replace: max recursion depth exceeded.") return None target = layer if layer is not None else model if target is None: target = kwargs.get("model", kwargs.get("layer")) if target is None: return None cfg = model_config if model_config is not None else getattr(target, "config", None) # Whole-model traversal layer_list = None if hasattr(target, "distilbert") and hasattr(target.distilbert, "transformer"): layer_list = target.distilbert.transformer.layer elif hasattr(target, "roberta") and hasattr(target.roberta, "encoder"): layer_list = target.roberta.encoder.layer elif hasattr(target, "transformer") and hasattr(target.transformer, "h"): layer_list = target.transformer.h elif hasattr(target, "h") and isinstance(target.h, (nn.ModuleList, list)): layer_list = target.h elif hasattr(target, "model") and hasattr(target.model, "layers"): layer_list = target.model.layers elif hasattr(target, "encoder") and hasattr(target.encoder, "layer"): layer_list = target.encoder.layer elif ( hasattr(target, "bert") and hasattr(target.bert, "encoder") and hasattr(target.bert.encoder, "layer") ): layer_list = target.bert.encoder.layer elif hasattr(target, "layers") and isinstance(target.layers, (nn.ModuleList, list)): layer_list = target.layers if layer_list is not None: num_injected = 0 tot = len(layer_list) for idx, lyr in enumerate(layer_list): res = inject_replace( layer=lyr, model_config=cfg, comp_config=comp_config, layer_idx=idx, total_layers=tot, _depth=_depth + 1, **kwargs, ) if res is not None: num_injected += 1 return target if num_injected > 0 else None # Single-layer injection try: src = old_attn if src is None: for attr in ("attention", "self_attn", "attn", "self"): if hasattr(target, attr): cand = getattr(target, attr) if isinstance(cand, nn.Module) and cand is not target: src = cand break if src is None: src = target if isinstance(src, ReplaceAttention) or "ReplaceAttention" in src.__class__.__name__: return None attn_params = {} if comp_config is not None: for key in ("rank", "dropout"): val = getattr(comp_config, key, None) if val is not None: attn_params[key] = val new_attn = ReplaceAttention( config=cfg or getattr(src, "config", None), old_attn=src if src is not target else None, **attn_params, **kwargs, ) # RE-29: return the module; _replace_layers will attach it. return new_attn except Exception as e: logger.exception("inject_replace failed at layer %d: %s", layer_idx, e) return NoneSafe injection of ReplaceAttention with recursion guard.
def inject_replace_attention(layer: Any = None,
model: Any = None,
model_config: Any = None,
comp_config: Any = None,
layer_idx: int = 0,
total_layers: int = 12,
old_attn: torch.nn.modules.module.Module | None = None,
**kwargs: Any) ‑> torch.nn.modules.module.Module | None-
Expand source code
@ATTENTION_REGISTRY.register("replace_inject") @ATTENTION_REGISTRY.register("replace_attention_inject") @ATTENTION_REGISTRY.register("inject_replace") @ATTENTION_REGISTRY.register("inject_replace_attention") @ATTENTION_REGISTRY.register("replace") @ATTENTION_REGISTRY.register("replace_attention") def inject_replace( layer: Any = None, model: Any = None, model_config: Any = None, comp_config: Any = None, layer_idx: int = 0, total_layers: int = 12, old_attn: nn.Module | None = None, _depth: int = 0, **kwargs: Any, ) -> nn.Module | None: """Safe injection of ReplaceAttention with recursion guard.""" if _depth > 8: logger.warning("inject_replace: max recursion depth exceeded.") return None target = layer if layer is not None else model if target is None: target = kwargs.get("model", kwargs.get("layer")) if target is None: return None cfg = model_config if model_config is not None else getattr(target, "config", None) # Whole-model traversal layer_list = None if hasattr(target, "distilbert") and hasattr(target.distilbert, "transformer"): layer_list = target.distilbert.transformer.layer elif hasattr(target, "roberta") and hasattr(target.roberta, "encoder"): layer_list = target.roberta.encoder.layer elif hasattr(target, "transformer") and hasattr(target.transformer, "h"): layer_list = target.transformer.h elif hasattr(target, "h") and isinstance(target.h, (nn.ModuleList, list)): layer_list = target.h elif hasattr(target, "model") and hasattr(target.model, "layers"): layer_list = target.model.layers elif hasattr(target, "encoder") and hasattr(target.encoder, "layer"): layer_list = target.encoder.layer elif ( hasattr(target, "bert") and hasattr(target.bert, "encoder") and hasattr(target.bert.encoder, "layer") ): layer_list = target.bert.encoder.layer elif hasattr(target, "layers") and isinstance(target.layers, (nn.ModuleList, list)): layer_list = target.layers if layer_list is not None: num_injected = 0 tot = len(layer_list) for idx, lyr in enumerate(layer_list): res = inject_replace( layer=lyr, model_config=cfg, comp_config=comp_config, layer_idx=idx, total_layers=tot, _depth=_depth + 1, **kwargs, ) if res is not None: num_injected += 1 return target if num_injected > 0 else None # Single-layer injection try: src = old_attn if src is None: for attr in ("attention", "self_attn", "attn", "self"): if hasattr(target, attr): cand = getattr(target, attr) if isinstance(cand, nn.Module) and cand is not target: src = cand break if src is None: src = target if isinstance(src, ReplaceAttention) or "ReplaceAttention" in src.__class__.__name__: return None attn_params = {} if comp_config is not None: for key in ("rank", "dropout"): val = getattr(comp_config, key, None) if val is not None: attn_params[key] = val new_attn = ReplaceAttention( config=cfg or getattr(src, "config", None), old_attn=src if src is not target else None, **attn_params, **kwargs, ) # RE-29: return the module; _replace_layers will attach it. return new_attn except Exception as e: logger.exception("inject_replace failed at layer %d: %s", layer_idx, e) return NoneSafe injection of ReplaceAttention with recursion guard.
def replace_attention_in_model(model: torch.nn.modules.module.Module,
attention_name: str,
attention_params: dict[str, typing.Any] | None = None) ‑> torch.nn.modules.module.Module-
Expand source code
def replace_attention_in_model( model: nn.Module, attention_name: str, attention_params: dict[str, Any] | None = None, ) -> nn.Module: if attention_params is None: attention_params = {} model_type = getattr(getattr(model, "config", None), "model_type", None) if model_type in ("t5", "mt5", "bart", "mbart", "pegasus", "marian"): raise NotImplementedError( f"replace_attention_in_model does not support encoder-decoder " f"models (model_type={model_type})." ) inject_fn = ATTENTION_REGISTRY.get(attention_name) if inject_fn is None: raise ValueError(f"Attention '{attention_name}' not registered.") if not callable(inject_fn): raise ValueError(f"Attention '{attention_name}' is not callable.") cfg = getattr(model, "config", None) if model_type in ("bert", "roberta", "xlm-roberta", "electra", "deberta"): encoder = getattr( getattr(model, model_type.replace("-", "_"), model), "encoder", None ) or getattr(getattr(model, "bert", None), "encoder", None) if encoder is None: raise AttributeError("Could not locate encoder.") _replace_layers(encoder.layer, "attention", inject_fn, cfg, attention_params, model_type) elif model_type == "distilbert": distilbert = getattr(model, "distilbert", model) _replace_layers( distilbert.transformer.layer, "attention", inject_fn, cfg, attention_params, model_type, ) elif model_type == "gpt2": transformer = getattr(model, "transformer", None) if transformer is None: raise AttributeError("Could not locate transformer in GPT-2.") _replace_layers(transformer.h, "attn", inject_fn, cfg, attention_params, model_type) elif model_type in ( "qwen2", "qwen3", "qwen2_5", "llama", "mistral", "gemma", "phi", "opt", "bloom", "falcon", "olmo", "mpt", "stablelm", ): layers = None for path in ("model.layers", "transformer.h", "layers", "decoder.layers"): obj = model ok = True for part in path.split("."): obj = getattr(obj, part, None) if obj is None: ok = False break if ok: layers = obj break if layers is None: raise AttributeError(f"Could not locate decoder layers for {model_type}.") _replace_layers(layers, "self_attn", inject_fn, cfg, attention_params, model_type) else: raise ValueError(f"Unsupported model type: {model_type}") return model
Classes
class ReplaceAttention (config: Any | None = None,
old_attn: torch.nn.modules.module.Module | None = None,
rank: int | None = None,
hidden_size: int | None = None,
num_heads: int | None = None,
num_attention_heads: int | None = None,
dropout: float = 0.1,
is_decoder: bool | None = None,
**kwargs: Any)-
Expand source code
@ATTENTION_REGISTRY.register("replace_class") @ATTENTION_REGISTRY.register("replace_attention_class") class ReplaceAttention(BaseAttention): """ Robust identity / low-rank SVD replacement attention. """ supports_causal: bool = True supports_gqa: bool = True is_custom_attention: bool = True # RE-28: class-level flag (not per-instance) so deepcopy doesn't reset it. _first_copy_done: bool = False def __init__( self, config: Any | None = None, old_attn: nn.Module | None = None, rank: int | None = None, hidden_size: int | None = None, num_heads: int | None = None, num_attention_heads: int | None = None, dropout: float = 0.1, is_decoder: bool | None = None, **kwargs: Any, ) -> None: try: super().__init__(config, rank, **kwargs) except TypeError: super().__init__(config) self.hidden_size = int( hidden_size or _cfg_get(config, "hidden_size", _cfg_get(config, "d_model", 768)) ) n_heads_candidate = ( num_heads or num_attention_heads or _cfg_get(config, "num_attention_heads", _cfg_get(config, "num_heads", 12)) ) self.num_heads = int(n_heads_candidate) if n_heads_candidate else 12 if self.num_heads <= 0: self.num_heads = 12 self.head_dim = max(1, self.hidden_size // self.num_heads) self.all_head_size = self.num_heads * self.head_dim self.num_kv_heads = int( _cfg_get( config, "num_key_value_heads", _cfg_get(config, "num_kv_heads", self.num_heads), ) ) if self.num_kv_heads <= 0 or self.num_kv_heads > self.num_heads: self.num_kv_heads = self.num_heads if is_decoder is not None: self.is_decoder = bool(is_decoder) else: self.is_decoder = bool(_cfg_get(config, "is_decoder", False)) self.is_nested_encoder: bool = False self.rank = int(rank) if rank and rank > 0 else 0 if self.rank > 0: self.q_proj = _FactorizedLinear( self.hidden_size, self.all_head_size, self.rank, bias=True ) self.k_proj = _FactorizedLinear( self.hidden_size, self.num_kv_heads * self.head_dim, self.rank, bias=True, ) self.v_proj = _FactorizedLinear( self.hidden_size, self.num_kv_heads * self.head_dim, self.rank, bias=True, ) self.out_proj = _FactorizedLinear( self.all_head_size, self.hidden_size, self.rank, bias=True ) else: self.q_proj = nn.Linear(self.hidden_size, self.all_head_size) self.k_proj = nn.Linear(self.hidden_size, self.num_kv_heads * self.head_dim) self.v_proj = nn.Linear(self.hidden_size, self.num_kv_heads * self.head_dim) self.out_proj = nn.Linear(self.all_head_size, self.hidden_size) self.dropout = nn.Dropout(dropout) if old_attn is not None: self.copy_weights(old_attn) # Aliases @property def query(self): return self.q_proj @property def key(self): return self.k_proj @property def value(self): return self.v_proj @property def q_lin(self): return self.q_proj @property def k_lin(self): return self.k_proj @property def v_lin(self): return self.v_proj @property def out_lin(self): return self.out_proj @property def o_proj(self): return self.out_proj @property def dense(self): return self.out_proj @property def c_proj(self): return self.out_proj # ------------------------------------------------------------------ # RE-26: _copy_into for _FactorizedLinear # ------------------------------------------------------------------ def _copy_into( self, target_proj: nn.Module, w: torch.Tensor | None, b: torch.Tensor | None, target_out: int, target_in: int, ) -> None: if w is None: return with torch.no_grad(): if isinstance(target_proj, _FactorizedLinear): try: W = w.detach().float() if W.shape[0] != target_out or W.shape[1] != target_in: logger.warning( "Shape mismatch in copy_into: source %s, target (%d, %d).", tuple(W.shape), target_out, target_in, ) # Use up to target_proj.rank. req_rank = target_proj.rank if W.shape[0] > 512 and W.shape[1] > 512: U, S, Vh = _randomized_svd(W, req_rank) else: U, S, Vh = torch.linalg.svd(W, full_matrices=False) effective_rank = min(req_rank, U.size(1), Vh.size(0)) # RE-26: zero first, then fill only effective_rank rows/cols. target_proj.down.weight.data.zero_() target_proj.up.weight.data.zero_() target_proj.down.weight.data[:effective_rank, :].copy_( Vh[:effective_rank, :].to(target_proj.down.weight.dtype) ) US = U[:, :effective_rank] @ torch.diag(S[:effective_rank]) target_proj.up.weight.data[:, :effective_rank].copy_( US.to(target_proj.up.weight.dtype) ) if b is not None and target_proj.up.bias is not None: n = min(b.numel(), target_proj.up.bias.numel()) target_proj.up.bias.data[:n].copy_(b[:n].to(target_proj.up.bias.dtype)) except Exception as e: logger.error("SVD init failed: %s", e) else: if w.shape != target_proj.weight.shape: raise ValueError( f"Weight shape mismatch: source {tuple(w.shape)} != " f"target {tuple(target_proj.weight.shape)}." ) target_proj.weight.data.copy_(w.to(target_proj.weight.dtype)) if b is not None and target_proj.bias is not None: if b.shape != target_proj.bias.shape: raise ValueError( f"Bias shape mismatch: {tuple(b.shape)} != " f"{tuple(target_proj.bias.shape)}" ) target_proj.bias.data.copy_(b.to(target_proj.bias.dtype)) # ------------------------------------------------------------------ def copy_weights(self, old_attn: Any) -> None: if old_attn is None: return src = getattr(old_attn, "base_layer", old_attn) w_q = b_q = w_k = b_k = w_v = b_v = w_o = b_o = None # Pattern 1: GPT-2 Conv1D / Linear fused if hasattr(src, "c_attn") and hasattr(src, "c_proj"): c_attn = src.c_attn c_proj = src.c_proj w_all = c_attn.weight.data b_all = c_attn.bias.data if c_attn.bias is not None else None hidden = w_all.shape[0] if w_all.shape[0] < w_all.shape[1] else w_all.shape[1] if w_all.shape[0] == 3 * hidden or w_all.shape[1] == 3 * hidden: if w_all.shape[1] == 3 * hidden: chunks = w_all.chunk(3, dim=1) w_q, w_k, w_v = [c.t().contiguous() for c in chunks] else: chunks = w_all.chunk(3, dim=0) w_q, w_k, w_v = chunks[0], chunks[1], chunks[2] if b_all is not None: b_q, b_k, b_v = b_all.chunk(3, dim=0) w_proj = c_proj.weight.data if w_proj.shape[0] != self.hidden_size and w_proj.shape[1] == self.hidden_size: w_o = w_proj.t().contiguous() else: w_o = w_proj b_o = c_proj.bias.data if c_proj.bias is not None else None # Pattern 2: BERT / RoBERTa nested elif hasattr(src, "self") and hasattr(src.self, "query"): s = src.self w_q = s.query.weight.data b_q = s.query.bias.data if s.query.bias is not None else None w_k = s.key.weight.data b_k = s.key.bias.data if s.key.bias is not None else None w_v = s.value.weight.data b_v = s.value.bias.data if s.value.bias is not None else None if hasattr(src, "output") and hasattr(src.output, "dense"): w_o = src.output.dense.weight.data b_o = src.output.dense.bias.data if src.output.dense.bias is not None else None self.is_nested_encoder = True self.out_proj = nn.Identity() # Pattern 3: Flat query/key/value elif hasattr(src, "query") and hasattr(src, "key") and hasattr(src, "value"): w_q = src.query.weight.data b_q = src.query.bias.data if src.query.bias is not None else None w_k = src.key.weight.data b_k = src.key.bias.data if src.key.bias is not None else None w_v = src.value.weight.data b_v = src.value.bias.data if src.value.bias is not None else None for attr in ("dense", "out_proj", "o_proj", "proj"): if hasattr(src, attr): proj = getattr(src, attr) if hasattr(proj, "weight"): w_o = proj.weight.data b_o = proj.bias.data if getattr(proj, "bias", None) is not None else None break if w_o is None: self.is_nested_encoder = True self.out_proj = nn.Identity() # Pattern 4: DistilBERT elif hasattr(src, "q_lin") and hasattr(src, "k_lin") and hasattr(src, "v_lin"): w_q = src.q_lin.weight.data b_q = src.q_lin.bias.data if src.q_lin.bias is not None else None w_k = src.k_lin.weight.data b_k = src.k_lin.bias.data if src.k_lin.bias is not None else None w_v = src.v_lin.weight.data b_v = src.v_lin.bias.data if src.v_lin.bias is not None else None if hasattr(src, "out_lin"): w_o = src.out_lin.weight.data b_o = src.out_lin.bias.data if src.out_lin.bias is not None else None # Pattern 5: Qwen / LLaMA elif hasattr(src, "q_proj") and hasattr(src, "k_proj") and hasattr(src, "v_proj"): w_q = src.q_proj.weight.data b_q = src.q_proj.bias.data if getattr(src.q_proj, "bias", None) is not None else None w_k = src.k_proj.weight.data b_k = src.k_proj.bias.data if getattr(src.k_proj, "bias", None) is not None else None w_v = src.v_proj.weight.data b_v = src.v_proj.bias.data if getattr(src.v_proj, "bias", None) is not None else None if hasattr(src, "o_proj"): w_o = src.o_proj.weight.data b_o = ( src.o_proj.bias.data if getattr(src.o_proj, "bias", None) is not None else None ) # Fallback if w_q is None or w_k is None or w_v is None: try: adapter = UniversalAdapter() if w_q is None: w_q, b_q = adapter.get_weight_bias(old_attn, "q") if w_k is None: w_k, b_k = adapter.get_weight_bias(old_attn, "k") if w_v is None: w_v, b_v = adapter.get_weight_bias(old_attn, "v") if w_o is None: w_o, b_o = adapter.get_weight_bias(old_attn, "o") except Exception as e: logger.warning("UniversalAdapter fallback failed: %s", e) # RE-28: use class-level flag if not ReplaceAttention._first_copy_done: for name, w in [("w_q", w_q), ("w_k", w_k), ("w_v", w_v), ("w_o", w_o)]: if w is not None: norm = float(torch.linalg.norm(w.float()).item()) if norm < 1e-4: logger.warning( "Copied weight %s has near-zero Frobenius norm (%.2e)", name, norm, ) ReplaceAttention._first_copy_done = True # RE-27: cast only the affected projections, not the whole module. if w_q is not None and hasattr(self.q_proj, "weight"): if w_q.dtype != self.q_proj.weight.dtype: self.q_proj.to(w_q.dtype) if w_k is not None and hasattr(self.k_proj, "weight"): if w_k.dtype != self.k_proj.weight.dtype: self.k_proj.to(w_k.dtype) if w_v is not None and hasattr(self.v_proj, "weight"): if w_v.dtype != self.v_proj.weight.dtype: self.v_proj.to(w_v.dtype) if w_o is not None and not self.is_nested_encoder and hasattr(self.out_proj, "weight"): if w_o.dtype != self.out_proj.weight.dtype: self.out_proj.to(w_o.dtype) self._copy_into(self.q_proj, w_q, b_q, self.all_head_size, self.hidden_size) self._copy_into(self.k_proj, w_k, b_k, self.num_kv_heads * self.head_dim, self.hidden_size) self._copy_into(self.v_proj, w_v, b_v, self.num_kv_heads * self.head_dim, self.hidden_size) if not self.is_nested_encoder: self._copy_into(self.out_proj, w_o, b_o, self.hidden_size, self.all_head_size) # ------------------------------------------------------------------ # Forward # ------------------------------------------------------------------ def forward( self, hidden_states: torch.Tensor, attention_mask: torch.Tensor | None = None, head_mask: torch.Tensor | None = None, encoder_hidden_states: torch.Tensor | None = None, encoder_attention_mask: torch.Tensor | None = None, output_attentions: bool = False, position_embeddings: tuple[torch.Tensor, torch.Tensor] | None = None, **kwargs: Any, ) -> tuple[torch.Tensor, torch.Tensor | None]: if encoder_hidden_states is not None: raise NotImplementedError("ReplaceAttention: cross-attention not supported.") B, N, _ = hidden_states.shape device = hidden_states.device dtype = hidden_states.dtype Q = ( self.q_proj(hidden_states) .view(B, N, self.num_heads, self.head_dim) .transpose(1, 2) .contiguous() ) K = ( self.k_proj(hidden_states) .view(B, N, self.num_kv_heads, self.head_dim) .transpose(1, 2) .contiguous() ) V = ( self.v_proj(hidden_states) .view(B, N, self.num_kv_heads, self.head_dim) .transpose(1, 2) .contiguous() ) if self.num_kv_heads < self.num_heads: repeat = self.num_heads // self.num_kv_heads K = K.repeat_interleave(repeat, dim=1) V = V.repeat_interleave(repeat, dim=1) is_causal = self.is_decoder and N > 1 attn_mask_sdpa = None if attention_mask is not None: if attention_mask.dim() == 2: attn_mask_sdpa = ( (attention_mask.unsqueeze(1).unsqueeze(2).float() - 1.0) * 10000.0 ).to(Q.dtype) elif attention_mask.dim() == 4: attn_mask_sdpa = attention_mask.to(Q.dtype) if not output_attentions and head_mask is None: try: q_sdpa = Q.half() if Q.dtype == torch.float32 else Q k_sdpa = K.half() if K.dtype == torch.float32 else K v_sdpa = V.half() if V.dtype == torch.float32 else V out_sdpa = F.scaled_dot_product_attention( q_sdpa, k_sdpa, v_sdpa, attn_mask=attn_mask_sdpa, is_causal=is_causal and attn_mask_sdpa is None, dropout_p=self.dropout.p if self.training else 0.0, ) out = out_sdpa.to(dtype).transpose(1, 2).contiguous().view(B, N, self.all_head_size) out = torch.nan_to_num(out, nan=0.0, posinf=1e4, neginf=-1e4) if self.is_nested_encoder: return out, None result = self.out_proj(out) result = torch.nan_to_num(result, nan=0.0, posinf=1e4, neginf=-1e4) return result, None except Exception as e: logger.debug("SDPA path failed (%s); falling back.", e) scale = 1.0 / math.sqrt(self.head_dim) scores = torch.matmul(Q.float(), K.float().transpose(-2, -1).contiguous()) * scale if is_causal: causal = ( torch.triu( torch.full((N, N), float("-inf"), device=device, dtype=torch.float32), diagonal=1, ) .unsqueeze(0) .unsqueeze(0) ) scores = scores + causal if attention_mask is not None: if attention_mask.dim() == 2: scores = scores + (attention_mask.unsqueeze(1).unsqueeze(2).float() - 1.0) * 10000.0 elif attention_mask.dim() == 3: scores = scores + attention_mask.unsqueeze(1).float() elif attention_mask.dim() == 4: scores = scores + attention_mask.float() scores = torch.nan_to_num(scores, nan=-1e4, posinf=1e4, neginf=-1e4) probs = F.softmax(scores, dim=-1).to(dtype) probs = self.dropout(probs) # RE-25: proper head_mask handling. if head_mask is not None: if head_mask.dim() == 1: hm = head_mask.view(1, -1, 1, 1) elif head_mask.dim() == 2: # (num_layers, num_heads) or (B, H): pick the right slice. if head_mask.shape[0] > 1 and head_mask.shape[1] == self.num_heads: hm = head_mask[:1, :].view(1, -1, 1, 1) else: hm = head_mask.view(head_mask.shape[0], -1, 1, 1) elif head_mask.dim() == 3: # Treat as (B, H, N) — mask per head; average over N. # Simpler: reduce to (B, H, 1, 1). hm = head_mask.mean(dim=-1).view(head_mask.shape[0], -1, 1, 1) elif head_mask.dim() == 4: hm = head_mask else: hm = None if hm is not None: probs = probs * hm.to(probs.dtype) context = torch.matmul(probs, V) valid_mask = _get_valid_mask(attention_mask, B, N, device) context = context * valid_mask.unsqueeze(1).unsqueeze(-1).to(dtype) context = context.transpose(1, 2).contiguous().view(B, N, self.all_head_size) if self.is_nested_encoder: return context, (probs if output_attentions else None) out = self.out_proj(context) out = torch.nan_to_num(out, nan=0.0, posinf=1e4, neginf=-1e4) return out, (probs if output_attentions else None)Robust identity / low-rank SVD replacement attention.
Initialize internal Module state, shared by both nn.Module and ScriptModule.
Ancestors
- BaseAttention
- torch.nn.modules.module.Module
Class variables
var is_custom_attention : bool-
The type of the None singleton.
var supports_causal : bool-
The type of the None singleton.
var supports_gqa : bool-
The type of the None singleton.
Instance variables
prop c_proj-
Expand source code
@property def c_proj(self): return self.out_proj prop dense-
Expand source code
@property def dense(self): return self.out_proj prop k_lin-
Expand source code
@property def k_lin(self): return self.k_proj prop key-
Expand source code
@property def key(self): return self.k_proj prop o_proj-
Expand source code
@property def o_proj(self): return self.out_proj prop out_lin-
Expand source code
@property def out_lin(self): return self.out_proj prop q_lin-
Expand source code
@property def q_lin(self): return self.q_proj prop query-
Expand source code
@property def query(self): return self.q_proj prop v_lin-
Expand source code
@property def v_lin(self): return self.v_proj prop value-
Expand source code
@property def value(self): return self.v_proj
Methods
def copy_weights(self, old_attn: Any) ‑> None-
Expand source code
def copy_weights(self, old_attn: Any) -> None: if old_attn is None: return src = getattr(old_attn, "base_layer", old_attn) w_q = b_q = w_k = b_k = w_v = b_v = w_o = b_o = None # Pattern 1: GPT-2 Conv1D / Linear fused if hasattr(src, "c_attn") and hasattr(src, "c_proj"): c_attn = src.c_attn c_proj = src.c_proj w_all = c_attn.weight.data b_all = c_attn.bias.data if c_attn.bias is not None else None hidden = w_all.shape[0] if w_all.shape[0] < w_all.shape[1] else w_all.shape[1] if w_all.shape[0] == 3 * hidden or w_all.shape[1] == 3 * hidden: if w_all.shape[1] == 3 * hidden: chunks = w_all.chunk(3, dim=1) w_q, w_k, w_v = [c.t().contiguous() for c in chunks] else: chunks = w_all.chunk(3, dim=0) w_q, w_k, w_v = chunks[0], chunks[1], chunks[2] if b_all is not None: b_q, b_k, b_v = b_all.chunk(3, dim=0) w_proj = c_proj.weight.data if w_proj.shape[0] != self.hidden_size and w_proj.shape[1] == self.hidden_size: w_o = w_proj.t().contiguous() else: w_o = w_proj b_o = c_proj.bias.data if c_proj.bias is not None else None # Pattern 2: BERT / RoBERTa nested elif hasattr(src, "self") and hasattr(src.self, "query"): s = src.self w_q = s.query.weight.data b_q = s.query.bias.data if s.query.bias is not None else None w_k = s.key.weight.data b_k = s.key.bias.data if s.key.bias is not None else None w_v = s.value.weight.data b_v = s.value.bias.data if s.value.bias is not None else None if hasattr(src, "output") and hasattr(src.output, "dense"): w_o = src.output.dense.weight.data b_o = src.output.dense.bias.data if src.output.dense.bias is not None else None self.is_nested_encoder = True self.out_proj = nn.Identity() # Pattern 3: Flat query/key/value elif hasattr(src, "query") and hasattr(src, "key") and hasattr(src, "value"): w_q = src.query.weight.data b_q = src.query.bias.data if src.query.bias is not None else None w_k = src.key.weight.data b_k = src.key.bias.data if src.key.bias is not None else None w_v = src.value.weight.data b_v = src.value.bias.data if src.value.bias is not None else None for attr in ("dense", "out_proj", "o_proj", "proj"): if hasattr(src, attr): proj = getattr(src, attr) if hasattr(proj, "weight"): w_o = proj.weight.data b_o = proj.bias.data if getattr(proj, "bias", None) is not None else None break if w_o is None: self.is_nested_encoder = True self.out_proj = nn.Identity() # Pattern 4: DistilBERT elif hasattr(src, "q_lin") and hasattr(src, "k_lin") and hasattr(src, "v_lin"): w_q = src.q_lin.weight.data b_q = src.q_lin.bias.data if src.q_lin.bias is not None else None w_k = src.k_lin.weight.data b_k = src.k_lin.bias.data if src.k_lin.bias is not None else None w_v = src.v_lin.weight.data b_v = src.v_lin.bias.data if src.v_lin.bias is not None else None if hasattr(src, "out_lin"): w_o = src.out_lin.weight.data b_o = src.out_lin.bias.data if src.out_lin.bias is not None else None # Pattern 5: Qwen / LLaMA elif hasattr(src, "q_proj") and hasattr(src, "k_proj") and hasattr(src, "v_proj"): w_q = src.q_proj.weight.data b_q = src.q_proj.bias.data if getattr(src.q_proj, "bias", None) is not None else None w_k = src.k_proj.weight.data b_k = src.k_proj.bias.data if getattr(src.k_proj, "bias", None) is not None else None w_v = src.v_proj.weight.data b_v = src.v_proj.bias.data if getattr(src.v_proj, "bias", None) is not None else None if hasattr(src, "o_proj"): w_o = src.o_proj.weight.data b_o = ( src.o_proj.bias.data if getattr(src.o_proj, "bias", None) is not None else None ) # Fallback if w_q is None or w_k is None or w_v is None: try: adapter = UniversalAdapter() if w_q is None: w_q, b_q = adapter.get_weight_bias(old_attn, "q") if w_k is None: w_k, b_k = adapter.get_weight_bias(old_attn, "k") if w_v is None: w_v, b_v = adapter.get_weight_bias(old_attn, "v") if w_o is None: w_o, b_o = adapter.get_weight_bias(old_attn, "o") except Exception as e: logger.warning("UniversalAdapter fallback failed: %s", e) # RE-28: use class-level flag if not ReplaceAttention._first_copy_done: for name, w in [("w_q", w_q), ("w_k", w_k), ("w_v", w_v), ("w_o", w_o)]: if w is not None: norm = float(torch.linalg.norm(w.float()).item()) if norm < 1e-4: logger.warning( "Copied weight %s has near-zero Frobenius norm (%.2e)", name, norm, ) ReplaceAttention._first_copy_done = True # RE-27: cast only the affected projections, not the whole module. if w_q is not None and hasattr(self.q_proj, "weight"): if w_q.dtype != self.q_proj.weight.dtype: self.q_proj.to(w_q.dtype) if w_k is not None and hasattr(self.k_proj, "weight"): if w_k.dtype != self.k_proj.weight.dtype: self.k_proj.to(w_k.dtype) if w_v is not None and hasattr(self.v_proj, "weight"): if w_v.dtype != self.v_proj.weight.dtype: self.v_proj.to(w_v.dtype) if w_o is not None and not self.is_nested_encoder and hasattr(self.out_proj, "weight"): if w_o.dtype != self.out_proj.weight.dtype: self.out_proj.to(w_o.dtype) self._copy_into(self.q_proj, w_q, b_q, self.all_head_size, self.hidden_size) self._copy_into(self.k_proj, w_k, b_k, self.num_kv_heads * self.head_dim, self.hidden_size) self._copy_into(self.v_proj, w_v, b_v, self.num_kv_heads * self.head_dim, self.hidden_size) if not self.is_nested_encoder: self._copy_into(self.out_proj, w_o, b_o, self.hidden_size, self.all_head_size)Universal fallback: copy pretrained weights from old attention layer.
def forward(self,
hidden_states: torch.Tensor,
attention_mask: torch.Tensor | None = None,
head_mask: torch.Tensor | None = None,
encoder_hidden_states: torch.Tensor | None = None,
encoder_attention_mask: torch.Tensor | None = None,
output_attentions: bool = False,
position_embeddings: tuple[torch.Tensor, torch.Tensor] | None = None,
**kwargs: Any) ‑> tuple[torch.Tensor, torch.Tensor | None]-
Expand source code
def forward( self, hidden_states: torch.Tensor, attention_mask: torch.Tensor | None = None, head_mask: torch.Tensor | None = None, encoder_hidden_states: torch.Tensor | None = None, encoder_attention_mask: torch.Tensor | None = None, output_attentions: bool = False, position_embeddings: tuple[torch.Tensor, torch.Tensor] | None = None, **kwargs: Any, ) -> tuple[torch.Tensor, torch.Tensor | None]: if encoder_hidden_states is not None: raise NotImplementedError("ReplaceAttention: cross-attention not supported.") B, N, _ = hidden_states.shape device = hidden_states.device dtype = hidden_states.dtype Q = ( self.q_proj(hidden_states) .view(B, N, self.num_heads, self.head_dim) .transpose(1, 2) .contiguous() ) K = ( self.k_proj(hidden_states) .view(B, N, self.num_kv_heads, self.head_dim) .transpose(1, 2) .contiguous() ) V = ( self.v_proj(hidden_states) .view(B, N, self.num_kv_heads, self.head_dim) .transpose(1, 2) .contiguous() ) if self.num_kv_heads < self.num_heads: repeat = self.num_heads // self.num_kv_heads K = K.repeat_interleave(repeat, dim=1) V = V.repeat_interleave(repeat, dim=1) is_causal = self.is_decoder and N > 1 attn_mask_sdpa = None if attention_mask is not None: if attention_mask.dim() == 2: attn_mask_sdpa = ( (attention_mask.unsqueeze(1).unsqueeze(2).float() - 1.0) * 10000.0 ).to(Q.dtype) elif attention_mask.dim() == 4: attn_mask_sdpa = attention_mask.to(Q.dtype) if not output_attentions and head_mask is None: try: q_sdpa = Q.half() if Q.dtype == torch.float32 else Q k_sdpa = K.half() if K.dtype == torch.float32 else K v_sdpa = V.half() if V.dtype == torch.float32 else V out_sdpa = F.scaled_dot_product_attention( q_sdpa, k_sdpa, v_sdpa, attn_mask=attn_mask_sdpa, is_causal=is_causal and attn_mask_sdpa is None, dropout_p=self.dropout.p if self.training else 0.0, ) out = out_sdpa.to(dtype).transpose(1, 2).contiguous().view(B, N, self.all_head_size) out = torch.nan_to_num(out, nan=0.0, posinf=1e4, neginf=-1e4) if self.is_nested_encoder: return out, None result = self.out_proj(out) result = torch.nan_to_num(result, nan=0.0, posinf=1e4, neginf=-1e4) return result, None except Exception as e: logger.debug("SDPA path failed (%s); falling back.", e) scale = 1.0 / math.sqrt(self.head_dim) scores = torch.matmul(Q.float(), K.float().transpose(-2, -1).contiguous()) * scale if is_causal: causal = ( torch.triu( torch.full((N, N), float("-inf"), device=device, dtype=torch.float32), diagonal=1, ) .unsqueeze(0) .unsqueeze(0) ) scores = scores + causal if attention_mask is not None: if attention_mask.dim() == 2: scores = scores + (attention_mask.unsqueeze(1).unsqueeze(2).float() - 1.0) * 10000.0 elif attention_mask.dim() == 3: scores = scores + attention_mask.unsqueeze(1).float() elif attention_mask.dim() == 4: scores = scores + attention_mask.float() scores = torch.nan_to_num(scores, nan=-1e4, posinf=1e4, neginf=-1e4) probs = F.softmax(scores, dim=-1).to(dtype) probs = self.dropout(probs) # RE-25: proper head_mask handling. if head_mask is not None: if head_mask.dim() == 1: hm = head_mask.view(1, -1, 1, 1) elif head_mask.dim() == 2: # (num_layers, num_heads) or (B, H): pick the right slice. if head_mask.shape[0] > 1 and head_mask.shape[1] == self.num_heads: hm = head_mask[:1, :].view(1, -1, 1, 1) else: hm = head_mask.view(head_mask.shape[0], -1, 1, 1) elif head_mask.dim() == 3: # Treat as (B, H, N) — mask per head; average over N. # Simpler: reduce to (B, H, 1, 1). hm = head_mask.mean(dim=-1).view(head_mask.shape[0], -1, 1, 1) elif head_mask.dim() == 4: hm = head_mask else: hm = None if hm is not None: probs = probs * hm.to(probs.dtype) context = torch.matmul(probs, V) valid_mask = _get_valid_mask(attention_mask, B, N, device) context = context * valid_mask.unsqueeze(1).unsqueeze(-1).to(dtype) context = context.transpose(1, 2).contiguous().view(B, N, self.all_head_size) if self.is_nested_encoder: return context, (probs if output_attentions else None) out = self.out_proj(context) out = torch.nan_to_num(out, nan=0.0, posinf=1e4, neginf=-1e4) return out, (probs if output_attentions else None)Must return (output, attention_weights_or_None).
class Replace (config: Any | None = None,
old_attn: torch.nn.modules.module.Module | None = None,
rank: int | None = None,
hidden_size: int | None = None,
num_heads: int | None = None,
num_attention_heads: int | None = None,
dropout: float = 0.1,
is_decoder: bool | None = None,
**kwargs: Any)-
Expand source code
@ATTENTION_REGISTRY.register("replace_class") @ATTENTION_REGISTRY.register("replace_attention_class") class ReplaceAttention(BaseAttention): """ Robust identity / low-rank SVD replacement attention. """ supports_causal: bool = True supports_gqa: bool = True is_custom_attention: bool = True # RE-28: class-level flag (not per-instance) so deepcopy doesn't reset it. _first_copy_done: bool = False def __init__( self, config: Any | None = None, old_attn: nn.Module | None = None, rank: int | None = None, hidden_size: int | None = None, num_heads: int | None = None, num_attention_heads: int | None = None, dropout: float = 0.1, is_decoder: bool | None = None, **kwargs: Any, ) -> None: try: super().__init__(config, rank, **kwargs) except TypeError: super().__init__(config) self.hidden_size = int( hidden_size or _cfg_get(config, "hidden_size", _cfg_get(config, "d_model", 768)) ) n_heads_candidate = ( num_heads or num_attention_heads or _cfg_get(config, "num_attention_heads", _cfg_get(config, "num_heads", 12)) ) self.num_heads = int(n_heads_candidate) if n_heads_candidate else 12 if self.num_heads <= 0: self.num_heads = 12 self.head_dim = max(1, self.hidden_size // self.num_heads) self.all_head_size = self.num_heads * self.head_dim self.num_kv_heads = int( _cfg_get( config, "num_key_value_heads", _cfg_get(config, "num_kv_heads", self.num_heads), ) ) if self.num_kv_heads <= 0 or self.num_kv_heads > self.num_heads: self.num_kv_heads = self.num_heads if is_decoder is not None: self.is_decoder = bool(is_decoder) else: self.is_decoder = bool(_cfg_get(config, "is_decoder", False)) self.is_nested_encoder: bool = False self.rank = int(rank) if rank and rank > 0 else 0 if self.rank > 0: self.q_proj = _FactorizedLinear( self.hidden_size, self.all_head_size, self.rank, bias=True ) self.k_proj = _FactorizedLinear( self.hidden_size, self.num_kv_heads * self.head_dim, self.rank, bias=True, ) self.v_proj = _FactorizedLinear( self.hidden_size, self.num_kv_heads * self.head_dim, self.rank, bias=True, ) self.out_proj = _FactorizedLinear( self.all_head_size, self.hidden_size, self.rank, bias=True ) else: self.q_proj = nn.Linear(self.hidden_size, self.all_head_size) self.k_proj = nn.Linear(self.hidden_size, self.num_kv_heads * self.head_dim) self.v_proj = nn.Linear(self.hidden_size, self.num_kv_heads * self.head_dim) self.out_proj = nn.Linear(self.all_head_size, self.hidden_size) self.dropout = nn.Dropout(dropout) if old_attn is not None: self.copy_weights(old_attn) # Aliases @property def query(self): return self.q_proj @property def key(self): return self.k_proj @property def value(self): return self.v_proj @property def q_lin(self): return self.q_proj @property def k_lin(self): return self.k_proj @property def v_lin(self): return self.v_proj @property def out_lin(self): return self.out_proj @property def o_proj(self): return self.out_proj @property def dense(self): return self.out_proj @property def c_proj(self): return self.out_proj # ------------------------------------------------------------------ # RE-26: _copy_into for _FactorizedLinear # ------------------------------------------------------------------ def _copy_into( self, target_proj: nn.Module, w: torch.Tensor | None, b: torch.Tensor | None, target_out: int, target_in: int, ) -> None: if w is None: return with torch.no_grad(): if isinstance(target_proj, _FactorizedLinear): try: W = w.detach().float() if W.shape[0] != target_out or W.shape[1] != target_in: logger.warning( "Shape mismatch in copy_into: source %s, target (%d, %d).", tuple(W.shape), target_out, target_in, ) # Use up to target_proj.rank. req_rank = target_proj.rank if W.shape[0] > 512 and W.shape[1] > 512: U, S, Vh = _randomized_svd(W, req_rank) else: U, S, Vh = torch.linalg.svd(W, full_matrices=False) effective_rank = min(req_rank, U.size(1), Vh.size(0)) # RE-26: zero first, then fill only effective_rank rows/cols. target_proj.down.weight.data.zero_() target_proj.up.weight.data.zero_() target_proj.down.weight.data[:effective_rank, :].copy_( Vh[:effective_rank, :].to(target_proj.down.weight.dtype) ) US = U[:, :effective_rank] @ torch.diag(S[:effective_rank]) target_proj.up.weight.data[:, :effective_rank].copy_( US.to(target_proj.up.weight.dtype) ) if b is not None and target_proj.up.bias is not None: n = min(b.numel(), target_proj.up.bias.numel()) target_proj.up.bias.data[:n].copy_(b[:n].to(target_proj.up.bias.dtype)) except Exception as e: logger.error("SVD init failed: %s", e) else: if w.shape != target_proj.weight.shape: raise ValueError( f"Weight shape mismatch: source {tuple(w.shape)} != " f"target {tuple(target_proj.weight.shape)}." ) target_proj.weight.data.copy_(w.to(target_proj.weight.dtype)) if b is not None and target_proj.bias is not None: if b.shape != target_proj.bias.shape: raise ValueError( f"Bias shape mismatch: {tuple(b.shape)} != " f"{tuple(target_proj.bias.shape)}" ) target_proj.bias.data.copy_(b.to(target_proj.bias.dtype)) # ------------------------------------------------------------------ def copy_weights(self, old_attn: Any) -> None: if old_attn is None: return src = getattr(old_attn, "base_layer", old_attn) w_q = b_q = w_k = b_k = w_v = b_v = w_o = b_o = None # Pattern 1: GPT-2 Conv1D / Linear fused if hasattr(src, "c_attn") and hasattr(src, "c_proj"): c_attn = src.c_attn c_proj = src.c_proj w_all = c_attn.weight.data b_all = c_attn.bias.data if c_attn.bias is not None else None hidden = w_all.shape[0] if w_all.shape[0] < w_all.shape[1] else w_all.shape[1] if w_all.shape[0] == 3 * hidden or w_all.shape[1] == 3 * hidden: if w_all.shape[1] == 3 * hidden: chunks = w_all.chunk(3, dim=1) w_q, w_k, w_v = [c.t().contiguous() for c in chunks] else: chunks = w_all.chunk(3, dim=0) w_q, w_k, w_v = chunks[0], chunks[1], chunks[2] if b_all is not None: b_q, b_k, b_v = b_all.chunk(3, dim=0) w_proj = c_proj.weight.data if w_proj.shape[0] != self.hidden_size and w_proj.shape[1] == self.hidden_size: w_o = w_proj.t().contiguous() else: w_o = w_proj b_o = c_proj.bias.data if c_proj.bias is not None else None # Pattern 2: BERT / RoBERTa nested elif hasattr(src, "self") and hasattr(src.self, "query"): s = src.self w_q = s.query.weight.data b_q = s.query.bias.data if s.query.bias is not None else None w_k = s.key.weight.data b_k = s.key.bias.data if s.key.bias is not None else None w_v = s.value.weight.data b_v = s.value.bias.data if s.value.bias is not None else None if hasattr(src, "output") and hasattr(src.output, "dense"): w_o = src.output.dense.weight.data b_o = src.output.dense.bias.data if src.output.dense.bias is not None else None self.is_nested_encoder = True self.out_proj = nn.Identity() # Pattern 3: Flat query/key/value elif hasattr(src, "query") and hasattr(src, "key") and hasattr(src, "value"): w_q = src.query.weight.data b_q = src.query.bias.data if src.query.bias is not None else None w_k = src.key.weight.data b_k = src.key.bias.data if src.key.bias is not None else None w_v = src.value.weight.data b_v = src.value.bias.data if src.value.bias is not None else None for attr in ("dense", "out_proj", "o_proj", "proj"): if hasattr(src, attr): proj = getattr(src, attr) if hasattr(proj, "weight"): w_o = proj.weight.data b_o = proj.bias.data if getattr(proj, "bias", None) is not None else None break if w_o is None: self.is_nested_encoder = True self.out_proj = nn.Identity() # Pattern 4: DistilBERT elif hasattr(src, "q_lin") and hasattr(src, "k_lin") and hasattr(src, "v_lin"): w_q = src.q_lin.weight.data b_q = src.q_lin.bias.data if src.q_lin.bias is not None else None w_k = src.k_lin.weight.data b_k = src.k_lin.bias.data if src.k_lin.bias is not None else None w_v = src.v_lin.weight.data b_v = src.v_lin.bias.data if src.v_lin.bias is not None else None if hasattr(src, "out_lin"): w_o = src.out_lin.weight.data b_o = src.out_lin.bias.data if src.out_lin.bias is not None else None # Pattern 5: Qwen / LLaMA elif hasattr(src, "q_proj") and hasattr(src, "k_proj") and hasattr(src, "v_proj"): w_q = src.q_proj.weight.data b_q = src.q_proj.bias.data if getattr(src.q_proj, "bias", None) is not None else None w_k = src.k_proj.weight.data b_k = src.k_proj.bias.data if getattr(src.k_proj, "bias", None) is not None else None w_v = src.v_proj.weight.data b_v = src.v_proj.bias.data if getattr(src.v_proj, "bias", None) is not None else None if hasattr(src, "o_proj"): w_o = src.o_proj.weight.data b_o = ( src.o_proj.bias.data if getattr(src.o_proj, "bias", None) is not None else None ) # Fallback if w_q is None or w_k is None or w_v is None: try: adapter = UniversalAdapter() if w_q is None: w_q, b_q = adapter.get_weight_bias(old_attn, "q") if w_k is None: w_k, b_k = adapter.get_weight_bias(old_attn, "k") if w_v is None: w_v, b_v = adapter.get_weight_bias(old_attn, "v") if w_o is None: w_o, b_o = adapter.get_weight_bias(old_attn, "o") except Exception as e: logger.warning("UniversalAdapter fallback failed: %s", e) # RE-28: use class-level flag if not ReplaceAttention._first_copy_done: for name, w in [("w_q", w_q), ("w_k", w_k), ("w_v", w_v), ("w_o", w_o)]: if w is not None: norm = float(torch.linalg.norm(w.float()).item()) if norm < 1e-4: logger.warning( "Copied weight %s has near-zero Frobenius norm (%.2e)", name, norm, ) ReplaceAttention._first_copy_done = True # RE-27: cast only the affected projections, not the whole module. if w_q is not None and hasattr(self.q_proj, "weight"): if w_q.dtype != self.q_proj.weight.dtype: self.q_proj.to(w_q.dtype) if w_k is not None and hasattr(self.k_proj, "weight"): if w_k.dtype != self.k_proj.weight.dtype: self.k_proj.to(w_k.dtype) if w_v is not None and hasattr(self.v_proj, "weight"): if w_v.dtype != self.v_proj.weight.dtype: self.v_proj.to(w_v.dtype) if w_o is not None and not self.is_nested_encoder and hasattr(self.out_proj, "weight"): if w_o.dtype != self.out_proj.weight.dtype: self.out_proj.to(w_o.dtype) self._copy_into(self.q_proj, w_q, b_q, self.all_head_size, self.hidden_size) self._copy_into(self.k_proj, w_k, b_k, self.num_kv_heads * self.head_dim, self.hidden_size) self._copy_into(self.v_proj, w_v, b_v, self.num_kv_heads * self.head_dim, self.hidden_size) if not self.is_nested_encoder: self._copy_into(self.out_proj, w_o, b_o, self.hidden_size, self.all_head_size) # ------------------------------------------------------------------ # Forward # ------------------------------------------------------------------ def forward( self, hidden_states: torch.Tensor, attention_mask: torch.Tensor | None = None, head_mask: torch.Tensor | None = None, encoder_hidden_states: torch.Tensor | None = None, encoder_attention_mask: torch.Tensor | None = None, output_attentions: bool = False, position_embeddings: tuple[torch.Tensor, torch.Tensor] | None = None, **kwargs: Any, ) -> tuple[torch.Tensor, torch.Tensor | None]: if encoder_hidden_states is not None: raise NotImplementedError("ReplaceAttention: cross-attention not supported.") B, N, _ = hidden_states.shape device = hidden_states.device dtype = hidden_states.dtype Q = ( self.q_proj(hidden_states) .view(B, N, self.num_heads, self.head_dim) .transpose(1, 2) .contiguous() ) K = ( self.k_proj(hidden_states) .view(B, N, self.num_kv_heads, self.head_dim) .transpose(1, 2) .contiguous() ) V = ( self.v_proj(hidden_states) .view(B, N, self.num_kv_heads, self.head_dim) .transpose(1, 2) .contiguous() ) if self.num_kv_heads < self.num_heads: repeat = self.num_heads // self.num_kv_heads K = K.repeat_interleave(repeat, dim=1) V = V.repeat_interleave(repeat, dim=1) is_causal = self.is_decoder and N > 1 attn_mask_sdpa = None if attention_mask is not None: if attention_mask.dim() == 2: attn_mask_sdpa = ( (attention_mask.unsqueeze(1).unsqueeze(2).float() - 1.0) * 10000.0 ).to(Q.dtype) elif attention_mask.dim() == 4: attn_mask_sdpa = attention_mask.to(Q.dtype) if not output_attentions and head_mask is None: try: q_sdpa = Q.half() if Q.dtype == torch.float32 else Q k_sdpa = K.half() if K.dtype == torch.float32 else K v_sdpa = V.half() if V.dtype == torch.float32 else V out_sdpa = F.scaled_dot_product_attention( q_sdpa, k_sdpa, v_sdpa, attn_mask=attn_mask_sdpa, is_causal=is_causal and attn_mask_sdpa is None, dropout_p=self.dropout.p if self.training else 0.0, ) out = out_sdpa.to(dtype).transpose(1, 2).contiguous().view(B, N, self.all_head_size) out = torch.nan_to_num(out, nan=0.0, posinf=1e4, neginf=-1e4) if self.is_nested_encoder: return out, None result = self.out_proj(out) result = torch.nan_to_num(result, nan=0.0, posinf=1e4, neginf=-1e4) return result, None except Exception as e: logger.debug("SDPA path failed (%s); falling back.", e) scale = 1.0 / math.sqrt(self.head_dim) scores = torch.matmul(Q.float(), K.float().transpose(-2, -1).contiguous()) * scale if is_causal: causal = ( torch.triu( torch.full((N, N), float("-inf"), device=device, dtype=torch.float32), diagonal=1, ) .unsqueeze(0) .unsqueeze(0) ) scores = scores + causal if attention_mask is not None: if attention_mask.dim() == 2: scores = scores + (attention_mask.unsqueeze(1).unsqueeze(2).float() - 1.0) * 10000.0 elif attention_mask.dim() == 3: scores = scores + attention_mask.unsqueeze(1).float() elif attention_mask.dim() == 4: scores = scores + attention_mask.float() scores = torch.nan_to_num(scores, nan=-1e4, posinf=1e4, neginf=-1e4) probs = F.softmax(scores, dim=-1).to(dtype) probs = self.dropout(probs) # RE-25: proper head_mask handling. if head_mask is not None: if head_mask.dim() == 1: hm = head_mask.view(1, -1, 1, 1) elif head_mask.dim() == 2: # (num_layers, num_heads) or (B, H): pick the right slice. if head_mask.shape[0] > 1 and head_mask.shape[1] == self.num_heads: hm = head_mask[:1, :].view(1, -1, 1, 1) else: hm = head_mask.view(head_mask.shape[0], -1, 1, 1) elif head_mask.dim() == 3: # Treat as (B, H, N) — mask per head; average over N. # Simpler: reduce to (B, H, 1, 1). hm = head_mask.mean(dim=-1).view(head_mask.shape[0], -1, 1, 1) elif head_mask.dim() == 4: hm = head_mask else: hm = None if hm is not None: probs = probs * hm.to(probs.dtype) context = torch.matmul(probs, V) valid_mask = _get_valid_mask(attention_mask, B, N, device) context = context * valid_mask.unsqueeze(1).unsqueeze(-1).to(dtype) context = context.transpose(1, 2).contiguous().view(B, N, self.all_head_size) if self.is_nested_encoder: return context, (probs if output_attentions else None) out = self.out_proj(context) out = torch.nan_to_num(out, nan=0.0, posinf=1e4, neginf=-1e4) return out, (probs if output_attentions else None)Robust identity / low-rank SVD replacement attention.
Initialize internal Module state, shared by both nn.Module and ScriptModule.
Ancestors
- BaseAttention
- torch.nn.modules.module.Module
Class variables
var is_custom_attention : bool-
The type of the None singleton.
var supports_causal : bool-
The type of the None singleton.
var supports_gqa : bool-
The type of the None singleton.
Instance variables
prop c_proj-
Expand source code
@property def c_proj(self): return self.out_proj prop dense-
Expand source code
@property def dense(self): return self.out_proj prop k_lin-
Expand source code
@property def k_lin(self): return self.k_proj prop key-
Expand source code
@property def key(self): return self.k_proj prop o_proj-
Expand source code
@property def o_proj(self): return self.out_proj prop out_lin-
Expand source code
@property def out_lin(self): return self.out_proj prop q_lin-
Expand source code
@property def q_lin(self): return self.q_proj prop query-
Expand source code
@property def query(self): return self.q_proj prop v_lin-
Expand source code
@property def v_lin(self): return self.v_proj prop value-
Expand source code
@property def value(self): return self.v_proj
Inherited members