Module neuralops.attentions.svd_compressed

SVD-Compressed Attention with Importance-Aware Lagrangian Rank Allocation.

Truncated Singular Value Decomposition (SVD) attention compression module with Lagrangian spectral-entropy optimal rank budgeting and optional low-rank plus sparse residual decomposition.

Mathematical Mechanism: For projection weight matrices ${W_i}{i=1}^M$ across all transformer attention sub-layers: 1. Spectral Information Loss & Parameter Cost: $$E_i(r) = \frac{\sum}^r \sigma_{i, j}^2}{\sum_{j=1}^{\min(d_{\text{in}}, d_{\text{out}})} \sigma_{i, j}^2}, \quad \operatorname{Costi(r) = 1 - E_i(r)$$ $$\operatorname{Params}_i(r) = r \cdot (d)$$ 2. }} + d_{i, \text{out}Lagrangian Global Rank Optimization: $$\min_{{r_i}} \sum_{i=1}^M \operatorname{Cost}i(r_i) \quad \text{subject to} \quad \sum}^M \operatorname{Paramsi(r_i) \le B$$ 3. }Low-Rank Factorized Multi-Head Projections: $$W_i \approx W_{i}^{\text{up}} W_{i}^{\text{down}}, \quad W_{i}^{\text{down}} \in \mathbb{R}^{r_i \times d_{\text{in}}}, \, W_{i}^{\text{up}} \in \mathbb{R}^{d_{\text{out}} \times r_i}$$ $$\operatorname{Attn}(Q, K, V) = \operatorname{Softmax}\left(\frac{Q K^T}{\sqrt{d}} + M\right) V$$

Computational Complexity: - Low-rank projection per token: $\mathcal{O}(d_{\text{in}} r_i + r_i d_{\text{out}})$. - Attention matrix evaluation: $\mathcal{O}(B \cdot H \cdot N^2 \cdot d)$. - Total per-layer complexity: $\mathcal{O}(B \cdot N \cdot (d_{\text{in}} r + r d_{\text{out}}) + B \cdot H \cdot N^2 \cdot d)$.

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_out

Applies Rotary Position Embedding (RoPE) with partial dimension and independent seq-len support.

def calculate_spectral_entropy(singular_values: numpy.ndarray) ‑> float
Expand source code
def calculate_spectral_entropy(singular_values: np.ndarray) -> float:
    r"""Compute normalized spectral entropy of singular value distribution."""
    s = singular_values.astype(np.float64)
    s_sum = s.sum()
    if s_sum == 0:
        return 0.0
    prob = s / s_sum
    prob = prob[prob > 1e-5]
    return float(-np.sum(prob * np.log(prob)))

Compute normalized spectral entropy of singular value distribution.

def compute_cumulative_entropy(singular_values: numpy.ndarray, max_rank: int) ‑> numpy.ndarray
Expand source code
def compute_cumulative_entropy(singular_values: np.ndarray, max_rank: int) -> np.ndarray:
    r"""Compute cumulative spectral energy fraction retained by top-$k$ singular values."""
    s = singular_values.astype(np.float64)
    s_sq = s**2
    total = s_sq.sum()
    if total == 0:
        return np.ones(max_rank)
    cum = np.cumsum(s_sq[:max_rank]) / total
    return cum

Compute cumulative spectral energy fraction retained by top-$k$ singular values.

def compute_metrics(eval_pred: Any) ‑> dict[str, float]
Expand source code
def compute_metrics(eval_pred: Any) -> dict[str, float]:
    r"""Compute classification evaluation accuracy."""
    logits, labels = eval_pred
    preds = logits.argmax(-1)
    return {"accuracy": float((preds == labels).mean().item())}

Compute classification evaluation accuracy.

def gather_all_attention_weights(model: Any) ‑> tuple[list[numpy.ndarray], list[str]]
Expand source code
def gather_all_attention_weights(model: Any) -> tuple[list[np.ndarray], list[str]]:
    r"""Gather attention projection weights and layer names across model layers."""
    weights = []
    names = []
    for layer_idx in range(len(model.distilbert.transformer.layer)):
        distil_layer = model.distilbert.transformer.layer[layer_idx]
        attn = distil_layer.attention
        for proj in ["q_lin", "k_lin", "v_lin", "out_lin"]:
            w = getattr(attn, proj).weight.data.cpu().numpy()
            weights.append(w)
            proj_short = proj.replace("_lin", "")
            names.append(f"layer{layer_idx}_{proj_short}")
    return weights, names

Gather attention projection weights and layer names across model layers.

def get_num_labels_and_is_regression(task: str) ‑> tuple[int, bool]
Expand source code
def get_num_labels_and_is_regression(task: str) -> tuple[int, bool]:
    r"""Return class count and regression indicator for GLUE task."""
    if task == "stsb":
        return 1, True
    if task == "cola":
        return 2, False
    mapping = {
        "sst2": 2,
        "mrpc": 2,
        "qqp": 2,
        "mnli": 3,
        "qnli": 2,
        "rte": 2,
        "wnli": 2,
    }
    return mapping[task], False

Return class count and regression indicator for GLUE task.

def inject_all_layers(model: Any = None,
rank_dict: dict[str, int] | None = None,
dropout: float = 0.1,
add_sparse: bool = False,
sparsity: float = 0.0,
config: Any = None,
**kwargs) ‑> Any
Expand source code
def inject_all_layers(
    model: Any = None,
    rank_dict: dict[str, int] | None = None,
    dropout: float = 0.1,
    add_sparse: bool = False,
    sparsity: float = 0.0,
    config: Any = None,
    **kwargs,
) -> Any:
    r"""Inject SVDCompressedAttention into all transformer layers of model."""
    if rank_dict is None:
        return inject_svd_compressed(model=model, model_config=config, **kwargs)
    for layer_idx in range(len(model.distilbert.transformer.layer)):
        distil_layer = model.distilbert.transformer.layer[layer_idx]
        old_attn = distil_layer.attention
        ranks = {}
        for proj in ["q", "k", "v", "out"]:
            key = f"layer{layer_idx}_{proj}"
            if proj == "out":
                ranks["o"] = rank_dict[key]
            else:
                ranks[proj] = rank_dict[key]
        new_attn = SVDCompressedAttention(
            model_config=config,
            dropout=dropout,
            layer_idx=layer_idx,
            pretrained_attn=old_attn,
            ranks=ranks,
            add_sparse=add_sparse,
            sparsity_level=sparsity,
        )
        if hasattr(distil_layer, "attention") and hasattr(distil_layer.attention, "self"):
            distil_layer.attention.self = new_attn
        else:
            distil_layer.attention = new_attn

Inject SVDCompressedAttention into all transformer layers of model.

def inject_svd_compressed(model: torch.nn.modules.module.Module | None = None,
layer: torch.nn.modules.module.Module | None = None,
model_config: Any | None = None,
comp_config: Any | None = None,
layer_idx: int = 0,
total_layers: int = 12,
old_attn: Any | None = None,
lora_rank: int | None = None,
**kwargs: Any) ‑> SVDCompressedAttention | torch.nn.modules.module.Module | None
Expand source code
@ATTENTION_REGISTRY.register("svd_compressed_inject")
@ATTENTION_REGISTRY.register("svd_compressed_attention_inject")
@ATTENTION_REGISTRY.register("inject_svd_compressed")
@ATTENTION_REGISTRY.register("inject_svd_compressed_attention")
def inject_svd_compressed(
    model: nn.Module | None = None,
    layer: nn.Module | None = None,
    model_config: Any | None = None,
    comp_config: Any | None = None,
    layer_idx: int = 0,
    total_layers: int = 12,
    old_attn: Any | None = None,
    lora_rank: int | None = None,
    **kwargs: Any,
) -> SVDCompressedAttention | nn.Module | None:
    r"""Bridge helper safely injecting SVDCompressedAttention into a transformer layer or model."""
    target = layer if layer is not None else model
    if target is None:
        return None

    cfg = model_config
    if cfg is None and hasattr(target, "config"):
        cfg = target.config

    # 1. 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, "model") and hasattr(target.model, "layers"):
        layer_list = target.model.layers
    elif hasattr(target, "encoder") and hasattr(target.encoder, "layer"):
        layer_list = target.encoder.layer
    elif (
        hasattr(target, "bert")
        and hasattr(target.bert, "encoder")
        and hasattr(target.bert.encoder, "layer")
    ):
        layer_list = target.bert.encoder.layer
    elif hasattr(target, "layers") and isinstance(target.layers, (nn.ModuleList, list)):
        layer_list = target.layers

    if layer_list is not None:
        num_injected = 0
        tot = len(layer_list)
        for idx, lyr in enumerate(layer_list):
            res = inject_svd_compressed(
                layer=lyr,
                model_config=cfg,
                comp_config=comp_config,
                layer_idx=idx,
                total_layers=tot,
                lora_rank=lora_rank,
                **kwargs,
            )
            if res is not None:
                num_injected += 1
        return target if num_injected > 0 else None

    # 2. Single layer injection
    try:
        src_attn = old_attn
        if src_attn is None and target is not None:
            for attr in ("attention", "self_attn", "attn", "self"):
                if hasattr(target, attr):
                    src_attn = getattr(target, attr)
                    break

        if src_attn is None and target is not None and hasattr(target, "forward"):
            src_attn = target

        if src_attn is None:
            return None

        # Double-injection guard
        if src_attn is not None:
            if isinstance(src_attn, SVDCompressedAttention):
                return None
            if "SVDCompressed" in src_attn.__class__.__name__:
                return None
            if hasattr(src_attn, "inner") and isinstance(src_attn.inner, SVDCompressedAttention):
                return None
            if hasattr(src_attn, "_wrapped_attention") and isinstance(
                src_attn._wrapped_attention, SVDCompressedAttention
            ):
                return None

        if target is not None:
            for attr in ("attention", "self_attn", "attn", "self"):
                cand = getattr(target, attr, None)
                if cand is None:
                    continue
                if isinstance(cand, SVDCompressedAttention):
                    return None
                if "SVDCompressed" in cand.__class__.__name__:
                    return None
                if hasattr(cand, "inner") and isinstance(cand.inner, SVDCompressedAttention):
                    return None
                if hasattr(cand, "_wrapped_attention") and isinstance(
                    cand._wrapped_attention, SVDCompressedAttention
                ):
                    return None

        # Skip Qwen3.5 hybrid DeltaNet/LinearAttention layers
        if src_attn is not None:
            c_name = src_attn.__class__.__name__
            if "DeltaNet" in c_name or "LinearAttention" in c_name:
                return None

        if cfg is None and hasattr(src_attn, "config"):
            cfg = src_attn.config

        h_sz = getattr(cfg, "hidden_size", getattr(cfg, "dim", 768)) if cfg else 768
        rank = _cfg_get(
            comp_config,
            "rank",
            _cfg_get(comp_config, "svd_rank", kwargs.pop("rank", h_sz)),
        )
        ranks = kwargs.pop("ranks", None)
        if ranks is None:
            ranks = {"q": rank, "k": rank, "v": rank, "o": rank}

        dropout = _cfg_get(comp_config, "dropout", 0.1)
        lr = lora_rank if lora_rank is not None else _cfg_get(comp_config, "lora_rank", 16)

        new_attn = SVDCompressedAttention(
            config=cfg,
            old_attn=src_attn,
            dropout=dropout,
            layer_idx=layer_idx,
            ranks=ranks,
            lora_rank=lr,
            **kwargs,
        )

        p = next(src_attn.parameters(), None)
        if p is not None:
            new_attn.to(device=p.device, dtype=p.dtype)

        if target is not None:
            if hasattr(target, "attention"):
                if hasattr(target.attention, "self"):
                    target.attention.self = new_attn
                    new_attn.is_nested_encoder = True
                else:
                    target.attention = new_attn
                    new_attn.is_nested_encoder = False
                return new_attn
            for attr in ("self_attn", "attn", "self"):
                if hasattr(target, attr):
                    setattr(target, attr, new_attn)
                    return new_attn
            target.attention = new_attn
            new_attn.is_nested_encoder = False
            return new_attn
        return new_attn
    except Exception as e:
        log.warning("inject_svd_compressed failed: %s", e)
        return None

Bridge helper safely injecting SVDCompressedAttention into a transformer layer or model.

def lagrangian_rank_allocation(weight_matrices: list[numpy.ndarray],
target_budget: int,
layer_proj_names: list[str],
max_rank_factor: float = 0.5,
lambda_bracket: tuple[float, float] = (1e-05, 10.0),
num_lambda_steps: int = 100) ‑> dict[str, int]
Expand source code
def lagrangian_rank_allocation(
    weight_matrices: list[np.ndarray],
    target_budget: int,
    layer_proj_names: list[str],
    max_rank_factor: float = 0.5,
    lambda_bracket: tuple[float, float] = (1e-5, 10.0),
    num_lambda_steps: int = 100,
) -> dict[str, int]:
    r"""Allocate per-projection ranks under global parameter constraint via binary search on Lagrange multiplier."""
    cost_table = []
    param_table = []

    for W in weight_matrices:
        d_in, d_out = W.shape[1], W.shape[0]
        max_r = min(d_in, d_out, int(max_rank_factor * min(d_in, d_out)))
        max_r = max(1, max_r)

        s = np.linalg.svd(W.astype(np.float64), compute_uv=False)
        cum_ent = compute_cumulative_entropy(s, max_r)
        cost = 1.0 - cum_ent
        cost_table.append(cost)

        params_per_r = np.arange(1, max_r + 1) * (d_in + d_out)
        param_table.append(params_per_r)

    low, high = lambda_bracket
    best_allocation = None

    for _ in range(num_lambda_steps):
        lam = (low + high) / 2.0
        ranks = []
        total_params = 0
        for i in range(len(weight_matrices)):
            obj = cost_table[i] + lam * param_table[i]
            best_r_idx = int(np.argmin(obj))
            r = best_r_idx + 1
            ranks.append(r)
            total_params += param_table[i][best_r_idx]

        if total_params <= target_budget:
            best_allocation = ranks.copy()
            high = lam
        else:
            low = lam

    if best_allocation is None:
        best_allocation = [1] * len(weight_matrices)
        log.warning("Could not meet budget, falling back to rank=1 everywhere.")

    rank_dict = {name: r for name, r in zip(layer_proj_names, best_allocation)}
    return rank_dict

Allocate per-projection ranks under global parameter constraint via binary search on Lagrange multiplier.

def load_glue_local(task_name: str) ‑> Any
Expand source code
def load_glue_local(task_name: str) -> Any:
    r"""Load GLUE benchmark dataset from local offline storage."""
    from datasets import Dataset, DatasetDict

    from neuralops.utils.paths import get_glue_dir

    base_path = str(get_glue_dir())
    task_path = os.path.join(base_path, task_name)
    if not os.path.exists(task_path):
        raise FileNotFoundError(f"Dataset not found: {task_path}")
    splits = {}
    for fname in os.listdir(task_path):
        if fname.endswith(".parquet"):
            split = fname.split("-")[0]
            splits[split] = Dataset.from_parquet(os.path.join(task_path, fname))
    if task_name == "mnli":
        for extra in ["validation_matched", "validation_mismatched"]:
            fpath = os.path.join(task_path, f"{extra}.parquet")
            if os.path.exists(fpath):
                splits[extra] = Dataset.from_parquet(fpath)
    return DatasetDict(splits)

Load GLUE benchmark dataset from local offline storage.

def main() ‑> None
Expand source code
def main() -> None:
    r"""Run full training workflow for SVDCompressedAttention on GLUE."""
    parser = argparse.ArgumentParser(
        description="Train DistilBERT with Importance‑Aware SVD‑Compressed Attention"
    )
    parser.add_argument("--task", type=str, default="sst2")
    parser.add_argument("--model", type=str, default="distilbert-base-uncased")
    parser.add_argument("--output_dir", type=str, default="./svd_compressed_output")
    parser.add_argument("--max_len", type=int, default=128)
    parser.add_argument("--epochs", type=int, default=5)
    parser.add_argument("--lr", type=float, default=1e-5)
    parser.add_argument("--batch_size", type=int, default=16)
    parser.add_argument("--seed", type=int, default=42)
    parser.add_argument(
        "--budget_ratio",
        type=float,
        default=0.2,
        help="Target fraction of original attention parameters (e.g. 0.2 = 20%)",
    )
    parser.add_argument("--dropout", type=float, default=0.1)
    parser.add_argument(
        "--add_sparse",
        action="store_true",
        help="Enable low‑rank + sparse decomposition (experimental)",
    )
    parser.add_argument(
        "--sparsity",
        type=float,
        default=0.7,
        help="Sparsity level for residual (if --add_sparse)",
    )
    args = parser.parse_args()

    set_seed(args.seed)
    os.makedirs(args.output_dir, exist_ok=True)

    raw_datasets = load_glue_local(args.task)
    tokenizer = AutoTokenizer.from_pretrained(args.model, use_fast=True, local_files_only=True)
    num_labels, is_regression = get_num_labels_and_is_regression(args.task)
    sent1_key, sent2_key = GLUE_TASK_KEYS[args.task]

    tokenized = raw_datasets.map(
        lambda ex: preprocess_function(
            ex, tokenizer, sent1_key, sent2_key, args.max_len, is_regression
        ),
        batched=True,
        remove_columns=raw_datasets["train"].column_names,
    )
    train_dataset = tokenized["train"]
    eval_dataset = tokenized.get("validation", tokenized.get("validation_matched"))
    log.info(f"Train size: {len(train_dataset)}, Eval size: {len(eval_dataset)}")

    config = AutoConfig.from_pretrained(args.model, local_files_only=True)
    model = AutoModelForSequenceClassification.from_pretrained(
        args.model, config=config, local_files_only=True
    )

    all_weights, all_names = gather_all_attention_weights(model)
    original_total_params = sum(w.size for w in all_weights)
    target_budget = int(original_total_params * args.budget_ratio)
    log.info(
        f"Original attention parameters: {original_total_params:,}, "
        f"target budget ({args.budget_ratio * 100:.0f}%): {target_budget:,}"
    )

    rank_dict = lagrangian_rank_allocation(
        weight_matrices=all_weights,
        target_budget=target_budget,
        layer_proj_names=all_names,
    )
    avg_rank = float(np.mean(list(rank_dict.values())))
    log.info(f"Optimised ranks across layers: average rank = {avg_rank:.1f}")

    inject_all_layers(
        model,
        rank_dict,
        args.dropout,
        add_sparse=args.add_sparse,
        sparsity=args.sparsity,
        config=config,
    )
    log.info("Injected importance‑aware SVD‑Compressed Attention.")

    total_steps = (len(train_dataset) // args.batch_size) * args.epochs
    warmup_steps = int(0.06 * total_steps)

    training_args = TrainingArguments(
        output_dir=os.path.join(args.output_dir, args.task),
        num_train_epochs=args.epochs,
        per_device_train_batch_size=args.batch_size,
        per_device_eval_batch_size=args.batch_size * 2,
        learning_rate=args.lr,
        weight_decay=0.01,
        lr_scheduler_type="linear",
        warmup_steps=warmup_steps,
        logging_steps=100,
        eval_strategy="steps",
        eval_steps=500,
        save_strategy="steps",
        save_steps=500,
        load_best_model_at_end=True,
        metric_for_best_model="accuracy",
        greater_is_better=True,
        remove_unused_columns=False,
        fp16=False,
        report_to="none",
    )

    trainer = Trainer(
        model=model,
        args=training_args,
        train_dataset=train_dataset,
        eval_dataset=eval_dataset,
        compute_metrics=compute_metrics,
        callbacks=[EarlyStoppingCallback(early_stopping_patience=3)],
    )

    log.info("Starting training with importance‑aware SVD‑Compressed Attention...")
    trainer.train()
    trainer.save_model(os.path.join(args.output_dir, args.task))
    log.info("Training completed.")

Run full training workflow for SVDCompressedAttention on GLUE.

def preprocess_function(examples: Any,
tokenizer: Any,
sent1_key: str,
sent2_key: str | None,
max_length: int,
is_regression: bool) ‑> dict[str, typing.Any]
Expand source code
def preprocess_function(
    examples: Any,
    tokenizer: Any,
    sent1_key: str,
    sent2_key: str | None,
    max_length: int,
    is_regression: bool,
) -> dict[str, Any]:
    r"""Preprocess and tokenize dataset sequence examples."""
    if sent2_key is None:
        t = tokenizer(
            examples[sent1_key],
            truncation=True,
            padding="max_length",
            max_length=max_length,
        )
    else:
        t = tokenizer(
            examples[sent1_key],
            examples[sent2_key],
            truncation=True,
            padding="max_length",
            max_length=max_length,
        )
    t["labels"] = [float(l) for l in examples["label"]] if is_regression else examples["label"]
    return t

Preprocess and tokenize dataset sequence examples.

Classes

class SVDCompressedAttention (config: Any | None = None,
old_attn: Any | None = None,
dropout: float = 0.1,
layer_idx: int = 0,
ranks: dict[str, int] | None = None,
lora_rank: int = 16,
lora_alpha: float = 32.0,
is_decoder: bool | None = None,
add_sparse: bool = False,
sparsity_level: float = 0.7,
**kwargs: Any)
Expand source code
@ATTENTION_REGISTRY.register("svd_compressed", supports_causal=True, supports_gqa=True)
@ATTENTION_REGISTRY.register("svd_compressed_attention", supports_causal=True, supports_gqa=True)
@ATTENTION_REGISTRY.register("svd_compressed_module", supports_causal=True, supports_gqa=True)
class SVDCompressedAttention(BaseAttention):
    r"""Multi-head attention with truncated SVD projections, trainable LoRA adapters,
    GQA support, RoPE support, and optional sparse residuals.
    """

    supports_gqa: bool = True
    is_custom_attention: bool = True

    def __init__(
        self,
        config: Any | None = None,
        old_attn: Any | None = None,
        dropout: float = 0.1,
        layer_idx: int = 0,
        ranks: dict[str, int] | None = None,
        lora_rank: int = 16,
        lora_alpha: float = 32.0,
        is_decoder: bool | None = None,
        add_sparse: bool = False,
        sparsity_level: float = 0.7,
        **kwargs: Any,
    ) -> None:
        model_config = config or kwargs.get("model_config")
        super().__init__(model_config)
        # 1. Dimensions & Architecture
        self.hidden_size: int = int(
            _cfg_get(model_config, "hidden_size", kwargs.get("hidden_size", 768))
        )
        self.num_heads: int = int(
            _cfg_get(
                model_config,
                "num_attention_heads",
                _cfg_get(model_config, "num_heads", kwargs.get("num_attention_heads", 12)),
            )
        )
        self.head_dim: int = int(
            _cfg_get(
                model_config,
                "head_dim",
                kwargs.get(
                    "head_dim",
                    self.hidden_size // self.num_heads if self.num_heads > 0 else 64,
                ),
            )
        )

        cfg_kv = _cfg_get(
            model_config,
            "num_key_value_heads",
            _cfg_get(model_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

        # 2. Causal decoder flag detection
        if is_decoder is not None:
            self.is_decoder = bool(is_decoder)
        else:
            _mt = str(_cfg_get(model_config, "model_type", "") or "").lower()
            self.is_decoder = (
                bool(_cfg_get(model_config, "is_decoder", False))
                or any(
                    _mt.startswith(p)
                    for p in (
                        "gpt",
                        "qwen",
                        "llama",
                        "mistral",
                        "falcon",
                        "bloom",
                        "opt",
                    )
                )
                or (old_attn is not None and hasattr(old_attn, "c_attn"))
            )

        self.is_nested_encoder: bool = False
        default_r = int(_cfg_get(model_config, "svd_rank", _cfg_get(model_config, "rank", 64)))
        default_r = max(1, min(default_r, self.hidden_size, self.all_head_size))
        self.ranks: dict[str, int] = {}
        for k in ("q", "k", "v", "o"):
            val = ranks.get(k, default_r) if ranks else default_r
            self.ranks[k] = max(1, min(int(val), self.hidden_size, self.all_head_size))

        self.add_sparse: bool = add_sparse
        self.sparsity_level: float = sparsity_level
        self.lora_rank = max(
            1,
            min(
                int(lora_rank if lora_rank is not None else 16),
                self.hidden_size,
                self.all_head_size,
            ),
        )
        self.lora_alpha = lora_alpha
        self.scaling = lora_alpha / max(1, self.lora_rank)

        # 3. Factorized projections
        self.q_down = nn.Linear(self.hidden_size, self.ranks["q"], bias=False)
        self.q_up = nn.Linear(self.ranks["q"], self.q_head_size, bias=True)
        self.k_down = nn.Linear(self.hidden_size, self.ranks["k"], bias=False)
        self.k_up = nn.Linear(self.ranks["k"], self.kv_head_size, bias=True)
        self.v_down = nn.Linear(self.hidden_size, self.ranks["v"], bias=False)
        self.v_up = nn.Linear(self.ranks["v"], self.kv_head_size, bias=True)
        self.o_down = nn.Linear(self.q_head_size, self.ranks["o"], bias=False)
        self.o_up = nn.Linear(self.ranks["o"], self.hidden_size, bias=True)

        for mod in [
            self.q_down,
            self.q_up,
            self.k_down,
            self.k_up,
            self.v_down,
            self.v_up,
            self.o_down,
            self.o_up,
        ]:
            if isinstance(mod, nn.Linear):
                nn.init.orthogonal_(mod.weight)
                if mod.bias is not None:
                    nn.init.zeros_(mod.bias)
                mod.weight.requires_grad = False
                if mod.bias is not None:
                    mod.bias.requires_grad = False

        # 4. Trainable LoRA parameters
        if self.lora_rank > 0:
            self.q_lora_A = nn.Parameter(torch.empty(self.lora_rank, self.hidden_size))
            self.q_lora_B = nn.Parameter(torch.empty(self.q_head_size, self.lora_rank))
            self.k_lora_A = nn.Parameter(torch.empty(self.lora_rank, self.hidden_size))
            self.k_lora_B = nn.Parameter(torch.empty(self.kv_head_size, self.lora_rank))
            self.v_lora_A = nn.Parameter(torch.empty(self.lora_rank, self.hidden_size))
            self.v_lora_B = nn.Parameter(torch.empty(self.kv_head_size, self.lora_rank))
            self.o_lora_A = nn.Parameter(torch.empty(self.lora_rank, self.q_head_size))
            self.o_lora_B = nn.Parameter(torch.empty(self.hidden_size, self.lora_rank))

            for p_a in (self.q_lora_A, self.k_lora_A, self.v_lora_A, self.o_lora_A):
                nn.init.kaiming_uniform_(p_a, a=math.sqrt(5))
            for p_b in (self.q_lora_B, self.k_lora_B, self.v_lora_B, self.o_lora_B):
                nn.init.zeros_(p_b)
        else:
            self.register_parameter("q_lora_A", None)
            self.register_parameter("q_lora_B", None)
            self.register_parameter("k_lora_A", None)
            self.register_parameter("k_lora_B", None)
            self.register_parameter("v_lora_A", None)
            self.register_parameter("v_lora_B", None)
            self.register_parameter("o_lora_A", None)
            self.register_parameter("o_lora_B", None)

        if add_sparse:
            self.q_sparse_weight = nn.Parameter(torch.zeros(self.q_head_size, self.hidden_size))
            self.k_sparse_weight = nn.Parameter(torch.zeros(self.kv_head_size, self.hidden_size))
            self.v_sparse_weight = nn.Parameter(torch.zeros(self.kv_head_size, self.hidden_size))
            self.o_sparse_weight = nn.Parameter(torch.zeros(self.hidden_size, self.q_head_size))
        else:
            self.register_parameter("q_sparse_weight", None)
            self.register_parameter("k_sparse_weight", None)
            self.register_parameter("v_sparse_weight", None)
            self.register_parameter("o_sparse_weight", None)

        dropout_p = _cfg_get(
            model_config,
            "dropout",
            _cfg_get(
                model_config,
                "hidden_dropout_prob",
                _cfg_get(model_config, "attention_probs_dropout_prob", dropout),
            ),
        )
        self.dropout = nn.Dropout(float(dropout_p) if dropout_p is not None else 0.1)

        src_attn = old_attn or kwargs.get("pretrained_attn")
        src_p = next(src_attn.parameters(), None) if src_attn is not None else None
        target_dtype = kwargs.get("dtype", src_p.dtype if src_p is not None else torch.float32)
        target_device = kwargs.get("device", src_p.device if src_p is not None else None)

        if src_attn is not None:
            self.copy_weights(src_attn)

        if target_device is not None and target_dtype is not None:
            self.to(device=target_device, dtype=target_dtype)
        elif target_device is not None:
            self.to(device=target_device)
        elif target_dtype is not None:
            self.to(target_dtype)

    def _set_factorised_weights(
        self,
        W_tensor: torch.Tensor,
        bias_tensor: torch.Tensor | None,
        down: nn.Linear,
        up: nn.Linear,
        rank: int,
    ) -> None:
        with torch.no_grad():
            W = W_tensor.float()
            d_out, d_in = W.shape
            r = min(rank, d_out, d_in, down.out_features, up.in_features)
            r = max(r, 1)

            U, s, VT = torch.linalg.svd(W, full_matrices=False)
            sqrt_s = torch.sqrt(torch.clamp(s[:r], min=1e-12))
            down_weight = (sqrt_s.unsqueeze(1) * VT[:r, :]).to(
                device=down.weight.device, dtype=down.weight.dtype
            )
            up_weight = (U[:, :r] * sqrt_s.unsqueeze(0)).to(
                device=up.weight.device, dtype=up.weight.dtype
            )

            down.weight.data.zero_()
            up.weight.data.zero_()
            down.weight.data[:r, :].copy_(down_weight)
            up.weight.data[:, :r].copy_(up_weight)
            if bias_tensor is not None and up.bias is not None:
                up.bias.data.copy_(bias_tensor.to(device=up.bias.device, dtype=up.bias.dtype))

    def copy_weights(self, old_attn: Any) -> 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__
            has_out_proj = any(
                hasattr(old_attn, attr)
                for attr in (
                    "dense",
                    "out_proj",
                    "c_proj",
                    "out_lin",
                    "o_proj",
                    "wo",
                    "o",
                    "proj",
                )
            )
            if src_cls_name in ("BertSelfAttention", "RobertaSelfAttention") or (
                hasattr(old_attn, "query") and not has_out_proj
            ):
                self.is_nested_encoder = True

            if hasattr(old_attn, "self") and hasattr(old_attn, "output"):
                self.is_nested_encoder = True
                src = old_attn.self
                if hasattr(old_attn.output, "dense"):
                    w_o, b_o = _unpack_weight_bias(old_attn.output.dense)
            else:
                src = old_attn

            # Pattern 1: GPT-2 Fused c_attn
            if hasattr(src, "c_attn") and hasattr(src, "c_proj"):
                c_w, c_b = _unpack_weight_bias(src.c_attn)
                if c_w is not None:
                    if c_w.shape[0] == self.hidden_size and c_w.shape[1] != self.hidden_size:
                        c_w = c_w.t().contiguous()

                    q_dim = self.q_head_size
                    k_dim = self.kv_head_size
                    v_dim = self.kv_head_size

                    w_q = c_w[:q_dim, :]
                    w_k = c_w[q_dim : q_dim + k_dim, :]
                    w_v = c_w[q_dim + k_dim : q_dim + k_dim + v_dim, :]

                    if c_b is not None:
                        b_q = c_b[:q_dim]
                        b_k = c_b[q_dim : q_dim + k_dim]
                        b_v = c_b[q_dim + k_dim : q_dim + k_dim + v_dim]

                out_w, out_b = _unpack_weight_bias(src.c_proj)
                if out_w is not None:
                    if 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 = out_b

            # Pattern 2: RoBERTa / BERT separate query, key, value
            elif hasattr(src, "query") and hasattr(src, "key") and hasattr(src, "value"):
                w_q, b_q = _unpack_weight_bias(src.query)
                w_k, b_k = _unpack_weight_bias(src.key)
                w_v, b_v = _unpack_weight_bias(src.value)

                if w_o is None:
                    for attr in ("dense", "out_proj", "o_proj"):
                        if hasattr(src, attr):
                            w_o, b_o = _unpack_weight_bias(getattr(src, attr))
                            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, b_q = _unpack_weight_bias(src.q_proj)
                w_k, b_k = _unpack_weight_bias(src.k_proj)
                w_v, b_v = _unpack_weight_bias(src.v_proj)
                if hasattr(src, "o_proj"):
                    w_o, b_o = _unpack_weight_bias(src.o_proj)

            # 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, b_q = _unpack_weight_bias(src.q_lin)
                w_k, b_k = _unpack_weight_bias(src.k_lin)
                w_v, b_v = _unpack_weight_bias(src.v_lin)
                if hasattr(src, "out_lin"):
                    w_o, b_o = _unpack_weight_bias(src.out_lin)

            # Fallback: UniversalAdapter
            if w_q is None or w_k is None or w_v is None:
                try:
                    adapter = UniversalAdapter(old_attn)
                    extracted = adapter.extract_qkv_out()
                    if extracted:
                        w_q = extracted.get("q_weight")
                        b_q = extracted.get("q_bias")
                        w_k = extracted.get("k_weight")
                        b_k = extracted.get("k_bias")
                        w_v = extracted.get("v_weight")
                        b_v = extracted.get("v_bias")
                        if w_o is None:
                            w_o = extracted.get("out_weight")
                            b_o = extracted.get("out_bias")
                except Exception as e:
                    log.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:
                log.warning("Failed to extract required projections: %s", missing)
                return

            # Frobenius norm checks
            for name, w in [("w_q", w_q), ("w_k", w_k), ("w_v", w_v)]:
                if torch.norm(w.float(), p="fro").item() < 1e-4:
                    log.warning(f"Copied weight {name} has near-zero Frobenius norm (< 1e-4)")

            # Orient weights if needed
            def _orient(w: torch.Tensor, out_dim: int, in_dim: int) -> torch.Tensor:
                if w.shape == (out_dim, in_dim):
                    return w
                if w.shape == (in_dim, out_dim):
                    return w.t().contiguous()
                return w

            w_q = _orient(w_q, self.q_head_size, self.hidden_size)
            # Factorize and set Q
            self._set_factorised_weights(w_q, b_q, self.q_down, self.q_up, self.ranks["q"])

            # Factorize and set K & V with GQA pooling if needed
            if w_k.shape[0] == self.kv_head_size:
                w_k = _orient(w_k, self.kv_head_size, self.hidden_size)
                self._set_factorised_weights(w_k, b_k, self.k_down, self.k_up, self.ranks["k"])
            else:
                rep = max(1, w_k.shape[0] // self.kv_head_size)
                w_k_pooled = (
                    w_k.view(self.num_kv_heads, rep, self.head_dim, -1)
                    .mean(dim=1)
                    .view(self.kv_head_size, -1)
                )
                b_k_pooled = (
                    b_k.view(self.num_kv_heads, rep, self.head_dim).mean(dim=1).view(-1)
                    if b_k is not None
                    else None
                )
                self._set_factorised_weights(
                    w_k_pooled, b_k_pooled, self.k_down, self.k_up, self.ranks["k"]
                )

            if w_v.shape[0] == self.kv_head_size:
                w_v = _orient(w_v, self.kv_head_size, self.hidden_size)
                self._set_factorised_weights(w_v, b_v, self.v_down, self.v_up, self.ranks["v"])
            else:
                rep = max(1, w_v.shape[0] // self.kv_head_size)
                w_v_pooled = (
                    w_v.view(self.num_kv_heads, rep, self.head_dim, -1)
                    .mean(dim=1)
                    .view(self.kv_head_size, -1)
                )
                b_v_pooled = (
                    b_v.view(self.num_kv_heads, rep, self.head_dim).mean(dim=1).view(-1)
                    if b_v is not None
                    else None
                )
                self._set_factorised_weights(
                    w_v_pooled, b_v_pooled, self.v_down, self.v_up, self.ranks["v"]
                )

            # Set Out projection if not nested encoder
            if w_o is not None and not self.is_nested_encoder:
                if torch.norm(w_o.float(), p="fro").item() < 1e-4:
                    log.warning("Copied out_proj weight has near-zero Frobenius norm (< 1e-4)")
                w_o = _orient(w_o, self.hidden_size, self.q_head_size)
                self._set_factorised_weights(w_o, b_o, self.o_down, self.o_up, self.ranks["o"])

            # Initialize sparse residuals if enabled
            if self.add_sparse:
                low_rank_q = self.q_up.weight @ self.q_down.weight
                res_q = w_q - low_rank_q
                self.q_sparse_weight.data.copy_(res_q)

                low_rank_k = self.k_up.weight @ self.k_down.weight
                res_k = (w_k if w_k.shape[0] == self.kv_head_size else w_k_pooled) - low_rank_k
                self.k_sparse_weight.data.copy_(res_k)

                low_rank_v = self.v_up.weight @ self.v_down.weight
                res_v = (w_v if w_v.shape[0] == self.kv_head_size else w_v_pooled) - low_rank_v
                self.v_sparse_weight.data.copy_(res_v)

                if w_o is not None and not self.is_nested_encoder:
                    low_rank_o = self.o_up.weight @ self.o_down.weight
                    res_o = w_o - low_rank_o
                    self.o_sparse_weight.data.copy_(res_o)

    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]:
        B, N, _ = hidden_states.shape
        # Defensive device alignment
        if self.q_down.weight.device != hidden_states.device:
            self.to(device=hidden_states.device)

        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]

        def proj_q(x: torch.Tensor) -> torch.Tensor:
            if x.device != self.q_down.weight.device:
                x = x.to(self.q_down.weight.device)
            out = self.q_up(self.q_down(x))
            if self.lora_rank > 0 and self.q_lora_A is not None and self.q_lora_B is not None:
                out = out + F.linear(F.linear(x, self.q_lora_A), self.q_lora_B) * self.scaling
            if self.add_sparse and self.q_sparse_weight is not None:
                out = out + F.linear(x, self.q_sparse_weight)
            return out

        def proj_k(x: torch.Tensor) -> torch.Tensor:
            if x.device != self.k_down.weight.device:
                x = x.to(self.k_down.weight.device)
            out = self.k_up(self.k_down(x))
            if self.lora_rank > 0 and self.k_lora_A is not None and self.k_lora_B is not None:
                out = out + F.linear(F.linear(x, self.k_lora_A), self.k_lora_B) * self.scaling
            if self.add_sparse and self.k_sparse_weight is not None:
                out = out + F.linear(x, self.k_sparse_weight)
            return out

        def proj_v(x: torch.Tensor) -> torch.Tensor:
            if x.device != self.v_down.weight.device:
                x = x.to(self.v_down.weight.device)
            out = self.v_up(self.v_down(x))
            if self.lora_rank > 0 and self.v_lora_A is not None and self.v_lora_B is not None:
                out = out + F.linear(F.linear(x, self.v_lora_A), self.v_lora_B) * self.scaling
            if self.add_sparse and self.v_sparse_weight is not None:
                out = out + F.linear(x, self.v_sparse_weight)
            return out

        def proj_o(x: torch.Tensor) -> torch.Tensor:
            if x.device != self.o_down.weight.device:
                x = x.to(self.o_down.weight.device)
            out = self.o_up(self.o_down(x))
            if self.lora_rank > 0 and self.o_lora_A is not None and self.o_lora_B is not None:
                out = out + F.linear(F.linear(x, self.o_lora_A), self.o_lora_B) * self.scaling
            if self.add_sparse and self.o_sparse_weight is not None:
                out = out + F.linear(x, self.o_sparse_weight)
            return out

        # 1. Projections
        Q = (
            proj_q(hidden_states)
            .view(B, N, self.num_heads, self.head_dim)
            .transpose(1, 2)
            .contiguous()
        )
        K = (
            proj_k(kv_input)
            .view(B, kv_len, self.num_kv_heads, self.head_dim)
            .transpose(1, 2)
            .contiguous()
        )
        V = (
            proj_v(kv_input)
            .view(B, 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)

        # 4. Attention scores in FP32
        scale = 1.0 / math.sqrt(self.head_dim)
        scores = torch.matmul(Q.float(), K.float().transpose(-1, -2).contiguous()) * scale

        # 5. Masking
        if self.is_decoder and not is_cross and N > 1:
            causal_mask = (
                torch.triu(
                    torch.full(
                        (N, kv_len),
                        float("-inf"),
                        device=scores.device,
                        dtype=torch.float32,
                    ),
                    diagonal=1,
                )
                .unsqueeze(0)
                .unsqueeze(0)
            )
            scores = scores + causal_mask

        eff_mask = encoder_attention_mask if is_cross else attention_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)
        attn_weights = F.softmax(scores, dim=-1).to(hidden_states.dtype)
        attn_weights = self.dropout(attn_weights)

        # 6. Safe head_mask application
        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)
            attn_weights = attn_weights * head_mask

        # 7. Context computation & Head Merge
        context = (
            torch.matmul(attn_weights, V)
            .transpose(1, 2)
            .contiguous()
            .view(B, N, self.all_head_size)
        )
        context = torch.nan_to_num(context, nan=0.0, posinf=1e4, neginf=-1e4)

        # 8. Output projection (bypassed if nested encoder)
        if self.is_nested_encoder:
            output = context
        else:
            output = proj_o(context)

        if output.shape[-1] != hidden_states.shape[-1]:
            if output.shape[-1] < hidden_states.shape[-1]:
                output = F.pad(output, (0, hidden_states.shape[-1] - output.shape[-1]))
            else:
                output = output[..., : hidden_states.shape[-1]]

        return output, (attn_weights if output_attentions else None)

Multi-head attention with truncated SVD projections, trainable LoRA adapters, GQA support, RoPE support, and optional sparse residuals.

Initialize internal Module state, shared by both nn.Module and ScriptModule.

Ancestors

Class variables

var is_custom_attention : bool

The type of the None singleton.

var supports_gqa : bool

The type of the None singleton.

Methods

def copy_weights(self, old_attn: Any) ‑> None
Expand source code
def copy_weights(self, old_attn: Any) -> 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__
        has_out_proj = any(
            hasattr(old_attn, attr)
            for attr in (
                "dense",
                "out_proj",
                "c_proj",
                "out_lin",
                "o_proj",
                "wo",
                "o",
                "proj",
            )
        )
        if src_cls_name in ("BertSelfAttention", "RobertaSelfAttention") or (
            hasattr(old_attn, "query") and not has_out_proj
        ):
            self.is_nested_encoder = True

        if hasattr(old_attn, "self") and hasattr(old_attn, "output"):
            self.is_nested_encoder = True
            src = old_attn.self
            if hasattr(old_attn.output, "dense"):
                w_o, b_o = _unpack_weight_bias(old_attn.output.dense)
        else:
            src = old_attn

        # Pattern 1: GPT-2 Fused c_attn
        if hasattr(src, "c_attn") and hasattr(src, "c_proj"):
            c_w, c_b = _unpack_weight_bias(src.c_attn)
            if c_w is not None:
                if c_w.shape[0] == self.hidden_size and c_w.shape[1] != self.hidden_size:
                    c_w = c_w.t().contiguous()

                q_dim = self.q_head_size
                k_dim = self.kv_head_size
                v_dim = self.kv_head_size

                w_q = c_w[:q_dim, :]
                w_k = c_w[q_dim : q_dim + k_dim, :]
                w_v = c_w[q_dim + k_dim : q_dim + k_dim + v_dim, :]

                if c_b is not None:
                    b_q = c_b[:q_dim]
                    b_k = c_b[q_dim : q_dim + k_dim]
                    b_v = c_b[q_dim + k_dim : q_dim + k_dim + v_dim]

            out_w, out_b = _unpack_weight_bias(src.c_proj)
            if out_w is not None:
                if 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 = out_b

        # Pattern 2: RoBERTa / BERT separate query, key, value
        elif hasattr(src, "query") and hasattr(src, "key") and hasattr(src, "value"):
            w_q, b_q = _unpack_weight_bias(src.query)
            w_k, b_k = _unpack_weight_bias(src.key)
            w_v, b_v = _unpack_weight_bias(src.value)

            if w_o is None:
                for attr in ("dense", "out_proj", "o_proj"):
                    if hasattr(src, attr):
                        w_o, b_o = _unpack_weight_bias(getattr(src, attr))
                        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, b_q = _unpack_weight_bias(src.q_proj)
            w_k, b_k = _unpack_weight_bias(src.k_proj)
            w_v, b_v = _unpack_weight_bias(src.v_proj)
            if hasattr(src, "o_proj"):
                w_o, b_o = _unpack_weight_bias(src.o_proj)

        # 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, b_q = _unpack_weight_bias(src.q_lin)
            w_k, b_k = _unpack_weight_bias(src.k_lin)
            w_v, b_v = _unpack_weight_bias(src.v_lin)
            if hasattr(src, "out_lin"):
                w_o, b_o = _unpack_weight_bias(src.out_lin)

        # Fallback: UniversalAdapter
        if w_q is None or w_k is None or w_v is None:
            try:
                adapter = UniversalAdapter(old_attn)
                extracted = adapter.extract_qkv_out()
                if extracted:
                    w_q = extracted.get("q_weight")
                    b_q = extracted.get("q_bias")
                    w_k = extracted.get("k_weight")
                    b_k = extracted.get("k_bias")
                    w_v = extracted.get("v_weight")
                    b_v = extracted.get("v_bias")
                    if w_o is None:
                        w_o = extracted.get("out_weight")
                        b_o = extracted.get("out_bias")
            except Exception as e:
                log.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:
            log.warning("Failed to extract required projections: %s", missing)
            return

        # Frobenius norm checks
        for name, w in [("w_q", w_q), ("w_k", w_k), ("w_v", w_v)]:
            if torch.norm(w.float(), p="fro").item() < 1e-4:
                log.warning(f"Copied weight {name} has near-zero Frobenius norm (< 1e-4)")

        # Orient weights if needed
        def _orient(w: torch.Tensor, out_dim: int, in_dim: int) -> torch.Tensor:
            if w.shape == (out_dim, in_dim):
                return w
            if w.shape == (in_dim, out_dim):
                return w.t().contiguous()
            return w

        w_q = _orient(w_q, self.q_head_size, self.hidden_size)
        # Factorize and set Q
        self._set_factorised_weights(w_q, b_q, self.q_down, self.q_up, self.ranks["q"])

        # Factorize and set K & V with GQA pooling if needed
        if w_k.shape[0] == self.kv_head_size:
            w_k = _orient(w_k, self.kv_head_size, self.hidden_size)
            self._set_factorised_weights(w_k, b_k, self.k_down, self.k_up, self.ranks["k"])
        else:
            rep = max(1, w_k.shape[0] // self.kv_head_size)
            w_k_pooled = (
                w_k.view(self.num_kv_heads, rep, self.head_dim, -1)
                .mean(dim=1)
                .view(self.kv_head_size, -1)
            )
            b_k_pooled = (
                b_k.view(self.num_kv_heads, rep, self.head_dim).mean(dim=1).view(-1)
                if b_k is not None
                else None
            )
            self._set_factorised_weights(
                w_k_pooled, b_k_pooled, self.k_down, self.k_up, self.ranks["k"]
            )

        if w_v.shape[0] == self.kv_head_size:
            w_v = _orient(w_v, self.kv_head_size, self.hidden_size)
            self._set_factorised_weights(w_v, b_v, self.v_down, self.v_up, self.ranks["v"])
        else:
            rep = max(1, w_v.shape[0] // self.kv_head_size)
            w_v_pooled = (
                w_v.view(self.num_kv_heads, rep, self.head_dim, -1)
                .mean(dim=1)
                .view(self.kv_head_size, -1)
            )
            b_v_pooled = (
                b_v.view(self.num_kv_heads, rep, self.head_dim).mean(dim=1).view(-1)
                if b_v is not None
                else None
            )
            self._set_factorised_weights(
                w_v_pooled, b_v_pooled, self.v_down, self.v_up, self.ranks["v"]
            )

        # Set Out projection if not nested encoder
        if w_o is not None and not self.is_nested_encoder:
            if torch.norm(w_o.float(), p="fro").item() < 1e-4:
                log.warning("Copied out_proj weight has near-zero Frobenius norm (< 1e-4)")
            w_o = _orient(w_o, self.hidden_size, self.q_head_size)
            self._set_factorised_weights(w_o, b_o, self.o_down, self.o_up, self.ranks["o"])

        # Initialize sparse residuals if enabled
        if self.add_sparse:
            low_rank_q = self.q_up.weight @ self.q_down.weight
            res_q = w_q - low_rank_q
            self.q_sparse_weight.data.copy_(res_q)

            low_rank_k = self.k_up.weight @ self.k_down.weight
            res_k = (w_k if w_k.shape[0] == self.kv_head_size else w_k_pooled) - low_rank_k
            self.k_sparse_weight.data.copy_(res_k)

            low_rank_v = self.v_up.weight @ self.v_down.weight
            res_v = (w_v if w_v.shape[0] == self.kv_head_size else w_v_pooled) - low_rank_v
            self.v_sparse_weight.data.copy_(res_v)

            if w_o is not None and not self.is_nested_encoder:
                low_rank_o = self.o_up.weight @ self.o_down.weight
                res_o = w_o - low_rank_o
                self.o_sparse_weight.data.copy_(res_o)

Robust weight copying from DistilBERT, RoBERTa nested, GPT-2 Conv1D, or Qwen/LLaMA.

Inherited members