Module neuralops.attentions.compressed_attention
Compressed Multi-Head Attention via Truncated Singular Value Decomposition (SVD).
Provides rank-constrained and singular-energy-thresholded low-rank approximations of standard PyTorch attention input (QKV) and output projection matrices with full pretrained weight transfer, trainable LoRA residual adapters, GQA, RoPE, and causal decoding support.
Functions
def inject_compressed_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)-
Expand source code
@ATTENTION_REGISTRY.register("compressed_attention_fn") @ATTENTION_REGISTRY.register("compressed_attention_layer_fn") @ATTENTION_REGISTRY.register("compressed_attention_inject") @ATTENTION_REGISTRY.register("compressed_attention_layer_inject") def inject_compressed_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_compressed_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 model_config is None: model_config = type("Cfg", (), {"hidden_size": 768, "num_heads": 12, "rank": 384})() src_attn = old_attn if src_attn is None: if hasattr(layer, "attention"): src_attn = layer.attention elif hasattr(layer, "self_attn"): src_attn = layer.self_attn elif hasattr(layer, "self"): src_attn = layer.self elif hasattr(layer, "attn"): src_attn = layer.attn try: h_sz = ( getattr(model_config, "hidden_size", getattr(model_config, "dim", 768)) if model_config else 768 ) default_rank = min(256, h_sz // 2) rank_qkv = ( getattr(comp_config, "rank_qkv", getattr(comp_config, "rank", default_rank)) if comp_config is not None else default_rank ) rank_out = ( getattr(comp_config, "rank_out", getattr(comp_config, "rank", default_rank)) if comp_config is not None else default_rank ) new_attn = CompressedAttentionLayer( config=model_config, rank_qkv=rank_qkv, rank_out=rank_out, pretrained_attn=src_attn, ) if hasattr(layer, "attention"): if hasattr(layer.attention, "self"): layer.attention.self = new_attn else: layer.attention = new_attn elif hasattr(layer, "self_attn"): layer.self_attn = new_attn elif hasattr(layer, "self"): layer.self = new_attn elif hasattr(layer, "attn"): layer.attn = new_attn return new_attn except Exception as e: logger.error(f"inject_compressed_attention: failed: {e}") return None
Classes
class CompressedAttention (config: Any | None = None,
rank: int | None = None,
rank_qkv: int = 256,
rank_out: int = 256,
lora_rank: int = 16,
original_layer: Any = None,
pretrained_attn: nn.Module | None = None,
**kwargs: Any)-
Expand source code
@ATTENTION_REGISTRY.register( "compressed_attention_layer", supports_causal=True, supports_bidirectional=True, supports_gqa=True, ) @ATTENTION_REGISTRY.register( "compressed_attention", supports_causal=True, supports_bidirectional=True, supports_gqa=True, ) class CompressedAttentionLayer(BaseAttention): supports_causal: bool = True supports_bidirectional: bool = True supports_gqa: bool = True supports_rope: bool = True is_custom_attention: bool = True def __init__( self, config: Any | None = None, rank: int | None = None, rank_qkv: int = 256, rank_out: int = 256, lora_rank: int = 16, original_layer: Any = None, pretrained_attn: nn.Module | None = None, **kwargs: Any, ) -> None: cfg = config if config is not None else original_layer super().__init__(cfg, rank) self.adapter = ModelAdapter(cfg) self.hidden_size: int = self.adapter.hidden_size or kwargs.get( "hidden_size", kwargs.get("d_model", 768) ) self.num_heads: int = self.adapter.num_heads or kwargs.get( "num_heads", kwargs.get("num_attention_heads", 12) ) self.num_kv_heads: int = self.adapter.num_key_value_heads or kwargs.get( "num_kv_heads", kwargs.get("num_key_value_heads", self.num_heads) ) self.head_dim: int = self.adapter.head_dim or (self.hidden_size // max(1, self.num_heads)) self.all_head_size: int = self.num_heads * self.head_dim self.kv_head_size: int = self.num_kv_heads * self.head_dim self.kv_repeat: int = max(1, self.num_heads // max(1, self.num_kv_heads)) _mt = getattr(cfg, "model_type", "").lower() if cfg else "" _decoder_prefixes = ( "gpt", "qwen", "llama", "mistral", "gemma", "phi", "opt", "bloom", "falcon", "olmo", "mpt", "stablelm", ) self.is_decoder: bool = bool( getattr(cfg, "is_decoder", False) or any(_mt.startswith(p) for p in _decoder_prefixes) ) self.is_nested_encoder: bool = False rank_val = rank or kwargs.get("rank_qkv", rank_qkv) self.rank_qkv = min(rank_val, self.all_head_size + 2 * self.kv_head_size, self.hidden_size) self.rank_out = min( rank or kwargs.get("rank_out", rank_out), self.all_head_size, self.hidden_size, ) self.lora_rank = getattr(cfg, "lora_rank", lora_rank) self.lora_scaling = (32.0 / self.lora_rank) if self.lora_rank > 0 else 1.0 qkv_out_dim = self.all_head_size + 2 * self.kv_head_size # Frozen base SVD parameters self.Vh_qkv = nn.Parameter( torch.randn(self.rank_qkv, self.hidden_size) * 0.02, requires_grad=False ) self.S_qkv_sqrt = nn.Parameter(torch.ones(self.rank_qkv), requires_grad=False) self.U_qkv = nn.Parameter( torch.randn(qkv_out_dim, self.rank_qkv) * 0.02, requires_grad=False ) self.bias_qkv = nn.Parameter(torch.zeros(qkv_out_dim), requires_grad=False) self.Vh_out = nn.Parameter( torch.randn(self.rank_out, self.all_head_size) * 0.02, requires_grad=False ) self.S_out_sqrt = nn.Parameter(torch.ones(self.rank_out), requires_grad=False) self.U_out = nn.Parameter( torch.randn(self.hidden_size, self.rank_out) * 0.02, requires_grad=False ) self.bias_out = nn.Parameter(torch.zeros(self.hidden_size), requires_grad=False) # Trainable LoRA residual adapters if self.lora_rank > 0: self.lora_A_qkv = nn.Parameter(torch.empty(self.lora_rank, self.hidden_size)) self.lora_B_qkv = nn.Parameter(torch.empty(qkv_out_dim, self.lora_rank)) self.lora_A_out = nn.Parameter(torch.empty(self.lora_rank, self.all_head_size)) self.lora_B_out = nn.Parameter(torch.empty(self.hidden_size, self.lora_rank)) self.reset_lora_parameters() else: self.register_parameter("lora_A_qkv", None) self.register_parameter("lora_B_qkv", None) self.register_parameter("lora_A_out", None) self.register_parameter("lora_B_out", None) dropout_p = ( getattr(cfg, "dropout", getattr(cfg, "attention_probs_dropout_prob", 0.1)) if cfg else 0.1 ) self.dropout = nn.Dropout(dropout_p) self.rotary_emb = getattr(pretrained_attn, "rotary_emb", None) src = pretrained_attn or ( original_layer if hasattr(original_layer, "q_lin") or hasattr(original_layer, "query") or hasattr(original_layer, "c_attn") else None ) if src is not None: self.copy_weights(src) @property def qkv_weight(self) -> torch.Tensor: """Reconstruct the effective base QKV weight from SVD factors.""" return (self.U_qkv * self.S_qkv_sqrt.view(1, -1)) @ ( self.Vh_qkv * self.S_qkv_sqrt.view(-1, 1) ) @property def out_weight(self) -> torch.Tensor: """Reconstruct the effective base Out weight from SVD factors.""" return (self.U_out * self.S_out_sqrt.view(1, -1)) @ ( self.Vh_out * self.S_out_sqrt.view(-1, 1) ) def reset_lora_parameters(self) -> None: if self.lora_rank > 0: nn.init.kaiming_uniform_(self.lora_A_qkv, a=math.sqrt(5)) nn.init.zeros_(self.lora_B_qkv) nn.init.kaiming_uniform_(self.lora_A_out, a=math.sqrt(5)) nn.init.zeros_(self.lora_B_out) self.lora_A_qkv.requires_grad = True self.lora_B_qkv.requires_grad = True self.lora_A_out.requires_grad = True self.lora_B_out.requires_grad = True def copy_weights(self, src: Any) -> None: from neuralops.core.adapters import UniversalAdapter extracted: dict[str, tuple[torch.Tensor | None, torch.Tensor | None]] = { "q": (None, None), "k": (None, None), "v": (None, None), "o": (None, None), } # 1. HuggingFace / standard UniversalAdapter extraction for proj in ["q", "k", "v", "o"]: try: w, b = UniversalAdapter.get_weight_bias(src, proj) if w is not None: extracted[proj] = (w, b) except Exception as e: logger.debug( f"UniversalAdapter failed for {proj}: {e}" ) # continue to fallback paths # 2. GPT-2 Fused c_attn Conv1D handling if hasattr(src, "c_attn") and ( extracted["q"][0] is None or extracted["k"][0] is None or extracted["v"][0] is None ): weight = src.c_attn.weight.data bias = getattr(src.c_attn, "bias", None) bias_data = bias.data if bias is not None else None if weight.dim() == 2 and weight.shape[1] == 3 * self.hidden_size: weight = weight.t().contiguous() split_size = weight.shape[0] // 3 q_w = weight[:split_size, :] k_w = weight[split_size : 2 * split_size, :] v_w = weight[2 * split_size :, :] q_b = bias_data[:split_size] if bias_data is not None else None k_b = bias_data[split_size : 2 * split_size] if bias_data is not None else None v_b = bias_data[2 * split_size :] if bias_data is not None else None extracted["q"] = (q_w, q_b) extracted["k"] = (k_w, k_b) extracted["v"] = (v_w, v_b) if hasattr(src, "c_proj") and extracted["o"][0] is None: w_o = src.c_proj.weight.data if ( w_o.dim() == 2 and w_o.shape[0] != self.hidden_size and w_o.shape[1] == self.hidden_size ): w_o = w_o.t().contiguous() b_o = getattr(src.c_proj, "bias", None) extracted["o"] = (w_o, b_o.data if b_o is not None else None) # 3. RoBERTa / BERT Nested Encoder handling if hasattr(src, "self") and ( extracted["q"][0] is None or extracted["k"][0] is None or extracted["v"][0] is None ): s = src.self if hasattr(s, "query"): extracted["q"] = (s.query.weight.data, getattr(s.query, "bias", None)) if hasattr(s, "key"): extracted["k"] = (s.key.weight.data, getattr(s.key, "bias", None)) if hasattr(s, "value"): extracted["v"] = (s.value.weight.data, getattr(s.value, "bias", None)) if hasattr(src, "output") and hasattr(src.output, "dense") and extracted["o"][0] is None: dense = src.output.dense extracted["o"] = (dense.weight.data, getattr(dense, "bias", None)) # 4. Modern Decoders (Qwen3.5, LLaMA, Mistral) & DistilBERT named_map = { "q": ["q_proj", "q_lin", "query"], "k": ["k_proj", "k_lin", "key"], "v": ["v_proj", "v_lin", "value"], "o": ["o_proj", "out_proj", "out_lin", "dense"], } for proj, names in named_map.items(): if extracted[proj][0] is None: for name in names: if hasattr(src, name): layer = getattr(src, name) if hasattr(layer, "weight"): extracted[proj] = ( layer.weight.data, getattr(layer, "bias", None), ) break # Detect nested encoder architecture cls_name = src.__class__.__name__ has_out_proj = any( hasattr(src, attr) for attr in ( "dense", "out_proj", "c_proj", "out_lin", "o_proj", "wo", "o", "proj", ) ) # Check output projection attrs FIRST — DistilBERT has out_lin and is NOT nested if hasattr(src, "out_lin") or hasattr(src, "c_proj") or hasattr(src, "o_proj"): self.is_nested_encoder = False elif ( ( hasattr(src, "self") and hasattr(src.self, "query") and hasattr(src, "output") and hasattr(src.output, "dense") ) or ( cls_name in ("BertSelfAttention", "RobertaSelfAttention") or (cls_name.endswith("SelfAttention") and not has_out_proj) ) and not has_out_proj ): self.is_nested_encoder = True else: self.is_nested_encoder = False # Check required weights missing = [k for k in ["q", "k", "v"] if extracted[k][0] is None] if not self.is_nested_encoder and extracted["o"][0] is None: missing.append("o") if missing: logger.warning( f"CompressedAttentionLayer: missing weights for projections {missing}; proceeding with random init" ) # Frobenius norm validation for proj_key in ["q", "k", "v"]: w = extracted[proj_key][0] if w is not None: norm = torch.linalg.norm(w.float()) if norm < 1e-4: logger.warning( f"CompressedAttentionLayer: extracted weight for {proj_key} has near-zero Frobenius norm ({norm.item():.6f}); continuing" ) if extracted["o"][0] is not None: norm_o = torch.linalg.norm(extracted["o"][0].float()) if norm_o < 1e-4: logger.warning( f"CompressedAttentionLayer: extracted weight for output projection has near-zero Frobenius norm ({norm_o.item():.6f}); continuing" ) # Joint SVD on Q, K, V def _orient(w: torch.Tensor, in_dim: int) -> torch.Tensor: if w.dim() == 2 and w.shape[1] != in_dim and w.shape[0] == in_dim: return w.t().contiguous() return w with torch.no_grad(): w_q = _orient(extracted["q"][0].float(), self.hidden_size) w_k = _orient(extracted["k"][0].float(), self.hidden_size) w_v = _orient(extracted["v"][0].float(), self.hidden_size) w_qkv = torch.cat([w_q, w_k, w_v], dim=0) U, S, Vh = torch.linalg.svd(w_qkv, full_matrices=False) r_qkv = min(self.rank_qkv, U.size(1), Vh.size(0)) sqrt_s = torch.sqrt(torch.clamp(S[:r_qkv], min=1e-12)) qkv_out_dim = self.all_head_size + 2 * self.kv_head_size self.U_qkv.data[: min(qkv_out_dim, U.size(0)), :r_qkv].copy_( U[: min(qkv_out_dim, U.size(0)), :r_qkv] ) self.S_qkv_sqrt.data[:r_qkv].copy_(sqrt_s) self.Vh_qkv.data[:r_qkv, : min(self.hidden_size, Vh.size(1))].copy_( Vh[:r_qkv, : min(self.hidden_size, Vh.size(1))] ) b_q = ( extracted["q"][1] if extracted["q"][1] is not None else torch.zeros(w_q.size(0), device=w_q.device) ) b_k = ( extracted["k"][1] if extracted["k"][1] is not None else torch.zeros(w_k.size(0), device=w_k.device) ) b_v = ( extracted["v"][1] if extracted["v"][1] is not None else torch.zeros(w_v.size(0), device=w_v.device) ) b_q_val = b_q.data if isinstance(b_q, nn.Parameter) else b_q b_k_val = b_k.data if isinstance(b_k, nn.Parameter) else b_k b_v_val = b_v.data if isinstance(b_v, nn.Parameter) else b_v b_qkv = torch.cat([b_q_val, b_k_val, b_v_val], dim=0) self.bias_qkv.data[: min(qkv_out_dim, b_qkv.size(0))].copy_( b_qkv[: min(qkv_out_dim, b_qkv.size(0))] ) # Output projection SVD if extracted["o"][0] is not None: w_o = _orient(extracted["o"][0].float(), self.all_head_size) U_o, S_o, Vh_o = torch.linalg.svd(w_o, full_matrices=False) r_out = min(self.rank_out, U_o.size(1), Vh_o.size(0)) sqrt_so = torch.sqrt(torch.clamp(S_o[:r_out], min=1e-12)) self.U_out.data[: min(self.hidden_size, U_o.size(0)), :r_out].copy_( U_o[: min(self.hidden_size, U_o.size(0)), :r_out] ) self.S_out_sqrt.data[:r_out].copy_(sqrt_so) self.Vh_out.data[:r_out, : min(self.all_head_size, Vh_o.size(1))].copy_( Vh_o[:r_out, : min(self.all_head_size, Vh_o.size(1))] ) if extracted["o"][1] is not None: b_o = extracted["o"][1] b_o_data = b_o.data if isinstance(b_o, nn.Parameter) else b_o self.bias_out.data[: min(self.hidden_size, b_o_data.size(0))].copy_( b_o_data[: min(self.hidden_size, b_o_data.size(0))] ) elif self.is_nested_encoder: self.U_out.data.zero_() min_d = min(self.hidden_size, self.rank_out) self.U_out.data[:min_d, :min_d].fill_diagonal_(1.0) self.S_out_sqrt.data.fill_(1.0) self.Vh_out.data.zero_() self.Vh_out.data[:min_d, :min_d].fill_diagonal_(1.0) self.bias_out.data.zero_() # Ensure base weights are frozen self.Vh_qkv.requires_grad = False self.S_qkv_sqrt.requires_grad = False self.U_qkv.requires_grad = False self.bias_qkv.requires_grad = False self.Vh_out.requires_grad = False self.S_out_sqrt.requires_grad = False self.U_out.requires_grad = False self.bias_out.requires_grad = False if self.lora_rank > 0: self.reset_lora_parameters() if hasattr(src, "is_decoder"): self.is_decoder = bool(src.is_decoder) 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, *args: Any, **kwargs: Any, ) -> tuple[torch.Tensor, torch.Tensor | None]: if hidden_states.dim() == 2: hidden_states = hidden_states.unsqueeze(0) B, N, D = hidden_states.shape device = hidden_states.device dtype = hidden_states.dtype # FIX 1: Pre-transpose weight matrices to avoid runtime transposes # Vh_qkv is (Rank, D), U_qkv is (Out_Dim, Rank) # S_qkv_sqrt is (Rank) # Optimize Matmul path v_proj = torch.matmul(hidden_states, self.Vh_qkv.t()) * self.S_qkv_sqrt qkv = torch.matmul(v_proj, (self.U_qkv * self.S_qkv_sqrt).t()) + self.bias_qkv # LoRA residual adapter if self.lora_rank > 0 and self.lora_A_qkv is not None and self.lora_B_qkv is not None: lora_qkv = ( F.linear(F.linear(hidden_states, self.lora_A_qkv), self.lora_B_qkv) * self.lora_scaling ) qkv = qkv + lora_qkv # Split into Q, K, V q, k, v = torch.split( qkv, [self.all_head_size, self.kv_head_size, self.kv_head_size], dim=-1 ) # FIX 2: Remove .contiguous() memory bombs q = q.view(B, N, self.num_heads, self.head_dim).transpose(1, 2) k = k.view(B, N, self.num_kv_heads, self.head_dim).transpose(1, 2) v = v.view(B, N, self.num_kv_heads, self.head_dim).transpose(1, 2) # Apply RoPE position_embeddings = kwargs.get("position_embeddings") if ( position_embeddings is not None and isinstance(position_embeddings, (tuple, list)) and len(position_embeddings) >= 2 ): cos, sin = position_embeddings[0], position_embeddings[1] q, k = apply_rotary_pos_emb(q, k, cos, sin, kwargs.get("position_ids")) # GQA repeat if self.kv_repeat > 1: k = repeat_kv(k, self.kv_repeat) v = repeat_kv(v, self.kv_repeat) scale = 1.0 / math.sqrt(self.head_dim) # Use torch.baddbmm or scaled_dot_product_attention for huge memory savings # Fallback to standard matmul if not available or unsupported shapes try: # PyTorch 2.0+ Flash Attention / Memory Efficient Attention # This handles Causal masking automatically and saves massive VRAM is_causal = self.is_decoder and (attention_mask is None or attention_mask.dim() != 4) attn_out = F.scaled_dot_product_attention( q, k, v, attn_mask=attention_mask, dropout_p=self.dropout.p if self.training else 0.0, is_causal=is_causal, ) probs = None # SDPA doesn't return probabilities to save memory except Exception: # Legacy Path (if SDPA fails due to old PyTorch or weird masks) scores = torch.matmul(q, k.transpose(-2, -1)) * scale mask = attention_mask if attention_mask is not None else kwargs.get("mask") if mask is not None: if mask.dim() == 4: if mask.dtype == torch.bool: scores = scores.masked_fill(~mask, -10000.0) else: scores = scores + mask.to(scores.dtype) else: valid_m = get_token_valid_mask(mask, B, N, device) if valid_m is not None: scores = scores.masked_fill((~valid_m).unsqueeze(1).unsqueeze(2), -10000.0) # Explicit Causal Masking if self.is_decoder and (mask is None or mask.dim() != 4): causal_tril = ( torch.tril(torch.ones((N, N), device=device, dtype=torch.bool)) .unsqueeze(0) .unsqueeze(0) ) scores = scores.masked_fill(~causal_tril, -10000.0) # FIX 3: Safe Softmax without extra type casts that create new tensors probs = F.softmax(scores, dim=-1, dtype=torch.float32).to(dtype) probs = self.dropout(probs) attn_out = torch.matmul(probs, v) # Transpose back and view (reshape handles non-contiguous memory safely without deep copies) attn_out = attn_out.transpose(1, 2).reshape(B, N, self.all_head_size) if self.is_nested_encoder: out = attn_out else: # SVD Out forward out_v = torch.matmul(attn_out, self.Vh_out.t()) * self.S_out_sqrt out = torch.matmul(out_v, (self.U_out * self.S_out_sqrt).t()) + self.bias_out if self.lora_rank > 0 and self.lora_A_out is not None and self.lora_B_out is not None: lora_out = ( F.linear(F.linear(attn_out, self.lora_A_out), self.lora_B_out) * self.lora_scaling ) out = out + lora_out return out, (probs if output_attentions and probs is not None else None)Every custom attention module must inherit from this.
Initialize internal Module state, shared by both nn.Module and ScriptModule.
Ancestors
- BaseAttention
- torch.nn.modules.module.Module
Subclasses
Class variables
var is_custom_attention : bool-
The type of the None singleton.
var supports_bidirectional : bool-
The type of the None singleton.
var supports_causal : bool-
The type of the None singleton.
var supports_gqa : bool-
The type of the None singleton.
var supports_rope : bool-
The type of the None singleton.
Instance variables
prop out_weight : torch.Tensor-
Expand source code
@property def out_weight(self) -> torch.Tensor: """Reconstruct the effective base Out weight from SVD factors.""" return (self.U_out * self.S_out_sqrt.view(1, -1)) @ ( self.Vh_out * self.S_out_sqrt.view(-1, 1) )Reconstruct the effective base Out weight from SVD factors.
prop qkv_weight : torch.Tensor-
Expand source code
@property def qkv_weight(self) -> torch.Tensor: """Reconstruct the effective base QKV weight from SVD factors.""" return (self.U_qkv * self.S_qkv_sqrt.view(1, -1)) @ ( self.Vh_qkv * self.S_qkv_sqrt.view(-1, 1) )Reconstruct the effective base QKV weight from SVD factors.
Methods
def copy_weights(self, src: Any) ‑> None-
Expand source code
def copy_weights(self, src: Any) -> None: from neuralops.core.adapters import UniversalAdapter extracted: dict[str, tuple[torch.Tensor | None, torch.Tensor | None]] = { "q": (None, None), "k": (None, None), "v": (None, None), "o": (None, None), } # 1. HuggingFace / standard UniversalAdapter extraction for proj in ["q", "k", "v", "o"]: try: w, b = UniversalAdapter.get_weight_bias(src, proj) if w is not None: extracted[proj] = (w, b) except Exception as e: logger.debug( f"UniversalAdapter failed for {proj}: {e}" ) # continue to fallback paths # 2. GPT-2 Fused c_attn Conv1D handling if hasattr(src, "c_attn") and ( extracted["q"][0] is None or extracted["k"][0] is None or extracted["v"][0] is None ): weight = src.c_attn.weight.data bias = getattr(src.c_attn, "bias", None) bias_data = bias.data if bias is not None else None if weight.dim() == 2 and weight.shape[1] == 3 * self.hidden_size: weight = weight.t().contiguous() split_size = weight.shape[0] // 3 q_w = weight[:split_size, :] k_w = weight[split_size : 2 * split_size, :] v_w = weight[2 * split_size :, :] q_b = bias_data[:split_size] if bias_data is not None else None k_b = bias_data[split_size : 2 * split_size] if bias_data is not None else None v_b = bias_data[2 * split_size :] if bias_data is not None else None extracted["q"] = (q_w, q_b) extracted["k"] = (k_w, k_b) extracted["v"] = (v_w, v_b) if hasattr(src, "c_proj") and extracted["o"][0] is None: w_o = src.c_proj.weight.data if ( w_o.dim() == 2 and w_o.shape[0] != self.hidden_size and w_o.shape[1] == self.hidden_size ): w_o = w_o.t().contiguous() b_o = getattr(src.c_proj, "bias", None) extracted["o"] = (w_o, b_o.data if b_o is not None else None) # 3. RoBERTa / BERT Nested Encoder handling if hasattr(src, "self") and ( extracted["q"][0] is None or extracted["k"][0] is None or extracted["v"][0] is None ): s = src.self if hasattr(s, "query"): extracted["q"] = (s.query.weight.data, getattr(s.query, "bias", None)) if hasattr(s, "key"): extracted["k"] = (s.key.weight.data, getattr(s.key, "bias", None)) if hasattr(s, "value"): extracted["v"] = (s.value.weight.data, getattr(s.value, "bias", None)) if hasattr(src, "output") and hasattr(src.output, "dense") and extracted["o"][0] is None: dense = src.output.dense extracted["o"] = (dense.weight.data, getattr(dense, "bias", None)) # 4. Modern Decoders (Qwen3.5, LLaMA, Mistral) & DistilBERT named_map = { "q": ["q_proj", "q_lin", "query"], "k": ["k_proj", "k_lin", "key"], "v": ["v_proj", "v_lin", "value"], "o": ["o_proj", "out_proj", "out_lin", "dense"], } for proj, names in named_map.items(): if extracted[proj][0] is None: for name in names: if hasattr(src, name): layer = getattr(src, name) if hasattr(layer, "weight"): extracted[proj] = ( layer.weight.data, getattr(layer, "bias", None), ) break # Detect nested encoder architecture cls_name = src.__class__.__name__ has_out_proj = any( hasattr(src, attr) for attr in ( "dense", "out_proj", "c_proj", "out_lin", "o_proj", "wo", "o", "proj", ) ) # Check output projection attrs FIRST — DistilBERT has out_lin and is NOT nested if hasattr(src, "out_lin") or hasattr(src, "c_proj") or hasattr(src, "o_proj"): self.is_nested_encoder = False elif ( ( hasattr(src, "self") and hasattr(src.self, "query") and hasattr(src, "output") and hasattr(src.output, "dense") ) or ( cls_name in ("BertSelfAttention", "RobertaSelfAttention") or (cls_name.endswith("SelfAttention") and not has_out_proj) ) and not has_out_proj ): self.is_nested_encoder = True else: self.is_nested_encoder = False # Check required weights missing = [k for k in ["q", "k", "v"] if extracted[k][0] is None] if not self.is_nested_encoder and extracted["o"][0] is None: missing.append("o") if missing: logger.warning( f"CompressedAttentionLayer: missing weights for projections {missing}; proceeding with random init" ) # Frobenius norm validation for proj_key in ["q", "k", "v"]: w = extracted[proj_key][0] if w is not None: norm = torch.linalg.norm(w.float()) if norm < 1e-4: logger.warning( f"CompressedAttentionLayer: extracted weight for {proj_key} has near-zero Frobenius norm ({norm.item():.6f}); continuing" ) if extracted["o"][0] is not None: norm_o = torch.linalg.norm(extracted["o"][0].float()) if norm_o < 1e-4: logger.warning( f"CompressedAttentionLayer: extracted weight for output projection has near-zero Frobenius norm ({norm_o.item():.6f}); continuing" ) # Joint SVD on Q, K, V def _orient(w: torch.Tensor, in_dim: int) -> torch.Tensor: if w.dim() == 2 and w.shape[1] != in_dim and w.shape[0] == in_dim: return w.t().contiguous() return w with torch.no_grad(): w_q = _orient(extracted["q"][0].float(), self.hidden_size) w_k = _orient(extracted["k"][0].float(), self.hidden_size) w_v = _orient(extracted["v"][0].float(), self.hidden_size) w_qkv = torch.cat([w_q, w_k, w_v], dim=0) U, S, Vh = torch.linalg.svd(w_qkv, full_matrices=False) r_qkv = min(self.rank_qkv, U.size(1), Vh.size(0)) sqrt_s = torch.sqrt(torch.clamp(S[:r_qkv], min=1e-12)) qkv_out_dim = self.all_head_size + 2 * self.kv_head_size self.U_qkv.data[: min(qkv_out_dim, U.size(0)), :r_qkv].copy_( U[: min(qkv_out_dim, U.size(0)), :r_qkv] ) self.S_qkv_sqrt.data[:r_qkv].copy_(sqrt_s) self.Vh_qkv.data[:r_qkv, : min(self.hidden_size, Vh.size(1))].copy_( Vh[:r_qkv, : min(self.hidden_size, Vh.size(1))] ) b_q = ( extracted["q"][1] if extracted["q"][1] is not None else torch.zeros(w_q.size(0), device=w_q.device) ) b_k = ( extracted["k"][1] if extracted["k"][1] is not None else torch.zeros(w_k.size(0), device=w_k.device) ) b_v = ( extracted["v"][1] if extracted["v"][1] is not None else torch.zeros(w_v.size(0), device=w_v.device) ) b_q_val = b_q.data if isinstance(b_q, nn.Parameter) else b_q b_k_val = b_k.data if isinstance(b_k, nn.Parameter) else b_k b_v_val = b_v.data if isinstance(b_v, nn.Parameter) else b_v b_qkv = torch.cat([b_q_val, b_k_val, b_v_val], dim=0) self.bias_qkv.data[: min(qkv_out_dim, b_qkv.size(0))].copy_( b_qkv[: min(qkv_out_dim, b_qkv.size(0))] ) # Output projection SVD if extracted["o"][0] is not None: w_o = _orient(extracted["o"][0].float(), self.all_head_size) U_o, S_o, Vh_o = torch.linalg.svd(w_o, full_matrices=False) r_out = min(self.rank_out, U_o.size(1), Vh_o.size(0)) sqrt_so = torch.sqrt(torch.clamp(S_o[:r_out], min=1e-12)) self.U_out.data[: min(self.hidden_size, U_o.size(0)), :r_out].copy_( U_o[: min(self.hidden_size, U_o.size(0)), :r_out] ) self.S_out_sqrt.data[:r_out].copy_(sqrt_so) self.Vh_out.data[:r_out, : min(self.all_head_size, Vh_o.size(1))].copy_( Vh_o[:r_out, : min(self.all_head_size, Vh_o.size(1))] ) if extracted["o"][1] is not None: b_o = extracted["o"][1] b_o_data = b_o.data if isinstance(b_o, nn.Parameter) else b_o self.bias_out.data[: min(self.hidden_size, b_o_data.size(0))].copy_( b_o_data[: min(self.hidden_size, b_o_data.size(0))] ) elif self.is_nested_encoder: self.U_out.data.zero_() min_d = min(self.hidden_size, self.rank_out) self.U_out.data[:min_d, :min_d].fill_diagonal_(1.0) self.S_out_sqrt.data.fill_(1.0) self.Vh_out.data.zero_() self.Vh_out.data[:min_d, :min_d].fill_diagonal_(1.0) self.bias_out.data.zero_() # Ensure base weights are frozen self.Vh_qkv.requires_grad = False self.S_qkv_sqrt.requires_grad = False self.U_qkv.requires_grad = False self.bias_qkv.requires_grad = False self.Vh_out.requires_grad = False self.S_out_sqrt.requires_grad = False self.U_out.requires_grad = False self.bias_out.requires_grad = False if self.lora_rank > 0: self.reset_lora_parameters() if hasattr(src, "is_decoder"): self.is_decoder = bool(src.is_decoder)Universal fallback: copy pretrained weights from old attention layer.
def forward(self,
hidden_states: torch.Tensor,
attention_mask: torch.Tensor | None = None,
head_mask: torch.Tensor | None = None,
encoder_hidden_states: torch.Tensor | None = None,
encoder_attention_mask: torch.Tensor | None = None,
past_key_value: Any | None = None,
output_attentions: bool | None = False,
output_hidden_states: bool | None = False,
return_dict: bool | None = True,
*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, *args: Any, **kwargs: Any, ) -> tuple[torch.Tensor, torch.Tensor | None]: if hidden_states.dim() == 2: hidden_states = hidden_states.unsqueeze(0) B, N, D = hidden_states.shape device = hidden_states.device dtype = hidden_states.dtype # FIX 1: Pre-transpose weight matrices to avoid runtime transposes # Vh_qkv is (Rank, D), U_qkv is (Out_Dim, Rank) # S_qkv_sqrt is (Rank) # Optimize Matmul path v_proj = torch.matmul(hidden_states, self.Vh_qkv.t()) * self.S_qkv_sqrt qkv = torch.matmul(v_proj, (self.U_qkv * self.S_qkv_sqrt).t()) + self.bias_qkv # LoRA residual adapter if self.lora_rank > 0 and self.lora_A_qkv is not None and self.lora_B_qkv is not None: lora_qkv = ( F.linear(F.linear(hidden_states, self.lora_A_qkv), self.lora_B_qkv) * self.lora_scaling ) qkv = qkv + lora_qkv # Split into Q, K, V q, k, v = torch.split( qkv, [self.all_head_size, self.kv_head_size, self.kv_head_size], dim=-1 ) # FIX 2: Remove .contiguous() memory bombs q = q.view(B, N, self.num_heads, self.head_dim).transpose(1, 2) k = k.view(B, N, self.num_kv_heads, self.head_dim).transpose(1, 2) v = v.view(B, N, self.num_kv_heads, self.head_dim).transpose(1, 2) # Apply RoPE position_embeddings = kwargs.get("position_embeddings") if ( position_embeddings is not None and isinstance(position_embeddings, (tuple, list)) and len(position_embeddings) >= 2 ): cos, sin = position_embeddings[0], position_embeddings[1] q, k = apply_rotary_pos_emb(q, k, cos, sin, kwargs.get("position_ids")) # GQA repeat if self.kv_repeat > 1: k = repeat_kv(k, self.kv_repeat) v = repeat_kv(v, self.kv_repeat) scale = 1.0 / math.sqrt(self.head_dim) # Use torch.baddbmm or scaled_dot_product_attention for huge memory savings # Fallback to standard matmul if not available or unsupported shapes try: # PyTorch 2.0+ Flash Attention / Memory Efficient Attention # This handles Causal masking automatically and saves massive VRAM is_causal = self.is_decoder and (attention_mask is None or attention_mask.dim() != 4) attn_out = F.scaled_dot_product_attention( q, k, v, attn_mask=attention_mask, dropout_p=self.dropout.p if self.training else 0.0, is_causal=is_causal, ) probs = None # SDPA doesn't return probabilities to save memory except Exception: # Legacy Path (if SDPA fails due to old PyTorch or weird masks) scores = torch.matmul(q, k.transpose(-2, -1)) * scale mask = attention_mask if attention_mask is not None else kwargs.get("mask") if mask is not None: if mask.dim() == 4: if mask.dtype == torch.bool: scores = scores.masked_fill(~mask, -10000.0) else: scores = scores + mask.to(scores.dtype) else: valid_m = get_token_valid_mask(mask, B, N, device) if valid_m is not None: scores = scores.masked_fill((~valid_m).unsqueeze(1).unsqueeze(2), -10000.0) # Explicit Causal Masking if self.is_decoder and (mask is None or mask.dim() != 4): causal_tril = ( torch.tril(torch.ones((N, N), device=device, dtype=torch.bool)) .unsqueeze(0) .unsqueeze(0) ) scores = scores.masked_fill(~causal_tril, -10000.0) # FIX 3: Safe Softmax without extra type casts that create new tensors probs = F.softmax(scores, dim=-1, dtype=torch.float32).to(dtype) probs = self.dropout(probs) attn_out = torch.matmul(probs, v) # Transpose back and view (reshape handles non-contiguous memory safely without deep copies) attn_out = attn_out.transpose(1, 2).reshape(B, N, self.all_head_size) if self.is_nested_encoder: out = attn_out else: # SVD Out forward out_v = torch.matmul(attn_out, self.Vh_out.t()) * self.S_out_sqrt out = torch.matmul(out_v, (self.U_out * self.S_out_sqrt).t()) + self.bias_out if self.lora_rank > 0 and self.lora_A_out is not None and self.lora_B_out is not None: lora_out = ( F.linear(F.linear(attn_out, self.lora_A_out), self.lora_B_out) * self.lora_scaling ) out = out + lora_out return out, (probs if output_attentions and probs is not None else None)Must return (output, attention_weights_or_None).
def reset_lora_parameters(self) ‑> None-
Expand source code
def reset_lora_parameters(self) -> None: if self.lora_rank > 0: nn.init.kaiming_uniform_(self.lora_A_qkv, a=math.sqrt(5)) nn.init.zeros_(self.lora_B_qkv) nn.init.kaiming_uniform_(self.lora_A_out, a=math.sqrt(5)) nn.init.zeros_(self.lora_B_out) self.lora_A_qkv.requires_grad = True self.lora_B_qkv.requires_grad = True self.lora_A_out.requires_grad = True self.lora_B_out.requires_grad = True
class CompressedAttentionLayer (config: Any | None = None,
rank: int | None = None,
rank_qkv: int = 256,
rank_out: int = 256,
lora_rank: int = 16,
original_layer: Any = None,
pretrained_attn: nn.Module | None = None,
**kwargs: Any)-
Expand source code
@ATTENTION_REGISTRY.register( "compressed_attention_layer", supports_causal=True, supports_bidirectional=True, supports_gqa=True, ) @ATTENTION_REGISTRY.register( "compressed_attention", supports_causal=True, supports_bidirectional=True, supports_gqa=True, ) class CompressedAttentionLayer(BaseAttention): supports_causal: bool = True supports_bidirectional: bool = True supports_gqa: bool = True supports_rope: bool = True is_custom_attention: bool = True def __init__( self, config: Any | None = None, rank: int | None = None, rank_qkv: int = 256, rank_out: int = 256, lora_rank: int = 16, original_layer: Any = None, pretrained_attn: nn.Module | None = None, **kwargs: Any, ) -> None: cfg = config if config is not None else original_layer super().__init__(cfg, rank) self.adapter = ModelAdapter(cfg) self.hidden_size: int = self.adapter.hidden_size or kwargs.get( "hidden_size", kwargs.get("d_model", 768) ) self.num_heads: int = self.adapter.num_heads or kwargs.get( "num_heads", kwargs.get("num_attention_heads", 12) ) self.num_kv_heads: int = self.adapter.num_key_value_heads or kwargs.get( "num_kv_heads", kwargs.get("num_key_value_heads", self.num_heads) ) self.head_dim: int = self.adapter.head_dim or (self.hidden_size // max(1, self.num_heads)) self.all_head_size: int = self.num_heads * self.head_dim self.kv_head_size: int = self.num_kv_heads * self.head_dim self.kv_repeat: int = max(1, self.num_heads // max(1, self.num_kv_heads)) _mt = getattr(cfg, "model_type", "").lower() if cfg else "" _decoder_prefixes = ( "gpt", "qwen", "llama", "mistral", "gemma", "phi", "opt", "bloom", "falcon", "olmo", "mpt", "stablelm", ) self.is_decoder: bool = bool( getattr(cfg, "is_decoder", False) or any(_mt.startswith(p) for p in _decoder_prefixes) ) self.is_nested_encoder: bool = False rank_val = rank or kwargs.get("rank_qkv", rank_qkv) self.rank_qkv = min(rank_val, self.all_head_size + 2 * self.kv_head_size, self.hidden_size) self.rank_out = min( rank or kwargs.get("rank_out", rank_out), self.all_head_size, self.hidden_size, ) self.lora_rank = getattr(cfg, "lora_rank", lora_rank) self.lora_scaling = (32.0 / self.lora_rank) if self.lora_rank > 0 else 1.0 qkv_out_dim = self.all_head_size + 2 * self.kv_head_size # Frozen base SVD parameters self.Vh_qkv = nn.Parameter( torch.randn(self.rank_qkv, self.hidden_size) * 0.02, requires_grad=False ) self.S_qkv_sqrt = nn.Parameter(torch.ones(self.rank_qkv), requires_grad=False) self.U_qkv = nn.Parameter( torch.randn(qkv_out_dim, self.rank_qkv) * 0.02, requires_grad=False ) self.bias_qkv = nn.Parameter(torch.zeros(qkv_out_dim), requires_grad=False) self.Vh_out = nn.Parameter( torch.randn(self.rank_out, self.all_head_size) * 0.02, requires_grad=False ) self.S_out_sqrt = nn.Parameter(torch.ones(self.rank_out), requires_grad=False) self.U_out = nn.Parameter( torch.randn(self.hidden_size, self.rank_out) * 0.02, requires_grad=False ) self.bias_out = nn.Parameter(torch.zeros(self.hidden_size), requires_grad=False) # Trainable LoRA residual adapters if self.lora_rank > 0: self.lora_A_qkv = nn.Parameter(torch.empty(self.lora_rank, self.hidden_size)) self.lora_B_qkv = nn.Parameter(torch.empty(qkv_out_dim, self.lora_rank)) self.lora_A_out = nn.Parameter(torch.empty(self.lora_rank, self.all_head_size)) self.lora_B_out = nn.Parameter(torch.empty(self.hidden_size, self.lora_rank)) self.reset_lora_parameters() else: self.register_parameter("lora_A_qkv", None) self.register_parameter("lora_B_qkv", None) self.register_parameter("lora_A_out", None) self.register_parameter("lora_B_out", None) dropout_p = ( getattr(cfg, "dropout", getattr(cfg, "attention_probs_dropout_prob", 0.1)) if cfg else 0.1 ) self.dropout = nn.Dropout(dropout_p) self.rotary_emb = getattr(pretrained_attn, "rotary_emb", None) src = pretrained_attn or ( original_layer if hasattr(original_layer, "q_lin") or hasattr(original_layer, "query") or hasattr(original_layer, "c_attn") else None ) if src is not None: self.copy_weights(src) @property def qkv_weight(self) -> torch.Tensor: """Reconstruct the effective base QKV weight from SVD factors.""" return (self.U_qkv * self.S_qkv_sqrt.view(1, -1)) @ ( self.Vh_qkv * self.S_qkv_sqrt.view(-1, 1) ) @property def out_weight(self) -> torch.Tensor: """Reconstruct the effective base Out weight from SVD factors.""" return (self.U_out * self.S_out_sqrt.view(1, -1)) @ ( self.Vh_out * self.S_out_sqrt.view(-1, 1) ) def reset_lora_parameters(self) -> None: if self.lora_rank > 0: nn.init.kaiming_uniform_(self.lora_A_qkv, a=math.sqrt(5)) nn.init.zeros_(self.lora_B_qkv) nn.init.kaiming_uniform_(self.lora_A_out, a=math.sqrt(5)) nn.init.zeros_(self.lora_B_out) self.lora_A_qkv.requires_grad = True self.lora_B_qkv.requires_grad = True self.lora_A_out.requires_grad = True self.lora_B_out.requires_grad = True def copy_weights(self, src: Any) -> None: from neuralops.core.adapters import UniversalAdapter extracted: dict[str, tuple[torch.Tensor | None, torch.Tensor | None]] = { "q": (None, None), "k": (None, None), "v": (None, None), "o": (None, None), } # 1. HuggingFace / standard UniversalAdapter extraction for proj in ["q", "k", "v", "o"]: try: w, b = UniversalAdapter.get_weight_bias(src, proj) if w is not None: extracted[proj] = (w, b) except Exception as e: logger.debug( f"UniversalAdapter failed for {proj}: {e}" ) # continue to fallback paths # 2. GPT-2 Fused c_attn Conv1D handling if hasattr(src, "c_attn") and ( extracted["q"][0] is None or extracted["k"][0] is None or extracted["v"][0] is None ): weight = src.c_attn.weight.data bias = getattr(src.c_attn, "bias", None) bias_data = bias.data if bias is not None else None if weight.dim() == 2 and weight.shape[1] == 3 * self.hidden_size: weight = weight.t().contiguous() split_size = weight.shape[0] // 3 q_w = weight[:split_size, :] k_w = weight[split_size : 2 * split_size, :] v_w = weight[2 * split_size :, :] q_b = bias_data[:split_size] if bias_data is not None else None k_b = bias_data[split_size : 2 * split_size] if bias_data is not None else None v_b = bias_data[2 * split_size :] if bias_data is not None else None extracted["q"] = (q_w, q_b) extracted["k"] = (k_w, k_b) extracted["v"] = (v_w, v_b) if hasattr(src, "c_proj") and extracted["o"][0] is None: w_o = src.c_proj.weight.data if ( w_o.dim() == 2 and w_o.shape[0] != self.hidden_size and w_o.shape[1] == self.hidden_size ): w_o = w_o.t().contiguous() b_o = getattr(src.c_proj, "bias", None) extracted["o"] = (w_o, b_o.data if b_o is not None else None) # 3. RoBERTa / BERT Nested Encoder handling if hasattr(src, "self") and ( extracted["q"][0] is None or extracted["k"][0] is None or extracted["v"][0] is None ): s = src.self if hasattr(s, "query"): extracted["q"] = (s.query.weight.data, getattr(s.query, "bias", None)) if hasattr(s, "key"): extracted["k"] = (s.key.weight.data, getattr(s.key, "bias", None)) if hasattr(s, "value"): extracted["v"] = (s.value.weight.data, getattr(s.value, "bias", None)) if hasattr(src, "output") and hasattr(src.output, "dense") and extracted["o"][0] is None: dense = src.output.dense extracted["o"] = (dense.weight.data, getattr(dense, "bias", None)) # 4. Modern Decoders (Qwen3.5, LLaMA, Mistral) & DistilBERT named_map = { "q": ["q_proj", "q_lin", "query"], "k": ["k_proj", "k_lin", "key"], "v": ["v_proj", "v_lin", "value"], "o": ["o_proj", "out_proj", "out_lin", "dense"], } for proj, names in named_map.items(): if extracted[proj][0] is None: for name in names: if hasattr(src, name): layer = getattr(src, name) if hasattr(layer, "weight"): extracted[proj] = ( layer.weight.data, getattr(layer, "bias", None), ) break # Detect nested encoder architecture cls_name = src.__class__.__name__ has_out_proj = any( hasattr(src, attr) for attr in ( "dense", "out_proj", "c_proj", "out_lin", "o_proj", "wo", "o", "proj", ) ) # Check output projection attrs FIRST — DistilBERT has out_lin and is NOT nested if hasattr(src, "out_lin") or hasattr(src, "c_proj") or hasattr(src, "o_proj"): self.is_nested_encoder = False elif ( ( hasattr(src, "self") and hasattr(src.self, "query") and hasattr(src, "output") and hasattr(src.output, "dense") ) or ( cls_name in ("BertSelfAttention", "RobertaSelfAttention") or (cls_name.endswith("SelfAttention") and not has_out_proj) ) and not has_out_proj ): self.is_nested_encoder = True else: self.is_nested_encoder = False # Check required weights missing = [k for k in ["q", "k", "v"] if extracted[k][0] is None] if not self.is_nested_encoder and extracted["o"][0] is None: missing.append("o") if missing: logger.warning( f"CompressedAttentionLayer: missing weights for projections {missing}; proceeding with random init" ) # Frobenius norm validation for proj_key in ["q", "k", "v"]: w = extracted[proj_key][0] if w is not None: norm = torch.linalg.norm(w.float()) if norm < 1e-4: logger.warning( f"CompressedAttentionLayer: extracted weight for {proj_key} has near-zero Frobenius norm ({norm.item():.6f}); continuing" ) if extracted["o"][0] is not None: norm_o = torch.linalg.norm(extracted["o"][0].float()) if norm_o < 1e-4: logger.warning( f"CompressedAttentionLayer: extracted weight for output projection has near-zero Frobenius norm ({norm_o.item():.6f}); continuing" ) # Joint SVD on Q, K, V def _orient(w: torch.Tensor, in_dim: int) -> torch.Tensor: if w.dim() == 2 and w.shape[1] != in_dim and w.shape[0] == in_dim: return w.t().contiguous() return w with torch.no_grad(): w_q = _orient(extracted["q"][0].float(), self.hidden_size) w_k = _orient(extracted["k"][0].float(), self.hidden_size) w_v = _orient(extracted["v"][0].float(), self.hidden_size) w_qkv = torch.cat([w_q, w_k, w_v], dim=0) U, S, Vh = torch.linalg.svd(w_qkv, full_matrices=False) r_qkv = min(self.rank_qkv, U.size(1), Vh.size(0)) sqrt_s = torch.sqrt(torch.clamp(S[:r_qkv], min=1e-12)) qkv_out_dim = self.all_head_size + 2 * self.kv_head_size self.U_qkv.data[: min(qkv_out_dim, U.size(0)), :r_qkv].copy_( U[: min(qkv_out_dim, U.size(0)), :r_qkv] ) self.S_qkv_sqrt.data[:r_qkv].copy_(sqrt_s) self.Vh_qkv.data[:r_qkv, : min(self.hidden_size, Vh.size(1))].copy_( Vh[:r_qkv, : min(self.hidden_size, Vh.size(1))] ) b_q = ( extracted["q"][1] if extracted["q"][1] is not None else torch.zeros(w_q.size(0), device=w_q.device) ) b_k = ( extracted["k"][1] if extracted["k"][1] is not None else torch.zeros(w_k.size(0), device=w_k.device) ) b_v = ( extracted["v"][1] if extracted["v"][1] is not None else torch.zeros(w_v.size(0), device=w_v.device) ) b_q_val = b_q.data if isinstance(b_q, nn.Parameter) else b_q b_k_val = b_k.data if isinstance(b_k, nn.Parameter) else b_k b_v_val = b_v.data if isinstance(b_v, nn.Parameter) else b_v b_qkv = torch.cat([b_q_val, b_k_val, b_v_val], dim=0) self.bias_qkv.data[: min(qkv_out_dim, b_qkv.size(0))].copy_( b_qkv[: min(qkv_out_dim, b_qkv.size(0))] ) # Output projection SVD if extracted["o"][0] is not None: w_o = _orient(extracted["o"][0].float(), self.all_head_size) U_o, S_o, Vh_o = torch.linalg.svd(w_o, full_matrices=False) r_out = min(self.rank_out, U_o.size(1), Vh_o.size(0)) sqrt_so = torch.sqrt(torch.clamp(S_o[:r_out], min=1e-12)) self.U_out.data[: min(self.hidden_size, U_o.size(0)), :r_out].copy_( U_o[: min(self.hidden_size, U_o.size(0)), :r_out] ) self.S_out_sqrt.data[:r_out].copy_(sqrt_so) self.Vh_out.data[:r_out, : min(self.all_head_size, Vh_o.size(1))].copy_( Vh_o[:r_out, : min(self.all_head_size, Vh_o.size(1))] ) if extracted["o"][1] is not None: b_o = extracted["o"][1] b_o_data = b_o.data if isinstance(b_o, nn.Parameter) else b_o self.bias_out.data[: min(self.hidden_size, b_o_data.size(0))].copy_( b_o_data[: min(self.hidden_size, b_o_data.size(0))] ) elif self.is_nested_encoder: self.U_out.data.zero_() min_d = min(self.hidden_size, self.rank_out) self.U_out.data[:min_d, :min_d].fill_diagonal_(1.0) self.S_out_sqrt.data.fill_(1.0) self.Vh_out.data.zero_() self.Vh_out.data[:min_d, :min_d].fill_diagonal_(1.0) self.bias_out.data.zero_() # Ensure base weights are frozen self.Vh_qkv.requires_grad = False self.S_qkv_sqrt.requires_grad = False self.U_qkv.requires_grad = False self.bias_qkv.requires_grad = False self.Vh_out.requires_grad = False self.S_out_sqrt.requires_grad = False self.U_out.requires_grad = False self.bias_out.requires_grad = False if self.lora_rank > 0: self.reset_lora_parameters() if hasattr(src, "is_decoder"): self.is_decoder = bool(src.is_decoder) 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, *args: Any, **kwargs: Any, ) -> tuple[torch.Tensor, torch.Tensor | None]: if hidden_states.dim() == 2: hidden_states = hidden_states.unsqueeze(0) B, N, D = hidden_states.shape device = hidden_states.device dtype = hidden_states.dtype # FIX 1: Pre-transpose weight matrices to avoid runtime transposes # Vh_qkv is (Rank, D), U_qkv is (Out_Dim, Rank) # S_qkv_sqrt is (Rank) # Optimize Matmul path v_proj = torch.matmul(hidden_states, self.Vh_qkv.t()) * self.S_qkv_sqrt qkv = torch.matmul(v_proj, (self.U_qkv * self.S_qkv_sqrt).t()) + self.bias_qkv # LoRA residual adapter if self.lora_rank > 0 and self.lora_A_qkv is not None and self.lora_B_qkv is not None: lora_qkv = ( F.linear(F.linear(hidden_states, self.lora_A_qkv), self.lora_B_qkv) * self.lora_scaling ) qkv = qkv + lora_qkv # Split into Q, K, V q, k, v = torch.split( qkv, [self.all_head_size, self.kv_head_size, self.kv_head_size], dim=-1 ) # FIX 2: Remove .contiguous() memory bombs q = q.view(B, N, self.num_heads, self.head_dim).transpose(1, 2) k = k.view(B, N, self.num_kv_heads, self.head_dim).transpose(1, 2) v = v.view(B, N, self.num_kv_heads, self.head_dim).transpose(1, 2) # Apply RoPE position_embeddings = kwargs.get("position_embeddings") if ( position_embeddings is not None and isinstance(position_embeddings, (tuple, list)) and len(position_embeddings) >= 2 ): cos, sin = position_embeddings[0], position_embeddings[1] q, k = apply_rotary_pos_emb(q, k, cos, sin, kwargs.get("position_ids")) # GQA repeat if self.kv_repeat > 1: k = repeat_kv(k, self.kv_repeat) v = repeat_kv(v, self.kv_repeat) scale = 1.0 / math.sqrt(self.head_dim) # Use torch.baddbmm or scaled_dot_product_attention for huge memory savings # Fallback to standard matmul if not available or unsupported shapes try: # PyTorch 2.0+ Flash Attention / Memory Efficient Attention # This handles Causal masking automatically and saves massive VRAM is_causal = self.is_decoder and (attention_mask is None or attention_mask.dim() != 4) attn_out = F.scaled_dot_product_attention( q, k, v, attn_mask=attention_mask, dropout_p=self.dropout.p if self.training else 0.0, is_causal=is_causal, ) probs = None # SDPA doesn't return probabilities to save memory except Exception: # Legacy Path (if SDPA fails due to old PyTorch or weird masks) scores = torch.matmul(q, k.transpose(-2, -1)) * scale mask = attention_mask if attention_mask is not None else kwargs.get("mask") if mask is not None: if mask.dim() == 4: if mask.dtype == torch.bool: scores = scores.masked_fill(~mask, -10000.0) else: scores = scores + mask.to(scores.dtype) else: valid_m = get_token_valid_mask(mask, B, N, device) if valid_m is not None: scores = scores.masked_fill((~valid_m).unsqueeze(1).unsqueeze(2), -10000.0) # Explicit Causal Masking if self.is_decoder and (mask is None or mask.dim() != 4): causal_tril = ( torch.tril(torch.ones((N, N), device=device, dtype=torch.bool)) .unsqueeze(0) .unsqueeze(0) ) scores = scores.masked_fill(~causal_tril, -10000.0) # FIX 3: Safe Softmax without extra type casts that create new tensors probs = F.softmax(scores, dim=-1, dtype=torch.float32).to(dtype) probs = self.dropout(probs) attn_out = torch.matmul(probs, v) # Transpose back and view (reshape handles non-contiguous memory safely without deep copies) attn_out = attn_out.transpose(1, 2).reshape(B, N, self.all_head_size) if self.is_nested_encoder: out = attn_out else: # SVD Out forward out_v = torch.matmul(attn_out, self.Vh_out.t()) * self.S_out_sqrt out = torch.matmul(out_v, (self.U_out * self.S_out_sqrt).t()) + self.bias_out if self.lora_rank > 0 and self.lora_A_out is not None and self.lora_B_out is not None: lora_out = ( F.linear(F.linear(attn_out, self.lora_A_out), self.lora_B_out) * self.lora_scaling ) out = out + lora_out return out, (probs if output_attentions and probs is not None else None)Every custom attention module must inherit from this.
Initialize internal Module state, shared by both nn.Module and ScriptModule.
Ancestors
- BaseAttention
- torch.nn.modules.module.Module
Subclasses
Class variables
var is_custom_attention : bool-
The type of the None singleton.
var supports_bidirectional : bool-
The type of the None singleton.
var supports_causal : bool-
The type of the None singleton.
var supports_gqa : bool-
The type of the None singleton.
var supports_rope : bool-
The type of the None singleton.
Instance variables
prop out_weight : torch.Tensor-
Expand source code
@property def out_weight(self) -> torch.Tensor: """Reconstruct the effective base Out weight from SVD factors.""" return (self.U_out * self.S_out_sqrt.view(1, -1)) @ ( self.Vh_out * self.S_out_sqrt.view(-1, 1) )Reconstruct the effective base Out weight from SVD factors.
prop qkv_weight : torch.Tensor-
Expand source code
@property def qkv_weight(self) -> torch.Tensor: """Reconstruct the effective base QKV weight from SVD factors.""" return (self.U_qkv * self.S_qkv_sqrt.view(1, -1)) @ ( self.Vh_qkv * self.S_qkv_sqrt.view(-1, 1) )Reconstruct the effective base QKV weight from SVD factors.
Methods
def reset_lora_parameters(self) ‑> None-
Expand source code
def reset_lora_parameters(self) -> None: if self.lora_rank > 0: nn.init.kaiming_uniform_(self.lora_A_qkv, a=math.sqrt(5)) nn.init.zeros_(self.lora_B_qkv) nn.init.kaiming_uniform_(self.lora_A_out, a=math.sqrt(5)) nn.init.zeros_(self.lora_B_out) self.lora_A_qkv.requires_grad = True self.lora_B_qkv.requires_grad = True self.lora_A_out.requires_grad = True self.lora_B_out.requires_grad = True
Inherited members
class CompressedAttentionLayer2 (config: Any | None = None,
rank: int | None = None,
rank_qkv: int = 256,
rank_out: int = 256,
lora_rank: int = 16,
original_layer: Any = None,
pretrained_attn: nn.Module | None = None,
**kwargs: Any)-
Expand source code
@ATTENTION_REGISTRY.register("compressed_attention_layer_energy") class CompressedAttentionLayer2(CompressedAttentionLayer): passEvery custom attention module must inherit from this.
Initialize internal Module state, shared by both nn.Module and ScriptModule.
Ancestors
- CompressedAttentionLayer
- BaseAttention
- torch.nn.modules.module.Module
Inherited members