Module neuralops.attentions.hybrid_attention

Hybrid softmax + linear attention with per-head gated fusion.

Valid attention REPLACEMENT (not a compressor). Adds a small number of parameters (the gate) but reduces attention computation at long sequence lengths by routing some heads to linear attention.

NOVELTY STATEMENT: Hybrid softmax + linear attention is NOT novel by itself (cf. MEGA 2023, BiGS 2024, Zipformer 2024). Our specific formulation differs in:

1. PER-HEAD learned gate:
     gate_h = sigmoid(W_h · hidden_states)
   This allows different heads to prefer different attention types,
   in contrast to MEGA's single-head gate.

2. Gate initialization to softmax-dominant (bias=10.0):
   Ensures the model starts IDENTICAL to the original softmax attention.
   The gate learns to route some heads to linear attention during training.

3. No extra parameters beyond the gate:
   768 * 12 + 12 = 9,228 params (+0.014% on DistilBERT).

Bug-fix map: HA-FIX-01 out_proj always executed (no dead parameters in nested mode). HA-FIX-02 _linear_branch: proper causal + padding masking. HA-FIX-03 injector recursion guard. HA-FIX-04 injector double-injection guard. HA-FIX-05 removed 'comp_config' in locals() hack. HA-FIX-06 is_nested_encoder set correctly in injector. HA-FIX-07 KV-cache implemented (tuple + DynamicCache). HA-FIX-08 RoPE past_len offset + position_embeddings support. HA-FIX-09 head_mask broadcasting corrected (B, H, 1, 1). HA-FIX-10 _standard_branch: optional SDPA fast path.

Functions

def _extract_valid_mask(mask: torch.Tensor | None, batch_size: int, seq_len: int, device: torch.device) ‑> torch.Tensor
Expand source code
def _extract_valid_mask(
    mask: torch.Tensor | None,
    batch_size: int,
    seq_len: int,
    device: torch.device,
) -> torch.Tensor:
    """
    Robustly extract a boolean valid-token mask from any attention_mask format:
      - (B, N) int/bool     → padding mask
      - (B, N) float        → -inf/0 additive mask
      - (B, 1, N) or (B, 1, 1, N) broadcastable
      - (B, H, N, N) full attention matrix
    """
    if mask is None:
        return torch.ones(batch_size, seq_len, device=device, dtype=torch.bool)

    with torch.no_grad():
        if mask.dim() == 2:
            if mask.dtype in (
                torch.bool,
                torch.int8,
                torch.int16,
                torch.int32,
                torch.int64,
            ):
                return mask.bool()
            return mask > -1e4

        if mask.dim() == 3:
            if mask.shape[1] == 1:
                m = mask[:, 0, :]
            else:
                m = mask.any(dim=1)
            if m.dtype not in (
                torch.bool,
                torch.int8,
                torch.int16,
                torch.int32,
                torch.int64,
            ):
                m = m > -1e4
            return m.bool()

        if mask.dim() == 4:
            m = mask.any(dim=1).any(dim=1)
            if m.dtype not in (
                torch.bool,
                torch.int8,
                torch.int16,
                torch.int32,
                torch.int64,
            ):
                m = m > -1e4
            return m.bool()

    return torch.ones(batch_size, seq_len, device=device, dtype=torch.bool)

Robustly extract a boolean valid-token mask from any attention_mask format: - (B, N) int/bool → padding mask - (B, N) float → -inf/0 additive mask - (B, 1, N) or (B, 1, 1, N) broadcastable - (B, H, N, N) full attention matrix

def inject_smart_weighted_ensemble(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) ‑> torch.nn.modules.module.Module | Any | None
Expand source code
@ATTENTION_REGISTRY.register("weighted_ensemble_module_inject")
@ATTENTION_REGISTRY.register("weighted_ensemble_inject")
@ATTENTION_REGISTRY.register("inject_weighted_ensemble")
@ATTENTION_REGISTRY.register("hybrid_attention_inject")
@ATTENTION_REGISTRY.register("inject_hybrid_attention")
def inject_weighted_ensemble(
    layer: Any = None,
    model: Any = None,
    model_config: Any = None,
    comp_config: Any = None,
    layer_idx: int = 0,
    total_layers: int = 12,
    old_attn: nn.Module | None = None,
    _depth: int = 0,
    **kwargs: Any,
) -> nn.Module | Any | None:
    # HA-FIX-03: recursion guard.
    if _depth > 8:
        logger.warning("inject_weighted_ensemble: max recursion depth exceeded.")
        return None

    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

    # 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_weighted_ensemble(
                layer=lyr,
                model_config=cfg,
                comp_config=comp_config,
                layer_idx=idx,
                total_layers=tot,
                _depth=_depth + 1,
                **kwargs,
            )
            if res is not None:
                num_injected += 1
        return target if num_injected > 0 else None

    layer = target
    try:
        if model_config is None:
            if any(hasattr(layer, a) for a in ("hidden_size", "d_model", "n_embd", "dim")):
                model_config = layer
            elif hasattr(layer, "attention"):
                old_attn = layer.attention

        src_attn = (
            old_attn
            if old_attn is not None
            else getattr(layer, "attention", getattr(layer, "self", getattr(layer, "attn", None)))
        )
        if src_attn is None:
            return None

        # HA-FIX-04: double-injection guard.
        if isinstance(src_attn, HybridAttention):
            return None
        if "HybridAttention" in src_attn.__class__.__name__:
            return None
        if isinstance(getattr(src_attn, "inner", None), HybridAttention):
            return None
        if isinstance(getattr(src_attn, "_wrapped_attention", None), HybridAttention):
            return None

        new_attn = HybridAttention(
            config=model_config,
            pretrained_attn=src_attn,
            **kwargs,
        )

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

        # HA-FIX-06: is_nested_encoder correctly set per branch.
        if hasattr(layer, "attention"):
            if hasattr(layer.attention, "self"):
                layer.attention.self = new_attn
                new_attn.is_nested_encoder = True
            else:
                layer.attention = new_attn
                new_attn.is_nested_encoder = False
        elif hasattr(layer, "self_attn"):
            layer.self_attn = new_attn
            new_attn.is_nested_encoder = False
        elif hasattr(layer, "self"):
            layer.self = new_attn
            new_attn.is_nested_encoder = True
        elif hasattr(layer, "attn"):
            layer.attn = new_attn
            new_attn.is_nested_encoder = False

        return new_attn
    except Exception as e:
        logger.exception("Failed to inject hybrid weighted ensemble: %s", e)
        return None
def inject_weighted_ensemble(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) ‑> torch.nn.modules.module.Module | Any | None
Expand source code
@ATTENTION_REGISTRY.register("weighted_ensemble_module_inject")
@ATTENTION_REGISTRY.register("weighted_ensemble_inject")
@ATTENTION_REGISTRY.register("inject_weighted_ensemble")
@ATTENTION_REGISTRY.register("hybrid_attention_inject")
@ATTENTION_REGISTRY.register("inject_hybrid_attention")
def inject_weighted_ensemble(
    layer: Any = None,
    model: Any = None,
    model_config: Any = None,
    comp_config: Any = None,
    layer_idx: int = 0,
    total_layers: int = 12,
    old_attn: nn.Module | None = None,
    _depth: int = 0,
    **kwargs: Any,
) -> nn.Module | Any | None:
    # HA-FIX-03: recursion guard.
    if _depth > 8:
        logger.warning("inject_weighted_ensemble: max recursion depth exceeded.")
        return None

    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

    # 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_weighted_ensemble(
                layer=lyr,
                model_config=cfg,
                comp_config=comp_config,
                layer_idx=idx,
                total_layers=tot,
                _depth=_depth + 1,
                **kwargs,
            )
            if res is not None:
                num_injected += 1
        return target if num_injected > 0 else None

    layer = target
    try:
        if model_config is None:
            if any(hasattr(layer, a) for a in ("hidden_size", "d_model", "n_embd", "dim")):
                model_config = layer
            elif hasattr(layer, "attention"):
                old_attn = layer.attention

        src_attn = (
            old_attn
            if old_attn is not None
            else getattr(layer, "attention", getattr(layer, "self", getattr(layer, "attn", None)))
        )
        if src_attn is None:
            return None

        # HA-FIX-04: double-injection guard.
        if isinstance(src_attn, HybridAttention):
            return None
        if "HybridAttention" in src_attn.__class__.__name__:
            return None
        if isinstance(getattr(src_attn, "inner", None), HybridAttention):
            return None
        if isinstance(getattr(src_attn, "_wrapped_attention", None), HybridAttention):
            return None

        new_attn = HybridAttention(
            config=model_config,
            pretrained_attn=src_attn,
            **kwargs,
        )

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

        # HA-FIX-06: is_nested_encoder correctly set per branch.
        if hasattr(layer, "attention"):
            if hasattr(layer.attention, "self"):
                layer.attention.self = new_attn
                new_attn.is_nested_encoder = True
            else:
                layer.attention = new_attn
                new_attn.is_nested_encoder = False
        elif hasattr(layer, "self_attn"):
            layer.self_attn = new_attn
            new_attn.is_nested_encoder = False
        elif hasattr(layer, "self"):
            layer.self = new_attn
            new_attn.is_nested_encoder = True
        elif hasattr(layer, "attn"):
            layer.attn = new_attn
            new_attn.is_nested_encoder = False

        return new_attn
    except Exception as e:
        logger.exception("Failed to inject hybrid weighted ensemble: %s", e)
        return None

Classes

class HybridAttention (config: Any | None = None,
rank: int | None = None,
pretrained_attn: nn.Module | None = None,
hidden_size: int | None = None,
num_heads: int | None = None,
head_dim: int | None = None,
dropout: float = 0.1,
is_decoder: bool | None = None,
**kwargs: Any)
Expand source code
@ATTENTION_REGISTRY.register("hybrid_attention", supports_causal=True, supports_gqa=True)
@ATTENTION_REGISTRY.register("weighted_ensemble", supports_causal=True, supports_gqa=True)
@ATTENTION_REGISTRY.register("smart_weighted_ensemble", supports_causal=True, supports_gqa=True)
class HybridAttention(BaseAttention):
    """
    Hybrid softmax + linear attention with per-head dynamic gated fusion.
    """

    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,
        pretrained_attn: nn.Module | None = None,
        hidden_size: int | None = None,
        num_heads: int | None = None,
        head_dim: int | None = None,
        dropout: float = 0.1,
        is_decoder: bool | None = None,
        **kwargs: Any,
    ) -> None:
        super().__init__(config, rank, **kwargs)

        cfg_source = getattr(config, "text_config", config) if config is not None else None
        h_size = (
            hidden_size
            or getattr(cfg_source, "hidden_size", None)
            or getattr(cfg_source, "d_model", None)
            or getattr(cfg_source, "n_embd", None)
            or getattr(cfg_source, "dim", None)
            or kwargs.get("hidden_size", 768)
        )
        n_heads = (
            num_heads
            or getattr(cfg_source, "num_attention_heads", None)
            or getattr(cfg_source, "num_heads", None)
            or getattr(cfg_source, "n_heads", None)
            or getattr(cfg_source, "n_head", None)
            or kwargs.get("num_heads", 12)
        )
        h_dim = (
            head_dim
            or getattr(cfg_source, "head_dim", None)
            or getattr(cfg_source, "d_kv", None)
            or (int(h_size) // max(1, int(n_heads)))
        )
        n_kv_heads = (
            getattr(cfg_source, "num_key_value_heads", None)
            or getattr(cfg_source, "num_kv_heads", None)
            or kwargs.get("num_key_value_heads", n_heads)
        )
        drop_rate = (
            getattr(cfg_source, "attention_probs_dropout_prob", None)
            or getattr(cfg_source, "dropout", None)
            or kwargs.get("dropout", dropout)
        )

        self.hidden_size: int = int(h_size)
        self.num_heads: int = int(n_heads)
        self.head_dim: int = int(h_dim)
        self.num_kv_heads: int = int(n_kv_heads)
        self.kv_repeat: int = max(1, self.num_heads // max(1, self.num_kv_heads))
        self.all_head_size: int = self.num_heads * self.head_dim
        self.all_kv_head_size: int = self.num_kv_heads * self.head_dim

        # Decoder detection
        self.is_decoder = _detect_decoder(config, is_decoder, pretrained_attn)
        self.is_nested_encoder: bool = False

        # Projections
        self.q_proj = nn.Linear(self.hidden_size, self.all_head_size, bias=True)
        self.k_proj = nn.Linear(self.hidden_size, self.all_kv_head_size, bias=True)
        self.v_proj = nn.Linear(self.hidden_size, self.all_kv_head_size, bias=True)
        self.out_proj = nn.Linear(self.all_head_size, self.hidden_size, bias=True)
        self.dropout = nn.Dropout(drop_rate)

        # Per-head gate
        self.ensemble_gate = nn.Linear(self.hidden_size, self.num_heads)

        # Initialize gate to prefer softmax (sigmoid(10.0) ≈ 0.99995)
        with torch.no_grad():
            self.ensemble_gate.weight.zero_()
            self.ensemble_gate.bias.fill_(10.0)

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

    # ------------------------------------------------------------------
    # Aliases
    # ------------------------------------------------------------------
    @property
    def query(self):
        return self.q_proj

    @property
    def key(self):
        return self.k_proj

    @property
    def value(self):
        return self.v_proj

    @property
    def dense(self):
        return self.out_proj

    @property
    def q_lin(self):
        return self.q_proj

    @property
    def k_lin(self):
        return self.k_proj

    @property
    def v_lin(self):
        return self.v_proj

    @property
    def out_lin(self):
        return self.out_proj

    @property
    def o_proj(self):
        return self.out_proj

    @property
    def c_proj(self):
        return self.out_proj

    # ------------------------------------------------------------------
    # Standard softmax branch (HA-FIX-10: optional SDPA fast path)
    # ------------------------------------------------------------------
    def _standard_branch(
        self,
        Q: torch.Tensor,
        K: torch.Tensor,
        V: torch.Tensor,
        attention_mask: torch.Tensor | None,
        past_len: int = 0,
        valid_mask: torch.Tensor | None = None,
    ) -> torch.Tensor:
        B, H, N, Dh = Q.shape
        N_k = K.shape[2]

        # HA-FIX-10: SDPA fast path when training/eval allows it.
        can_sdpa = (not self.training or self.dropout.p == 0.0) and True
        if can_sdpa:
            try:
                q_sdp = Q.half() if Q.dtype == torch.float32 else Q
                k_sdp = K.half() if K.dtype == torch.float32 else K
                v_sdp = V.half() if V.dtype == torch.float32 else V

                attn_bias_sdp = None
                if valid_mask is not None:
                    # Build a proper (B, 1, 1, N_k) additive mask.
                    attn_bias_sdp = ((valid_mask[:, None, None, :].float() - 1.0) * 1e4).to(
                        q_sdp.dtype
                    )

                is_causal_sdp = self.is_decoder and past_len == 0 and N > 1

                out_sdp = F.scaled_dot_product_attention(
                    q_sdp,
                    k_sdp,
                    v_sdp,
                    attn_mask=attn_bias_sdp,
                    is_causal=is_causal_sdp,
                    dropout_p=self.dropout.p if self.training else 0.0,
                )
                out_sdp = out_sdp.to(Q.dtype)
                return torch.nan_to_num(out_sdp, nan=0.0)
            except Exception as e:
                logger.debug("SDPA fast path failed: %s; falling back to manual.", e)

        # Manual path
        scale = 1.0 / math.sqrt(Dh)
        scores = torch.matmul(Q.float(), K.float().transpose(-2, -1).contiguous()) * scale

        # Causal mask (offset-aware for cache)
        if self.is_decoder and N > 1:
            if past_len > 0:
                abs_q = (past_len + torch.arange(N, device=Q.device)).unsqueeze(1)
                abs_k = torch.arange(N_k, device=Q.device).unsqueeze(0)
                causal_mask = abs_k <= abs_q
            else:
                causal_mask = torch.tril(torch.ones(N, N_k, device=Q.device, dtype=torch.bool))
            scores = scores.masked_fill(~causal_mask.unsqueeze(0).unsqueeze(0), -1e4)

        # Padding mask
        if valid_mask is not None:
            scores = scores.masked_fill(~valid_mask[:, None, None, :].bool(), -1e4)
        elif attention_mask is not None:
            if attention_mask.dim() == 2:
                ext_mask = (1.0 - attention_mask.unsqueeze(1).unsqueeze(2).float()) * -1e4
                scores = scores + ext_mask.to(scores.dtype)
            elif attention_mask.dim() == 3:
                scores = scores + attention_mask.unsqueeze(1).to(scores.dtype)
            elif attention_mask.dim() == 4:
                scores = scores + attention_mask.to(scores.dtype)

        scores = torch.nan_to_num(scores, nan=-1e4, posinf=1e4, neginf=-1e4)
        attn = F.softmax(scores, dim=-1).to(Q.dtype)
        attn = self.dropout(attn)
        return torch.matmul(attn, V)

    # ------------------------------------------------------------------
    # Linear branch (HA-FIX-02: proper causal + padding)
    # ------------------------------------------------------------------
    def _linear_branch(
        self,
        Q: torch.Tensor,
        K: torch.Tensor,
        V: torch.Tensor,
        valid_mask: torch.Tensor,
        past_len: int = 0,
    ) -> torch.Tensor:
        phi_Q = F.elu(Q.float()) + 1.0
        phi_K = F.elu(K.float()) + 1.0
        V_f = V.float()

        # Padding mask on K and V
        mask = valid_mask.unsqueeze(1).unsqueeze(-1).to(phi_K.dtype)
        phi_K = phi_K * mask
        V_f = V_f * mask

        B_h, H_h, N_h, D_h = phi_Q.shape
        phi_K.shape[2]

        # Handle cache: query positions are [past_len .. past_len + N_h), keys are [0 .. N_k)
        if self.is_decoder and past_len > 0:
            # Only new queries, keys include cache.
            # Non-causal over the full key set (past + new).
            KV = torch.matmul(phi_K.transpose(-2, -1).contiguous(), V_f)
            Z = phi_K.sum(dim=-2, keepdim=True)
            num = torch.matmul(phi_Q, KV)
            den = torch.matmul(phi_Q, Z.transpose(-2, -1).contiguous()).clamp(min=1e-6)
            out = num / den
            return torch.nan_to_num(out, nan=0.0).to(Q.dtype)

        if self.is_decoder:
            # Chunked causal linear attention.
            chunk_size = 128
            num_chunks = (N_h + chunk_size - 1) // chunk_size

            state_KV = torch.zeros(B_h, H_h, D_h, D_h, device=phi_Q.device, dtype=phi_Q.dtype)
            state_K = torch.zeros(B_h, H_h, D_h, device=phi_Q.device, dtype=phi_Q.dtype)

            num_list = []
            den_list = []

            for c in range(num_chunks):
                s = c * chunk_size
                e = min(N_h, s + chunk_size)

                phi_Q_c = phi_Q[:, :, s:e]
                phi_K_c = phi_K[:, :, s:e]
                V_c = V_f[:, :, s:e]

                KV_c = torch.einsum("bhnd,bhne->bhnde", phi_K_c, V_c)
                KV_c_cumsum = torch.cumsum(KV_c, dim=2)
                num_intra = torch.einsum("bhnd,bhnde->bhne", phi_Q_c, KV_c_cumsum)

                K_c_cumsum = torch.cumsum(phi_K_c, dim=2)
                den_intra = torch.einsum("bhnd,bhnd->bhn", phi_Q_c, K_c_cumsum)

                num_inter = torch.einsum("bhnd,bhde->bhne", phi_Q_c, state_KV)
                den_inter = torch.einsum("bhnd,bhd->bhn", phi_Q_c, state_K)

                num_list.append(num_intra + num_inter)
                den_list.append(den_intra + den_inter)

                state_KV = state_KV + KV_c_cumsum[:, :, -1]
                state_K = state_K + K_c_cumsum[:, :, -1]

            num = torch.cat(num_list, dim=2)
            den = torch.cat(den_list, dim=2).unsqueeze(-1)
        else:
            KV = torch.matmul(phi_K.transpose(-2, -1).contiguous(), V_f)
            Z = phi_K.sum(dim=-2, keepdim=True)
            num = torch.matmul(phi_Q, KV)
            den = torch.matmul(phi_Q, Z.transpose(-2, -1).contiguous())

        den = den.clamp(min=1e-6)
        out = num / den
        return torch.nan_to_num(out, nan=0.0).to(Q.dtype)

    # ------------------------------------------------------------------
    # Forward
    # ------------------------------------------------------------------
    def forward(
        self,
        hidden_states: torch.Tensor,
        attention_mask: torch.Tensor | None = None,
        head_mask: torch.Tensor | None = None,
        encoder_hidden_states: torch.Tensor | None = None,
        encoder_attention_mask: torch.Tensor | None = None,
        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,
        layer_idx: int = 0,
        *args: Any,
        **kwargs: Any,
    ) -> tuple[torch.Tensor, torch.Tensor | None]:
        B, N, _ = hidden_states.shape
        device = hidden_states.device

        is_cross = encoder_hidden_states is not None
        kv_input = encoder_hidden_states if is_cross else hidden_states
        N_kv_new = kv_input.shape[1]

        eff_mask = encoder_attention_mask if is_cross else attention_mask
        valid_mask_new = _extract_valid_mask(eff_mask, B, N_kv_new, device)

        # Projections
        Q = (
            self.q_proj(hidden_states)
            .view(B, N, self.num_heads, self.head_dim)
            .transpose(1, 2)
            .contiguous()
        )
        K_new = (
            self.k_proj(kv_input)
            .view(B, N_kv_new, self.num_kv_heads, self.head_dim)
            .transpose(1, 2)
            .contiguous()
        )
        V_new = (
            self.v_proj(kv_input)
            .view(B, N_kv_new, self.num_kv_heads, self.head_dim)
            .transpose(1, 2)
            .contiguous()
        )

        # HA-FIX-08: RoPE with past_len offset
        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
            past_len_rope = 0
            if past_key_value is not None:
                if isinstance(past_key_value, (tuple, list)) and len(past_key_value) >= 1:
                    past_len_rope = past_key_value[0].shape[-2]
                elif hasattr(past_key_value, "key_cache") and past_key_value.key_cache:
                    past_len_rope = past_key_value.key_cache[0].shape[-2]
            cos = cos[..., past_len_rope : past_len_rope + N, :]
            sin = sin[..., past_len_rope : past_len_rope + N, :]
            Q, K_new = _apply_rope(Q, K_new, cos, sin)

        # HA-FIX-07: KV-cache
        past_k = past_v = None
        if past_key_value is not None and not is_cross:
            if isinstance(past_key_value, (tuple, list)) and len(past_key_value) >= 2:
                past_k, past_v = past_key_value[0], past_key_value[1]
            elif hasattr(past_key_value, "key_cache") and past_key_value.key_cache:
                idx = min(layer_idx, len(past_key_value.key_cache) - 1)
                past_k = past_key_value.key_cache[idx]
                past_v = past_key_value.value_cache[idx]

        if past_k is not None and not is_cross:
            K_cat = torch.cat([past_k, K_new], dim=-2)
            V_cat = torch.cat([past_v, V_new], dim=-2)
        else:
            K_cat = K_new
            V_cat = V_new

        if past_key_value is not None and hasattr(past_key_value, "update") and not is_cross:
            past_key_value.update(K_new, V_new, layer_idx)

        N_kv = K_cat.shape[2]
        past_len = N_kv - N if (self.is_decoder and not is_cross) else 0

        if self.kv_repeat > 1:
            K_cat = repeat_kv(K_cat, self.kv_repeat)
            V_cat = repeat_kv(V_cat, self.kv_repeat)

        # Valid mask for the full key set (with past)
        if past_len > 0:
            past_valid = torch.ones(B, past_len, device=device, dtype=torch.bool)
            valid_mask_full = torch.cat([past_valid, valid_mask_new], dim=1)
        else:
            valid_mask_full = valid_mask_new

        # Two branches
        out_standard = self._standard_branch(
            Q,
            K_cat,
            V_cat,
            eff_mask,
            past_len=past_len,
            valid_mask=valid_mask_full,
        )
        out_linear = self._linear_branch(
            Q,
            K_cat,
            V_cat,
            valid_mask_full,
            past_len=past_len,
        )

        # Per-head gating
        gate = (
            torch.sigmoid(self.ensemble_gate(hidden_states))
            .view(B, N, self.num_heads, 1)
            .transpose(1, 2)
            .contiguous()
        )
        fused = gate * out_standard + (1.0 - gate) * out_linear

        # HA-FIX-09: head_mask broadcasting corrected.
        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)  # (B, H, 1, 1)
            elif head_mask.dim() == 3:
                head_mask = head_mask[:, :, :1].unsqueeze(-1)  # (B, H, 1, 1)
            fused = fused * head_mask.to(fused.dtype)

        fused = fused.transpose(1, 2).contiguous().view(B, N, self.all_head_size)
        fused = torch.nan_to_num(fused, nan=0.0, posinf=1e4, neginf=-1e4)

        # HA-FIX-01: out_proj ALWAYS executed.
        if self.is_nested_encoder:
            # Nested encoder: outer module supplies out_proj, but we still run
            # the (identity-initialised) internal out_proj for parameter stability.
            out = self.out_proj(fused)
        else:
            out = self.out_proj(fused)
            out = self.dropout(out)

        out = torch.nan_to_num(out, nan=0.0, posinf=1e4, neginf=-1e4)
        return out, None

    # ------------------------------------------------------------------
    # copy_weights
    # ------------------------------------------------------------------
    def copy_weights(self, old_attn: Any) -> None:
        if old_attn is None:
            return

        cls_name = old_attn.__class__.__name__
        has_out_proj = any(
            hasattr(old_attn, attr)
            for attr in ("dense", "out_proj", "c_proj", "out_lin", "o_proj", "wo", "o")
        )
        self.is_nested_encoder = (
            cls_name in ("BertSelfAttention", "RobertaSelfAttention")
            or (cls_name.endswith("SelfAttention") and not has_out_proj)
            or (
                hasattr(old_attn, "self")
                and hasattr(getattr(old_attn, "self", None), "query")
                and hasattr(old_attn, "output")
                and hasattr(getattr(old_attn, "output", None), "dense")
            )
        )
        logger.debug("HybridAttention.copy_weights: nested_encoder=%s", self.is_nested_encoder)

        if hasattr(old_attn, "is_decoder"):
            self.is_decoder = bool(old_attn.is_decoder)
        elif cls_name in (
            "GPT2Attention",
            "Qwen2Attention",
            "LlamaAttention",
        ) or hasattr(old_attn, "c_attn"):
            self.is_decoder = True

        from neuralops.core.adapters import UniversalAdapter

        proj_targets = [
            ("q", self.q_proj),
            ("k", self.k_proj),
            ("v", self.v_proj),
        ]
        if not self.is_nested_encoder:
            proj_targets.append(("o", self.out_proj))

        for proj, target in proj_targets:
            try:
                w, b = UniversalAdapter.get_weight_bias(old_attn, proj)
            except Exception as e:
                logger.debug("UniversalAdapter extraction failed for %s: %s", proj, e)
                w, b = None, None

            if w is None:
                logger.warning("HybridAttention: no weight for '%s' in %s", proj, cls_name)
                continue

            if w.shape != target.weight.shape:
                if w.t().shape == target.weight.shape:
                    w = w.t().contiguous()
                else:
                    logger.warning(
                        "HybridAttention: shape mismatch for %s: src=%s, tgt=%s. Truncating.",
                        proj,
                        tuple(w.shape),
                        tuple(target.weight.shape),
                    )
                    out_d, in_d = target.weight.shape
                    s_out, s_in = w.shape
                    with torch.no_grad():
                        target.weight.data.zero_()
                        target.weight.data[: min(out_d, s_out), : min(in_d, s_in)].copy_(
                            w[: min(out_d, s_out), : min(in_d, s_in)]
                        )
                    if b is not None and target.bias is not None:
                        min_b = min(target.bias.numel(), b.numel())
                        with torch.no_grad():
                            target.bias.data.zero_()
                            target.bias.data[:min_b].copy_(b[:min_b])
                    continue

            with torch.no_grad():
                norm = torch.linalg.norm(w.float()).item()
                if norm < 1e-4:
                    logger.warning(
                        "HybridAttention: %s weight has near-zero Frobenius norm (%s)",
                        proj,
                        norm,
                    )
                target.weight.copy_(w)
                if b is not None and target.bias is not None:
                    target.bias.copy_(b)

Hybrid softmax + linear attention with per-head dynamic gated fusion.

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_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 c_proj
Expand source code
@property
def c_proj(self):
    return self.out_proj
prop dense
Expand source code
@property
def dense(self):
    return self.out_proj
prop k_lin
Expand source code
@property
def k_lin(self):
    return self.k_proj
prop key
Expand source code
@property
def key(self):
    return self.k_proj
prop o_proj
Expand source code
@property
def o_proj(self):
    return self.out_proj
prop out_lin
Expand source code
@property
def out_lin(self):
    return self.out_proj
prop q_lin
Expand source code
@property
def q_lin(self):
    return self.q_proj
prop query
Expand source code
@property
def query(self):
    return self.q_proj
prop v_lin
Expand source code
@property
def v_lin(self):
    return self.v_proj
prop value
Expand source code
@property
def value(self):
    return self.v_proj

Methods

def copy_weights(self, old_attn: Any) ‑> None
Expand source code
def copy_weights(self, old_attn: Any) -> None:
    if old_attn is None:
        return

    cls_name = old_attn.__class__.__name__
    has_out_proj = any(
        hasattr(old_attn, attr)
        for attr in ("dense", "out_proj", "c_proj", "out_lin", "o_proj", "wo", "o")
    )
    self.is_nested_encoder = (
        cls_name in ("BertSelfAttention", "RobertaSelfAttention")
        or (cls_name.endswith("SelfAttention") and not has_out_proj)
        or (
            hasattr(old_attn, "self")
            and hasattr(getattr(old_attn, "self", None), "query")
            and hasattr(old_attn, "output")
            and hasattr(getattr(old_attn, "output", None), "dense")
        )
    )
    logger.debug("HybridAttention.copy_weights: nested_encoder=%s", self.is_nested_encoder)

    if hasattr(old_attn, "is_decoder"):
        self.is_decoder = bool(old_attn.is_decoder)
    elif cls_name in (
        "GPT2Attention",
        "Qwen2Attention",
        "LlamaAttention",
    ) or hasattr(old_attn, "c_attn"):
        self.is_decoder = True

    from neuralops.core.adapters import UniversalAdapter

    proj_targets = [
        ("q", self.q_proj),
        ("k", self.k_proj),
        ("v", self.v_proj),
    ]
    if not self.is_nested_encoder:
        proj_targets.append(("o", self.out_proj))

    for proj, target in proj_targets:
        try:
            w, b = UniversalAdapter.get_weight_bias(old_attn, proj)
        except Exception as e:
            logger.debug("UniversalAdapter extraction failed for %s: %s", proj, e)
            w, b = None, None

        if w is None:
            logger.warning("HybridAttention: no weight for '%s' in %s", proj, cls_name)
            continue

        if w.shape != target.weight.shape:
            if w.t().shape == target.weight.shape:
                w = w.t().contiguous()
            else:
                logger.warning(
                    "HybridAttention: shape mismatch for %s: src=%s, tgt=%s. Truncating.",
                    proj,
                    tuple(w.shape),
                    tuple(target.weight.shape),
                )
                out_d, in_d = target.weight.shape
                s_out, s_in = w.shape
                with torch.no_grad():
                    target.weight.data.zero_()
                    target.weight.data[: min(out_d, s_out), : min(in_d, s_in)].copy_(
                        w[: min(out_d, s_out), : min(in_d, s_in)]
                    )
                if b is not None and target.bias is not None:
                    min_b = min(target.bias.numel(), b.numel())
                    with torch.no_grad():
                        target.bias.data.zero_()
                        target.bias.data[:min_b].copy_(b[:min_b])
                continue

        with torch.no_grad():
            norm = torch.linalg.norm(w.float()).item()
            if norm < 1e-4:
                logger.warning(
                    "HybridAttention: %s weight has near-zero Frobenius norm (%s)",
                    proj,
                    norm,
                )
            target.weight.copy_(w)
            if b is not None and target.bias is not None:
                target.bias.copy_(b)

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,
position_embeddings: tuple[torch.Tensor, torch.Tensor] | None = None,
layer_idx: int = 0,
*args: Any,
**kwargs: Any) ‑> tuple[torch.Tensor, torch.Tensor | None]
Expand source code
def forward(
    self,
    hidden_states: torch.Tensor,
    attention_mask: torch.Tensor | None = None,
    head_mask: torch.Tensor | None = None,
    encoder_hidden_states: torch.Tensor | None = None,
    encoder_attention_mask: torch.Tensor | None = None,
    past_key_value: Any | None = None,
    output_attentions: bool | None = False,
    output_hidden_states: bool | None = False,
    return_dict: bool | None = True,
    position_embeddings: tuple[torch.Tensor, torch.Tensor] | None = None,
    layer_idx: int = 0,
    *args: Any,
    **kwargs: Any,
) -> tuple[torch.Tensor, torch.Tensor | None]:
    B, N, _ = hidden_states.shape
    device = hidden_states.device

    is_cross = encoder_hidden_states is not None
    kv_input = encoder_hidden_states if is_cross else hidden_states
    N_kv_new = kv_input.shape[1]

    eff_mask = encoder_attention_mask if is_cross else attention_mask
    valid_mask_new = _extract_valid_mask(eff_mask, B, N_kv_new, device)

    # Projections
    Q = (
        self.q_proj(hidden_states)
        .view(B, N, self.num_heads, self.head_dim)
        .transpose(1, 2)
        .contiguous()
    )
    K_new = (
        self.k_proj(kv_input)
        .view(B, N_kv_new, self.num_kv_heads, self.head_dim)
        .transpose(1, 2)
        .contiguous()
    )
    V_new = (
        self.v_proj(kv_input)
        .view(B, N_kv_new, self.num_kv_heads, self.head_dim)
        .transpose(1, 2)
        .contiguous()
    )

    # HA-FIX-08: RoPE with past_len offset
    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
        past_len_rope = 0
        if past_key_value is not None:
            if isinstance(past_key_value, (tuple, list)) and len(past_key_value) >= 1:
                past_len_rope = past_key_value[0].shape[-2]
            elif hasattr(past_key_value, "key_cache") and past_key_value.key_cache:
                past_len_rope = past_key_value.key_cache[0].shape[-2]
        cos = cos[..., past_len_rope : past_len_rope + N, :]
        sin = sin[..., past_len_rope : past_len_rope + N, :]
        Q, K_new = _apply_rope(Q, K_new, cos, sin)

    # HA-FIX-07: KV-cache
    past_k = past_v = None
    if past_key_value is not None and not is_cross:
        if isinstance(past_key_value, (tuple, list)) and len(past_key_value) >= 2:
            past_k, past_v = past_key_value[0], past_key_value[1]
        elif hasattr(past_key_value, "key_cache") and past_key_value.key_cache:
            idx = min(layer_idx, len(past_key_value.key_cache) - 1)
            past_k = past_key_value.key_cache[idx]
            past_v = past_key_value.value_cache[idx]

    if past_k is not None and not is_cross:
        K_cat = torch.cat([past_k, K_new], dim=-2)
        V_cat = torch.cat([past_v, V_new], dim=-2)
    else:
        K_cat = K_new
        V_cat = V_new

    if past_key_value is not None and hasattr(past_key_value, "update") and not is_cross:
        past_key_value.update(K_new, V_new, layer_idx)

    N_kv = K_cat.shape[2]
    past_len = N_kv - N if (self.is_decoder and not is_cross) else 0

    if self.kv_repeat > 1:
        K_cat = repeat_kv(K_cat, self.kv_repeat)
        V_cat = repeat_kv(V_cat, self.kv_repeat)

    # Valid mask for the full key set (with past)
    if past_len > 0:
        past_valid = torch.ones(B, past_len, device=device, dtype=torch.bool)
        valid_mask_full = torch.cat([past_valid, valid_mask_new], dim=1)
    else:
        valid_mask_full = valid_mask_new

    # Two branches
    out_standard = self._standard_branch(
        Q,
        K_cat,
        V_cat,
        eff_mask,
        past_len=past_len,
        valid_mask=valid_mask_full,
    )
    out_linear = self._linear_branch(
        Q,
        K_cat,
        V_cat,
        valid_mask_full,
        past_len=past_len,
    )

    # Per-head gating
    gate = (
        torch.sigmoid(self.ensemble_gate(hidden_states))
        .view(B, N, self.num_heads, 1)
        .transpose(1, 2)
        .contiguous()
    )
    fused = gate * out_standard + (1.0 - gate) * out_linear

    # HA-FIX-09: head_mask broadcasting corrected.
    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)  # (B, H, 1, 1)
        elif head_mask.dim() == 3:
            head_mask = head_mask[:, :, :1].unsqueeze(-1)  # (B, H, 1, 1)
        fused = fused * head_mask.to(fused.dtype)

    fused = fused.transpose(1, 2).contiguous().view(B, N, self.all_head_size)
    fused = torch.nan_to_num(fused, nan=0.0, posinf=1e4, neginf=-1e4)

    # HA-FIX-01: out_proj ALWAYS executed.
    if self.is_nested_encoder:
        # Nested encoder: outer module supplies out_proj, but we still run
        # the (identity-initialised) internal out_proj for parameter stability.
        out = self.out_proj(fused)
    else:
        out = self.out_proj(fused)
        out = self.dropout(out)

    out = torch.nan_to_num(out, nan=0.0, posinf=1e4, neginf=-1e4)
    return out, None

Must return (output, attention_weights_or_None).

class WeightedEnsembleAttention (config: Any | None = None,
rank: int | None = None,
pretrained_attn: nn.Module | None = None,
hidden_size: int | None = None,
num_heads: int | None = None,
head_dim: int | None = None,
dropout: float = 0.1,
is_decoder: bool | None = None,
**kwargs: Any)
Expand source code
@ATTENTION_REGISTRY.register("hybrid_attention", supports_causal=True, supports_gqa=True)
@ATTENTION_REGISTRY.register("weighted_ensemble", supports_causal=True, supports_gqa=True)
@ATTENTION_REGISTRY.register("smart_weighted_ensemble", supports_causal=True, supports_gqa=True)
class HybridAttention(BaseAttention):
    """
    Hybrid softmax + linear attention with per-head dynamic gated fusion.
    """

    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,
        pretrained_attn: nn.Module | None = None,
        hidden_size: int | None = None,
        num_heads: int | None = None,
        head_dim: int | None = None,
        dropout: float = 0.1,
        is_decoder: bool | None = None,
        **kwargs: Any,
    ) -> None:
        super().__init__(config, rank, **kwargs)

        cfg_source = getattr(config, "text_config", config) if config is not None else None
        h_size = (
            hidden_size
            or getattr(cfg_source, "hidden_size", None)
            or getattr(cfg_source, "d_model", None)
            or getattr(cfg_source, "n_embd", None)
            or getattr(cfg_source, "dim", None)
            or kwargs.get("hidden_size", 768)
        )
        n_heads = (
            num_heads
            or getattr(cfg_source, "num_attention_heads", None)
            or getattr(cfg_source, "num_heads", None)
            or getattr(cfg_source, "n_heads", None)
            or getattr(cfg_source, "n_head", None)
            or kwargs.get("num_heads", 12)
        )
        h_dim = (
            head_dim
            or getattr(cfg_source, "head_dim", None)
            or getattr(cfg_source, "d_kv", None)
            or (int(h_size) // max(1, int(n_heads)))
        )
        n_kv_heads = (
            getattr(cfg_source, "num_key_value_heads", None)
            or getattr(cfg_source, "num_kv_heads", None)
            or kwargs.get("num_key_value_heads", n_heads)
        )
        drop_rate = (
            getattr(cfg_source, "attention_probs_dropout_prob", None)
            or getattr(cfg_source, "dropout", None)
            or kwargs.get("dropout", dropout)
        )

        self.hidden_size: int = int(h_size)
        self.num_heads: int = int(n_heads)
        self.head_dim: int = int(h_dim)
        self.num_kv_heads: int = int(n_kv_heads)
        self.kv_repeat: int = max(1, self.num_heads // max(1, self.num_kv_heads))
        self.all_head_size: int = self.num_heads * self.head_dim
        self.all_kv_head_size: int = self.num_kv_heads * self.head_dim

        # Decoder detection
        self.is_decoder = _detect_decoder(config, is_decoder, pretrained_attn)
        self.is_nested_encoder: bool = False

        # Projections
        self.q_proj = nn.Linear(self.hidden_size, self.all_head_size, bias=True)
        self.k_proj = nn.Linear(self.hidden_size, self.all_kv_head_size, bias=True)
        self.v_proj = nn.Linear(self.hidden_size, self.all_kv_head_size, bias=True)
        self.out_proj = nn.Linear(self.all_head_size, self.hidden_size, bias=True)
        self.dropout = nn.Dropout(drop_rate)

        # Per-head gate
        self.ensemble_gate = nn.Linear(self.hidden_size, self.num_heads)

        # Initialize gate to prefer softmax (sigmoid(10.0) ≈ 0.99995)
        with torch.no_grad():
            self.ensemble_gate.weight.zero_()
            self.ensemble_gate.bias.fill_(10.0)

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

    # ------------------------------------------------------------------
    # Aliases
    # ------------------------------------------------------------------
    @property
    def query(self):
        return self.q_proj

    @property
    def key(self):
        return self.k_proj

    @property
    def value(self):
        return self.v_proj

    @property
    def dense(self):
        return self.out_proj

    @property
    def q_lin(self):
        return self.q_proj

    @property
    def k_lin(self):
        return self.k_proj

    @property
    def v_lin(self):
        return self.v_proj

    @property
    def out_lin(self):
        return self.out_proj

    @property
    def o_proj(self):
        return self.out_proj

    @property
    def c_proj(self):
        return self.out_proj

    # ------------------------------------------------------------------
    # Standard softmax branch (HA-FIX-10: optional SDPA fast path)
    # ------------------------------------------------------------------
    def _standard_branch(
        self,
        Q: torch.Tensor,
        K: torch.Tensor,
        V: torch.Tensor,
        attention_mask: torch.Tensor | None,
        past_len: int = 0,
        valid_mask: torch.Tensor | None = None,
    ) -> torch.Tensor:
        B, H, N, Dh = Q.shape
        N_k = K.shape[2]

        # HA-FIX-10: SDPA fast path when training/eval allows it.
        can_sdpa = (not self.training or self.dropout.p == 0.0) and True
        if can_sdpa:
            try:
                q_sdp = Q.half() if Q.dtype == torch.float32 else Q
                k_sdp = K.half() if K.dtype == torch.float32 else K
                v_sdp = V.half() if V.dtype == torch.float32 else V

                attn_bias_sdp = None
                if valid_mask is not None:
                    # Build a proper (B, 1, 1, N_k) additive mask.
                    attn_bias_sdp = ((valid_mask[:, None, None, :].float() - 1.0) * 1e4).to(
                        q_sdp.dtype
                    )

                is_causal_sdp = self.is_decoder and past_len == 0 and N > 1

                out_sdp = F.scaled_dot_product_attention(
                    q_sdp,
                    k_sdp,
                    v_sdp,
                    attn_mask=attn_bias_sdp,
                    is_causal=is_causal_sdp,
                    dropout_p=self.dropout.p if self.training else 0.0,
                )
                out_sdp = out_sdp.to(Q.dtype)
                return torch.nan_to_num(out_sdp, nan=0.0)
            except Exception as e:
                logger.debug("SDPA fast path failed: %s; falling back to manual.", e)

        # Manual path
        scale = 1.0 / math.sqrt(Dh)
        scores = torch.matmul(Q.float(), K.float().transpose(-2, -1).contiguous()) * scale

        # Causal mask (offset-aware for cache)
        if self.is_decoder and N > 1:
            if past_len > 0:
                abs_q = (past_len + torch.arange(N, device=Q.device)).unsqueeze(1)
                abs_k = torch.arange(N_k, device=Q.device).unsqueeze(0)
                causal_mask = abs_k <= abs_q
            else:
                causal_mask = torch.tril(torch.ones(N, N_k, device=Q.device, dtype=torch.bool))
            scores = scores.masked_fill(~causal_mask.unsqueeze(0).unsqueeze(0), -1e4)

        # Padding mask
        if valid_mask is not None:
            scores = scores.masked_fill(~valid_mask[:, None, None, :].bool(), -1e4)
        elif attention_mask is not None:
            if attention_mask.dim() == 2:
                ext_mask = (1.0 - attention_mask.unsqueeze(1).unsqueeze(2).float()) * -1e4
                scores = scores + ext_mask.to(scores.dtype)
            elif attention_mask.dim() == 3:
                scores = scores + attention_mask.unsqueeze(1).to(scores.dtype)
            elif attention_mask.dim() == 4:
                scores = scores + attention_mask.to(scores.dtype)

        scores = torch.nan_to_num(scores, nan=-1e4, posinf=1e4, neginf=-1e4)
        attn = F.softmax(scores, dim=-1).to(Q.dtype)
        attn = self.dropout(attn)
        return torch.matmul(attn, V)

    # ------------------------------------------------------------------
    # Linear branch (HA-FIX-02: proper causal + padding)
    # ------------------------------------------------------------------
    def _linear_branch(
        self,
        Q: torch.Tensor,
        K: torch.Tensor,
        V: torch.Tensor,
        valid_mask: torch.Tensor,
        past_len: int = 0,
    ) -> torch.Tensor:
        phi_Q = F.elu(Q.float()) + 1.0
        phi_K = F.elu(K.float()) + 1.0
        V_f = V.float()

        # Padding mask on K and V
        mask = valid_mask.unsqueeze(1).unsqueeze(-1).to(phi_K.dtype)
        phi_K = phi_K * mask
        V_f = V_f * mask

        B_h, H_h, N_h, D_h = phi_Q.shape
        phi_K.shape[2]

        # Handle cache: query positions are [past_len .. past_len + N_h), keys are [0 .. N_k)
        if self.is_decoder and past_len > 0:
            # Only new queries, keys include cache.
            # Non-causal over the full key set (past + new).
            KV = torch.matmul(phi_K.transpose(-2, -1).contiguous(), V_f)
            Z = phi_K.sum(dim=-2, keepdim=True)
            num = torch.matmul(phi_Q, KV)
            den = torch.matmul(phi_Q, Z.transpose(-2, -1).contiguous()).clamp(min=1e-6)
            out = num / den
            return torch.nan_to_num(out, nan=0.0).to(Q.dtype)

        if self.is_decoder:
            # Chunked causal linear attention.
            chunk_size = 128
            num_chunks = (N_h + chunk_size - 1) // chunk_size

            state_KV = torch.zeros(B_h, H_h, D_h, D_h, device=phi_Q.device, dtype=phi_Q.dtype)
            state_K = torch.zeros(B_h, H_h, D_h, device=phi_Q.device, dtype=phi_Q.dtype)

            num_list = []
            den_list = []

            for c in range(num_chunks):
                s = c * chunk_size
                e = min(N_h, s + chunk_size)

                phi_Q_c = phi_Q[:, :, s:e]
                phi_K_c = phi_K[:, :, s:e]
                V_c = V_f[:, :, s:e]

                KV_c = torch.einsum("bhnd,bhne->bhnde", phi_K_c, V_c)
                KV_c_cumsum = torch.cumsum(KV_c, dim=2)
                num_intra = torch.einsum("bhnd,bhnde->bhne", phi_Q_c, KV_c_cumsum)

                K_c_cumsum = torch.cumsum(phi_K_c, dim=2)
                den_intra = torch.einsum("bhnd,bhnd->bhn", phi_Q_c, K_c_cumsum)

                num_inter = torch.einsum("bhnd,bhde->bhne", phi_Q_c, state_KV)
                den_inter = torch.einsum("bhnd,bhd->bhn", phi_Q_c, state_K)

                num_list.append(num_intra + num_inter)
                den_list.append(den_intra + den_inter)

                state_KV = state_KV + KV_c_cumsum[:, :, -1]
                state_K = state_K + K_c_cumsum[:, :, -1]

            num = torch.cat(num_list, dim=2)
            den = torch.cat(den_list, dim=2).unsqueeze(-1)
        else:
            KV = torch.matmul(phi_K.transpose(-2, -1).contiguous(), V_f)
            Z = phi_K.sum(dim=-2, keepdim=True)
            num = torch.matmul(phi_Q, KV)
            den = torch.matmul(phi_Q, Z.transpose(-2, -1).contiguous())

        den = den.clamp(min=1e-6)
        out = num / den
        return torch.nan_to_num(out, nan=0.0).to(Q.dtype)

    # ------------------------------------------------------------------
    # Forward
    # ------------------------------------------------------------------
    def forward(
        self,
        hidden_states: torch.Tensor,
        attention_mask: torch.Tensor | None = None,
        head_mask: torch.Tensor | None = None,
        encoder_hidden_states: torch.Tensor | None = None,
        encoder_attention_mask: torch.Tensor | None = None,
        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,
        layer_idx: int = 0,
        *args: Any,
        **kwargs: Any,
    ) -> tuple[torch.Tensor, torch.Tensor | None]:
        B, N, _ = hidden_states.shape
        device = hidden_states.device

        is_cross = encoder_hidden_states is not None
        kv_input = encoder_hidden_states if is_cross else hidden_states
        N_kv_new = kv_input.shape[1]

        eff_mask = encoder_attention_mask if is_cross else attention_mask
        valid_mask_new = _extract_valid_mask(eff_mask, B, N_kv_new, device)

        # Projections
        Q = (
            self.q_proj(hidden_states)
            .view(B, N, self.num_heads, self.head_dim)
            .transpose(1, 2)
            .contiguous()
        )
        K_new = (
            self.k_proj(kv_input)
            .view(B, N_kv_new, self.num_kv_heads, self.head_dim)
            .transpose(1, 2)
            .contiguous()
        )
        V_new = (
            self.v_proj(kv_input)
            .view(B, N_kv_new, self.num_kv_heads, self.head_dim)
            .transpose(1, 2)
            .contiguous()
        )

        # HA-FIX-08: RoPE with past_len offset
        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
            past_len_rope = 0
            if past_key_value is not None:
                if isinstance(past_key_value, (tuple, list)) and len(past_key_value) >= 1:
                    past_len_rope = past_key_value[0].shape[-2]
                elif hasattr(past_key_value, "key_cache") and past_key_value.key_cache:
                    past_len_rope = past_key_value.key_cache[0].shape[-2]
            cos = cos[..., past_len_rope : past_len_rope + N, :]
            sin = sin[..., past_len_rope : past_len_rope + N, :]
            Q, K_new = _apply_rope(Q, K_new, cos, sin)

        # HA-FIX-07: KV-cache
        past_k = past_v = None
        if past_key_value is not None and not is_cross:
            if isinstance(past_key_value, (tuple, list)) and len(past_key_value) >= 2:
                past_k, past_v = past_key_value[0], past_key_value[1]
            elif hasattr(past_key_value, "key_cache") and past_key_value.key_cache:
                idx = min(layer_idx, len(past_key_value.key_cache) - 1)
                past_k = past_key_value.key_cache[idx]
                past_v = past_key_value.value_cache[idx]

        if past_k is not None and not is_cross:
            K_cat = torch.cat([past_k, K_new], dim=-2)
            V_cat = torch.cat([past_v, V_new], dim=-2)
        else:
            K_cat = K_new
            V_cat = V_new

        if past_key_value is not None and hasattr(past_key_value, "update") and not is_cross:
            past_key_value.update(K_new, V_new, layer_idx)

        N_kv = K_cat.shape[2]
        past_len = N_kv - N if (self.is_decoder and not is_cross) else 0

        if self.kv_repeat > 1:
            K_cat = repeat_kv(K_cat, self.kv_repeat)
            V_cat = repeat_kv(V_cat, self.kv_repeat)

        # Valid mask for the full key set (with past)
        if past_len > 0:
            past_valid = torch.ones(B, past_len, device=device, dtype=torch.bool)
            valid_mask_full = torch.cat([past_valid, valid_mask_new], dim=1)
        else:
            valid_mask_full = valid_mask_new

        # Two branches
        out_standard = self._standard_branch(
            Q,
            K_cat,
            V_cat,
            eff_mask,
            past_len=past_len,
            valid_mask=valid_mask_full,
        )
        out_linear = self._linear_branch(
            Q,
            K_cat,
            V_cat,
            valid_mask_full,
            past_len=past_len,
        )

        # Per-head gating
        gate = (
            torch.sigmoid(self.ensemble_gate(hidden_states))
            .view(B, N, self.num_heads, 1)
            .transpose(1, 2)
            .contiguous()
        )
        fused = gate * out_standard + (1.0 - gate) * out_linear

        # HA-FIX-09: head_mask broadcasting corrected.
        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)  # (B, H, 1, 1)
            elif head_mask.dim() == 3:
                head_mask = head_mask[:, :, :1].unsqueeze(-1)  # (B, H, 1, 1)
            fused = fused * head_mask.to(fused.dtype)

        fused = fused.transpose(1, 2).contiguous().view(B, N, self.all_head_size)
        fused = torch.nan_to_num(fused, nan=0.0, posinf=1e4, neginf=-1e4)

        # HA-FIX-01: out_proj ALWAYS executed.
        if self.is_nested_encoder:
            # Nested encoder: outer module supplies out_proj, but we still run
            # the (identity-initialised) internal out_proj for parameter stability.
            out = self.out_proj(fused)
        else:
            out = self.out_proj(fused)
            out = self.dropout(out)

        out = torch.nan_to_num(out, nan=0.0, posinf=1e4, neginf=-1e4)
        return out, None

    # ------------------------------------------------------------------
    # copy_weights
    # ------------------------------------------------------------------
    def copy_weights(self, old_attn: Any) -> None:
        if old_attn is None:
            return

        cls_name = old_attn.__class__.__name__
        has_out_proj = any(
            hasattr(old_attn, attr)
            for attr in ("dense", "out_proj", "c_proj", "out_lin", "o_proj", "wo", "o")
        )
        self.is_nested_encoder = (
            cls_name in ("BertSelfAttention", "RobertaSelfAttention")
            or (cls_name.endswith("SelfAttention") and not has_out_proj)
            or (
                hasattr(old_attn, "self")
                and hasattr(getattr(old_attn, "self", None), "query")
                and hasattr(old_attn, "output")
                and hasattr(getattr(old_attn, "output", None), "dense")
            )
        )
        logger.debug("HybridAttention.copy_weights: nested_encoder=%s", self.is_nested_encoder)

        if hasattr(old_attn, "is_decoder"):
            self.is_decoder = bool(old_attn.is_decoder)
        elif cls_name in (
            "GPT2Attention",
            "Qwen2Attention",
            "LlamaAttention",
        ) or hasattr(old_attn, "c_attn"):
            self.is_decoder = True

        from neuralops.core.adapters import UniversalAdapter

        proj_targets = [
            ("q", self.q_proj),
            ("k", self.k_proj),
            ("v", self.v_proj),
        ]
        if not self.is_nested_encoder:
            proj_targets.append(("o", self.out_proj))

        for proj, target in proj_targets:
            try:
                w, b = UniversalAdapter.get_weight_bias(old_attn, proj)
            except Exception as e:
                logger.debug("UniversalAdapter extraction failed for %s: %s", proj, e)
                w, b = None, None

            if w is None:
                logger.warning("HybridAttention: no weight for '%s' in %s", proj, cls_name)
                continue

            if w.shape != target.weight.shape:
                if w.t().shape == target.weight.shape:
                    w = w.t().contiguous()
                else:
                    logger.warning(
                        "HybridAttention: shape mismatch for %s: src=%s, tgt=%s. Truncating.",
                        proj,
                        tuple(w.shape),
                        tuple(target.weight.shape),
                    )
                    out_d, in_d = target.weight.shape
                    s_out, s_in = w.shape
                    with torch.no_grad():
                        target.weight.data.zero_()
                        target.weight.data[: min(out_d, s_out), : min(in_d, s_in)].copy_(
                            w[: min(out_d, s_out), : min(in_d, s_in)]
                        )
                    if b is not None and target.bias is not None:
                        min_b = min(target.bias.numel(), b.numel())
                        with torch.no_grad():
                            target.bias.data.zero_()
                            target.bias.data[:min_b].copy_(b[:min_b])
                    continue

            with torch.no_grad():
                norm = torch.linalg.norm(w.float()).item()
                if norm < 1e-4:
                    logger.warning(
                        "HybridAttention: %s weight has near-zero Frobenius norm (%s)",
                        proj,
                        norm,
                    )
                target.weight.copy_(w)
                if b is not None and target.bias is not None:
                    target.bias.copy_(b)

Hybrid softmax + linear attention with per-head dynamic gated fusion.

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_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 c_proj
Expand source code
@property
def c_proj(self):
    return self.out_proj
prop dense
Expand source code
@property
def dense(self):
    return self.out_proj
prop k_lin
Expand source code
@property
def k_lin(self):
    return self.k_proj
prop key
Expand source code
@property
def key(self):
    return self.k_proj
prop o_proj
Expand source code
@property
def o_proj(self):
    return self.out_proj
prop out_lin
Expand source code
@property
def out_lin(self):
    return self.out_proj
prop q_lin
Expand source code
@property
def q_lin(self):
    return self.q_proj
prop query
Expand source code
@property
def query(self):
    return self.q_proj
prop v_lin
Expand source code
@property
def v_lin(self):
    return self.v_proj
prop value
Expand source code
@property
def value(self):
    return self.v_proj

Methods

def copy_weights(self, old_attn: Any) ‑> None
Expand source code
def copy_weights(self, old_attn: Any) -> None:
    if old_attn is None:
        return

    cls_name = old_attn.__class__.__name__
    has_out_proj = any(
        hasattr(old_attn, attr)
        for attr in ("dense", "out_proj", "c_proj", "out_lin", "o_proj", "wo", "o")
    )
    self.is_nested_encoder = (
        cls_name in ("BertSelfAttention", "RobertaSelfAttention")
        or (cls_name.endswith("SelfAttention") and not has_out_proj)
        or (
            hasattr(old_attn, "self")
            and hasattr(getattr(old_attn, "self", None), "query")
            and hasattr(old_attn, "output")
            and hasattr(getattr(old_attn, "output", None), "dense")
        )
    )
    logger.debug("HybridAttention.copy_weights: nested_encoder=%s", self.is_nested_encoder)

    if hasattr(old_attn, "is_decoder"):
        self.is_decoder = bool(old_attn.is_decoder)
    elif cls_name in (
        "GPT2Attention",
        "Qwen2Attention",
        "LlamaAttention",
    ) or hasattr(old_attn, "c_attn"):
        self.is_decoder = True

    from neuralops.core.adapters import UniversalAdapter

    proj_targets = [
        ("q", self.q_proj),
        ("k", self.k_proj),
        ("v", self.v_proj),
    ]
    if not self.is_nested_encoder:
        proj_targets.append(("o", self.out_proj))

    for proj, target in proj_targets:
        try:
            w, b = UniversalAdapter.get_weight_bias(old_attn, proj)
        except Exception as e:
            logger.debug("UniversalAdapter extraction failed for %s: %s", proj, e)
            w, b = None, None

        if w is None:
            logger.warning("HybridAttention: no weight for '%s' in %s", proj, cls_name)
            continue

        if w.shape != target.weight.shape:
            if w.t().shape == target.weight.shape:
                w = w.t().contiguous()
            else:
                logger.warning(
                    "HybridAttention: shape mismatch for %s: src=%s, tgt=%s. Truncating.",
                    proj,
                    tuple(w.shape),
                    tuple(target.weight.shape),
                )
                out_d, in_d = target.weight.shape
                s_out, s_in = w.shape
                with torch.no_grad():
                    target.weight.data.zero_()
                    target.weight.data[: min(out_d, s_out), : min(in_d, s_in)].copy_(
                        w[: min(out_d, s_out), : min(in_d, s_in)]
                    )
                if b is not None and target.bias is not None:
                    min_b = min(target.bias.numel(), b.numel())
                    with torch.no_grad():
                        target.bias.data.zero_()
                        target.bias.data[:min_b].copy_(b[:min_b])
                continue

        with torch.no_grad():
            norm = torch.linalg.norm(w.float()).item()
            if norm < 1e-4:
                logger.warning(
                    "HybridAttention: %s weight has near-zero Frobenius norm (%s)",
                    proj,
                    norm,
                )
            target.weight.copy_(w)
            if b is not None and target.bias is not None:
                target.bias.copy_(b)

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,
position_embeddings: tuple[torch.Tensor, torch.Tensor] | None = None,
layer_idx: int = 0,
*args: Any,
**kwargs: Any) ‑> tuple[torch.Tensor, torch.Tensor | None]
Expand source code
def forward(
    self,
    hidden_states: torch.Tensor,
    attention_mask: torch.Tensor | None = None,
    head_mask: torch.Tensor | None = None,
    encoder_hidden_states: torch.Tensor | None = None,
    encoder_attention_mask: torch.Tensor | None = None,
    past_key_value: Any | None = None,
    output_attentions: bool | None = False,
    output_hidden_states: bool | None = False,
    return_dict: bool | None = True,
    position_embeddings: tuple[torch.Tensor, torch.Tensor] | None = None,
    layer_idx: int = 0,
    *args: Any,
    **kwargs: Any,
) -> tuple[torch.Tensor, torch.Tensor | None]:
    B, N, _ = hidden_states.shape
    device = hidden_states.device

    is_cross = encoder_hidden_states is not None
    kv_input = encoder_hidden_states if is_cross else hidden_states
    N_kv_new = kv_input.shape[1]

    eff_mask = encoder_attention_mask if is_cross else attention_mask
    valid_mask_new = _extract_valid_mask(eff_mask, B, N_kv_new, device)

    # Projections
    Q = (
        self.q_proj(hidden_states)
        .view(B, N, self.num_heads, self.head_dim)
        .transpose(1, 2)
        .contiguous()
    )
    K_new = (
        self.k_proj(kv_input)
        .view(B, N_kv_new, self.num_kv_heads, self.head_dim)
        .transpose(1, 2)
        .contiguous()
    )
    V_new = (
        self.v_proj(kv_input)
        .view(B, N_kv_new, self.num_kv_heads, self.head_dim)
        .transpose(1, 2)
        .contiguous()
    )

    # HA-FIX-08: RoPE with past_len offset
    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
        past_len_rope = 0
        if past_key_value is not None:
            if isinstance(past_key_value, (tuple, list)) and len(past_key_value) >= 1:
                past_len_rope = past_key_value[0].shape[-2]
            elif hasattr(past_key_value, "key_cache") and past_key_value.key_cache:
                past_len_rope = past_key_value.key_cache[0].shape[-2]
        cos = cos[..., past_len_rope : past_len_rope + N, :]
        sin = sin[..., past_len_rope : past_len_rope + N, :]
        Q, K_new = _apply_rope(Q, K_new, cos, sin)

    # HA-FIX-07: KV-cache
    past_k = past_v = None
    if past_key_value is not None and not is_cross:
        if isinstance(past_key_value, (tuple, list)) and len(past_key_value) >= 2:
            past_k, past_v = past_key_value[0], past_key_value[1]
        elif hasattr(past_key_value, "key_cache") and past_key_value.key_cache:
            idx = min(layer_idx, len(past_key_value.key_cache) - 1)
            past_k = past_key_value.key_cache[idx]
            past_v = past_key_value.value_cache[idx]

    if past_k is not None and not is_cross:
        K_cat = torch.cat([past_k, K_new], dim=-2)
        V_cat = torch.cat([past_v, V_new], dim=-2)
    else:
        K_cat = K_new
        V_cat = V_new

    if past_key_value is not None and hasattr(past_key_value, "update") and not is_cross:
        past_key_value.update(K_new, V_new, layer_idx)

    N_kv = K_cat.shape[2]
    past_len = N_kv - N if (self.is_decoder and not is_cross) else 0

    if self.kv_repeat > 1:
        K_cat = repeat_kv(K_cat, self.kv_repeat)
        V_cat = repeat_kv(V_cat, self.kv_repeat)

    # Valid mask for the full key set (with past)
    if past_len > 0:
        past_valid = torch.ones(B, past_len, device=device, dtype=torch.bool)
        valid_mask_full = torch.cat([past_valid, valid_mask_new], dim=1)
    else:
        valid_mask_full = valid_mask_new

    # Two branches
    out_standard = self._standard_branch(
        Q,
        K_cat,
        V_cat,
        eff_mask,
        past_len=past_len,
        valid_mask=valid_mask_full,
    )
    out_linear = self._linear_branch(
        Q,
        K_cat,
        V_cat,
        valid_mask_full,
        past_len=past_len,
    )

    # Per-head gating
    gate = (
        torch.sigmoid(self.ensemble_gate(hidden_states))
        .view(B, N, self.num_heads, 1)
        .transpose(1, 2)
        .contiguous()
    )
    fused = gate * out_standard + (1.0 - gate) * out_linear

    # HA-FIX-09: head_mask broadcasting corrected.
    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)  # (B, H, 1, 1)
        elif head_mask.dim() == 3:
            head_mask = head_mask[:, :, :1].unsqueeze(-1)  # (B, H, 1, 1)
        fused = fused * head_mask.to(fused.dtype)

    fused = fused.transpose(1, 2).contiguous().view(B, N, self.all_head_size)
    fused = torch.nan_to_num(fused, nan=0.0, posinf=1e4, neginf=-1e4)

    # HA-FIX-01: out_proj ALWAYS executed.
    if self.is_nested_encoder:
        # Nested encoder: outer module supplies out_proj, but we still run
        # the (identity-initialised) internal out_proj for parameter stability.
        out = self.out_proj(fused)
    else:
        out = self.out_proj(fused)
        out = self.dropout(out)

    out = torch.nan_to_num(out, nan=0.0, posinf=1e4, neginf=-1e4)
    return out, None

Must return (output, attention_weights_or_None).

class SmartWeightedEnsembleAttention (config: Any | None = None,
rank: int | None = None,
pretrained_attn: nn.Module | None = None,
hidden_size: int | None = None,
num_heads: int | None = None,
head_dim: int | None = None,
dropout: float = 0.1,
is_decoder: bool | None = None,
**kwargs: Any)
Expand source code
@ATTENTION_REGISTRY.register("hybrid_attention", supports_causal=True, supports_gqa=True)
@ATTENTION_REGISTRY.register("weighted_ensemble", supports_causal=True, supports_gqa=True)
@ATTENTION_REGISTRY.register("smart_weighted_ensemble", supports_causal=True, supports_gqa=True)
class HybridAttention(BaseAttention):
    """
    Hybrid softmax + linear attention with per-head dynamic gated fusion.
    """

    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,
        pretrained_attn: nn.Module | None = None,
        hidden_size: int | None = None,
        num_heads: int | None = None,
        head_dim: int | None = None,
        dropout: float = 0.1,
        is_decoder: bool | None = None,
        **kwargs: Any,
    ) -> None:
        super().__init__(config, rank, **kwargs)

        cfg_source = getattr(config, "text_config", config) if config is not None else None
        h_size = (
            hidden_size
            or getattr(cfg_source, "hidden_size", None)
            or getattr(cfg_source, "d_model", None)
            or getattr(cfg_source, "n_embd", None)
            or getattr(cfg_source, "dim", None)
            or kwargs.get("hidden_size", 768)
        )
        n_heads = (
            num_heads
            or getattr(cfg_source, "num_attention_heads", None)
            or getattr(cfg_source, "num_heads", None)
            or getattr(cfg_source, "n_heads", None)
            or getattr(cfg_source, "n_head", None)
            or kwargs.get("num_heads", 12)
        )
        h_dim = (
            head_dim
            or getattr(cfg_source, "head_dim", None)
            or getattr(cfg_source, "d_kv", None)
            or (int(h_size) // max(1, int(n_heads)))
        )
        n_kv_heads = (
            getattr(cfg_source, "num_key_value_heads", None)
            or getattr(cfg_source, "num_kv_heads", None)
            or kwargs.get("num_key_value_heads", n_heads)
        )
        drop_rate = (
            getattr(cfg_source, "attention_probs_dropout_prob", None)
            or getattr(cfg_source, "dropout", None)
            or kwargs.get("dropout", dropout)
        )

        self.hidden_size: int = int(h_size)
        self.num_heads: int = int(n_heads)
        self.head_dim: int = int(h_dim)
        self.num_kv_heads: int = int(n_kv_heads)
        self.kv_repeat: int = max(1, self.num_heads // max(1, self.num_kv_heads))
        self.all_head_size: int = self.num_heads * self.head_dim
        self.all_kv_head_size: int = self.num_kv_heads * self.head_dim

        # Decoder detection
        self.is_decoder = _detect_decoder(config, is_decoder, pretrained_attn)
        self.is_nested_encoder: bool = False

        # Projections
        self.q_proj = nn.Linear(self.hidden_size, self.all_head_size, bias=True)
        self.k_proj = nn.Linear(self.hidden_size, self.all_kv_head_size, bias=True)
        self.v_proj = nn.Linear(self.hidden_size, self.all_kv_head_size, bias=True)
        self.out_proj = nn.Linear(self.all_head_size, self.hidden_size, bias=True)
        self.dropout = nn.Dropout(drop_rate)

        # Per-head gate
        self.ensemble_gate = nn.Linear(self.hidden_size, self.num_heads)

        # Initialize gate to prefer softmax (sigmoid(10.0) ≈ 0.99995)
        with torch.no_grad():
            self.ensemble_gate.weight.zero_()
            self.ensemble_gate.bias.fill_(10.0)

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

    # ------------------------------------------------------------------
    # Aliases
    # ------------------------------------------------------------------
    @property
    def query(self):
        return self.q_proj

    @property
    def key(self):
        return self.k_proj

    @property
    def value(self):
        return self.v_proj

    @property
    def dense(self):
        return self.out_proj

    @property
    def q_lin(self):
        return self.q_proj

    @property
    def k_lin(self):
        return self.k_proj

    @property
    def v_lin(self):
        return self.v_proj

    @property
    def out_lin(self):
        return self.out_proj

    @property
    def o_proj(self):
        return self.out_proj

    @property
    def c_proj(self):
        return self.out_proj

    # ------------------------------------------------------------------
    # Standard softmax branch (HA-FIX-10: optional SDPA fast path)
    # ------------------------------------------------------------------
    def _standard_branch(
        self,
        Q: torch.Tensor,
        K: torch.Tensor,
        V: torch.Tensor,
        attention_mask: torch.Tensor | None,
        past_len: int = 0,
        valid_mask: torch.Tensor | None = None,
    ) -> torch.Tensor:
        B, H, N, Dh = Q.shape
        N_k = K.shape[2]

        # HA-FIX-10: SDPA fast path when training/eval allows it.
        can_sdpa = (not self.training or self.dropout.p == 0.0) and True
        if can_sdpa:
            try:
                q_sdp = Q.half() if Q.dtype == torch.float32 else Q
                k_sdp = K.half() if K.dtype == torch.float32 else K
                v_sdp = V.half() if V.dtype == torch.float32 else V

                attn_bias_sdp = None
                if valid_mask is not None:
                    # Build a proper (B, 1, 1, N_k) additive mask.
                    attn_bias_sdp = ((valid_mask[:, None, None, :].float() - 1.0) * 1e4).to(
                        q_sdp.dtype
                    )

                is_causal_sdp = self.is_decoder and past_len == 0 and N > 1

                out_sdp = F.scaled_dot_product_attention(
                    q_sdp,
                    k_sdp,
                    v_sdp,
                    attn_mask=attn_bias_sdp,
                    is_causal=is_causal_sdp,
                    dropout_p=self.dropout.p if self.training else 0.0,
                )
                out_sdp = out_sdp.to(Q.dtype)
                return torch.nan_to_num(out_sdp, nan=0.0)
            except Exception as e:
                logger.debug("SDPA fast path failed: %s; falling back to manual.", e)

        # Manual path
        scale = 1.0 / math.sqrt(Dh)
        scores = torch.matmul(Q.float(), K.float().transpose(-2, -1).contiguous()) * scale

        # Causal mask (offset-aware for cache)
        if self.is_decoder and N > 1:
            if past_len > 0:
                abs_q = (past_len + torch.arange(N, device=Q.device)).unsqueeze(1)
                abs_k = torch.arange(N_k, device=Q.device).unsqueeze(0)
                causal_mask = abs_k <= abs_q
            else:
                causal_mask = torch.tril(torch.ones(N, N_k, device=Q.device, dtype=torch.bool))
            scores = scores.masked_fill(~causal_mask.unsqueeze(0).unsqueeze(0), -1e4)

        # Padding mask
        if valid_mask is not None:
            scores = scores.masked_fill(~valid_mask[:, None, None, :].bool(), -1e4)
        elif attention_mask is not None:
            if attention_mask.dim() == 2:
                ext_mask = (1.0 - attention_mask.unsqueeze(1).unsqueeze(2).float()) * -1e4
                scores = scores + ext_mask.to(scores.dtype)
            elif attention_mask.dim() == 3:
                scores = scores + attention_mask.unsqueeze(1).to(scores.dtype)
            elif attention_mask.dim() == 4:
                scores = scores + attention_mask.to(scores.dtype)

        scores = torch.nan_to_num(scores, nan=-1e4, posinf=1e4, neginf=-1e4)
        attn = F.softmax(scores, dim=-1).to(Q.dtype)
        attn = self.dropout(attn)
        return torch.matmul(attn, V)

    # ------------------------------------------------------------------
    # Linear branch (HA-FIX-02: proper causal + padding)
    # ------------------------------------------------------------------
    def _linear_branch(
        self,
        Q: torch.Tensor,
        K: torch.Tensor,
        V: torch.Tensor,
        valid_mask: torch.Tensor,
        past_len: int = 0,
    ) -> torch.Tensor:
        phi_Q = F.elu(Q.float()) + 1.0
        phi_K = F.elu(K.float()) + 1.0
        V_f = V.float()

        # Padding mask on K and V
        mask = valid_mask.unsqueeze(1).unsqueeze(-1).to(phi_K.dtype)
        phi_K = phi_K * mask
        V_f = V_f * mask

        B_h, H_h, N_h, D_h = phi_Q.shape
        phi_K.shape[2]

        # Handle cache: query positions are [past_len .. past_len + N_h), keys are [0 .. N_k)
        if self.is_decoder and past_len > 0:
            # Only new queries, keys include cache.
            # Non-causal over the full key set (past + new).
            KV = torch.matmul(phi_K.transpose(-2, -1).contiguous(), V_f)
            Z = phi_K.sum(dim=-2, keepdim=True)
            num = torch.matmul(phi_Q, KV)
            den = torch.matmul(phi_Q, Z.transpose(-2, -1).contiguous()).clamp(min=1e-6)
            out = num / den
            return torch.nan_to_num(out, nan=0.0).to(Q.dtype)

        if self.is_decoder:
            # Chunked causal linear attention.
            chunk_size = 128
            num_chunks = (N_h + chunk_size - 1) // chunk_size

            state_KV = torch.zeros(B_h, H_h, D_h, D_h, device=phi_Q.device, dtype=phi_Q.dtype)
            state_K = torch.zeros(B_h, H_h, D_h, device=phi_Q.device, dtype=phi_Q.dtype)

            num_list = []
            den_list = []

            for c in range(num_chunks):
                s = c * chunk_size
                e = min(N_h, s + chunk_size)

                phi_Q_c = phi_Q[:, :, s:e]
                phi_K_c = phi_K[:, :, s:e]
                V_c = V_f[:, :, s:e]

                KV_c = torch.einsum("bhnd,bhne->bhnde", phi_K_c, V_c)
                KV_c_cumsum = torch.cumsum(KV_c, dim=2)
                num_intra = torch.einsum("bhnd,bhnde->bhne", phi_Q_c, KV_c_cumsum)

                K_c_cumsum = torch.cumsum(phi_K_c, dim=2)
                den_intra = torch.einsum("bhnd,bhnd->bhn", phi_Q_c, K_c_cumsum)

                num_inter = torch.einsum("bhnd,bhde->bhne", phi_Q_c, state_KV)
                den_inter = torch.einsum("bhnd,bhd->bhn", phi_Q_c, state_K)

                num_list.append(num_intra + num_inter)
                den_list.append(den_intra + den_inter)

                state_KV = state_KV + KV_c_cumsum[:, :, -1]
                state_K = state_K + K_c_cumsum[:, :, -1]

            num = torch.cat(num_list, dim=2)
            den = torch.cat(den_list, dim=2).unsqueeze(-1)
        else:
            KV = torch.matmul(phi_K.transpose(-2, -1).contiguous(), V_f)
            Z = phi_K.sum(dim=-2, keepdim=True)
            num = torch.matmul(phi_Q, KV)
            den = torch.matmul(phi_Q, Z.transpose(-2, -1).contiguous())

        den = den.clamp(min=1e-6)
        out = num / den
        return torch.nan_to_num(out, nan=0.0).to(Q.dtype)

    # ------------------------------------------------------------------
    # Forward
    # ------------------------------------------------------------------
    def forward(
        self,
        hidden_states: torch.Tensor,
        attention_mask: torch.Tensor | None = None,
        head_mask: torch.Tensor | None = None,
        encoder_hidden_states: torch.Tensor | None = None,
        encoder_attention_mask: torch.Tensor | None = None,
        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,
        layer_idx: int = 0,
        *args: Any,
        **kwargs: Any,
    ) -> tuple[torch.Tensor, torch.Tensor | None]:
        B, N, _ = hidden_states.shape
        device = hidden_states.device

        is_cross = encoder_hidden_states is not None
        kv_input = encoder_hidden_states if is_cross else hidden_states
        N_kv_new = kv_input.shape[1]

        eff_mask = encoder_attention_mask if is_cross else attention_mask
        valid_mask_new = _extract_valid_mask(eff_mask, B, N_kv_new, device)

        # Projections
        Q = (
            self.q_proj(hidden_states)
            .view(B, N, self.num_heads, self.head_dim)
            .transpose(1, 2)
            .contiguous()
        )
        K_new = (
            self.k_proj(kv_input)
            .view(B, N_kv_new, self.num_kv_heads, self.head_dim)
            .transpose(1, 2)
            .contiguous()
        )
        V_new = (
            self.v_proj(kv_input)
            .view(B, N_kv_new, self.num_kv_heads, self.head_dim)
            .transpose(1, 2)
            .contiguous()
        )

        # HA-FIX-08: RoPE with past_len offset
        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
            past_len_rope = 0
            if past_key_value is not None:
                if isinstance(past_key_value, (tuple, list)) and len(past_key_value) >= 1:
                    past_len_rope = past_key_value[0].shape[-2]
                elif hasattr(past_key_value, "key_cache") and past_key_value.key_cache:
                    past_len_rope = past_key_value.key_cache[0].shape[-2]
            cos = cos[..., past_len_rope : past_len_rope + N, :]
            sin = sin[..., past_len_rope : past_len_rope + N, :]
            Q, K_new = _apply_rope(Q, K_new, cos, sin)

        # HA-FIX-07: KV-cache
        past_k = past_v = None
        if past_key_value is not None and not is_cross:
            if isinstance(past_key_value, (tuple, list)) and len(past_key_value) >= 2:
                past_k, past_v = past_key_value[0], past_key_value[1]
            elif hasattr(past_key_value, "key_cache") and past_key_value.key_cache:
                idx = min(layer_idx, len(past_key_value.key_cache) - 1)
                past_k = past_key_value.key_cache[idx]
                past_v = past_key_value.value_cache[idx]

        if past_k is not None and not is_cross:
            K_cat = torch.cat([past_k, K_new], dim=-2)
            V_cat = torch.cat([past_v, V_new], dim=-2)
        else:
            K_cat = K_new
            V_cat = V_new

        if past_key_value is not None and hasattr(past_key_value, "update") and not is_cross:
            past_key_value.update(K_new, V_new, layer_idx)

        N_kv = K_cat.shape[2]
        past_len = N_kv - N if (self.is_decoder and not is_cross) else 0

        if self.kv_repeat > 1:
            K_cat = repeat_kv(K_cat, self.kv_repeat)
            V_cat = repeat_kv(V_cat, self.kv_repeat)

        # Valid mask for the full key set (with past)
        if past_len > 0:
            past_valid = torch.ones(B, past_len, device=device, dtype=torch.bool)
            valid_mask_full = torch.cat([past_valid, valid_mask_new], dim=1)
        else:
            valid_mask_full = valid_mask_new

        # Two branches
        out_standard = self._standard_branch(
            Q,
            K_cat,
            V_cat,
            eff_mask,
            past_len=past_len,
            valid_mask=valid_mask_full,
        )
        out_linear = self._linear_branch(
            Q,
            K_cat,
            V_cat,
            valid_mask_full,
            past_len=past_len,
        )

        # Per-head gating
        gate = (
            torch.sigmoid(self.ensemble_gate(hidden_states))
            .view(B, N, self.num_heads, 1)
            .transpose(1, 2)
            .contiguous()
        )
        fused = gate * out_standard + (1.0 - gate) * out_linear

        # HA-FIX-09: head_mask broadcasting corrected.
        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)  # (B, H, 1, 1)
            elif head_mask.dim() == 3:
                head_mask = head_mask[:, :, :1].unsqueeze(-1)  # (B, H, 1, 1)
            fused = fused * head_mask.to(fused.dtype)

        fused = fused.transpose(1, 2).contiguous().view(B, N, self.all_head_size)
        fused = torch.nan_to_num(fused, nan=0.0, posinf=1e4, neginf=-1e4)

        # HA-FIX-01: out_proj ALWAYS executed.
        if self.is_nested_encoder:
            # Nested encoder: outer module supplies out_proj, but we still run
            # the (identity-initialised) internal out_proj for parameter stability.
            out = self.out_proj(fused)
        else:
            out = self.out_proj(fused)
            out = self.dropout(out)

        out = torch.nan_to_num(out, nan=0.0, posinf=1e4, neginf=-1e4)
        return out, None

    # ------------------------------------------------------------------
    # copy_weights
    # ------------------------------------------------------------------
    def copy_weights(self, old_attn: Any) -> None:
        if old_attn is None:
            return

        cls_name = old_attn.__class__.__name__
        has_out_proj = any(
            hasattr(old_attn, attr)
            for attr in ("dense", "out_proj", "c_proj", "out_lin", "o_proj", "wo", "o")
        )
        self.is_nested_encoder = (
            cls_name in ("BertSelfAttention", "RobertaSelfAttention")
            or (cls_name.endswith("SelfAttention") and not has_out_proj)
            or (
                hasattr(old_attn, "self")
                and hasattr(getattr(old_attn, "self", None), "query")
                and hasattr(old_attn, "output")
                and hasattr(getattr(old_attn, "output", None), "dense")
            )
        )
        logger.debug("HybridAttention.copy_weights: nested_encoder=%s", self.is_nested_encoder)

        if hasattr(old_attn, "is_decoder"):
            self.is_decoder = bool(old_attn.is_decoder)
        elif cls_name in (
            "GPT2Attention",
            "Qwen2Attention",
            "LlamaAttention",
        ) or hasattr(old_attn, "c_attn"):
            self.is_decoder = True

        from neuralops.core.adapters import UniversalAdapter

        proj_targets = [
            ("q", self.q_proj),
            ("k", self.k_proj),
            ("v", self.v_proj),
        ]
        if not self.is_nested_encoder:
            proj_targets.append(("o", self.out_proj))

        for proj, target in proj_targets:
            try:
                w, b = UniversalAdapter.get_weight_bias(old_attn, proj)
            except Exception as e:
                logger.debug("UniversalAdapter extraction failed for %s: %s", proj, e)
                w, b = None, None

            if w is None:
                logger.warning("HybridAttention: no weight for '%s' in %s", proj, cls_name)
                continue

            if w.shape != target.weight.shape:
                if w.t().shape == target.weight.shape:
                    w = w.t().contiguous()
                else:
                    logger.warning(
                        "HybridAttention: shape mismatch for %s: src=%s, tgt=%s. Truncating.",
                        proj,
                        tuple(w.shape),
                        tuple(target.weight.shape),
                    )
                    out_d, in_d = target.weight.shape
                    s_out, s_in = w.shape
                    with torch.no_grad():
                        target.weight.data.zero_()
                        target.weight.data[: min(out_d, s_out), : min(in_d, s_in)].copy_(
                            w[: min(out_d, s_out), : min(in_d, s_in)]
                        )
                    if b is not None and target.bias is not None:
                        min_b = min(target.bias.numel(), b.numel())
                        with torch.no_grad():
                            target.bias.data.zero_()
                            target.bias.data[:min_b].copy_(b[:min_b])
                    continue

            with torch.no_grad():
                norm = torch.linalg.norm(w.float()).item()
                if norm < 1e-4:
                    logger.warning(
                        "HybridAttention: %s weight has near-zero Frobenius norm (%s)",
                        proj,
                        norm,
                    )
                target.weight.copy_(w)
                if b is not None and target.bias is not None:
                    target.bias.copy_(b)

Hybrid softmax + linear attention with per-head dynamic gated fusion.

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_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 c_proj
Expand source code
@property
def c_proj(self):
    return self.out_proj
prop dense
Expand source code
@property
def dense(self):
    return self.out_proj
prop k_lin
Expand source code
@property
def k_lin(self):
    return self.k_proj
prop key
Expand source code
@property
def key(self):
    return self.k_proj
prop o_proj
Expand source code
@property
def o_proj(self):
    return self.out_proj
prop out_lin
Expand source code
@property
def out_lin(self):
    return self.out_proj
prop q_lin
Expand source code
@property
def q_lin(self):
    return self.q_proj
prop query
Expand source code
@property
def query(self):
    return self.q_proj
prop v_lin
Expand source code
@property
def v_lin(self):
    return self.v_proj
prop value
Expand source code
@property
def value(self):
    return self.v_proj

Inherited members