Module neuralops.attentions.svd_attention
SVD-Compressed Sequence Attention.
This module reduces attention complexity from O(N²) to O(N·r) by projecting K and V onto a rank-r subspace along the sequence dimension.
CRITICAL LIMITATION — Read This:
The projection basis is precomputed ONCE from the SVD of
the key weight matrix W_K in copy_weights(). This means
the projection does NOT depend on the input sequence —
it is fixed at initialization time.
A more principled approach would compute a per-input
projection (e.g., via Linformer-style learned projections
or Nyström landmark selection). The current implementation
is a HEURISTIC and may not preserve attention semantics
for all inputs.
For CAUSAL decoders (GPT-2, Qwen, LLaMA), applying a
sequence-axis SVD breaks causality because the projection
combines future tokens into the subspace. The current
implementation DISABLES SVD compression for causal mode
and falls back to full attention. This means decoders
get no speedup from this module.
Future Work: - Learnable input-dependent projection (Linformer-style) - Nyström landmark selection - Causal-aware projection with causal masking
Registered as: "svd_seq", "svd_sequence", "svd_sequence_attention".
Functions
def apply_rotary_pos_emb(q: torch.Tensor,
k: torch.Tensor,
cos: torch.Tensor,
sin: torch.Tensor,
position_ids: torch.Tensor | None = None,
unsqueeze_dim: int = 1) ‑> tuple[torch.Tensor, torch.Tensor]-
Expand source code
def apply_rotary_pos_emb( q: torch.Tensor, k: torch.Tensor, cos: torch.Tensor, sin: torch.Tensor, position_ids: torch.Tensor | None = None, unsqueeze_dim: int = 1, ) -> tuple[torch.Tensor, torch.Tensor]: """Applies Rotary Position Embedding (RoPE) with partial dimension and independent seq-len support.""" if cos is None or sin is None: return q, k if cos.dim() == 2: cos = cos.unsqueeze(0).unsqueeze(1) sin = sin.unsqueeze(0).unsqueeze(1) elif cos.dim() == 3: cos = cos.unsqueeze(1) sin = sin.unsqueeze(1) while cos.dim() < q.dim(): cos = cos.unsqueeze(0) sin = sin.unsqueeze(0) head_dim = q.shape[-1] rotary_dim = 2 * (min(cos.shape[-1], head_dim, k.shape[-1]) // 2) if rotary_dim == 0: return q, k q_rot, q_pass = q[..., :rotary_dim], q[..., rotary_dim:] k_rot, k_pass = k[..., :rotary_dim], k[..., rotary_dim:] cos_q = cos[..., : q.shape[-2], :rotary_dim].to(q.device, q.dtype) sin_q = sin[..., : q.shape[-2], :rotary_dim].to(q.device, q.dtype) cos_k = cos[..., : k.shape[-2], :rotary_dim].to(k.device, k.dtype) sin_k = sin[..., : k.shape[-2], :rotary_dim].to(k.device, k.dtype) q_rot_embed = (q_rot * cos_q) + (_rotate_half(q_rot) * sin_q) k_rot_embed = (k_rot * cos_k) + (_rotate_half(k_rot) * sin_k) q_out = torch.cat([q_rot_embed, q_pass], dim=-1) if q_pass.shape[-1] > 0 else q_rot_embed k_out = torch.cat([k_rot_embed, k_pass], dim=-1) if k_pass.shape[-1] > 0 else k_rot_embed return q_out, k_outApplies Rotary Position Embedding (RoPE) with partial dimension and independent seq-len support.
def inject_svd_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)-
Expand source code
@ATTENTION_REGISTRY.register("svd_sequence_inject") @ATTENTION_REGISTRY.register("svd_sequence_attention_inject") @ATTENTION_REGISTRY.register("inject_svd_sequence_attention") def inject_svd_linear_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: nn.Module | None = None, **kwargs: Any, ): 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, (torch.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, (torch.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_svd_linear_attention( layer=lyr, model_config=cfg, comp_config=( comp_config if "comp_config" in locals() or "comp_config" in kwargs else None ), layer_idx=idx, total_layers=tot, **kwargs, ) if res is not None: num_injected += 1 return target if num_injected > 0 else None layer = target if old_attn is None and layer is not None: old_attn = getattr( layer, "attention", getattr(layer, "attn", getattr(layer, "self_attn", None)), ) if old_attn is None and hasattr(layer, "self"): old_attn = layer if old_attn is None: return None config = cfg if config is None and hasattr(layer, "config"): config = layer.config if config is None and hasattr(old_attn, "config"): config = old_attn.config rank = kwargs.pop("rank", None) lora_rank = kwargs.pop("lora_rank", None) r = ( rank if rank is not None else _cfg_get(comp_config, "rank", _cfg_get(comp_config, "svd_rank", 64)) ) lr = lora_rank if lora_rank is not None else _cfg_get(comp_config, "lora_rank", 16) dropout = _cfg_get(comp_config, "dropout", 0.1) try: new_attn = SVDLinearAttention( config=config, old_attn=old_attn, lora_rank=lr, rank=r, dropout=dropout, **kwargs, ) except Exception as e: logger.warning(f"Failed to instantiate SVDLinearAttention: {e}") return None if hasattr(layer, "attention"): if hasattr(layer.attention, "self"): layer.attention.self = new_attn else: layer.attention = new_attn elif hasattr(layer, "attn"): layer.attn = new_attn elif hasattr(layer, "self_attn"): layer.self_attn = new_attn elif hasattr(layer, "self"): layer.self = new_attn return new_attn def inject_svd_linear_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)-
Expand source code
@ATTENTION_REGISTRY.register("svd_sequence_inject") @ATTENTION_REGISTRY.register("svd_sequence_attention_inject") @ATTENTION_REGISTRY.register("inject_svd_sequence_attention") def inject_svd_linear_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: nn.Module | None = None, **kwargs: Any, ): 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, (torch.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, (torch.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_svd_linear_attention( layer=lyr, model_config=cfg, comp_config=( comp_config if "comp_config" in locals() or "comp_config" in kwargs else None ), layer_idx=idx, total_layers=tot, **kwargs, ) if res is not None: num_injected += 1 return target if num_injected > 0 else None layer = target if old_attn is None and layer is not None: old_attn = getattr( layer, "attention", getattr(layer, "attn", getattr(layer, "self_attn", None)), ) if old_attn is None and hasattr(layer, "self"): old_attn = layer if old_attn is None: return None config = cfg if config is None and hasattr(layer, "config"): config = layer.config if config is None and hasattr(old_attn, "config"): config = old_attn.config rank = kwargs.pop("rank", None) lora_rank = kwargs.pop("lora_rank", None) r = ( rank if rank is not None else _cfg_get(comp_config, "rank", _cfg_get(comp_config, "svd_rank", 64)) ) lr = lora_rank if lora_rank is not None else _cfg_get(comp_config, "lora_rank", 16) dropout = _cfg_get(comp_config, "dropout", 0.1) try: new_attn = SVDLinearAttention( config=config, old_attn=old_attn, lora_rank=lr, rank=r, dropout=dropout, **kwargs, ) except Exception as e: logger.warning(f"Failed to instantiate SVDLinearAttention: {e}") return None if hasattr(layer, "attention"): if hasattr(layer.attention, "self"): layer.attention.self = new_attn else: layer.attention = new_attn elif hasattr(layer, "attn"): layer.attn = new_attn elif hasattr(layer, "self_attn"): layer.self_attn = new_attn elif hasattr(layer, "self"): layer.self = new_attn return new_attn
Classes
class LoRALinear (in_features: int,
out_features: int,
bias: bool = True,
lora_rank: int = 0,
lora_alpha: float = 1.0,
freeze_base: bool = True)-
Expand source code
class LoRALinear(nn.Module): """Linear projection with frozen base weights and trainable LoRA adapters.""" def __init__( self, in_features: int, out_features: int, bias: bool = True, lora_rank: int = 0, lora_alpha: float = 1.0, freeze_base: bool = True, ): super().__init__() self.in_features = in_features self.out_features = out_features self.lora_rank = lora_rank self.lora_alpha = lora_alpha self.scaling = lora_alpha / max(1, lora_rank) self.weight = nn.Parameter(torch.empty(out_features, in_features)) if bias: self.bias = nn.Parameter(torch.empty(out_features)) else: self.register_parameter("bias", None) nn.init.kaiming_uniform_(self.weight, a=math.sqrt(5)) if self.bias is not None: fan_in, _ = nn.init._calculate_fan_in_and_fan_out(self.weight) bound = 1 / math.sqrt(fan_in) if fan_in > 0 else 0 nn.init.uniform_(self.bias, -bound, bound) if lora_rank > 0: self.lora_A = nn.Parameter(torch.empty(lora_rank, in_features)) self.lora_B = nn.Parameter(torch.empty(out_features, lora_rank)) nn.init.kaiming_uniform_(self.lora_A, a=math.sqrt(5)) nn.init.zeros_(self.lora_B) else: self.register_parameter("lora_A", None) self.register_parameter("lora_B", None) if freeze_base: self.weight.requires_grad = False if self.bias is not None: self.bias.requires_grad = False def forward(self, x: torch.Tensor) -> torch.Tensor: out = F.linear(x, self.weight, self.bias) if self.lora_rank > 0 and self.lora_A is not None and self.lora_B is not None: lora_out = F.linear(F.linear(x, self.lora_A), self.lora_B) * self.scaling out = out + lora_out return outLinear projection with frozen base weights and trainable LoRA adapters.
Initialize internal Module state, shared by both nn.Module and ScriptModule.
Ancestors
- torch.nn.modules.module.Module
Methods
def forward(self, x: torch.Tensor) ‑> torch.Tensor-
Expand source code
def forward(self, x: torch.Tensor) -> torch.Tensor: out = F.linear(x, self.weight, self.bias) if self.lora_rank > 0 and self.lora_A is not None and self.lora_B is not None: lora_out = F.linear(F.linear(x, self.lora_A), self.lora_B) * self.scaling out = out + lora_out return outDefine 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.
class SVDLinearAttention (config: Any | None = None,
old_attn: torch.nn.modules.module.Module | None = None,
rank: int | None = 64,
lora_rank: int = 16,
lora_alpha: float = 32.0,
is_decoder: bool | None = None,
dropout: float | None = 0.1,
**kwargs: Any)-
Expand source code
@ATTENTION_REGISTRY.register("svd_seq", supports_causal=True, supports_gqa=True) @ATTENTION_REGISTRY.register("svd_sequence", supports_causal=True, supports_gqa=True) @ATTENTION_REGISTRY.register("svd_sequence_attention", supports_causal=True, supports_gqa=True) class SVDLinearAttention(BaseAttention): r"""Low-Rank SVD-Compressed Sequence Attention Mechanism. Hardened for production use across diverse transformer architectures. """ supports_gqa: bool = True is_custom_attention: bool = True triton_kernel: str = "low_rank" def __init__( self, config: Any | None = None, old_attn: nn.Module | None = None, rank: int | None = 64, lora_rank: int = 16, lora_alpha: float = 32.0, is_decoder: bool | None = None, dropout: float | None = 0.1, **kwargs: Any, ) -> None: super().__init__(config, rank) # 1. Dimensions & Architecture self.hidden_size: int = int(_cfg_get(config, "hidden_size", kwargs.get("hidden_size", 768))) self.num_heads: int = int( kwargs.get("num_heads") or kwargs.get("num_attention_heads") or _cfg_get( config, "num_attention_heads", _cfg_get(config, "num_heads", 12), ) ) self.head_dim: int = int( _cfg_get( config, "head_dim", kwargs.get( "head_dim", self.hidden_size // self.num_heads if self.num_heads > 0 else 64, ), ) ) cfg_kv = _cfg_get(config, "num_key_value_heads", _cfg_get(config, "num_kv_heads", None)) if cfg_kv is not None: self.num_kv_heads = int(cfg_kv) elif kwargs.get("num_kv_heads") is not None: self.num_kv_heads = int(kwargs["num_kv_heads"]) else: self.num_kv_heads = self.num_heads self.num_kv_heads = max(1, min(self.num_kv_heads, self.num_heads)) self.kv_repeat: int = max(1, self.num_heads // self.num_kv_heads) self.kv_head_size: int = self.num_kv_heads * self.head_dim self.q_head_size: int = self.num_heads * self.head_dim self.all_head_size: int = self.q_head_size # Explicitly allow None to disable SVD compression if rank is None: svd_disabled = _cfg_get(config, "svd_disabled", False) if svd_disabled or ("rank" in kwargs and kwargs["rank"] is None): self.rank: int | None = None else: cfg_r = _cfg_get(config, "svd_rank", _cfg_get(config, "rank", None)) self.rank = int(cfg_r) if cfg_r is not None else None else: self.rank = int(rank) self.lora_rank: int = int(lora_rank if lora_rank is not None else 16) self.lora_alpha: float = lora_alpha # 2. Causal decoder flag detection if is_decoder is not None: self.is_decoder = bool(is_decoder) else: self.is_decoder = ( bool(_cfg_get(config, "is_decoder", False)) or any( str(_cfg_get(config, "model_type", "")).lower().startswith(p) for p in ( "gpt", "qwen", "llama", "mistral", "gemma", "phi", "opt", "bloom", "falcon", "olmo", "mpt", "stablelm", ) ) or (old_attn is not None and hasattr(old_attn, "c_attn")) ) self.is_nested_encoder: bool = False # 3. Projections with LoRA self.query = LoRALinear( self.hidden_size, self.q_head_size, bias=True, lora_rank=self.lora_rank, lora_alpha=self.lora_alpha, freeze_base=True, ) self.key = LoRALinear( self.hidden_size, self.kv_head_size, bias=True, lora_rank=self.lora_rank, lora_alpha=self.lora_alpha, freeze_base=True, ) self.value = LoRALinear( self.hidden_size, self.kv_head_size, bias=True, lora_rank=self.lora_rank, lora_alpha=self.lora_alpha, freeze_base=True, ) self.out_proj = LoRALinear( self.q_head_size, self.hidden_size, bias=True, lora_rank=self.lora_rank, lora_alpha=self.lora_alpha, freeze_base=True, ) dropout_val: float = dropout if dropout is not None else getattr(config, "dropout", 0.1) self.dropout: nn.Dropout = nn.Dropout(dropout_val) self._last_u_r: torch.Tensor | None = None # Persistent SVD projection buffer (populated in copy_weights from W_K) # Shape: [hidden_size, rank] — the top-r right singular vectors of W_K init_rank = max(1, self.rank or 64) self.register_buffer("svd_proj", torch.eye(self.hidden_size, init_rank), persistent=True) self._svd_proj_ready: bool = False self.triton_kwargs = {} if old_attn is not None: self.copy_weights(old_attn) @property def q_proj(self) -> LoRALinear: return self.query @property def k_proj(self) -> LoRALinear: return self.key @property def v_proj(self) -> LoRALinear: return self.value @property def q_lin(self) -> LoRALinear: return self.query @property def k_lin(self) -> LoRALinear: return self.key @property def v_lin(self) -> LoRALinear: return self.value def copy_weights(self, old_attn: nn.Module) -> None: """Robust weight copying from DistilBERT, RoBERTa nested, GPT-2 Conv1D, or Qwen/LLaMA.""" with torch.no_grad(): w_q, b_q = None, None w_k, b_k = None, None w_v, b_v = None, None w_o, b_o = None, None # Detect nested encoder container or inner self-attention src_cls_name = getattr(old_attn, "__class__", type(old_attn)).__name__ if src_cls_name in ("BertSelfAttention", "RobertaSelfAttention") or ( hasattr(old_attn, "query") and not hasattr(old_attn, "dense") and not hasattr(old_attn, "out_proj") ): self.is_nested_encoder = True if hasattr(old_attn, "self") and hasattr(old_attn, "output"): self.is_nested_encoder = True # Unwrap nested encoder modules to find innermost attention src = old_attn while hasattr(src, "self") and not any( hasattr(src, attr) for attr in ["query", "key", "value", "q_proj", "k_proj", "v_proj"] ): src = src.self # Copy output projection if available if hasattr(old_attn.output, "dense"): w_o = old_attn.output.dense.weight b_o = old_attn.output.dense.bias else: src = old_attn # Pattern 1: GPT-2 Fused c_attn if hasattr(src, "c_attn") and hasattr(src, "c_proj"): c_attn_w = src.c_attn.weight.detach() c_attn_b = src.c_attn.bias.detach() if src.c_attn.bias is not None else None p_cls = getattr(src.c_attn, "__class__", type(src.c_attn)).__name__ if ( ("Conv1D" in p_cls) or hasattr(src.c_attn, "nf") or ( c_attn_w.shape[0] == self.hidden_size and c_attn_w.shape[1] != self.hidden_size ) ): c_attn_w = c_attn_w.t().contiguous() q_dim = self.q_head_size k_dim = self.kv_head_size v_dim = self.kv_head_size w_q = c_attn_w[:q_dim, :] w_k = c_attn_w[q_dim : q_dim + k_dim, :] w_v = c_attn_w[q_dim + k_dim : q_dim + k_dim + v_dim, :] if c_attn_b is not None: b_q = c_attn_b[:q_dim] b_k = c_attn_b[q_dim : q_dim + k_dim] b_v = c_attn_b[q_dim + k_dim : q_dim + k_dim + v_dim] out_w = src.c_proj.weight.detach() p_proj_cls = getattr(src.c_proj, "__class__", type(src.c_proj)).__name__ if ( ("Conv1D" in p_proj_cls) or hasattr(src.c_proj, "nf") or (out_w.shape[0] == self.hidden_size and out_w.shape[1] != self.hidden_size) ): out_w = out_w.t().contiguous() w_o = out_w b_o = src.c_proj.bias.detach() if src.c_proj.bias is not None else None # Pattern 2: RoBERTa / BERT separate query, key, value elif hasattr(src, "query") and hasattr(src, "key") and hasattr(src, "value"): w_q = src.query.weight.detach() b_q = src.query.bias.detach() if src.query.bias is not None else None w_k = src.key.weight.detach() b_k = src.key.bias.detach() if src.key.bias is not None else None w_v = src.value.weight.detach() b_v = src.value.bias.detach() if src.value.bias is not None else None if w_o is None: for attr in ("dense", "out_proj", "o_proj"): if hasattr(src, attr): proj = getattr(src, attr) w_o = proj.weight.detach() b_o = proj.bias.detach() if proj.bias is not None else None break # Pattern 3: Qwen / LLaMA (q_proj, k_proj, v_proj, o_proj) elif hasattr(src, "q_proj") and hasattr(src, "k_proj") and hasattr(src, "v_proj"): w_q = src.q_proj.weight.detach() b_q = src.q_proj.bias.detach() if src.q_proj.bias is not None else None w_k = src.k_proj.weight.detach() b_k = src.k_proj.bias.detach() if src.k_proj.bias is not None else None w_v = src.v_proj.weight.detach() b_v = src.v_proj.bias.detach() if src.v_proj.bias is not None else None if hasattr(src, "o_proj"): w_o = src.o_proj.weight.detach() b_o = src.o_proj.bias.detach() if src.o_proj.bias is not None else None # Pattern 4: DistilBERT (q_lin, k_lin, v_lin, out_lin) elif hasattr(src, "q_lin") and hasattr(src, "k_lin") and hasattr(src, "v_lin"): w_q = src.q_lin.weight.detach() b_q = src.q_lin.bias.detach() if src.q_lin.bias is not None else None w_k = src.k_lin.weight.detach() b_k = src.k_lin.bias.detach() if src.k_lin.bias is not None else None w_v = src.v_lin.weight.detach() b_v = src.v_lin.bias.detach() if src.v_lin.bias is not None else None if hasattr(src, "out_lin"): w_o = src.out_lin.weight.detach() b_o = src.out_lin.bias.detach() if src.out_lin.bias is not None else None # Fallback: UniversalAdapter if w_q is None or w_k is None or w_v is None: try: w_q, b_q = UniversalAdapter.get_weight_bias(old_attn, "q") w_k, b_k = UniversalAdapter.get_weight_bias(old_attn, "k") w_v, b_v = UniversalAdapter.get_weight_bias(old_attn, "v") if w_o is None: w_o, b_o = UniversalAdapter.get_weight_bias(old_attn, "o") except Exception as e: logger.debug("UniversalAdapter fallback failed: %s", e) # Verification of essential projections missing = [] if w_q is None: missing.append("query/q_proj") if w_k is None: missing.append("key/k_proj") if w_v is None: missing.append("value/v_proj") if missing: raise RuntimeError( f"SVD-Compressed Sequence Attention: cannot extract " f"{missing} from {old_attn.__class__.__name__}. " f"Aborting to avoid silent corruption." ) # Frobenius norm checks with None guard for name, w in [("w_q", w_q), ("w_k", w_k), ("w_v", w_v)]: if w is None: continue if torch.norm(w.float(), p="fro").item() < 1e-4: raise RuntimeError( f"SVD-Sequence: {name} weight has near-zero " f"Frobenius norm. This will destroy the model." ) # Copy Q if w_q.shape == self.query.weight.shape: self.query.weight.copy_(w_q) else: self.query.weight.data[ : min(self.q_head_size, w_q.shape[0]), : min(self.hidden_size, w_q.shape[1]), ].copy_( w_q[ : min(self.q_head_size, w_q.shape[0]), : min(self.hidden_size, w_q.shape[1]), ] ) if b_q is not None and self.query.bias is not None: self.query.bias.data[: min(len(self.query.bias), len(b_q))].copy_( b_q[: min(len(self.query.bias), len(b_q))] ) # Copy K & V with GQA pooling/repetition if needed def _copy_kv(target_proj, w, b): if w.shape[0] == self.kv_head_size: target_proj.weight.copy_(w) if b is not None and target_proj.bias is not None: target_proj.bias.copy_(b) elif w.shape[0] > self.kv_head_size and w.shape[0] % self.kv_head_size == 0: # GQA: take the first group from each cluster (deterministic) rep = w.shape[0] // self.kv_head_size w_grouped = w.view(self.num_kv_heads, rep, self.head_dim, -1)[:, 0].reshape( self.kv_head_size, -1 ) target_proj.weight.copy_(w_grouped) if b is not None and target_proj.bias is not None: b_grouped = b.view(self.num_kv_heads, rep, self.head_dim)[:, 0].reshape(-1) target_proj.bias.copy_(b_grouped) elif w.shape[0] < self.kv_head_size and self.kv_head_size % w.shape[0] == 0: rep = self.kv_head_size // w.shape[0] target_proj.weight.copy_(w.repeat(rep, 1)) if b is not None and target_proj.bias is not None: target_proj.bias.copy_(b.repeat(rep)) else: target_proj.weight.data[ : min(self.kv_head_size, w.shape[0]), : min(self.hidden_size, w.shape[1]), ].copy_( w[ : min(self.kv_head_size, w.shape[0]), : min(self.hidden_size, w.shape[1]), ] ) if b is not None and target_proj.bias is not None: target_proj.bias.data[: min(len(target_proj.bias), len(b))].copy_( b[: min(len(target_proj.bias), len(b))] ) _copy_kv(self.key, w_k, b_k) _copy_kv(self.value, w_v, b_v) # Always copy Out projection — our module fully replaces BertAttention # (including BertSelfOutput), so we must apply out_proj in all cases. if w_o is not None: if torch.norm(w_o.float(), p="fro").item() < 1e-4: logger.warning("out_proj weight has near-zero Frobenius norm — skipping copy") else: if w_o.shape == self.out_proj.weight.shape: self.out_proj.weight.copy_(w_o) else: r = min(self.out_proj.weight.shape[0], w_o.shape[0]) c = min(self.out_proj.weight.shape[1], w_o.shape[1]) self.out_proj.weight.data[:r, :c].copy_(w_o[:r, :c]) if b_o is not None and self.out_proj.bias is not None: mn = min(len(self.out_proj.bias), len(b_o)) self.out_proj.bias.data[:mn].copy_(b_o[:mn]) # Compute SVD of W_K to get a fixed weight-space projection # W_K shape: [kv_head_size, hidden_size] → SVD gives right singular vectors of shape [hidden_size, ...] # We use the top-r right singular vectors (Vh rows) as the projection basis try: with torch.no_grad(): wk = self.key.weight.float() # [kv_head_size, hidden_size] _, _, Vh = torch.linalg.svd(wk, full_matrices=False) # Vh: [min(kv_head_size, hidden_size), hidden_size] r = min(self.rank or 64, Vh.shape[0], self.hidden_size) # svd_proj: [hidden_size, r] — project hidden_states → rank-r space proj = Vh[:r, :].T # [hidden_size, r] if proj.shape == self.svd_proj.shape: self.svd_proj.copy_(proj) else: mn_r = min(proj.shape[1], self.svd_proj.shape[1]) self.svd_proj[:, :mn_r].copy_(proj[:, :mn_r]) self._svd_proj_ready = True logger.info( "SVD projection computed from W_K: shape=%s, rank=%d", self.svd_proj.shape, r, ) except Exception as e: logger.warning( "SVD projection computation failed: %s — falling back to identity", e, ) self._svd_proj_ready = False 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, past_key_value: Any | None = None, output_attentions: bool | None = False, output_hidden_states: bool | None = False, return_dict: bool | None = True, position_embeddings: tuple[torch.Tensor, torch.Tensor] | None = None, *args: Any, **kwargs: Any, ) -> tuple[torch.Tensor, torch.Tensor | None]: batch_size, seq_len, _ = hidden_states.shape device = hidden_states.device dtype = hidden_states.dtype is_cross = encoder_hidden_states is not None kv_input = encoder_hidden_states if is_cross else hidden_states kv_len = kv_input.shape[1] # 1. Project Q, K, V Q = ( self.query(hidden_states) .view(batch_size, seq_len, self.num_heads, self.head_dim) .transpose(1, 2) .contiguous() ) K = ( self.key(kv_input) .view(batch_size, kv_len, self.num_kv_heads, self.head_dim) .transpose(1, 2) .contiguous() ) V = ( self.value(kv_input) .view(batch_size, kv_len, self.num_kv_heads, self.head_dim) .transpose(1, 2) .contiguous() ) # 2. RoPE support if position_embeddings is None: position_embeddings = kwargs.get("position_embeddings") if position_embeddings is not None and not is_cross: cos, sin = position_embeddings Q, K = apply_rotary_pos_emb(Q, K, cos, sin) # 3. GQA expansion if self.num_kv_heads < self.num_heads: K = repeat_kv(K, self.kv_repeat) V = repeat_kv(V, self.kv_repeat) eff_mask = encoder_attention_mask if is_cross else attention_mask is_causal = self.is_decoder and not is_cross # Full attention if sequence is too short for SVD compression # (SVD with r >= N is meaningless — the projection is identity) use_full_attention = ( self.rank is None or kv_len <= (self.rank or 0) + 8 # small buffer ) if use_full_attention: # Use SDPA when possible (no explicit head_mask/output_attentions) _use_sdpa = head_mask is None and output_attentions is False if _use_sdpa: try: _q = Q if Q.dtype in (torch.float16, torch.bfloat16) else Q.half() _k = K if K.dtype in (torch.float16, torch.bfloat16) else K.half() _v = V if V.dtype in (torch.float16, torch.bfloat16) else V.half() # Prepare attn_mask for SDPA if eff_mask is provided _attn_mask = None if eff_mask is not None and eff_mask.dim() == 2: _attn_mask = eff_mask.bool().view( eff_mask.shape[0], 1, 1, eff_mask.shape[1] ) elif eff_mask is not None and eff_mask.dim() == 4: _attn_mask = eff_mask.bool() if eff_mask.dtype == torch.bool else eff_mask context = torch.nn.functional.scaled_dot_product_attention( _q, _k, _v, attn_mask=_attn_mask, is_causal=is_causal if _attn_mask is None else False, dropout_p=self.dropout.p if self.training else 0.0, ).to(dtype) probs = None # not available in SDPA except Exception as e: logger.debug("SDPA failed: %s, falling back", e) _use_sdpa = False if not _use_sdpa: # Manual softmax path scores = torch.matmul( Q.float(), K.float().transpose(-2, -1).contiguous() ) / math.sqrt(self.head_dim) if is_causal and seq_len > 1: causal_mask = ( torch.triu( torch.full( (seq_len, kv_len), float("-inf"), device=device, dtype=torch.float32, ), diagonal=1, ) .unsqueeze(0) .unsqueeze(0) ) scores = scores + causal_mask if eff_mask is not None: scores = apply_attention_mask(scores, eff_mask) scores = torch.nan_to_num(scores, nan=-1e4, posinf=1e4, neginf=-1e4) probs = F.softmax(scores, dim=-1).to(dtype) probs = self.dropout(probs) if head_mask is not None: if head_mask.dim() == 1: head_mask = head_mask.view(1, -1, 1, 1) elif head_mask.dim() == 2: head_mask = head_mask.unsqueeze(1).unsqueeze(-1) elif head_mask.dim() == 3: head_mask = head_mask.unsqueeze(-1) probs = probs * head_mask context = torch.matmul(probs, V) if attention_mask is not None: q_valid_mask = get_token_valid_mask( attention_mask, batch_size, seq_len, device, ) if q_valid_mask is not None: context = context * q_valid_mask.unsqueeze(1).unsqueeze(-1).to(dtype) attn_ret = probs if output_attentions else None else: # Use the fixed SVD subspace precomputed from W_K in copy_weights. # This is stable across batches — LoRA can converge on a fixed subspace. # svd_proj: [hidden_size, r] (registered buffer, frozen) with torch.no_grad(): proj = self.svd_proj.to(device=device, dtype=dtype) # [hidden_size, r] actual_r = proj.shape[1] # Project the kv_input sequence through the fixed subspace # kv_input: [B, kv_len, hidden_size] → scores: [B, kv_len, actual_r] seq_scores = torch.matmul(kv_input.float(), proj.float()) # [B, kv_len, r] try: U_r, _ = torch.linalg.qr(seq_scores.float(), mode="reduced") # [B, kv_len, r] except Exception: U_r, _ = torch.linalg.qr(seq_scores.cpu().float(), mode="reduced") U_r = U_r.to(device=device) U_r = U_r.to(dtype) # [B, kv_len, actual_r] # Store detached — the ortho loss is a metric, not a gradient source self._last_u_r = U_r.detach() # Project K and V through the stable subspace U_r_t = U_r.unsqueeze(1) # [B, 1, kv_len, actual_r] K_proj = torch.matmul( U_r_t.transpose(-2, -1).contiguous(), K ) # [B, H, actual_r, head_dim] V_proj = torch.matmul( U_r_t.transpose(-2, -1).contiguous(), V ) # [B, H, actual_r, head_dim] scores = torch.matmul( Q.float(), K_proj.float().transpose(-2, -1).contiguous() ) / math.sqrt(self.head_dim) if is_causal and seq_len > 1 and actual_r == kv_len: # Only apply causal mask if we kept the full sequence causal_mask = ( torch.triu( torch.full( (seq_len, actual_r), float("-inf"), device=device, dtype=torch.float32, ), diagonal=1, ) .unsqueeze(0) .unsqueeze(0) ) scores = scores + causal_mask elif is_causal: logger.debug( "SVD-Sequence: causal mode with r=%d < kv_len=%d. " "Causal masking not strictly enforced because SVD " "compression combines multiple tokens.", actual_r, kv_len, ) scores = torch.nan_to_num(scores, nan=-1e4, posinf=1e4, neginf=-1e4) probs = F.softmax(scores, dim=-1).to(dtype) probs = self.dropout(probs) if head_mask is not None: if head_mask.dim() == 1: head_mask = head_mask.view(1, -1, 1, 1) elif head_mask.dim() == 2: head_mask = head_mask.unsqueeze(1).unsqueeze(-1) elif head_mask.dim() == 3: head_mask = head_mask.unsqueeze(-1) probs = probs * head_mask context = torch.matmul(probs, V_proj) # [B, H, seq_len, head_dim] if attention_mask is not None: q_valid_mask = get_token_valid_mask( attention_mask, batch_size, seq_len, device, ) if q_valid_mask is not None: context = context * q_valid_mask.unsqueeze(1).unsqueeze(-1).to(dtype) attn_ret = probs if output_attentions else None # 4. Context merge & Output projection context = context.transpose(1, 2).contiguous().view(batch_size, seq_len, self.all_head_size) context = torch.nan_to_num(context, nan=0.0, posinf=1e4, neginf=-1e4) # Always apply out_proj — our module replaces the full BertAttention container out = self.out_proj(context) if out.shape[-1] != hidden_states.shape[-1]: if out.shape[-1] < hidden_states.shape[-1]: out = F.pad(out, (0, hidden_states.shape[-1] - out.shape[-1])) else: out = out[..., : hidden_states.shape[-1]] return (out, attn_ret) def get_ortho_loss(self) -> torch.Tensor: """ Orthogonality diagnostic for the QR basis. Returns a scalar (no gradient). Use only for logging. """ device = self.query.weight.device if self._last_u_r is None: return torch.tensor(0.0, device=device) U = self._last_u_r r = U.size(-1) # Mean per-batch orthogonality error gram = torch.matmul(U.transpose(-2, -1).float(), U.float()) I = torch.eye(r, device=U.device, dtype=torch.float32) # Average over batch loss = (gram - I.unsqueeze(0)).abs().mean() return loss.detach()Low-Rank SVD-Compressed Sequence Attention Mechanism. Hardened for production use across diverse transformer architectures.
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_gqa : bool-
The type of the None singleton.
var triton_kernel : str-
The type of the None singleton.
Instance variables
prop k_lin : LoRALinear-
Expand source code
@property def k_lin(self) -> LoRALinear: return self.key prop k_proj : LoRALinear-
Expand source code
@property def k_proj(self) -> LoRALinear: return self.key prop q_lin : LoRALinear-
Expand source code
@property def q_lin(self) -> LoRALinear: return self.query prop q_proj : LoRALinear-
Expand source code
@property def q_proj(self) -> LoRALinear: return self.query prop v_lin : LoRALinear-
Expand source code
@property def v_lin(self) -> LoRALinear: return self.value prop v_proj : LoRALinear-
Expand source code
@property def v_proj(self) -> LoRALinear: return self.value
Methods
def copy_weights(self, old_attn: torch.nn.modules.module.Module) ‑> None-
Expand source code
def copy_weights(self, old_attn: nn.Module) -> None: """Robust weight copying from DistilBERT, RoBERTa nested, GPT-2 Conv1D, or Qwen/LLaMA.""" with torch.no_grad(): w_q, b_q = None, None w_k, b_k = None, None w_v, b_v = None, None w_o, b_o = None, None # Detect nested encoder container or inner self-attention src_cls_name = getattr(old_attn, "__class__", type(old_attn)).__name__ if src_cls_name in ("BertSelfAttention", "RobertaSelfAttention") or ( hasattr(old_attn, "query") and not hasattr(old_attn, "dense") and not hasattr(old_attn, "out_proj") ): self.is_nested_encoder = True if hasattr(old_attn, "self") and hasattr(old_attn, "output"): self.is_nested_encoder = True # Unwrap nested encoder modules to find innermost attention src = old_attn while hasattr(src, "self") and not any( hasattr(src, attr) for attr in ["query", "key", "value", "q_proj", "k_proj", "v_proj"] ): src = src.self # Copy output projection if available if hasattr(old_attn.output, "dense"): w_o = old_attn.output.dense.weight b_o = old_attn.output.dense.bias else: src = old_attn # Pattern 1: GPT-2 Fused c_attn if hasattr(src, "c_attn") and hasattr(src, "c_proj"): c_attn_w = src.c_attn.weight.detach() c_attn_b = src.c_attn.bias.detach() if src.c_attn.bias is not None else None p_cls = getattr(src.c_attn, "__class__", type(src.c_attn)).__name__ if ( ("Conv1D" in p_cls) or hasattr(src.c_attn, "nf") or ( c_attn_w.shape[0] == self.hidden_size and c_attn_w.shape[1] != self.hidden_size ) ): c_attn_w = c_attn_w.t().contiguous() q_dim = self.q_head_size k_dim = self.kv_head_size v_dim = self.kv_head_size w_q = c_attn_w[:q_dim, :] w_k = c_attn_w[q_dim : q_dim + k_dim, :] w_v = c_attn_w[q_dim + k_dim : q_dim + k_dim + v_dim, :] if c_attn_b is not None: b_q = c_attn_b[:q_dim] b_k = c_attn_b[q_dim : q_dim + k_dim] b_v = c_attn_b[q_dim + k_dim : q_dim + k_dim + v_dim] out_w = src.c_proj.weight.detach() p_proj_cls = getattr(src.c_proj, "__class__", type(src.c_proj)).__name__ if ( ("Conv1D" in p_proj_cls) or hasattr(src.c_proj, "nf") or (out_w.shape[0] == self.hidden_size and out_w.shape[1] != self.hidden_size) ): out_w = out_w.t().contiguous() w_o = out_w b_o = src.c_proj.bias.detach() if src.c_proj.bias is not None else None # Pattern 2: RoBERTa / BERT separate query, key, value elif hasattr(src, "query") and hasattr(src, "key") and hasattr(src, "value"): w_q = src.query.weight.detach() b_q = src.query.bias.detach() if src.query.bias is not None else None w_k = src.key.weight.detach() b_k = src.key.bias.detach() if src.key.bias is not None else None w_v = src.value.weight.detach() b_v = src.value.bias.detach() if src.value.bias is not None else None if w_o is None: for attr in ("dense", "out_proj", "o_proj"): if hasattr(src, attr): proj = getattr(src, attr) w_o = proj.weight.detach() b_o = proj.bias.detach() if proj.bias is not None else None break # Pattern 3: Qwen / LLaMA (q_proj, k_proj, v_proj, o_proj) elif hasattr(src, "q_proj") and hasattr(src, "k_proj") and hasattr(src, "v_proj"): w_q = src.q_proj.weight.detach() b_q = src.q_proj.bias.detach() if src.q_proj.bias is not None else None w_k = src.k_proj.weight.detach() b_k = src.k_proj.bias.detach() if src.k_proj.bias is not None else None w_v = src.v_proj.weight.detach() b_v = src.v_proj.bias.detach() if src.v_proj.bias is not None else None if hasattr(src, "o_proj"): w_o = src.o_proj.weight.detach() b_o = src.o_proj.bias.detach() if src.o_proj.bias is not None else None # Pattern 4: DistilBERT (q_lin, k_lin, v_lin, out_lin) elif hasattr(src, "q_lin") and hasattr(src, "k_lin") and hasattr(src, "v_lin"): w_q = src.q_lin.weight.detach() b_q = src.q_lin.bias.detach() if src.q_lin.bias is not None else None w_k = src.k_lin.weight.detach() b_k = src.k_lin.bias.detach() if src.k_lin.bias is not None else None w_v = src.v_lin.weight.detach() b_v = src.v_lin.bias.detach() if src.v_lin.bias is not None else None if hasattr(src, "out_lin"): w_o = src.out_lin.weight.detach() b_o = src.out_lin.bias.detach() if src.out_lin.bias is not None else None # Fallback: UniversalAdapter if w_q is None or w_k is None or w_v is None: try: w_q, b_q = UniversalAdapter.get_weight_bias(old_attn, "q") w_k, b_k = UniversalAdapter.get_weight_bias(old_attn, "k") w_v, b_v = UniversalAdapter.get_weight_bias(old_attn, "v") if w_o is None: w_o, b_o = UniversalAdapter.get_weight_bias(old_attn, "o") except Exception as e: logger.debug("UniversalAdapter fallback failed: %s", e) # Verification of essential projections missing = [] if w_q is None: missing.append("query/q_proj") if w_k is None: missing.append("key/k_proj") if w_v is None: missing.append("value/v_proj") if missing: raise RuntimeError( f"SVD-Compressed Sequence Attention: cannot extract " f"{missing} from {old_attn.__class__.__name__}. " f"Aborting to avoid silent corruption." ) # Frobenius norm checks with None guard for name, w in [("w_q", w_q), ("w_k", w_k), ("w_v", w_v)]: if w is None: continue if torch.norm(w.float(), p="fro").item() < 1e-4: raise RuntimeError( f"SVD-Sequence: {name} weight has near-zero " f"Frobenius norm. This will destroy the model." ) # Copy Q if w_q.shape == self.query.weight.shape: self.query.weight.copy_(w_q) else: self.query.weight.data[ : min(self.q_head_size, w_q.shape[0]), : min(self.hidden_size, w_q.shape[1]), ].copy_( w_q[ : min(self.q_head_size, w_q.shape[0]), : min(self.hidden_size, w_q.shape[1]), ] ) if b_q is not None and self.query.bias is not None: self.query.bias.data[: min(len(self.query.bias), len(b_q))].copy_( b_q[: min(len(self.query.bias), len(b_q))] ) # Copy K & V with GQA pooling/repetition if needed def _copy_kv(target_proj, w, b): if w.shape[0] == self.kv_head_size: target_proj.weight.copy_(w) if b is not None and target_proj.bias is not None: target_proj.bias.copy_(b) elif w.shape[0] > self.kv_head_size and w.shape[0] % self.kv_head_size == 0: # GQA: take the first group from each cluster (deterministic) rep = w.shape[0] // self.kv_head_size w_grouped = w.view(self.num_kv_heads, rep, self.head_dim, -1)[:, 0].reshape( self.kv_head_size, -1 ) target_proj.weight.copy_(w_grouped) if b is not None and target_proj.bias is not None: b_grouped = b.view(self.num_kv_heads, rep, self.head_dim)[:, 0].reshape(-1) target_proj.bias.copy_(b_grouped) elif w.shape[0] < self.kv_head_size and self.kv_head_size % w.shape[0] == 0: rep = self.kv_head_size // w.shape[0] target_proj.weight.copy_(w.repeat(rep, 1)) if b is not None and target_proj.bias is not None: target_proj.bias.copy_(b.repeat(rep)) else: target_proj.weight.data[ : min(self.kv_head_size, w.shape[0]), : min(self.hidden_size, w.shape[1]), ].copy_( w[ : min(self.kv_head_size, w.shape[0]), : min(self.hidden_size, w.shape[1]), ] ) if b is not None and target_proj.bias is not None: target_proj.bias.data[: min(len(target_proj.bias), len(b))].copy_( b[: min(len(target_proj.bias), len(b))] ) _copy_kv(self.key, w_k, b_k) _copy_kv(self.value, w_v, b_v) # Always copy Out projection — our module fully replaces BertAttention # (including BertSelfOutput), so we must apply out_proj in all cases. if w_o is not None: if torch.norm(w_o.float(), p="fro").item() < 1e-4: logger.warning("out_proj weight has near-zero Frobenius norm — skipping copy") else: if w_o.shape == self.out_proj.weight.shape: self.out_proj.weight.copy_(w_o) else: r = min(self.out_proj.weight.shape[0], w_o.shape[0]) c = min(self.out_proj.weight.shape[1], w_o.shape[1]) self.out_proj.weight.data[:r, :c].copy_(w_o[:r, :c]) if b_o is not None and self.out_proj.bias is not None: mn = min(len(self.out_proj.bias), len(b_o)) self.out_proj.bias.data[:mn].copy_(b_o[:mn]) # Compute SVD of W_K to get a fixed weight-space projection # W_K shape: [kv_head_size, hidden_size] → SVD gives right singular vectors of shape [hidden_size, ...] # We use the top-r right singular vectors (Vh rows) as the projection basis try: with torch.no_grad(): wk = self.key.weight.float() # [kv_head_size, hidden_size] _, _, Vh = torch.linalg.svd(wk, full_matrices=False) # Vh: [min(kv_head_size, hidden_size), hidden_size] r = min(self.rank or 64, Vh.shape[0], self.hidden_size) # svd_proj: [hidden_size, r] — project hidden_states → rank-r space proj = Vh[:r, :].T # [hidden_size, r] if proj.shape == self.svd_proj.shape: self.svd_proj.copy_(proj) else: mn_r = min(proj.shape[1], self.svd_proj.shape[1]) self.svd_proj[:, :mn_r].copy_(proj[:, :mn_r]) self._svd_proj_ready = True logger.info( "SVD projection computed from W_K: shape=%s, rank=%d", self.svd_proj.shape, r, ) except Exception as e: logger.warning( "SVD projection computation failed: %s — falling back to identity", e, ) self._svd_proj_ready = FalseRobust weight copying from DistilBERT, RoBERTa nested, GPT-2 Conv1D, or Qwen/LLaMA.
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,
past_key_value: Any | None = None,
output_attentions: bool | None = False,
output_hidden_states: bool | None = False,
return_dict: bool | None = True,
position_embeddings: tuple[torch.Tensor, torch.Tensor] | None = None,
*args: Any,
**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, past_key_value: Any | None = None, output_attentions: bool | None = False, output_hidden_states: bool | None = False, return_dict: bool | None = True, position_embeddings: tuple[torch.Tensor, torch.Tensor] | None = None, *args: Any, **kwargs: Any, ) -> tuple[torch.Tensor, torch.Tensor | None]: batch_size, seq_len, _ = hidden_states.shape device = hidden_states.device dtype = hidden_states.dtype is_cross = encoder_hidden_states is not None kv_input = encoder_hidden_states if is_cross else hidden_states kv_len = kv_input.shape[1] # 1. Project Q, K, V Q = ( self.query(hidden_states) .view(batch_size, seq_len, self.num_heads, self.head_dim) .transpose(1, 2) .contiguous() ) K = ( self.key(kv_input) .view(batch_size, kv_len, self.num_kv_heads, self.head_dim) .transpose(1, 2) .contiguous() ) V = ( self.value(kv_input) .view(batch_size, kv_len, self.num_kv_heads, self.head_dim) .transpose(1, 2) .contiguous() ) # 2. RoPE support if position_embeddings is None: position_embeddings = kwargs.get("position_embeddings") if position_embeddings is not None and not is_cross: cos, sin = position_embeddings Q, K = apply_rotary_pos_emb(Q, K, cos, sin) # 3. GQA expansion if self.num_kv_heads < self.num_heads: K = repeat_kv(K, self.kv_repeat) V = repeat_kv(V, self.kv_repeat) eff_mask = encoder_attention_mask if is_cross else attention_mask is_causal = self.is_decoder and not is_cross # Full attention if sequence is too short for SVD compression # (SVD with r >= N is meaningless — the projection is identity) use_full_attention = ( self.rank is None or kv_len <= (self.rank or 0) + 8 # small buffer ) if use_full_attention: # Use SDPA when possible (no explicit head_mask/output_attentions) _use_sdpa = head_mask is None and output_attentions is False if _use_sdpa: try: _q = Q if Q.dtype in (torch.float16, torch.bfloat16) else Q.half() _k = K if K.dtype in (torch.float16, torch.bfloat16) else K.half() _v = V if V.dtype in (torch.float16, torch.bfloat16) else V.half() # Prepare attn_mask for SDPA if eff_mask is provided _attn_mask = None if eff_mask is not None and eff_mask.dim() == 2: _attn_mask = eff_mask.bool().view( eff_mask.shape[0], 1, 1, eff_mask.shape[1] ) elif eff_mask is not None and eff_mask.dim() == 4: _attn_mask = eff_mask.bool() if eff_mask.dtype == torch.bool else eff_mask context = torch.nn.functional.scaled_dot_product_attention( _q, _k, _v, attn_mask=_attn_mask, is_causal=is_causal if _attn_mask is None else False, dropout_p=self.dropout.p if self.training else 0.0, ).to(dtype) probs = None # not available in SDPA except Exception as e: logger.debug("SDPA failed: %s, falling back", e) _use_sdpa = False if not _use_sdpa: # Manual softmax path scores = torch.matmul( Q.float(), K.float().transpose(-2, -1).contiguous() ) / math.sqrt(self.head_dim) if is_causal and seq_len > 1: causal_mask = ( torch.triu( torch.full( (seq_len, kv_len), float("-inf"), device=device, dtype=torch.float32, ), diagonal=1, ) .unsqueeze(0) .unsqueeze(0) ) scores = scores + causal_mask if eff_mask is not None: scores = apply_attention_mask(scores, eff_mask) scores = torch.nan_to_num(scores, nan=-1e4, posinf=1e4, neginf=-1e4) probs = F.softmax(scores, dim=-1).to(dtype) probs = self.dropout(probs) if head_mask is not None: if head_mask.dim() == 1: head_mask = head_mask.view(1, -1, 1, 1) elif head_mask.dim() == 2: head_mask = head_mask.unsqueeze(1).unsqueeze(-1) elif head_mask.dim() == 3: head_mask = head_mask.unsqueeze(-1) probs = probs * head_mask context = torch.matmul(probs, V) if attention_mask is not None: q_valid_mask = get_token_valid_mask( attention_mask, batch_size, seq_len, device, ) if q_valid_mask is not None: context = context * q_valid_mask.unsqueeze(1).unsqueeze(-1).to(dtype) attn_ret = probs if output_attentions else None else: # Use the fixed SVD subspace precomputed from W_K in copy_weights. # This is stable across batches — LoRA can converge on a fixed subspace. # svd_proj: [hidden_size, r] (registered buffer, frozen) with torch.no_grad(): proj = self.svd_proj.to(device=device, dtype=dtype) # [hidden_size, r] actual_r = proj.shape[1] # Project the kv_input sequence through the fixed subspace # kv_input: [B, kv_len, hidden_size] → scores: [B, kv_len, actual_r] seq_scores = torch.matmul(kv_input.float(), proj.float()) # [B, kv_len, r] try: U_r, _ = torch.linalg.qr(seq_scores.float(), mode="reduced") # [B, kv_len, r] except Exception: U_r, _ = torch.linalg.qr(seq_scores.cpu().float(), mode="reduced") U_r = U_r.to(device=device) U_r = U_r.to(dtype) # [B, kv_len, actual_r] # Store detached — the ortho loss is a metric, not a gradient source self._last_u_r = U_r.detach() # Project K and V through the stable subspace U_r_t = U_r.unsqueeze(1) # [B, 1, kv_len, actual_r] K_proj = torch.matmul( U_r_t.transpose(-2, -1).contiguous(), K ) # [B, H, actual_r, head_dim] V_proj = torch.matmul( U_r_t.transpose(-2, -1).contiguous(), V ) # [B, H, actual_r, head_dim] scores = torch.matmul( Q.float(), K_proj.float().transpose(-2, -1).contiguous() ) / math.sqrt(self.head_dim) if is_causal and seq_len > 1 and actual_r == kv_len: # Only apply causal mask if we kept the full sequence causal_mask = ( torch.triu( torch.full( (seq_len, actual_r), float("-inf"), device=device, dtype=torch.float32, ), diagonal=1, ) .unsqueeze(0) .unsqueeze(0) ) scores = scores + causal_mask elif is_causal: logger.debug( "SVD-Sequence: causal mode with r=%d < kv_len=%d. " "Causal masking not strictly enforced because SVD " "compression combines multiple tokens.", actual_r, kv_len, ) scores = torch.nan_to_num(scores, nan=-1e4, posinf=1e4, neginf=-1e4) probs = F.softmax(scores, dim=-1).to(dtype) probs = self.dropout(probs) if head_mask is not None: if head_mask.dim() == 1: head_mask = head_mask.view(1, -1, 1, 1) elif head_mask.dim() == 2: head_mask = head_mask.unsqueeze(1).unsqueeze(-1) elif head_mask.dim() == 3: head_mask = head_mask.unsqueeze(-1) probs = probs * head_mask context = torch.matmul(probs, V_proj) # [B, H, seq_len, head_dim] if attention_mask is not None: q_valid_mask = get_token_valid_mask( attention_mask, batch_size, seq_len, device, ) if q_valid_mask is not None: context = context * q_valid_mask.unsqueeze(1).unsqueeze(-1).to(dtype) attn_ret = probs if output_attentions else None # 4. Context merge & Output projection context = context.transpose(1, 2).contiguous().view(batch_size, seq_len, self.all_head_size) context = torch.nan_to_num(context, nan=0.0, posinf=1e4, neginf=-1e4) # Always apply out_proj — our module replaces the full BertAttention container out = self.out_proj(context) if out.shape[-1] != hidden_states.shape[-1]: if out.shape[-1] < hidden_states.shape[-1]: out = F.pad(out, (0, hidden_states.shape[-1] - out.shape[-1])) else: out = out[..., : hidden_states.shape[-1]] return (out, attn_ret)Must return (output, attention_weights_or_None).
def get_ortho_loss(self) ‑> torch.Tensor-
Expand source code
def get_ortho_loss(self) -> torch.Tensor: """ Orthogonality diagnostic for the QR basis. Returns a scalar (no gradient). Use only for logging. """ device = self.query.weight.device if self._last_u_r is None: return torch.tensor(0.0, device=device) U = self._last_u_r r = U.size(-1) # Mean per-batch orthogonality error gram = torch.matmul(U.transpose(-2, -1).float(), U.float()) I = torch.eye(r, device=U.device, dtype=torch.float32) # Average over batch loss = (gram - I.unsqueeze(0)).abs().mean() return loss.detach()Orthogonality diagnostic for the QR basis. Returns a scalar (no gradient). Use only for logging.
class LowRankSVDSequenceAttention (config: Any | None = None,
old_attn: torch.nn.modules.module.Module | None = None,
rank: int | None = 64,
lora_rank: int = 16,
lora_alpha: float = 32.0,
is_decoder: bool | None = None,
dropout: float | None = 0.1,
**kwargs: Any)-
Expand source code
@ATTENTION_REGISTRY.register("svd_seq", supports_causal=True, supports_gqa=True) @ATTENTION_REGISTRY.register("svd_sequence", supports_causal=True, supports_gqa=True) @ATTENTION_REGISTRY.register("svd_sequence_attention", supports_causal=True, supports_gqa=True) class SVDLinearAttention(BaseAttention): r"""Low-Rank SVD-Compressed Sequence Attention Mechanism. Hardened for production use across diverse transformer architectures. """ supports_gqa: bool = True is_custom_attention: bool = True triton_kernel: str = "low_rank" def __init__( self, config: Any | None = None, old_attn: nn.Module | None = None, rank: int | None = 64, lora_rank: int = 16, lora_alpha: float = 32.0, is_decoder: bool | None = None, dropout: float | None = 0.1, **kwargs: Any, ) -> None: super().__init__(config, rank) # 1. Dimensions & Architecture self.hidden_size: int = int(_cfg_get(config, "hidden_size", kwargs.get("hidden_size", 768))) self.num_heads: int = int( kwargs.get("num_heads") or kwargs.get("num_attention_heads") or _cfg_get( config, "num_attention_heads", _cfg_get(config, "num_heads", 12), ) ) self.head_dim: int = int( _cfg_get( config, "head_dim", kwargs.get( "head_dim", self.hidden_size // self.num_heads if self.num_heads > 0 else 64, ), ) ) cfg_kv = _cfg_get(config, "num_key_value_heads", _cfg_get(config, "num_kv_heads", None)) if cfg_kv is not None: self.num_kv_heads = int(cfg_kv) elif kwargs.get("num_kv_heads") is not None: self.num_kv_heads = int(kwargs["num_kv_heads"]) else: self.num_kv_heads = self.num_heads self.num_kv_heads = max(1, min(self.num_kv_heads, self.num_heads)) self.kv_repeat: int = max(1, self.num_heads // self.num_kv_heads) self.kv_head_size: int = self.num_kv_heads * self.head_dim self.q_head_size: int = self.num_heads * self.head_dim self.all_head_size: int = self.q_head_size # Explicitly allow None to disable SVD compression if rank is None: svd_disabled = _cfg_get(config, "svd_disabled", False) if svd_disabled or ("rank" in kwargs and kwargs["rank"] is None): self.rank: int | None = None else: cfg_r = _cfg_get(config, "svd_rank", _cfg_get(config, "rank", None)) self.rank = int(cfg_r) if cfg_r is not None else None else: self.rank = int(rank) self.lora_rank: int = int(lora_rank if lora_rank is not None else 16) self.lora_alpha: float = lora_alpha # 2. Causal decoder flag detection if is_decoder is not None: self.is_decoder = bool(is_decoder) else: self.is_decoder = ( bool(_cfg_get(config, "is_decoder", False)) or any( str(_cfg_get(config, "model_type", "")).lower().startswith(p) for p in ( "gpt", "qwen", "llama", "mistral", "gemma", "phi", "opt", "bloom", "falcon", "olmo", "mpt", "stablelm", ) ) or (old_attn is not None and hasattr(old_attn, "c_attn")) ) self.is_nested_encoder: bool = False # 3. Projections with LoRA self.query = LoRALinear( self.hidden_size, self.q_head_size, bias=True, lora_rank=self.lora_rank, lora_alpha=self.lora_alpha, freeze_base=True, ) self.key = LoRALinear( self.hidden_size, self.kv_head_size, bias=True, lora_rank=self.lora_rank, lora_alpha=self.lora_alpha, freeze_base=True, ) self.value = LoRALinear( self.hidden_size, self.kv_head_size, bias=True, lora_rank=self.lora_rank, lora_alpha=self.lora_alpha, freeze_base=True, ) self.out_proj = LoRALinear( self.q_head_size, self.hidden_size, bias=True, lora_rank=self.lora_rank, lora_alpha=self.lora_alpha, freeze_base=True, ) dropout_val: float = dropout if dropout is not None else getattr(config, "dropout", 0.1) self.dropout: nn.Dropout = nn.Dropout(dropout_val) self._last_u_r: torch.Tensor | None = None # Persistent SVD projection buffer (populated in copy_weights from W_K) # Shape: [hidden_size, rank] — the top-r right singular vectors of W_K init_rank = max(1, self.rank or 64) self.register_buffer("svd_proj", torch.eye(self.hidden_size, init_rank), persistent=True) self._svd_proj_ready: bool = False self.triton_kwargs = {} if old_attn is not None: self.copy_weights(old_attn) @property def q_proj(self) -> LoRALinear: return self.query @property def k_proj(self) -> LoRALinear: return self.key @property def v_proj(self) -> LoRALinear: return self.value @property def q_lin(self) -> LoRALinear: return self.query @property def k_lin(self) -> LoRALinear: return self.key @property def v_lin(self) -> LoRALinear: return self.value def copy_weights(self, old_attn: nn.Module) -> None: """Robust weight copying from DistilBERT, RoBERTa nested, GPT-2 Conv1D, or Qwen/LLaMA.""" with torch.no_grad(): w_q, b_q = None, None w_k, b_k = None, None w_v, b_v = None, None w_o, b_o = None, None # Detect nested encoder container or inner self-attention src_cls_name = getattr(old_attn, "__class__", type(old_attn)).__name__ if src_cls_name in ("BertSelfAttention", "RobertaSelfAttention") or ( hasattr(old_attn, "query") and not hasattr(old_attn, "dense") and not hasattr(old_attn, "out_proj") ): self.is_nested_encoder = True if hasattr(old_attn, "self") and hasattr(old_attn, "output"): self.is_nested_encoder = True # Unwrap nested encoder modules to find innermost attention src = old_attn while hasattr(src, "self") and not any( hasattr(src, attr) for attr in ["query", "key", "value", "q_proj", "k_proj", "v_proj"] ): src = src.self # Copy output projection if available if hasattr(old_attn.output, "dense"): w_o = old_attn.output.dense.weight b_o = old_attn.output.dense.bias else: src = old_attn # Pattern 1: GPT-2 Fused c_attn if hasattr(src, "c_attn") and hasattr(src, "c_proj"): c_attn_w = src.c_attn.weight.detach() c_attn_b = src.c_attn.bias.detach() if src.c_attn.bias is not None else None p_cls = getattr(src.c_attn, "__class__", type(src.c_attn)).__name__ if ( ("Conv1D" in p_cls) or hasattr(src.c_attn, "nf") or ( c_attn_w.shape[0] == self.hidden_size and c_attn_w.shape[1] != self.hidden_size ) ): c_attn_w = c_attn_w.t().contiguous() q_dim = self.q_head_size k_dim = self.kv_head_size v_dim = self.kv_head_size w_q = c_attn_w[:q_dim, :] w_k = c_attn_w[q_dim : q_dim + k_dim, :] w_v = c_attn_w[q_dim + k_dim : q_dim + k_dim + v_dim, :] if c_attn_b is not None: b_q = c_attn_b[:q_dim] b_k = c_attn_b[q_dim : q_dim + k_dim] b_v = c_attn_b[q_dim + k_dim : q_dim + k_dim + v_dim] out_w = src.c_proj.weight.detach() p_proj_cls = getattr(src.c_proj, "__class__", type(src.c_proj)).__name__ if ( ("Conv1D" in p_proj_cls) or hasattr(src.c_proj, "nf") or (out_w.shape[0] == self.hidden_size and out_w.shape[1] != self.hidden_size) ): out_w = out_w.t().contiguous() w_o = out_w b_o = src.c_proj.bias.detach() if src.c_proj.bias is not None else None # Pattern 2: RoBERTa / BERT separate query, key, value elif hasattr(src, "query") and hasattr(src, "key") and hasattr(src, "value"): w_q = src.query.weight.detach() b_q = src.query.bias.detach() if src.query.bias is not None else None w_k = src.key.weight.detach() b_k = src.key.bias.detach() if src.key.bias is not None else None w_v = src.value.weight.detach() b_v = src.value.bias.detach() if src.value.bias is not None else None if w_o is None: for attr in ("dense", "out_proj", "o_proj"): if hasattr(src, attr): proj = getattr(src, attr) w_o = proj.weight.detach() b_o = proj.bias.detach() if proj.bias is not None else None break # Pattern 3: Qwen / LLaMA (q_proj, k_proj, v_proj, o_proj) elif hasattr(src, "q_proj") and hasattr(src, "k_proj") and hasattr(src, "v_proj"): w_q = src.q_proj.weight.detach() b_q = src.q_proj.bias.detach() if src.q_proj.bias is not None else None w_k = src.k_proj.weight.detach() b_k = src.k_proj.bias.detach() if src.k_proj.bias is not None else None w_v = src.v_proj.weight.detach() b_v = src.v_proj.bias.detach() if src.v_proj.bias is not None else None if hasattr(src, "o_proj"): w_o = src.o_proj.weight.detach() b_o = src.o_proj.bias.detach() if src.o_proj.bias is not None else None # Pattern 4: DistilBERT (q_lin, k_lin, v_lin, out_lin) elif hasattr(src, "q_lin") and hasattr(src, "k_lin") and hasattr(src, "v_lin"): w_q = src.q_lin.weight.detach() b_q = src.q_lin.bias.detach() if src.q_lin.bias is not None else None w_k = src.k_lin.weight.detach() b_k = src.k_lin.bias.detach() if src.k_lin.bias is not None else None w_v = src.v_lin.weight.detach() b_v = src.v_lin.bias.detach() if src.v_lin.bias is not None else None if hasattr(src, "out_lin"): w_o = src.out_lin.weight.detach() b_o = src.out_lin.bias.detach() if src.out_lin.bias is not None else None # Fallback: UniversalAdapter if w_q is None or w_k is None or w_v is None: try: w_q, b_q = UniversalAdapter.get_weight_bias(old_attn, "q") w_k, b_k = UniversalAdapter.get_weight_bias(old_attn, "k") w_v, b_v = UniversalAdapter.get_weight_bias(old_attn, "v") if w_o is None: w_o, b_o = UniversalAdapter.get_weight_bias(old_attn, "o") except Exception as e: logger.debug("UniversalAdapter fallback failed: %s", e) # Verification of essential projections missing = [] if w_q is None: missing.append("query/q_proj") if w_k is None: missing.append("key/k_proj") if w_v is None: missing.append("value/v_proj") if missing: raise RuntimeError( f"SVD-Compressed Sequence Attention: cannot extract " f"{missing} from {old_attn.__class__.__name__}. " f"Aborting to avoid silent corruption." ) # Frobenius norm checks with None guard for name, w in [("w_q", w_q), ("w_k", w_k), ("w_v", w_v)]: if w is None: continue if torch.norm(w.float(), p="fro").item() < 1e-4: raise RuntimeError( f"SVD-Sequence: {name} weight has near-zero " f"Frobenius norm. This will destroy the model." ) # Copy Q if w_q.shape == self.query.weight.shape: self.query.weight.copy_(w_q) else: self.query.weight.data[ : min(self.q_head_size, w_q.shape[0]), : min(self.hidden_size, w_q.shape[1]), ].copy_( w_q[ : min(self.q_head_size, w_q.shape[0]), : min(self.hidden_size, w_q.shape[1]), ] ) if b_q is not None and self.query.bias is not None: self.query.bias.data[: min(len(self.query.bias), len(b_q))].copy_( b_q[: min(len(self.query.bias), len(b_q))] ) # Copy K & V with GQA pooling/repetition if needed def _copy_kv(target_proj, w, b): if w.shape[0] == self.kv_head_size: target_proj.weight.copy_(w) if b is not None and target_proj.bias is not None: target_proj.bias.copy_(b) elif w.shape[0] > self.kv_head_size and w.shape[0] % self.kv_head_size == 0: # GQA: take the first group from each cluster (deterministic) rep = w.shape[0] // self.kv_head_size w_grouped = w.view(self.num_kv_heads, rep, self.head_dim, -1)[:, 0].reshape( self.kv_head_size, -1 ) target_proj.weight.copy_(w_grouped) if b is not None and target_proj.bias is not None: b_grouped = b.view(self.num_kv_heads, rep, self.head_dim)[:, 0].reshape(-1) target_proj.bias.copy_(b_grouped) elif w.shape[0] < self.kv_head_size and self.kv_head_size % w.shape[0] == 0: rep = self.kv_head_size // w.shape[0] target_proj.weight.copy_(w.repeat(rep, 1)) if b is not None and target_proj.bias is not None: target_proj.bias.copy_(b.repeat(rep)) else: target_proj.weight.data[ : min(self.kv_head_size, w.shape[0]), : min(self.hidden_size, w.shape[1]), ].copy_( w[ : min(self.kv_head_size, w.shape[0]), : min(self.hidden_size, w.shape[1]), ] ) if b is not None and target_proj.bias is not None: target_proj.bias.data[: min(len(target_proj.bias), len(b))].copy_( b[: min(len(target_proj.bias), len(b))] ) _copy_kv(self.key, w_k, b_k) _copy_kv(self.value, w_v, b_v) # Always copy Out projection — our module fully replaces BertAttention # (including BertSelfOutput), so we must apply out_proj in all cases. if w_o is not None: if torch.norm(w_o.float(), p="fro").item() < 1e-4: logger.warning("out_proj weight has near-zero Frobenius norm — skipping copy") else: if w_o.shape == self.out_proj.weight.shape: self.out_proj.weight.copy_(w_o) else: r = min(self.out_proj.weight.shape[0], w_o.shape[0]) c = min(self.out_proj.weight.shape[1], w_o.shape[1]) self.out_proj.weight.data[:r, :c].copy_(w_o[:r, :c]) if b_o is not None and self.out_proj.bias is not None: mn = min(len(self.out_proj.bias), len(b_o)) self.out_proj.bias.data[:mn].copy_(b_o[:mn]) # Compute SVD of W_K to get a fixed weight-space projection # W_K shape: [kv_head_size, hidden_size] → SVD gives right singular vectors of shape [hidden_size, ...] # We use the top-r right singular vectors (Vh rows) as the projection basis try: with torch.no_grad(): wk = self.key.weight.float() # [kv_head_size, hidden_size] _, _, Vh = torch.linalg.svd(wk, full_matrices=False) # Vh: [min(kv_head_size, hidden_size), hidden_size] r = min(self.rank or 64, Vh.shape[0], self.hidden_size) # svd_proj: [hidden_size, r] — project hidden_states → rank-r space proj = Vh[:r, :].T # [hidden_size, r] if proj.shape == self.svd_proj.shape: self.svd_proj.copy_(proj) else: mn_r = min(proj.shape[1], self.svd_proj.shape[1]) self.svd_proj[:, :mn_r].copy_(proj[:, :mn_r]) self._svd_proj_ready = True logger.info( "SVD projection computed from W_K: shape=%s, rank=%d", self.svd_proj.shape, r, ) except Exception as e: logger.warning( "SVD projection computation failed: %s — falling back to identity", e, ) self._svd_proj_ready = False 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, past_key_value: Any | None = None, output_attentions: bool | None = False, output_hidden_states: bool | None = False, return_dict: bool | None = True, position_embeddings: tuple[torch.Tensor, torch.Tensor] | None = None, *args: Any, **kwargs: Any, ) -> tuple[torch.Tensor, torch.Tensor | None]: batch_size, seq_len, _ = hidden_states.shape device = hidden_states.device dtype = hidden_states.dtype is_cross = encoder_hidden_states is not None kv_input = encoder_hidden_states if is_cross else hidden_states kv_len = kv_input.shape[1] # 1. Project Q, K, V Q = ( self.query(hidden_states) .view(batch_size, seq_len, self.num_heads, self.head_dim) .transpose(1, 2) .contiguous() ) K = ( self.key(kv_input) .view(batch_size, kv_len, self.num_kv_heads, self.head_dim) .transpose(1, 2) .contiguous() ) V = ( self.value(kv_input) .view(batch_size, kv_len, self.num_kv_heads, self.head_dim) .transpose(1, 2) .contiguous() ) # 2. RoPE support if position_embeddings is None: position_embeddings = kwargs.get("position_embeddings") if position_embeddings is not None and not is_cross: cos, sin = position_embeddings Q, K = apply_rotary_pos_emb(Q, K, cos, sin) # 3. GQA expansion if self.num_kv_heads < self.num_heads: K = repeat_kv(K, self.kv_repeat) V = repeat_kv(V, self.kv_repeat) eff_mask = encoder_attention_mask if is_cross else attention_mask is_causal = self.is_decoder and not is_cross # Full attention if sequence is too short for SVD compression # (SVD with r >= N is meaningless — the projection is identity) use_full_attention = ( self.rank is None or kv_len <= (self.rank or 0) + 8 # small buffer ) if use_full_attention: # Use SDPA when possible (no explicit head_mask/output_attentions) _use_sdpa = head_mask is None and output_attentions is False if _use_sdpa: try: _q = Q if Q.dtype in (torch.float16, torch.bfloat16) else Q.half() _k = K if K.dtype in (torch.float16, torch.bfloat16) else K.half() _v = V if V.dtype in (torch.float16, torch.bfloat16) else V.half() # Prepare attn_mask for SDPA if eff_mask is provided _attn_mask = None if eff_mask is not None and eff_mask.dim() == 2: _attn_mask = eff_mask.bool().view( eff_mask.shape[0], 1, 1, eff_mask.shape[1] ) elif eff_mask is not None and eff_mask.dim() == 4: _attn_mask = eff_mask.bool() if eff_mask.dtype == torch.bool else eff_mask context = torch.nn.functional.scaled_dot_product_attention( _q, _k, _v, attn_mask=_attn_mask, is_causal=is_causal if _attn_mask is None else False, dropout_p=self.dropout.p if self.training else 0.0, ).to(dtype) probs = None # not available in SDPA except Exception as e: logger.debug("SDPA failed: %s, falling back", e) _use_sdpa = False if not _use_sdpa: # Manual softmax path scores = torch.matmul( Q.float(), K.float().transpose(-2, -1).contiguous() ) / math.sqrt(self.head_dim) if is_causal and seq_len > 1: causal_mask = ( torch.triu( torch.full( (seq_len, kv_len), float("-inf"), device=device, dtype=torch.float32, ), diagonal=1, ) .unsqueeze(0) .unsqueeze(0) ) scores = scores + causal_mask if eff_mask is not None: scores = apply_attention_mask(scores, eff_mask) scores = torch.nan_to_num(scores, nan=-1e4, posinf=1e4, neginf=-1e4) probs = F.softmax(scores, dim=-1).to(dtype) probs = self.dropout(probs) if head_mask is not None: if head_mask.dim() == 1: head_mask = head_mask.view(1, -1, 1, 1) elif head_mask.dim() == 2: head_mask = head_mask.unsqueeze(1).unsqueeze(-1) elif head_mask.dim() == 3: head_mask = head_mask.unsqueeze(-1) probs = probs * head_mask context = torch.matmul(probs, V) if attention_mask is not None: q_valid_mask = get_token_valid_mask( attention_mask, batch_size, seq_len, device, ) if q_valid_mask is not None: context = context * q_valid_mask.unsqueeze(1).unsqueeze(-1).to(dtype) attn_ret = probs if output_attentions else None else: # Use the fixed SVD subspace precomputed from W_K in copy_weights. # This is stable across batches — LoRA can converge on a fixed subspace. # svd_proj: [hidden_size, r] (registered buffer, frozen) with torch.no_grad(): proj = self.svd_proj.to(device=device, dtype=dtype) # [hidden_size, r] actual_r = proj.shape[1] # Project the kv_input sequence through the fixed subspace # kv_input: [B, kv_len, hidden_size] → scores: [B, kv_len, actual_r] seq_scores = torch.matmul(kv_input.float(), proj.float()) # [B, kv_len, r] try: U_r, _ = torch.linalg.qr(seq_scores.float(), mode="reduced") # [B, kv_len, r] except Exception: U_r, _ = torch.linalg.qr(seq_scores.cpu().float(), mode="reduced") U_r = U_r.to(device=device) U_r = U_r.to(dtype) # [B, kv_len, actual_r] # Store detached — the ortho loss is a metric, not a gradient source self._last_u_r = U_r.detach() # Project K and V through the stable subspace U_r_t = U_r.unsqueeze(1) # [B, 1, kv_len, actual_r] K_proj = torch.matmul( U_r_t.transpose(-2, -1).contiguous(), K ) # [B, H, actual_r, head_dim] V_proj = torch.matmul( U_r_t.transpose(-2, -1).contiguous(), V ) # [B, H, actual_r, head_dim] scores = torch.matmul( Q.float(), K_proj.float().transpose(-2, -1).contiguous() ) / math.sqrt(self.head_dim) if is_causal and seq_len > 1 and actual_r == kv_len: # Only apply causal mask if we kept the full sequence causal_mask = ( torch.triu( torch.full( (seq_len, actual_r), float("-inf"), device=device, dtype=torch.float32, ), diagonal=1, ) .unsqueeze(0) .unsqueeze(0) ) scores = scores + causal_mask elif is_causal: logger.debug( "SVD-Sequence: causal mode with r=%d < kv_len=%d. " "Causal masking not strictly enforced because SVD " "compression combines multiple tokens.", actual_r, kv_len, ) scores = torch.nan_to_num(scores, nan=-1e4, posinf=1e4, neginf=-1e4) probs = F.softmax(scores, dim=-1).to(dtype) probs = self.dropout(probs) if head_mask is not None: if head_mask.dim() == 1: head_mask = head_mask.view(1, -1, 1, 1) elif head_mask.dim() == 2: head_mask = head_mask.unsqueeze(1).unsqueeze(-1) elif head_mask.dim() == 3: head_mask = head_mask.unsqueeze(-1) probs = probs * head_mask context = torch.matmul(probs, V_proj) # [B, H, seq_len, head_dim] if attention_mask is not None: q_valid_mask = get_token_valid_mask( attention_mask, batch_size, seq_len, device, ) if q_valid_mask is not None: context = context * q_valid_mask.unsqueeze(1).unsqueeze(-1).to(dtype) attn_ret = probs if output_attentions else None # 4. Context merge & Output projection context = context.transpose(1, 2).contiguous().view(batch_size, seq_len, self.all_head_size) context = torch.nan_to_num(context, nan=0.0, posinf=1e4, neginf=-1e4) # Always apply out_proj — our module replaces the full BertAttention container out = self.out_proj(context) if out.shape[-1] != hidden_states.shape[-1]: if out.shape[-1] < hidden_states.shape[-1]: out = F.pad(out, (0, hidden_states.shape[-1] - out.shape[-1])) else: out = out[..., : hidden_states.shape[-1]] return (out, attn_ret) def get_ortho_loss(self) -> torch.Tensor: """ Orthogonality diagnostic for the QR basis. Returns a scalar (no gradient). Use only for logging. """ device = self.query.weight.device if self._last_u_r is None: return torch.tensor(0.0, device=device) U = self._last_u_r r = U.size(-1) # Mean per-batch orthogonality error gram = torch.matmul(U.transpose(-2, -1).float(), U.float()) I = torch.eye(r, device=U.device, dtype=torch.float32) # Average over batch loss = (gram - I.unsqueeze(0)).abs().mean() return loss.detach()Low-Rank SVD-Compressed Sequence Attention Mechanism. Hardened for production use across diverse transformer architectures.
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_gqa : bool-
The type of the None singleton.
var triton_kernel : str-
The type of the None singleton.
Instance variables
prop k_lin : LoRALinear-
Expand source code
@property def k_lin(self) -> LoRALinear: return self.key prop k_proj : LoRALinear-
Expand source code
@property def k_proj(self) -> LoRALinear: return self.key prop q_lin : LoRALinear-
Expand source code
@property def q_lin(self) -> LoRALinear: return self.query prop q_proj : LoRALinear-
Expand source code
@property def q_proj(self) -> LoRALinear: return self.query prop v_lin : LoRALinear-
Expand source code
@property def v_lin(self) -> LoRALinear: return self.value prop v_proj : LoRALinear-
Expand source code
@property def v_proj(self) -> LoRALinear: return self.value
Methods
def copy_weights(self, old_attn: torch.nn.modules.module.Module) ‑> None-
Expand source code
def copy_weights(self, old_attn: nn.Module) -> None: """Robust weight copying from DistilBERT, RoBERTa nested, GPT-2 Conv1D, or Qwen/LLaMA.""" with torch.no_grad(): w_q, b_q = None, None w_k, b_k = None, None w_v, b_v = None, None w_o, b_o = None, None # Detect nested encoder container or inner self-attention src_cls_name = getattr(old_attn, "__class__", type(old_attn)).__name__ if src_cls_name in ("BertSelfAttention", "RobertaSelfAttention") or ( hasattr(old_attn, "query") and not hasattr(old_attn, "dense") and not hasattr(old_attn, "out_proj") ): self.is_nested_encoder = True if hasattr(old_attn, "self") and hasattr(old_attn, "output"): self.is_nested_encoder = True # Unwrap nested encoder modules to find innermost attention src = old_attn while hasattr(src, "self") and not any( hasattr(src, attr) for attr in ["query", "key", "value", "q_proj", "k_proj", "v_proj"] ): src = src.self # Copy output projection if available if hasattr(old_attn.output, "dense"): w_o = old_attn.output.dense.weight b_o = old_attn.output.dense.bias else: src = old_attn # Pattern 1: GPT-2 Fused c_attn if hasattr(src, "c_attn") and hasattr(src, "c_proj"): c_attn_w = src.c_attn.weight.detach() c_attn_b = src.c_attn.bias.detach() if src.c_attn.bias is not None else None p_cls = getattr(src.c_attn, "__class__", type(src.c_attn)).__name__ if ( ("Conv1D" in p_cls) or hasattr(src.c_attn, "nf") or ( c_attn_w.shape[0] == self.hidden_size and c_attn_w.shape[1] != self.hidden_size ) ): c_attn_w = c_attn_w.t().contiguous() q_dim = self.q_head_size k_dim = self.kv_head_size v_dim = self.kv_head_size w_q = c_attn_w[:q_dim, :] w_k = c_attn_w[q_dim : q_dim + k_dim, :] w_v = c_attn_w[q_dim + k_dim : q_dim + k_dim + v_dim, :] if c_attn_b is not None: b_q = c_attn_b[:q_dim] b_k = c_attn_b[q_dim : q_dim + k_dim] b_v = c_attn_b[q_dim + k_dim : q_dim + k_dim + v_dim] out_w = src.c_proj.weight.detach() p_proj_cls = getattr(src.c_proj, "__class__", type(src.c_proj)).__name__ if ( ("Conv1D" in p_proj_cls) or hasattr(src.c_proj, "nf") or (out_w.shape[0] == self.hidden_size and out_w.shape[1] != self.hidden_size) ): out_w = out_w.t().contiguous() w_o = out_w b_o = src.c_proj.bias.detach() if src.c_proj.bias is not None else None # Pattern 2: RoBERTa / BERT separate query, key, value elif hasattr(src, "query") and hasattr(src, "key") and hasattr(src, "value"): w_q = src.query.weight.detach() b_q = src.query.bias.detach() if src.query.bias is not None else None w_k = src.key.weight.detach() b_k = src.key.bias.detach() if src.key.bias is not None else None w_v = src.value.weight.detach() b_v = src.value.bias.detach() if src.value.bias is not None else None if w_o is None: for attr in ("dense", "out_proj", "o_proj"): if hasattr(src, attr): proj = getattr(src, attr) w_o = proj.weight.detach() b_o = proj.bias.detach() if proj.bias is not None else None break # Pattern 3: Qwen / LLaMA (q_proj, k_proj, v_proj, o_proj) elif hasattr(src, "q_proj") and hasattr(src, "k_proj") and hasattr(src, "v_proj"): w_q = src.q_proj.weight.detach() b_q = src.q_proj.bias.detach() if src.q_proj.bias is not None else None w_k = src.k_proj.weight.detach() b_k = src.k_proj.bias.detach() if src.k_proj.bias is not None else None w_v = src.v_proj.weight.detach() b_v = src.v_proj.bias.detach() if src.v_proj.bias is not None else None if hasattr(src, "o_proj"): w_o = src.o_proj.weight.detach() b_o = src.o_proj.bias.detach() if src.o_proj.bias is not None else None # Pattern 4: DistilBERT (q_lin, k_lin, v_lin, out_lin) elif hasattr(src, "q_lin") and hasattr(src, "k_lin") and hasattr(src, "v_lin"): w_q = src.q_lin.weight.detach() b_q = src.q_lin.bias.detach() if src.q_lin.bias is not None else None w_k = src.k_lin.weight.detach() b_k = src.k_lin.bias.detach() if src.k_lin.bias is not None else None w_v = src.v_lin.weight.detach() b_v = src.v_lin.bias.detach() if src.v_lin.bias is not None else None if hasattr(src, "out_lin"): w_o = src.out_lin.weight.detach() b_o = src.out_lin.bias.detach() if src.out_lin.bias is not None else None # Fallback: UniversalAdapter if w_q is None or w_k is None or w_v is None: try: w_q, b_q = UniversalAdapter.get_weight_bias(old_attn, "q") w_k, b_k = UniversalAdapter.get_weight_bias(old_attn, "k") w_v, b_v = UniversalAdapter.get_weight_bias(old_attn, "v") if w_o is None: w_o, b_o = UniversalAdapter.get_weight_bias(old_attn, "o") except Exception as e: logger.debug("UniversalAdapter fallback failed: %s", e) # Verification of essential projections missing = [] if w_q is None: missing.append("query/q_proj") if w_k is None: missing.append("key/k_proj") if w_v is None: missing.append("value/v_proj") if missing: raise RuntimeError( f"SVD-Compressed Sequence Attention: cannot extract " f"{missing} from {old_attn.__class__.__name__}. " f"Aborting to avoid silent corruption." ) # Frobenius norm checks with None guard for name, w in [("w_q", w_q), ("w_k", w_k), ("w_v", w_v)]: if w is None: continue if torch.norm(w.float(), p="fro").item() < 1e-4: raise RuntimeError( f"SVD-Sequence: {name} weight has near-zero " f"Frobenius norm. This will destroy the model." ) # Copy Q if w_q.shape == self.query.weight.shape: self.query.weight.copy_(w_q) else: self.query.weight.data[ : min(self.q_head_size, w_q.shape[0]), : min(self.hidden_size, w_q.shape[1]), ].copy_( w_q[ : min(self.q_head_size, w_q.shape[0]), : min(self.hidden_size, w_q.shape[1]), ] ) if b_q is not None and self.query.bias is not None: self.query.bias.data[: min(len(self.query.bias), len(b_q))].copy_( b_q[: min(len(self.query.bias), len(b_q))] ) # Copy K & V with GQA pooling/repetition if needed def _copy_kv(target_proj, w, b): if w.shape[0] == self.kv_head_size: target_proj.weight.copy_(w) if b is not None and target_proj.bias is not None: target_proj.bias.copy_(b) elif w.shape[0] > self.kv_head_size and w.shape[0] % self.kv_head_size == 0: # GQA: take the first group from each cluster (deterministic) rep = w.shape[0] // self.kv_head_size w_grouped = w.view(self.num_kv_heads, rep, self.head_dim, -1)[:, 0].reshape( self.kv_head_size, -1 ) target_proj.weight.copy_(w_grouped) if b is not None and target_proj.bias is not None: b_grouped = b.view(self.num_kv_heads, rep, self.head_dim)[:, 0].reshape(-1) target_proj.bias.copy_(b_grouped) elif w.shape[0] < self.kv_head_size and self.kv_head_size % w.shape[0] == 0: rep = self.kv_head_size // w.shape[0] target_proj.weight.copy_(w.repeat(rep, 1)) if b is not None and target_proj.bias is not None: target_proj.bias.copy_(b.repeat(rep)) else: target_proj.weight.data[ : min(self.kv_head_size, w.shape[0]), : min(self.hidden_size, w.shape[1]), ].copy_( w[ : min(self.kv_head_size, w.shape[0]), : min(self.hidden_size, w.shape[1]), ] ) if b is not None and target_proj.bias is not None: target_proj.bias.data[: min(len(target_proj.bias), len(b))].copy_( b[: min(len(target_proj.bias), len(b))] ) _copy_kv(self.key, w_k, b_k) _copy_kv(self.value, w_v, b_v) # Always copy Out projection — our module fully replaces BertAttention # (including BertSelfOutput), so we must apply out_proj in all cases. if w_o is not None: if torch.norm(w_o.float(), p="fro").item() < 1e-4: logger.warning("out_proj weight has near-zero Frobenius norm — skipping copy") else: if w_o.shape == self.out_proj.weight.shape: self.out_proj.weight.copy_(w_o) else: r = min(self.out_proj.weight.shape[0], w_o.shape[0]) c = min(self.out_proj.weight.shape[1], w_o.shape[1]) self.out_proj.weight.data[:r, :c].copy_(w_o[:r, :c]) if b_o is not None and self.out_proj.bias is not None: mn = min(len(self.out_proj.bias), len(b_o)) self.out_proj.bias.data[:mn].copy_(b_o[:mn]) # Compute SVD of W_K to get a fixed weight-space projection # W_K shape: [kv_head_size, hidden_size] → SVD gives right singular vectors of shape [hidden_size, ...] # We use the top-r right singular vectors (Vh rows) as the projection basis try: with torch.no_grad(): wk = self.key.weight.float() # [kv_head_size, hidden_size] _, _, Vh = torch.linalg.svd(wk, full_matrices=False) # Vh: [min(kv_head_size, hidden_size), hidden_size] r = min(self.rank or 64, Vh.shape[0], self.hidden_size) # svd_proj: [hidden_size, r] — project hidden_states → rank-r space proj = Vh[:r, :].T # [hidden_size, r] if proj.shape == self.svd_proj.shape: self.svd_proj.copy_(proj) else: mn_r = min(proj.shape[1], self.svd_proj.shape[1]) self.svd_proj[:, :mn_r].copy_(proj[:, :mn_r]) self._svd_proj_ready = True logger.info( "SVD projection computed from W_K: shape=%s, rank=%d", self.svd_proj.shape, r, ) except Exception as e: logger.warning( "SVD projection computation failed: %s — falling back to identity", e, ) self._svd_proj_ready = FalseRobust weight copying from DistilBERT, RoBERTa nested, GPT-2 Conv1D, or Qwen/LLaMA.
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,
past_key_value: Any | None = None,
output_attentions: bool | None = False,
output_hidden_states: bool | None = False,
return_dict: bool | None = True,
position_embeddings: tuple[torch.Tensor, torch.Tensor] | None = None,
*args: Any,
**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, past_key_value: Any | None = None, output_attentions: bool | None = False, output_hidden_states: bool | None = False, return_dict: bool | None = True, position_embeddings: tuple[torch.Tensor, torch.Tensor] | None = None, *args: Any, **kwargs: Any, ) -> tuple[torch.Tensor, torch.Tensor | None]: batch_size, seq_len, _ = hidden_states.shape device = hidden_states.device dtype = hidden_states.dtype is_cross = encoder_hidden_states is not None kv_input = encoder_hidden_states if is_cross else hidden_states kv_len = kv_input.shape[1] # 1. Project Q, K, V Q = ( self.query(hidden_states) .view(batch_size, seq_len, self.num_heads, self.head_dim) .transpose(1, 2) .contiguous() ) K = ( self.key(kv_input) .view(batch_size, kv_len, self.num_kv_heads, self.head_dim) .transpose(1, 2) .contiguous() ) V = ( self.value(kv_input) .view(batch_size, kv_len, self.num_kv_heads, self.head_dim) .transpose(1, 2) .contiguous() ) # 2. RoPE support if position_embeddings is None: position_embeddings = kwargs.get("position_embeddings") if position_embeddings is not None and not is_cross: cos, sin = position_embeddings Q, K = apply_rotary_pos_emb(Q, K, cos, sin) # 3. GQA expansion if self.num_kv_heads < self.num_heads: K = repeat_kv(K, self.kv_repeat) V = repeat_kv(V, self.kv_repeat) eff_mask = encoder_attention_mask if is_cross else attention_mask is_causal = self.is_decoder and not is_cross # Full attention if sequence is too short for SVD compression # (SVD with r >= N is meaningless — the projection is identity) use_full_attention = ( self.rank is None or kv_len <= (self.rank or 0) + 8 # small buffer ) if use_full_attention: # Use SDPA when possible (no explicit head_mask/output_attentions) _use_sdpa = head_mask is None and output_attentions is False if _use_sdpa: try: _q = Q if Q.dtype in (torch.float16, torch.bfloat16) else Q.half() _k = K if K.dtype in (torch.float16, torch.bfloat16) else K.half() _v = V if V.dtype in (torch.float16, torch.bfloat16) else V.half() # Prepare attn_mask for SDPA if eff_mask is provided _attn_mask = None if eff_mask is not None and eff_mask.dim() == 2: _attn_mask = eff_mask.bool().view( eff_mask.shape[0], 1, 1, eff_mask.shape[1] ) elif eff_mask is not None and eff_mask.dim() == 4: _attn_mask = eff_mask.bool() if eff_mask.dtype == torch.bool else eff_mask context = torch.nn.functional.scaled_dot_product_attention( _q, _k, _v, attn_mask=_attn_mask, is_causal=is_causal if _attn_mask is None else False, dropout_p=self.dropout.p if self.training else 0.0, ).to(dtype) probs = None # not available in SDPA except Exception as e: logger.debug("SDPA failed: %s, falling back", e) _use_sdpa = False if not _use_sdpa: # Manual softmax path scores = torch.matmul( Q.float(), K.float().transpose(-2, -1).contiguous() ) / math.sqrt(self.head_dim) if is_causal and seq_len > 1: causal_mask = ( torch.triu( torch.full( (seq_len, kv_len), float("-inf"), device=device, dtype=torch.float32, ), diagonal=1, ) .unsqueeze(0) .unsqueeze(0) ) scores = scores + causal_mask if eff_mask is not None: scores = apply_attention_mask(scores, eff_mask) scores = torch.nan_to_num(scores, nan=-1e4, posinf=1e4, neginf=-1e4) probs = F.softmax(scores, dim=-1).to(dtype) probs = self.dropout(probs) if head_mask is not None: if head_mask.dim() == 1: head_mask = head_mask.view(1, -1, 1, 1) elif head_mask.dim() == 2: head_mask = head_mask.unsqueeze(1).unsqueeze(-1) elif head_mask.dim() == 3: head_mask = head_mask.unsqueeze(-1) probs = probs * head_mask context = torch.matmul(probs, V) if attention_mask is not None: q_valid_mask = get_token_valid_mask( attention_mask, batch_size, seq_len, device, ) if q_valid_mask is not None: context = context * q_valid_mask.unsqueeze(1).unsqueeze(-1).to(dtype) attn_ret = probs if output_attentions else None else: # Use the fixed SVD subspace precomputed from W_K in copy_weights. # This is stable across batches — LoRA can converge on a fixed subspace. # svd_proj: [hidden_size, r] (registered buffer, frozen) with torch.no_grad(): proj = self.svd_proj.to(device=device, dtype=dtype) # [hidden_size, r] actual_r = proj.shape[1] # Project the kv_input sequence through the fixed subspace # kv_input: [B, kv_len, hidden_size] → scores: [B, kv_len, actual_r] seq_scores = torch.matmul(kv_input.float(), proj.float()) # [B, kv_len, r] try: U_r, _ = torch.linalg.qr(seq_scores.float(), mode="reduced") # [B, kv_len, r] except Exception: U_r, _ = torch.linalg.qr(seq_scores.cpu().float(), mode="reduced") U_r = U_r.to(device=device) U_r = U_r.to(dtype) # [B, kv_len, actual_r] # Store detached — the ortho loss is a metric, not a gradient source self._last_u_r = U_r.detach() # Project K and V through the stable subspace U_r_t = U_r.unsqueeze(1) # [B, 1, kv_len, actual_r] K_proj = torch.matmul( U_r_t.transpose(-2, -1).contiguous(), K ) # [B, H, actual_r, head_dim] V_proj = torch.matmul( U_r_t.transpose(-2, -1).contiguous(), V ) # [B, H, actual_r, head_dim] scores = torch.matmul( Q.float(), K_proj.float().transpose(-2, -1).contiguous() ) / math.sqrt(self.head_dim) if is_causal and seq_len > 1 and actual_r == kv_len: # Only apply causal mask if we kept the full sequence causal_mask = ( torch.triu( torch.full( (seq_len, actual_r), float("-inf"), device=device, dtype=torch.float32, ), diagonal=1, ) .unsqueeze(0) .unsqueeze(0) ) scores = scores + causal_mask elif is_causal: logger.debug( "SVD-Sequence: causal mode with r=%d < kv_len=%d. " "Causal masking not strictly enforced because SVD " "compression combines multiple tokens.", actual_r, kv_len, ) scores = torch.nan_to_num(scores, nan=-1e4, posinf=1e4, neginf=-1e4) probs = F.softmax(scores, dim=-1).to(dtype) probs = self.dropout(probs) if head_mask is not None: if head_mask.dim() == 1: head_mask = head_mask.view(1, -1, 1, 1) elif head_mask.dim() == 2: head_mask = head_mask.unsqueeze(1).unsqueeze(-1) elif head_mask.dim() == 3: head_mask = head_mask.unsqueeze(-1) probs = probs * head_mask context = torch.matmul(probs, V_proj) # [B, H, seq_len, head_dim] if attention_mask is not None: q_valid_mask = get_token_valid_mask( attention_mask, batch_size, seq_len, device, ) if q_valid_mask is not None: context = context * q_valid_mask.unsqueeze(1).unsqueeze(-1).to(dtype) attn_ret = probs if output_attentions else None # 4. Context merge & Output projection context = context.transpose(1, 2).contiguous().view(batch_size, seq_len, self.all_head_size) context = torch.nan_to_num(context, nan=0.0, posinf=1e4, neginf=-1e4) # Always apply out_proj — our module replaces the full BertAttention container out = self.out_proj(context) if out.shape[-1] != hidden_states.shape[-1]: if out.shape[-1] < hidden_states.shape[-1]: out = F.pad(out, (0, hidden_states.shape[-1] - out.shape[-1])) else: out = out[..., : hidden_states.shape[-1]] return (out, attn_ret)Must return (output, attention_weights_or_None).
def get_ortho_loss(self) ‑> torch.Tensor-
Expand source code
def get_ortho_loss(self) -> torch.Tensor: """ Orthogonality diagnostic for the QR basis. Returns a scalar (no gradient). Use only for logging. """ device = self.query.weight.device if self._last_u_r is None: return torch.tensor(0.0, device=device) U = self._last_u_r r = U.size(-1) # Mean per-batch orthogonality error gram = torch.matmul(U.transpose(-2, -1).float(), U.float()) I = torch.eye(r, device=U.device, dtype=torch.float32) # Average over batch loss = (gram - I.unsqueeze(0)).abs().mean() return loss.detach()Orthogonality diagnostic for the QR basis. Returns a scalar (no gradient). Use only for logging.
class SVDAttention (config: Any | None = None,
old_attn: torch.nn.modules.module.Module | None = None,
rank: int | None = 64,
lora_rank: int = 16,
lora_alpha: float = 32.0,
is_decoder: bool | None = None,
dropout: float | None = 0.1,
**kwargs: Any)-
Expand source code
@ATTENTION_REGISTRY.register("svd_seq", supports_causal=True, supports_gqa=True) @ATTENTION_REGISTRY.register("svd_sequence", supports_causal=True, supports_gqa=True) @ATTENTION_REGISTRY.register("svd_sequence_attention", supports_causal=True, supports_gqa=True) class SVDLinearAttention(BaseAttention): r"""Low-Rank SVD-Compressed Sequence Attention Mechanism. Hardened for production use across diverse transformer architectures. """ supports_gqa: bool = True is_custom_attention: bool = True triton_kernel: str = "low_rank" def __init__( self, config: Any | None = None, old_attn: nn.Module | None = None, rank: int | None = 64, lora_rank: int = 16, lora_alpha: float = 32.0, is_decoder: bool | None = None, dropout: float | None = 0.1, **kwargs: Any, ) -> None: super().__init__(config, rank) # 1. Dimensions & Architecture self.hidden_size: int = int(_cfg_get(config, "hidden_size", kwargs.get("hidden_size", 768))) self.num_heads: int = int( kwargs.get("num_heads") or kwargs.get("num_attention_heads") or _cfg_get( config, "num_attention_heads", _cfg_get(config, "num_heads", 12), ) ) self.head_dim: int = int( _cfg_get( config, "head_dim", kwargs.get( "head_dim", self.hidden_size // self.num_heads if self.num_heads > 0 else 64, ), ) ) cfg_kv = _cfg_get(config, "num_key_value_heads", _cfg_get(config, "num_kv_heads", None)) if cfg_kv is not None: self.num_kv_heads = int(cfg_kv) elif kwargs.get("num_kv_heads") is not None: self.num_kv_heads = int(kwargs["num_kv_heads"]) else: self.num_kv_heads = self.num_heads self.num_kv_heads = max(1, min(self.num_kv_heads, self.num_heads)) self.kv_repeat: int = max(1, self.num_heads // self.num_kv_heads) self.kv_head_size: int = self.num_kv_heads * self.head_dim self.q_head_size: int = self.num_heads * self.head_dim self.all_head_size: int = self.q_head_size # Explicitly allow None to disable SVD compression if rank is None: svd_disabled = _cfg_get(config, "svd_disabled", False) if svd_disabled or ("rank" in kwargs and kwargs["rank"] is None): self.rank: int | None = None else: cfg_r = _cfg_get(config, "svd_rank", _cfg_get(config, "rank", None)) self.rank = int(cfg_r) if cfg_r is not None else None else: self.rank = int(rank) self.lora_rank: int = int(lora_rank if lora_rank is not None else 16) self.lora_alpha: float = lora_alpha # 2. Causal decoder flag detection if is_decoder is not None: self.is_decoder = bool(is_decoder) else: self.is_decoder = ( bool(_cfg_get(config, "is_decoder", False)) or any( str(_cfg_get(config, "model_type", "")).lower().startswith(p) for p in ( "gpt", "qwen", "llama", "mistral", "gemma", "phi", "opt", "bloom", "falcon", "olmo", "mpt", "stablelm", ) ) or (old_attn is not None and hasattr(old_attn, "c_attn")) ) self.is_nested_encoder: bool = False # 3. Projections with LoRA self.query = LoRALinear( self.hidden_size, self.q_head_size, bias=True, lora_rank=self.lora_rank, lora_alpha=self.lora_alpha, freeze_base=True, ) self.key = LoRALinear( self.hidden_size, self.kv_head_size, bias=True, lora_rank=self.lora_rank, lora_alpha=self.lora_alpha, freeze_base=True, ) self.value = LoRALinear( self.hidden_size, self.kv_head_size, bias=True, lora_rank=self.lora_rank, lora_alpha=self.lora_alpha, freeze_base=True, ) self.out_proj = LoRALinear( self.q_head_size, self.hidden_size, bias=True, lora_rank=self.lora_rank, lora_alpha=self.lora_alpha, freeze_base=True, ) dropout_val: float = dropout if dropout is not None else getattr(config, "dropout", 0.1) self.dropout: nn.Dropout = nn.Dropout(dropout_val) self._last_u_r: torch.Tensor | None = None # Persistent SVD projection buffer (populated in copy_weights from W_K) # Shape: [hidden_size, rank] — the top-r right singular vectors of W_K init_rank = max(1, self.rank or 64) self.register_buffer("svd_proj", torch.eye(self.hidden_size, init_rank), persistent=True) self._svd_proj_ready: bool = False self.triton_kwargs = {} if old_attn is not None: self.copy_weights(old_attn) @property def q_proj(self) -> LoRALinear: return self.query @property def k_proj(self) -> LoRALinear: return self.key @property def v_proj(self) -> LoRALinear: return self.value @property def q_lin(self) -> LoRALinear: return self.query @property def k_lin(self) -> LoRALinear: return self.key @property def v_lin(self) -> LoRALinear: return self.value def copy_weights(self, old_attn: nn.Module) -> None: """Robust weight copying from DistilBERT, RoBERTa nested, GPT-2 Conv1D, or Qwen/LLaMA.""" with torch.no_grad(): w_q, b_q = None, None w_k, b_k = None, None w_v, b_v = None, None w_o, b_o = None, None # Detect nested encoder container or inner self-attention src_cls_name = getattr(old_attn, "__class__", type(old_attn)).__name__ if src_cls_name in ("BertSelfAttention", "RobertaSelfAttention") or ( hasattr(old_attn, "query") and not hasattr(old_attn, "dense") and not hasattr(old_attn, "out_proj") ): self.is_nested_encoder = True if hasattr(old_attn, "self") and hasattr(old_attn, "output"): self.is_nested_encoder = True # Unwrap nested encoder modules to find innermost attention src = old_attn while hasattr(src, "self") and not any( hasattr(src, attr) for attr in ["query", "key", "value", "q_proj", "k_proj", "v_proj"] ): src = src.self # Copy output projection if available if hasattr(old_attn.output, "dense"): w_o = old_attn.output.dense.weight b_o = old_attn.output.dense.bias else: src = old_attn # Pattern 1: GPT-2 Fused c_attn if hasattr(src, "c_attn") and hasattr(src, "c_proj"): c_attn_w = src.c_attn.weight.detach() c_attn_b = src.c_attn.bias.detach() if src.c_attn.bias is not None else None p_cls = getattr(src.c_attn, "__class__", type(src.c_attn)).__name__ if ( ("Conv1D" in p_cls) or hasattr(src.c_attn, "nf") or ( c_attn_w.shape[0] == self.hidden_size and c_attn_w.shape[1] != self.hidden_size ) ): c_attn_w = c_attn_w.t().contiguous() q_dim = self.q_head_size k_dim = self.kv_head_size v_dim = self.kv_head_size w_q = c_attn_w[:q_dim, :] w_k = c_attn_w[q_dim : q_dim + k_dim, :] w_v = c_attn_w[q_dim + k_dim : q_dim + k_dim + v_dim, :] if c_attn_b is not None: b_q = c_attn_b[:q_dim] b_k = c_attn_b[q_dim : q_dim + k_dim] b_v = c_attn_b[q_dim + k_dim : q_dim + k_dim + v_dim] out_w = src.c_proj.weight.detach() p_proj_cls = getattr(src.c_proj, "__class__", type(src.c_proj)).__name__ if ( ("Conv1D" in p_proj_cls) or hasattr(src.c_proj, "nf") or (out_w.shape[0] == self.hidden_size and out_w.shape[1] != self.hidden_size) ): out_w = out_w.t().contiguous() w_o = out_w b_o = src.c_proj.bias.detach() if src.c_proj.bias is not None else None # Pattern 2: RoBERTa / BERT separate query, key, value elif hasattr(src, "query") and hasattr(src, "key") and hasattr(src, "value"): w_q = src.query.weight.detach() b_q = src.query.bias.detach() if src.query.bias is not None else None w_k = src.key.weight.detach() b_k = src.key.bias.detach() if src.key.bias is not None else None w_v = src.value.weight.detach() b_v = src.value.bias.detach() if src.value.bias is not None else None if w_o is None: for attr in ("dense", "out_proj", "o_proj"): if hasattr(src, attr): proj = getattr(src, attr) w_o = proj.weight.detach() b_o = proj.bias.detach() if proj.bias is not None else None break # Pattern 3: Qwen / LLaMA (q_proj, k_proj, v_proj, o_proj) elif hasattr(src, "q_proj") and hasattr(src, "k_proj") and hasattr(src, "v_proj"): w_q = src.q_proj.weight.detach() b_q = src.q_proj.bias.detach() if src.q_proj.bias is not None else None w_k = src.k_proj.weight.detach() b_k = src.k_proj.bias.detach() if src.k_proj.bias is not None else None w_v = src.v_proj.weight.detach() b_v = src.v_proj.bias.detach() if src.v_proj.bias is not None else None if hasattr(src, "o_proj"): w_o = src.o_proj.weight.detach() b_o = src.o_proj.bias.detach() if src.o_proj.bias is not None else None # Pattern 4: DistilBERT (q_lin, k_lin, v_lin, out_lin) elif hasattr(src, "q_lin") and hasattr(src, "k_lin") and hasattr(src, "v_lin"): w_q = src.q_lin.weight.detach() b_q = src.q_lin.bias.detach() if src.q_lin.bias is not None else None w_k = src.k_lin.weight.detach() b_k = src.k_lin.bias.detach() if src.k_lin.bias is not None else None w_v = src.v_lin.weight.detach() b_v = src.v_lin.bias.detach() if src.v_lin.bias is not None else None if hasattr(src, "out_lin"): w_o = src.out_lin.weight.detach() b_o = src.out_lin.bias.detach() if src.out_lin.bias is not None else None # Fallback: UniversalAdapter if w_q is None or w_k is None or w_v is None: try: w_q, b_q = UniversalAdapter.get_weight_bias(old_attn, "q") w_k, b_k = UniversalAdapter.get_weight_bias(old_attn, "k") w_v, b_v = UniversalAdapter.get_weight_bias(old_attn, "v") if w_o is None: w_o, b_o = UniversalAdapter.get_weight_bias(old_attn, "o") except Exception as e: logger.debug("UniversalAdapter fallback failed: %s", e) # Verification of essential projections missing = [] if w_q is None: missing.append("query/q_proj") if w_k is None: missing.append("key/k_proj") if w_v is None: missing.append("value/v_proj") if missing: raise RuntimeError( f"SVD-Compressed Sequence Attention: cannot extract " f"{missing} from {old_attn.__class__.__name__}. " f"Aborting to avoid silent corruption." ) # Frobenius norm checks with None guard for name, w in [("w_q", w_q), ("w_k", w_k), ("w_v", w_v)]: if w is None: continue if torch.norm(w.float(), p="fro").item() < 1e-4: raise RuntimeError( f"SVD-Sequence: {name} weight has near-zero " f"Frobenius norm. This will destroy the model." ) # Copy Q if w_q.shape == self.query.weight.shape: self.query.weight.copy_(w_q) else: self.query.weight.data[ : min(self.q_head_size, w_q.shape[0]), : min(self.hidden_size, w_q.shape[1]), ].copy_( w_q[ : min(self.q_head_size, w_q.shape[0]), : min(self.hidden_size, w_q.shape[1]), ] ) if b_q is not None and self.query.bias is not None: self.query.bias.data[: min(len(self.query.bias), len(b_q))].copy_( b_q[: min(len(self.query.bias), len(b_q))] ) # Copy K & V with GQA pooling/repetition if needed def _copy_kv(target_proj, w, b): if w.shape[0] == self.kv_head_size: target_proj.weight.copy_(w) if b is not None and target_proj.bias is not None: target_proj.bias.copy_(b) elif w.shape[0] > self.kv_head_size and w.shape[0] % self.kv_head_size == 0: # GQA: take the first group from each cluster (deterministic) rep = w.shape[0] // self.kv_head_size w_grouped = w.view(self.num_kv_heads, rep, self.head_dim, -1)[:, 0].reshape( self.kv_head_size, -1 ) target_proj.weight.copy_(w_grouped) if b is not None and target_proj.bias is not None: b_grouped = b.view(self.num_kv_heads, rep, self.head_dim)[:, 0].reshape(-1) target_proj.bias.copy_(b_grouped) elif w.shape[0] < self.kv_head_size and self.kv_head_size % w.shape[0] == 0: rep = self.kv_head_size // w.shape[0] target_proj.weight.copy_(w.repeat(rep, 1)) if b is not None and target_proj.bias is not None: target_proj.bias.copy_(b.repeat(rep)) else: target_proj.weight.data[ : min(self.kv_head_size, w.shape[0]), : min(self.hidden_size, w.shape[1]), ].copy_( w[ : min(self.kv_head_size, w.shape[0]), : min(self.hidden_size, w.shape[1]), ] ) if b is not None and target_proj.bias is not None: target_proj.bias.data[: min(len(target_proj.bias), len(b))].copy_( b[: min(len(target_proj.bias), len(b))] ) _copy_kv(self.key, w_k, b_k) _copy_kv(self.value, w_v, b_v) # Always copy Out projection — our module fully replaces BertAttention # (including BertSelfOutput), so we must apply out_proj in all cases. if w_o is not None: if torch.norm(w_o.float(), p="fro").item() < 1e-4: logger.warning("out_proj weight has near-zero Frobenius norm — skipping copy") else: if w_o.shape == self.out_proj.weight.shape: self.out_proj.weight.copy_(w_o) else: r = min(self.out_proj.weight.shape[0], w_o.shape[0]) c = min(self.out_proj.weight.shape[1], w_o.shape[1]) self.out_proj.weight.data[:r, :c].copy_(w_o[:r, :c]) if b_o is not None and self.out_proj.bias is not None: mn = min(len(self.out_proj.bias), len(b_o)) self.out_proj.bias.data[:mn].copy_(b_o[:mn]) # Compute SVD of W_K to get a fixed weight-space projection # W_K shape: [kv_head_size, hidden_size] → SVD gives right singular vectors of shape [hidden_size, ...] # We use the top-r right singular vectors (Vh rows) as the projection basis try: with torch.no_grad(): wk = self.key.weight.float() # [kv_head_size, hidden_size] _, _, Vh = torch.linalg.svd(wk, full_matrices=False) # Vh: [min(kv_head_size, hidden_size), hidden_size] r = min(self.rank or 64, Vh.shape[0], self.hidden_size) # svd_proj: [hidden_size, r] — project hidden_states → rank-r space proj = Vh[:r, :].T # [hidden_size, r] if proj.shape == self.svd_proj.shape: self.svd_proj.copy_(proj) else: mn_r = min(proj.shape[1], self.svd_proj.shape[1]) self.svd_proj[:, :mn_r].copy_(proj[:, :mn_r]) self._svd_proj_ready = True logger.info( "SVD projection computed from W_K: shape=%s, rank=%d", self.svd_proj.shape, r, ) except Exception as e: logger.warning( "SVD projection computation failed: %s — falling back to identity", e, ) self._svd_proj_ready = False 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, past_key_value: Any | None = None, output_attentions: bool | None = False, output_hidden_states: bool | None = False, return_dict: bool | None = True, position_embeddings: tuple[torch.Tensor, torch.Tensor] | None = None, *args: Any, **kwargs: Any, ) -> tuple[torch.Tensor, torch.Tensor | None]: batch_size, seq_len, _ = hidden_states.shape device = hidden_states.device dtype = hidden_states.dtype is_cross = encoder_hidden_states is not None kv_input = encoder_hidden_states if is_cross else hidden_states kv_len = kv_input.shape[1] # 1. Project Q, K, V Q = ( self.query(hidden_states) .view(batch_size, seq_len, self.num_heads, self.head_dim) .transpose(1, 2) .contiguous() ) K = ( self.key(kv_input) .view(batch_size, kv_len, self.num_kv_heads, self.head_dim) .transpose(1, 2) .contiguous() ) V = ( self.value(kv_input) .view(batch_size, kv_len, self.num_kv_heads, self.head_dim) .transpose(1, 2) .contiguous() ) # 2. RoPE support if position_embeddings is None: position_embeddings = kwargs.get("position_embeddings") if position_embeddings is not None and not is_cross: cos, sin = position_embeddings Q, K = apply_rotary_pos_emb(Q, K, cos, sin) # 3. GQA expansion if self.num_kv_heads < self.num_heads: K = repeat_kv(K, self.kv_repeat) V = repeat_kv(V, self.kv_repeat) eff_mask = encoder_attention_mask if is_cross else attention_mask is_causal = self.is_decoder and not is_cross # Full attention if sequence is too short for SVD compression # (SVD with r >= N is meaningless — the projection is identity) use_full_attention = ( self.rank is None or kv_len <= (self.rank or 0) + 8 # small buffer ) if use_full_attention: # Use SDPA when possible (no explicit head_mask/output_attentions) _use_sdpa = head_mask is None and output_attentions is False if _use_sdpa: try: _q = Q if Q.dtype in (torch.float16, torch.bfloat16) else Q.half() _k = K if K.dtype in (torch.float16, torch.bfloat16) else K.half() _v = V if V.dtype in (torch.float16, torch.bfloat16) else V.half() # Prepare attn_mask for SDPA if eff_mask is provided _attn_mask = None if eff_mask is not None and eff_mask.dim() == 2: _attn_mask = eff_mask.bool().view( eff_mask.shape[0], 1, 1, eff_mask.shape[1] ) elif eff_mask is not None and eff_mask.dim() == 4: _attn_mask = eff_mask.bool() if eff_mask.dtype == torch.bool else eff_mask context = torch.nn.functional.scaled_dot_product_attention( _q, _k, _v, attn_mask=_attn_mask, is_causal=is_causal if _attn_mask is None else False, dropout_p=self.dropout.p if self.training else 0.0, ).to(dtype) probs = None # not available in SDPA except Exception as e: logger.debug("SDPA failed: %s, falling back", e) _use_sdpa = False if not _use_sdpa: # Manual softmax path scores = torch.matmul( Q.float(), K.float().transpose(-2, -1).contiguous() ) / math.sqrt(self.head_dim) if is_causal and seq_len > 1: causal_mask = ( torch.triu( torch.full( (seq_len, kv_len), float("-inf"), device=device, dtype=torch.float32, ), diagonal=1, ) .unsqueeze(0) .unsqueeze(0) ) scores = scores + causal_mask if eff_mask is not None: scores = apply_attention_mask(scores, eff_mask) scores = torch.nan_to_num(scores, nan=-1e4, posinf=1e4, neginf=-1e4) probs = F.softmax(scores, dim=-1).to(dtype) probs = self.dropout(probs) if head_mask is not None: if head_mask.dim() == 1: head_mask = head_mask.view(1, -1, 1, 1) elif head_mask.dim() == 2: head_mask = head_mask.unsqueeze(1).unsqueeze(-1) elif head_mask.dim() == 3: head_mask = head_mask.unsqueeze(-1) probs = probs * head_mask context = torch.matmul(probs, V) if attention_mask is not None: q_valid_mask = get_token_valid_mask( attention_mask, batch_size, seq_len, device, ) if q_valid_mask is not None: context = context * q_valid_mask.unsqueeze(1).unsqueeze(-1).to(dtype) attn_ret = probs if output_attentions else None else: # Use the fixed SVD subspace precomputed from W_K in copy_weights. # This is stable across batches — LoRA can converge on a fixed subspace. # svd_proj: [hidden_size, r] (registered buffer, frozen) with torch.no_grad(): proj = self.svd_proj.to(device=device, dtype=dtype) # [hidden_size, r] actual_r = proj.shape[1] # Project the kv_input sequence through the fixed subspace # kv_input: [B, kv_len, hidden_size] → scores: [B, kv_len, actual_r] seq_scores = torch.matmul(kv_input.float(), proj.float()) # [B, kv_len, r] try: U_r, _ = torch.linalg.qr(seq_scores.float(), mode="reduced") # [B, kv_len, r] except Exception: U_r, _ = torch.linalg.qr(seq_scores.cpu().float(), mode="reduced") U_r = U_r.to(device=device) U_r = U_r.to(dtype) # [B, kv_len, actual_r] # Store detached — the ortho loss is a metric, not a gradient source self._last_u_r = U_r.detach() # Project K and V through the stable subspace U_r_t = U_r.unsqueeze(1) # [B, 1, kv_len, actual_r] K_proj = torch.matmul( U_r_t.transpose(-2, -1).contiguous(), K ) # [B, H, actual_r, head_dim] V_proj = torch.matmul( U_r_t.transpose(-2, -1).contiguous(), V ) # [B, H, actual_r, head_dim] scores = torch.matmul( Q.float(), K_proj.float().transpose(-2, -1).contiguous() ) / math.sqrt(self.head_dim) if is_causal and seq_len > 1 and actual_r == kv_len: # Only apply causal mask if we kept the full sequence causal_mask = ( torch.triu( torch.full( (seq_len, actual_r), float("-inf"), device=device, dtype=torch.float32, ), diagonal=1, ) .unsqueeze(0) .unsqueeze(0) ) scores = scores + causal_mask elif is_causal: logger.debug( "SVD-Sequence: causal mode with r=%d < kv_len=%d. " "Causal masking not strictly enforced because SVD " "compression combines multiple tokens.", actual_r, kv_len, ) scores = torch.nan_to_num(scores, nan=-1e4, posinf=1e4, neginf=-1e4) probs = F.softmax(scores, dim=-1).to(dtype) probs = self.dropout(probs) if head_mask is not None: if head_mask.dim() == 1: head_mask = head_mask.view(1, -1, 1, 1) elif head_mask.dim() == 2: head_mask = head_mask.unsqueeze(1).unsqueeze(-1) elif head_mask.dim() == 3: head_mask = head_mask.unsqueeze(-1) probs = probs * head_mask context = torch.matmul(probs, V_proj) # [B, H, seq_len, head_dim] if attention_mask is not None: q_valid_mask = get_token_valid_mask( attention_mask, batch_size, seq_len, device, ) if q_valid_mask is not None: context = context * q_valid_mask.unsqueeze(1).unsqueeze(-1).to(dtype) attn_ret = probs if output_attentions else None # 4. Context merge & Output projection context = context.transpose(1, 2).contiguous().view(batch_size, seq_len, self.all_head_size) context = torch.nan_to_num(context, nan=0.0, posinf=1e4, neginf=-1e4) # Always apply out_proj — our module replaces the full BertAttention container out = self.out_proj(context) if out.shape[-1] != hidden_states.shape[-1]: if out.shape[-1] < hidden_states.shape[-1]: out = F.pad(out, (0, hidden_states.shape[-1] - out.shape[-1])) else: out = out[..., : hidden_states.shape[-1]] return (out, attn_ret) def get_ortho_loss(self) -> torch.Tensor: """ Orthogonality diagnostic for the QR basis. Returns a scalar (no gradient). Use only for logging. """ device = self.query.weight.device if self._last_u_r is None: return torch.tensor(0.0, device=device) U = self._last_u_r r = U.size(-1) # Mean per-batch orthogonality error gram = torch.matmul(U.transpose(-2, -1).float(), U.float()) I = torch.eye(r, device=U.device, dtype=torch.float32) # Average over batch loss = (gram - I.unsqueeze(0)).abs().mean() return loss.detach()Low-Rank SVD-Compressed Sequence Attention Mechanism. Hardened for production use across diverse transformer architectures.
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_gqa : bool-
The type of the None singleton.
var triton_kernel : str-
The type of the None singleton.
Instance variables
prop k_lin : LoRALinear-
Expand source code
@property def k_lin(self) -> LoRALinear: return self.key prop k_proj : LoRALinear-
Expand source code
@property def k_proj(self) -> LoRALinear: return self.key prop q_lin : LoRALinear-
Expand source code
@property def q_lin(self) -> LoRALinear: return self.query prop q_proj : LoRALinear-
Expand source code
@property def q_proj(self) -> LoRALinear: return self.query prop v_lin : LoRALinear-
Expand source code
@property def v_lin(self) -> LoRALinear: return self.value prop v_proj : LoRALinear-
Expand source code
@property def v_proj(self) -> LoRALinear: return self.value
Methods
def copy_weights(self, old_attn: torch.nn.modules.module.Module) ‑> None-
Expand source code
def copy_weights(self, old_attn: nn.Module) -> None: """Robust weight copying from DistilBERT, RoBERTa nested, GPT-2 Conv1D, or Qwen/LLaMA.""" with torch.no_grad(): w_q, b_q = None, None w_k, b_k = None, None w_v, b_v = None, None w_o, b_o = None, None # Detect nested encoder container or inner self-attention src_cls_name = getattr(old_attn, "__class__", type(old_attn)).__name__ if src_cls_name in ("BertSelfAttention", "RobertaSelfAttention") or ( hasattr(old_attn, "query") and not hasattr(old_attn, "dense") and not hasattr(old_attn, "out_proj") ): self.is_nested_encoder = True if hasattr(old_attn, "self") and hasattr(old_attn, "output"): self.is_nested_encoder = True # Unwrap nested encoder modules to find innermost attention src = old_attn while hasattr(src, "self") and not any( hasattr(src, attr) for attr in ["query", "key", "value", "q_proj", "k_proj", "v_proj"] ): src = src.self # Copy output projection if available if hasattr(old_attn.output, "dense"): w_o = old_attn.output.dense.weight b_o = old_attn.output.dense.bias else: src = old_attn # Pattern 1: GPT-2 Fused c_attn if hasattr(src, "c_attn") and hasattr(src, "c_proj"): c_attn_w = src.c_attn.weight.detach() c_attn_b = src.c_attn.bias.detach() if src.c_attn.bias is not None else None p_cls = getattr(src.c_attn, "__class__", type(src.c_attn)).__name__ if ( ("Conv1D" in p_cls) or hasattr(src.c_attn, "nf") or ( c_attn_w.shape[0] == self.hidden_size and c_attn_w.shape[1] != self.hidden_size ) ): c_attn_w = c_attn_w.t().contiguous() q_dim = self.q_head_size k_dim = self.kv_head_size v_dim = self.kv_head_size w_q = c_attn_w[:q_dim, :] w_k = c_attn_w[q_dim : q_dim + k_dim, :] w_v = c_attn_w[q_dim + k_dim : q_dim + k_dim + v_dim, :] if c_attn_b is not None: b_q = c_attn_b[:q_dim] b_k = c_attn_b[q_dim : q_dim + k_dim] b_v = c_attn_b[q_dim + k_dim : q_dim + k_dim + v_dim] out_w = src.c_proj.weight.detach() p_proj_cls = getattr(src.c_proj, "__class__", type(src.c_proj)).__name__ if ( ("Conv1D" in p_proj_cls) or hasattr(src.c_proj, "nf") or (out_w.shape[0] == self.hidden_size and out_w.shape[1] != self.hidden_size) ): out_w = out_w.t().contiguous() w_o = out_w b_o = src.c_proj.bias.detach() if src.c_proj.bias is not None else None # Pattern 2: RoBERTa / BERT separate query, key, value elif hasattr(src, "query") and hasattr(src, "key") and hasattr(src, "value"): w_q = src.query.weight.detach() b_q = src.query.bias.detach() if src.query.bias is not None else None w_k = src.key.weight.detach() b_k = src.key.bias.detach() if src.key.bias is not None else None w_v = src.value.weight.detach() b_v = src.value.bias.detach() if src.value.bias is not None else None if w_o is None: for attr in ("dense", "out_proj", "o_proj"): if hasattr(src, attr): proj = getattr(src, attr) w_o = proj.weight.detach() b_o = proj.bias.detach() if proj.bias is not None else None break # Pattern 3: Qwen / LLaMA (q_proj, k_proj, v_proj, o_proj) elif hasattr(src, "q_proj") and hasattr(src, "k_proj") and hasattr(src, "v_proj"): w_q = src.q_proj.weight.detach() b_q = src.q_proj.bias.detach() if src.q_proj.bias is not None else None w_k = src.k_proj.weight.detach() b_k = src.k_proj.bias.detach() if src.k_proj.bias is not None else None w_v = src.v_proj.weight.detach() b_v = src.v_proj.bias.detach() if src.v_proj.bias is not None else None if hasattr(src, "o_proj"): w_o = src.o_proj.weight.detach() b_o = src.o_proj.bias.detach() if src.o_proj.bias is not None else None # Pattern 4: DistilBERT (q_lin, k_lin, v_lin, out_lin) elif hasattr(src, "q_lin") and hasattr(src, "k_lin") and hasattr(src, "v_lin"): w_q = src.q_lin.weight.detach() b_q = src.q_lin.bias.detach() if src.q_lin.bias is not None else None w_k = src.k_lin.weight.detach() b_k = src.k_lin.bias.detach() if src.k_lin.bias is not None else None w_v = src.v_lin.weight.detach() b_v = src.v_lin.bias.detach() if src.v_lin.bias is not None else None if hasattr(src, "out_lin"): w_o = src.out_lin.weight.detach() b_o = src.out_lin.bias.detach() if src.out_lin.bias is not None else None # Fallback: UniversalAdapter if w_q is None or w_k is None or w_v is None: try: w_q, b_q = UniversalAdapter.get_weight_bias(old_attn, "q") w_k, b_k = UniversalAdapter.get_weight_bias(old_attn, "k") w_v, b_v = UniversalAdapter.get_weight_bias(old_attn, "v") if w_o is None: w_o, b_o = UniversalAdapter.get_weight_bias(old_attn, "o") except Exception as e: logger.debug("UniversalAdapter fallback failed: %s", e) # Verification of essential projections missing = [] if w_q is None: missing.append("query/q_proj") if w_k is None: missing.append("key/k_proj") if w_v is None: missing.append("value/v_proj") if missing: raise RuntimeError( f"SVD-Compressed Sequence Attention: cannot extract " f"{missing} from {old_attn.__class__.__name__}. " f"Aborting to avoid silent corruption." ) # Frobenius norm checks with None guard for name, w in [("w_q", w_q), ("w_k", w_k), ("w_v", w_v)]: if w is None: continue if torch.norm(w.float(), p="fro").item() < 1e-4: raise RuntimeError( f"SVD-Sequence: {name} weight has near-zero " f"Frobenius norm. This will destroy the model." ) # Copy Q if w_q.shape == self.query.weight.shape: self.query.weight.copy_(w_q) else: self.query.weight.data[ : min(self.q_head_size, w_q.shape[0]), : min(self.hidden_size, w_q.shape[1]), ].copy_( w_q[ : min(self.q_head_size, w_q.shape[0]), : min(self.hidden_size, w_q.shape[1]), ] ) if b_q is not None and self.query.bias is not None: self.query.bias.data[: min(len(self.query.bias), len(b_q))].copy_( b_q[: min(len(self.query.bias), len(b_q))] ) # Copy K & V with GQA pooling/repetition if needed def _copy_kv(target_proj, w, b): if w.shape[0] == self.kv_head_size: target_proj.weight.copy_(w) if b is not None and target_proj.bias is not None: target_proj.bias.copy_(b) elif w.shape[0] > self.kv_head_size and w.shape[0] % self.kv_head_size == 0: # GQA: take the first group from each cluster (deterministic) rep = w.shape[0] // self.kv_head_size w_grouped = w.view(self.num_kv_heads, rep, self.head_dim, -1)[:, 0].reshape( self.kv_head_size, -1 ) target_proj.weight.copy_(w_grouped) if b is not None and target_proj.bias is not None: b_grouped = b.view(self.num_kv_heads, rep, self.head_dim)[:, 0].reshape(-1) target_proj.bias.copy_(b_grouped) elif w.shape[0] < self.kv_head_size and self.kv_head_size % w.shape[0] == 0: rep = self.kv_head_size // w.shape[0] target_proj.weight.copy_(w.repeat(rep, 1)) if b is not None and target_proj.bias is not None: target_proj.bias.copy_(b.repeat(rep)) else: target_proj.weight.data[ : min(self.kv_head_size, w.shape[0]), : min(self.hidden_size, w.shape[1]), ].copy_( w[ : min(self.kv_head_size, w.shape[0]), : min(self.hidden_size, w.shape[1]), ] ) if b is not None and target_proj.bias is not None: target_proj.bias.data[: min(len(target_proj.bias), len(b))].copy_( b[: min(len(target_proj.bias), len(b))] ) _copy_kv(self.key, w_k, b_k) _copy_kv(self.value, w_v, b_v) # Always copy Out projection — our module fully replaces BertAttention # (including BertSelfOutput), so we must apply out_proj in all cases. if w_o is not None: if torch.norm(w_o.float(), p="fro").item() < 1e-4: logger.warning("out_proj weight has near-zero Frobenius norm — skipping copy") else: if w_o.shape == self.out_proj.weight.shape: self.out_proj.weight.copy_(w_o) else: r = min(self.out_proj.weight.shape[0], w_o.shape[0]) c = min(self.out_proj.weight.shape[1], w_o.shape[1]) self.out_proj.weight.data[:r, :c].copy_(w_o[:r, :c]) if b_o is not None and self.out_proj.bias is not None: mn = min(len(self.out_proj.bias), len(b_o)) self.out_proj.bias.data[:mn].copy_(b_o[:mn]) # Compute SVD of W_K to get a fixed weight-space projection # W_K shape: [kv_head_size, hidden_size] → SVD gives right singular vectors of shape [hidden_size, ...] # We use the top-r right singular vectors (Vh rows) as the projection basis try: with torch.no_grad(): wk = self.key.weight.float() # [kv_head_size, hidden_size] _, _, Vh = torch.linalg.svd(wk, full_matrices=False) # Vh: [min(kv_head_size, hidden_size), hidden_size] r = min(self.rank or 64, Vh.shape[0], self.hidden_size) # svd_proj: [hidden_size, r] — project hidden_states → rank-r space proj = Vh[:r, :].T # [hidden_size, r] if proj.shape == self.svd_proj.shape: self.svd_proj.copy_(proj) else: mn_r = min(proj.shape[1], self.svd_proj.shape[1]) self.svd_proj[:, :mn_r].copy_(proj[:, :mn_r]) self._svd_proj_ready = True logger.info( "SVD projection computed from W_K: shape=%s, rank=%d", self.svd_proj.shape, r, ) except Exception as e: logger.warning( "SVD projection computation failed: %s — falling back to identity", e, ) self._svd_proj_ready = FalseRobust weight copying from DistilBERT, RoBERTa nested, GPT-2 Conv1D, or Qwen/LLaMA.
def get_ortho_loss(self) ‑> torch.Tensor-
Expand source code
def get_ortho_loss(self) -> torch.Tensor: """ Orthogonality diagnostic for the QR basis. Returns a scalar (no gradient). Use only for logging. """ device = self.query.weight.device if self._last_u_r is None: return torch.tensor(0.0, device=device) U = self._last_u_r r = U.size(-1) # Mean per-batch orthogonality error gram = torch.matmul(U.transpose(-2, -1).float(), U.float()) I = torch.eye(r, device=U.device, dtype=torch.float32) # Average over batch loss = (gram - I.unsqueeze(0)).abs().mean() return loss.detach()Orthogonality diagnostic for the QR basis. Returns a scalar (no gradient). Use only for logging.
Inherited members