Update modeling_neollm.py
Browse files- modeling_neollm.py +206 -69
modeling_neollm.py
CHANGED
|
@@ -26,16 +26,18 @@ Attention stack (orthogonal, all active simultaneously when enabled):
|
|
| 26 |
increases attention entropy, and is complementary to Gated Attention
|
| 27 |
(Bae et al. 2026, Table 2: Affine-Scaled + Gated > either alone).
|
| 28 |
|
| 29 |
-
Flash/SDPA path
|
| 30 |
Expanding [α·softmax(QKᵀ)+β]V distributively yields two terms:
|
| 31 |
-
α ·
|
| 32 |
-
+ β · Σ_{j
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
|
|
|
| 37 |
|
| 38 |
-
Eager path:
|
|
|
|
| 39 |
|
| 40 |
References:
|
| 41 |
FANformer: "FANformer: Improving Large Language Models Through Effective
|
|
@@ -86,7 +88,7 @@ from transformers.modeling_rope_utils import ROPE_INIT_FUNCTIONS, dynamic_rope_u
|
|
| 86 |
from transformers.modeling_utils import ALL_ATTENTION_FUNCTIONS, PreTrainedModel
|
| 87 |
from transformers.processing_utils import Unpack
|
| 88 |
from transformers.utils import TransformersKwargs, logging
|
| 89 |
-
from
|
| 90 |
|
| 91 |
from transformers import AutoConfig, AutoModel, AutoModelForCausalLM
|
| 92 |
torch._dynamo.config.capture_scalar_outputs = True
|
|
@@ -351,7 +353,7 @@ class AttentionAnalysis:
|
|
| 351 |
beta_per_head: Optional[torch.Tensor] = None # β [B,H,S,1] moving-avg bias
|
| 352 |
alpha_moving_avg: Optional[torch.Tensor] = None # alpha_ma EMA snapshot
|
| 353 |
attn_weights_pre_affine: Optional[torch.Tensor] = None # softmax weights before α,β
|
| 354 |
-
attn_weights_post_affine: Optional[torch.Tensor] = None # α·softmax + β
|
| 355 |
|
| 356 |
# ── Standard attention weights (eager non-affine path) ────────────
|
| 357 |
attn_weights: Optional[torch.Tensor] = None # softmax weights (None for flash/sdpa)
|
|
@@ -1920,6 +1922,122 @@ def infer_key_validity(
|
|
| 1920 |
return valid
|
| 1921 |
|
| 1922 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1923 |
def head_linear_compose(
|
| 1924 |
hidden_states: torch.Tensor, mixing_matrix: torch.Tensor
|
| 1925 |
) -> torch.Tensor:
|
|
@@ -2061,21 +2179,38 @@ def affine_scaled_eager_attention_forward(
|
|
| 2061 |
value_states = repeat_kv(value, module.num_key_value_groups)
|
| 2062 |
|
| 2063 |
attn_weights = torch.matmul(query, key_states.transpose(2, 3)) * scaling
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2064 |
if attention_mask is not None:
|
| 2065 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2066 |
|
| 2067 |
attn_weights_softmax = nn.functional.softmax(
|
| 2068 |
attn_weights, dim=-1, dtype=torch.float32
|
| 2069 |
).to(query.dtype)
|
| 2070 |
|
|
|
|
|
|
|
|
|
|
| 2071 |
if attn_analysis is not None:
|
| 2072 |
attn_analysis.attn_weights_pre_affine = attn_weights_softmax.detach()
|
| 2073 |
|
| 2074 |
-
# Affine reweighting
|
| 2075 |
-
#
|
| 2076 |
-
# β
|
| 2077 |
-
# Shapes: α, β are [B, H, S_q, 1], attn_weights is [B, H, S_q, S_k] → broadcast.
|
| 2078 |
attn_weights_affine = alpha * attn_weights_softmax + beta
|
|
|
|
|
|
|
| 2079 |
|
| 2080 |
if attn_analysis is not None:
|
| 2081 |
attn_analysis.attn_weights_post_affine = attn_weights_affine.detach()
|
|
@@ -2101,7 +2236,7 @@ def affine_scaled_flash_attention_forward(
|
|
| 2101 |
"""
|
| 2102 |
Affine-Scaled Attention — flash/sdpa path.
|
| 2103 |
|
| 2104 |
-
|
| 2105 |
public flash/sdpa interface — no kernel modification required.
|
| 2106 |
|
| 2107 |
Derivation
|
|
@@ -2109,12 +2244,12 @@ def affine_scaled_flash_attention_forward(
|
|
| 2109 |
The paper formula expands distributively:
|
| 2110 |
|
| 2111 |
[α · softmax(QKᵀ/√dk) + β] · V
|
| 2112 |
-
= α · [softmax(QKᵀ/√dk) · V] ← term 1: standard
|
| 2113 |
-
+ β · [Σ_{j
|
| 2114 |
|
| 2115 |
-
|
| 2116 |
-
|
| 2117 |
-
|
| 2118 |
|
| 2119 |
Dropout
|
| 2120 |
-------
|
|
@@ -2126,20 +2261,16 @@ def affine_scaled_flash_attention_forward(
|
|
| 2126 |
regularisation that achieves the same intent without the intermediate
|
| 2127 |
weight matrix. During inference dropout=0 so the paths are identical.
|
| 2128 |
|
| 2129 |
-
|
| 2130 |
-
------------
|
| 2131 |
-
The
|
| 2132 |
-
|
| 2133 |
-
|
| 2134 |
-
2D padding mask [B, S] (flash_attention_2/3) or a 4D additive mask
|
| 2135 |
-
[B, 1, S, S] (eager/sdpa-style paths). We normalize both to a per-token
|
| 2136 |
-
validity mask before the cumsum.
|
| 2137 |
|
| 2138 |
Memory overhead vs standard flash call
|
| 2139 |
---------------------------------------
|
| 2140 |
-
|
| 2141 |
-
|
| 2142 |
-
across 12 layers. Allocated and freed within each forward call.
|
| 2143 |
|
| 2144 |
Attention-weight analysis fields (attn_weights_pre_affine,
|
| 2145 |
attn_weights_post_affine) remain None — those tensors are never
|
|
@@ -2161,28 +2292,18 @@ def affine_scaled_flash_attention_forward(
|
|
| 2161 |
)
|
| 2162 |
# flash_out: [B, S, H_q, d_head] — HF wrappers all return this layout
|
| 2163 |
|
| 2164 |
-
# ── Term 2: β ·
|
| 2165 |
-
# Expand V from KV heads to query heads for GQA
|
| 2166 |
-
|
| 2167 |
-
|
| 2168 |
-
|
| 2169 |
-
|
| 2170 |
-
|
| 2171 |
-
|
| 2172 |
-
|
| 2173 |
-
|
| 2174 |
-
valid = valid.unsqueeze(1).unsqueeze(-1) # [B, 1, S, 1]
|
| 2175 |
-
value_expanded = value_expanded * valid # [B, H_q, S, d_head]
|
| 2176 |
-
elif attention_mask.ndim == 4:
|
| 2177 |
-
diag = attention_mask.diagonal(dim1=-2, dim2=-1) # [B, 1, S]
|
| 2178 |
-
valid = (diag == 0).to(value_expanded.dtype) # [B, 1, S]
|
| 2179 |
-
valid = valid.unsqueeze(-1) # [B, 1, S, 1]
|
| 2180 |
-
value_expanded = value_expanded * valid # [B, H_q, S, d_head]
|
| 2181 |
-
|
| 2182 |
-
# Causal prefix-sum: position i accumulates all valid j ≤ i.
|
| 2183 |
-
v_cumsum = value_expanded.cumsum(dim=2) # [B, H_q, S, d_head]
|
| 2184 |
# Transpose to match flash_out layout.
|
| 2185 |
-
v_cumsum_t =
|
| 2186 |
|
| 2187 |
# α, β: [B, H_q, S, 1] → [B, S, H_q, 1] to broadcast over d_head.
|
| 2188 |
alpha_t = alpha.permute(0, 2, 1, 3) # [B, S, H_q, 1]
|
|
@@ -3596,8 +3717,8 @@ class NeoLLMAttention(nn.Module):
|
|
| 3596 |
|
| 3597 |
Calls flash_attn_func (dense path) or flash_attn_varlen_func (packed
|
| 3598 |
path) directly. Affine-Scaled Attention is also computed inline here
|
| 3599 |
-
so that
|
| 3600 |
-
|
| 3601 |
|
| 3602 |
Packing detection
|
| 3603 |
-----------------
|
|
@@ -3614,12 +3735,11 @@ class NeoLLMAttention(nn.Module):
|
|
| 3614 |
Affine-Scaled Attention (inline)
|
| 3615 |
---------------------------------
|
| 3616 |
When use_affine=True:
|
| 3617 |
-
output = α · flash_out + β ·
|
| 3618 |
|
| 3619 |
α, β are [B, H_q, S·P, 1] (already expanded over the seq dim before
|
| 3620 |
-
this call).
|
| 3621 |
-
|
| 3622 |
-
accumulates all virtual tokens before it in interleaved order.
|
| 3623 |
|
| 3624 |
Args
|
| 3625 |
----
|
|
@@ -3735,16 +3855,34 @@ class NeoLLMAttention(nn.Module):
|
|
| 3735 |
) # [B, S·P, H_q, d]
|
| 3736 |
|
| 3737 |
# ── Affine-Scaled Attention inline (flash path) ───────────────────────
|
| 3738 |
-
#
|
| 3739 |
# Term 1: α · flash_out (standard flash output)
|
| 3740 |
-
# Term 2: β · Σ_{j
|
| 3741 |
-
#
|
| 3742 |
-
#
|
| 3743 |
# α, β arrive pre-expanded to [B, H_q, S·P, 1]; permute to [B,S·P,H,1]
|
| 3744 |
# to match the flash output layout [B, S·P, H_q, d].
|
| 3745 |
if use_affine:
|
| 3746 |
value_expanded = repeat_kv(v, self.num_key_value_groups) # [B,H_q,S·P,d]
|
| 3747 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3748 |
alpha_t = alpha.permute(0, 2, 1, 3) # [B,S·P,H_q,1]
|
| 3749 |
beta_t = beta.permute(0, 2, 1, 3) # [B,S·P,H_q,1]
|
| 3750 |
attn_out = alpha_t * flash_out + beta_t * v_cumsum_t # [B,S·P,H_q,d]
|
|
@@ -4095,7 +4233,7 @@ class NeoLLMAttention(nn.Module):
|
|
| 4095 |
# Active whenever use_affine_scaled_attention=True, regardless of
|
| 4096 |
# attention backend. Two code paths — same math, different execution:
|
| 4097 |
# eager : full weight access, attn_weights_pre/post_affine captured.
|
| 4098 |
-
# flash/sdpa: α·
|
| 4099 |
alpha = None
|
| 4100 |
beta = None
|
| 4101 |
use_affine = self.use_affine_scaled_attention
|
|
@@ -4131,8 +4269,7 @@ class NeoLLMAttention(nn.Module):
|
|
| 4131 |
if _iha_seq_expand:
|
| 4132 |
alpha = alpha.repeat_interleave(self.iha_P, dim=2) # [B,H_q,S·P,1]
|
| 4133 |
beta = beta.repeat_interleave(self.iha_P, dim=2) # [B,H_q,S·P,1]
|
| 4134 |
-
# Recompute beta with
|
| 4135 |
-
# with the number of virtual keys the query can attend to).
|
| 4136 |
# alpha_ma shape [1,H,1,1] broadcasts correctly against expanded alpha.
|
| 4137 |
N_expanded = k.shape[-2] # S·P after IHA expansion
|
| 4138 |
beta = (self.alpha_ma.to(alpha.dtype) - alpha) / max(N_expanded, 1)
|
|
@@ -4147,7 +4284,7 @@ class NeoLLMAttention(nn.Module):
|
|
| 4147 |
# 3. attention_mask shaped [B,S] is incompatible with S·P tokens
|
| 4148 |
# _iha_seq_expand_flash_forward calls flash_attn_func /
|
| 4149 |
# flash_attn_varlen_func directly, handling packing, sliding window,
|
| 4150 |
-
# and the Affine-Scaled inline decomposition (α·flash + β·
|
| 4151 |
# alpha/beta are [B, H_q, S·P, 1] at this point (already expanded).
|
| 4152 |
attn_out, attn_weights = self._iha_seq_expand_flash_forward(
|
| 4153 |
q, k, v,
|
|
@@ -4169,7 +4306,7 @@ class NeoLLMAttention(nn.Module):
|
|
| 4169 |
**kwargs,
|
| 4170 |
)
|
| 4171 |
else:
|
| 4172 |
-
# Flash / SDPA:
|
| 4173 |
# attn_weights_pre/post_affine remain None in AnalysisState.
|
| 4174 |
backend_kwargs = kwargs
|
| 4175 |
if flash_local_sliding_window is not None:
|
|
|
|
| 26 |
increases attention entropy, and is complementary to Gated Attention
|
| 27 |
(Bae et al. 2026, Table 2: Affine-Scaled + Gated > either alone).
|
| 28 |
|
| 29 |
+
Flash/SDPA path:
|
| 30 |
Expanding [α·softmax(QKᵀ)+β]V distributively yields two terms:
|
| 31 |
+
α · attention(Q,K,V) — backend computes this directly
|
| 32 |
+
+ β · Σ_{j∈A(i)} V_j — sum over the same valid keys A(i)
|
| 33 |
+
For global causal attention A(i) is a prefix and this is V.cumsum.
|
| 34 |
+
For sliding-window or packed sequences the sum is windowed or
|
| 35 |
+
segmented so β cannot leak outside the backend attention mask.
|
| 36 |
+
Per-weight tensors (attn_weights_pre/post_affine) are unavailable
|
| 37 |
+
in flash mode and remain None in AnalysisState.
|
| 38 |
|
| 39 |
+
Eager path: applies the same valid-key mask to α·softmax + β and keeps
|
| 40 |
+
full weight access for interpretability.
|
| 41 |
|
| 42 |
References:
|
| 43 |
FANformer: "FANformer: Improving Large Language Models Through Effective
|
|
|
|
| 88 |
from transformers.modeling_utils import ALL_ATTENTION_FUNCTIONS, PreTrainedModel
|
| 89 |
from transformers.processing_utils import Unpack
|
| 90 |
from transformers.utils import TransformersKwargs, logging
|
| 91 |
+
from configuration_neollm import NeoLLMConfig
|
| 92 |
|
| 93 |
from transformers import AutoConfig, AutoModel, AutoModelForCausalLM
|
| 94 |
torch._dynamo.config.capture_scalar_outputs = True
|
|
|
|
| 353 |
beta_per_head: Optional[torch.Tensor] = None # β [B,H,S,1] moving-avg bias
|
| 354 |
alpha_moving_avg: Optional[torch.Tensor] = None # alpha_ma EMA snapshot
|
| 355 |
attn_weights_pre_affine: Optional[torch.Tensor] = None # softmax weights before α,β
|
| 356 |
+
attn_weights_post_affine: Optional[torch.Tensor] = None # valid_mask·(α·softmax + β)
|
| 357 |
|
| 358 |
# ── Standard attention weights (eager non-affine path) ────────────
|
| 359 |
attn_weights: Optional[torch.Tensor] = None # softmax weights (None for flash/sdpa)
|
|
|
|
| 1922 |
return valid
|
| 1923 |
|
| 1924 |
|
| 1925 |
+
|
| 1926 |
+
|
| 1927 |
+
def _expand_pair_valid_mask(
|
| 1928 |
+
attention_mask: Optional[torch.Tensor],
|
| 1929 |
+
batch_size: int,
|
| 1930 |
+
num_heads: int,
|
| 1931 |
+
query_len: int,
|
| 1932 |
+
key_len: int,
|
| 1933 |
+
) -> Optional[torch.Tensor]:
|
| 1934 |
+
"""
|
| 1935 |
+
Return a boolean [B,H,Sq,Sk] mask for positions that attention may read.
|
| 1936 |
+
|
| 1937 |
+
Supported contracts used in this file:
|
| 1938 |
+
• 2D padding mask [B,Sk], with truthy values for valid keys.
|
| 1939 |
+
• 4D additive mask [B,1|H,Sq,Sk], with 0 on valid pairs and -inf/large
|
| 1940 |
+
negative values on invalid causal, local or padding pairs.
|
| 1941 |
+
"""
|
| 1942 |
+
if attention_mask is None:
|
| 1943 |
+
return None
|
| 1944 |
+
|
| 1945 |
+
if attention_mask.ndim == 2:
|
| 1946 |
+
if attention_mask.shape[-1] < key_len:
|
| 1947 |
+
return None
|
| 1948 |
+
valid = attention_mask[:, :key_len].to(dtype=torch.bool) # [B,Sk]
|
| 1949 |
+
valid = valid[:, None, None, :].expand(batch_size, num_heads, query_len, key_len)
|
| 1950 |
+
return valid
|
| 1951 |
+
|
| 1952 |
+
if attention_mask.ndim != 4:
|
| 1953 |
+
return None
|
| 1954 |
+
if attention_mask.shape[-2] < query_len or attention_mask.shape[-1] < key_len:
|
| 1955 |
+
return None
|
| 1956 |
+
|
| 1957 |
+
valid = attention_mask[:, :, :query_len, :key_len] == 0
|
| 1958 |
+
if valid.shape[1] == 1 and num_heads != 1:
|
| 1959 |
+
valid = valid.expand(-1, num_heads, -1, -1)
|
| 1960 |
+
elif valid.shape[1] != num_heads:
|
| 1961 |
+
valid = valid[:, :1, :, :].expand(-1, num_heads, -1, -1)
|
| 1962 |
+
return valid
|
| 1963 |
+
|
| 1964 |
+
|
| 1965 |
+
def _masked_value_sum_from_pairs(
|
| 1966 |
+
value_expanded: torch.Tensor,
|
| 1967 |
+
pair_valid: torch.Tensor,
|
| 1968 |
+
) -> torch.Tensor:
|
| 1969 |
+
"""Compute Σ_{j∈A(i)} V_j from an explicit pair-valid mask."""
|
| 1970 |
+
return torch.matmul(pair_valid.to(value_expanded.dtype), value_expanded)
|
| 1971 |
+
|
| 1972 |
+
|
| 1973 |
+
def _causal_value_sum(
|
| 1974 |
+
value_expanded: torch.Tensor,
|
| 1975 |
+
query_len: int,
|
| 1976 |
+
window_left: Optional[int] = None,
|
| 1977 |
+
) -> torch.Tensor:
|
| 1978 |
+
"""
|
| 1979 |
+
Compute Σ_{j∈A(i)} V_j for causal or sliding-window causal attention.
|
| 1980 |
+
|
| 1981 |
+
value_expanded: [B,H,Sk,d]. The returned tensor is [B,H,Sq,d].
|
| 1982 |
+
For cached decoding, Sq may be smaller than Sk; query rows are aligned to
|
| 1983 |
+
the last Sq key positions, matching causal self-attention with cache.
|
| 1984 |
+
"""
|
| 1985 |
+
key_len = value_expanded.shape[2]
|
| 1986 |
+
prefix = value_expanded.cumsum(dim=2) # [B,H,Sk,d]
|
| 1987 |
+
|
| 1988 |
+
if window_left is None or window_left < 0 or window_left >= key_len:
|
| 1989 |
+
sums_all = prefix
|
| 1990 |
+
else:
|
| 1991 |
+
# Valid keys for absolute position i are max(0, i-window_left) ... i.
|
| 1992 |
+
prefix_padded = F.pad(prefix, (0, 0, 1, 0)) # [B,H,Sk+1,d]
|
| 1993 |
+
end_idx = torch.arange(key_len, device=value_expanded.device) + 1
|
| 1994 |
+
start_idx = torch.clamp(end_idx - (int(window_left) + 1), min=0)
|
| 1995 |
+
end_vals = prefix_padded.index_select(2, end_idx)
|
| 1996 |
+
start_vals = prefix_padded.index_select(2, start_idx)
|
| 1997 |
+
sums_all = end_vals - start_vals
|
| 1998 |
+
|
| 1999 |
+
if query_len == key_len:
|
| 2000 |
+
return sums_all
|
| 2001 |
+
start = max(key_len - query_len, 0)
|
| 2002 |
+
return sums_all[:, :, start:start + query_len, :]
|
| 2003 |
+
|
| 2004 |
+
|
| 2005 |
+
def _affine_valid_value_sum(
|
| 2006 |
+
value_expanded: torch.Tensor,
|
| 2007 |
+
attention_mask: Optional[torch.Tensor],
|
| 2008 |
+
query_len: int,
|
| 2009 |
+
sliding_window: Optional[Union[int, Tuple[int, int]]] = None,
|
| 2010 |
+
) -> torch.Tensor:
|
| 2011 |
+
"""
|
| 2012 |
+
Compute the β term support S_i = Σ_{j∈A(i)} V_j for Affine attention.
|
| 2013 |
+
|
| 2014 |
+
It first respects an explicit 4D additive mask exactly. Otherwise it uses
|
| 2015 |
+
causal prefix sums, optionally restricted by a left sliding-window size.
|
| 2016 |
+
"""
|
| 2017 |
+
batch_size, num_heads, key_len, _ = value_expanded.shape
|
| 2018 |
+
|
| 2019 |
+
pair_valid = None
|
| 2020 |
+
if attention_mask is not None and attention_mask.ndim == 4:
|
| 2021 |
+
pair_valid = _expand_pair_valid_mask(
|
| 2022 |
+
attention_mask, batch_size, num_heads, query_len, key_len
|
| 2023 |
+
)
|
| 2024 |
+
if pair_valid is not None:
|
| 2025 |
+
return _masked_value_sum_from_pairs(value_expanded, pair_valid)
|
| 2026 |
+
|
| 2027 |
+
# 2D padding masks zero invalid keys before the prefix/window sum.
|
| 2028 |
+
if attention_mask is not None and attention_mask.ndim == 2:
|
| 2029 |
+
if attention_mask.shape[-1] >= key_len:
|
| 2030 |
+
valid = attention_mask[:, :key_len].to(value_expanded.dtype)
|
| 2031 |
+
value_expanded = value_expanded * valid[:, None, :, None]
|
| 2032 |
+
|
| 2033 |
+
window_left = None
|
| 2034 |
+
if isinstance(sliding_window, tuple):
|
| 2035 |
+
window_left = int(sliding_window[0]) if len(sliding_window) > 0 else None
|
| 2036 |
+
elif sliding_window is not None:
|
| 2037 |
+
window_left = int(sliding_window)
|
| 2038 |
+
|
| 2039 |
+
return _causal_value_sum(value_expanded, query_len, window_left)
|
| 2040 |
+
|
| 2041 |
def head_linear_compose(
|
| 2042 |
hidden_states: torch.Tensor, mixing_matrix: torch.Tensor
|
| 2043 |
) -> torch.Tensor:
|
|
|
|
| 2179 |
value_states = repeat_kv(value, module.num_key_value_groups)
|
| 2180 |
|
| 2181 |
attn_weights = torch.matmul(query, key_states.transpose(2, 3)) * scaling
|
| 2182 |
+
pair_valid = _expand_pair_valid_mask(
|
| 2183 |
+
attention_mask,
|
| 2184 |
+
batch_size=query.shape[0],
|
| 2185 |
+
num_heads=key_states.shape[1],
|
| 2186 |
+
query_len=query.shape[-2],
|
| 2187 |
+
key_len=key_states.shape[-2],
|
| 2188 |
+
)
|
| 2189 |
if attention_mask is not None:
|
| 2190 |
+
if attention_mask.ndim == 4:
|
| 2191 |
+
attn_weights = attn_weights + attention_mask[:, :, : query.shape[-2], : key_states.shape[-2]]
|
| 2192 |
+
elif attention_mask.ndim == 2:
|
| 2193 |
+
key_valid = attention_mask[:, : key_states.shape[-2]].to(dtype=torch.bool)
|
| 2194 |
+
additive = torch.zeros_like(attn_weights)
|
| 2195 |
+
additive = additive.masked_fill(~key_valid[:, None, None, :], torch.finfo(attn_weights.dtype).min)
|
| 2196 |
+
attn_weights = attn_weights + additive
|
| 2197 |
|
| 2198 |
attn_weights_softmax = nn.functional.softmax(
|
| 2199 |
attn_weights, dim=-1, dtype=torch.float32
|
| 2200 |
).to(query.dtype)
|
| 2201 |
|
| 2202 |
+
if pair_valid is not None:
|
| 2203 |
+
attn_weights_softmax = attn_weights_softmax.masked_fill(~pair_valid, 0.0)
|
| 2204 |
+
|
| 2205 |
if attn_analysis is not None:
|
| 2206 |
attn_analysis.attn_weights_pre_affine = attn_weights_softmax.detach()
|
| 2207 |
|
| 2208 |
+
# Affine reweighting over valid keys only. β must obey the same causal,
|
| 2209 |
+
# local and padding mask as softmax; otherwise invalid V_j would leak in.
|
| 2210 |
+
# Shapes: α, β are [B, H, S_q, 1], weights are [B, H, S_q, S_k].
|
|
|
|
| 2211 |
attn_weights_affine = alpha * attn_weights_softmax + beta
|
| 2212 |
+
if pair_valid is not None:
|
| 2213 |
+
attn_weights_affine = attn_weights_affine.masked_fill(~pair_valid, 0.0)
|
| 2214 |
|
| 2215 |
if attn_analysis is not None:
|
| 2216 |
attn_analysis.attn_weights_post_affine = attn_weights_affine.detach()
|
|
|
|
| 2236 |
"""
|
| 2237 |
Affine-Scaled Attention — flash/sdpa path.
|
| 2238 |
|
| 2239 |
+
Mathematical decomposition of [α·softmax(QKᵀ)+β]V using only the
|
| 2240 |
public flash/sdpa interface — no kernel modification required.
|
| 2241 |
|
| 2242 |
Derivation
|
|
|
|
| 2244 |
The paper formula expands distributively:
|
| 2245 |
|
| 2246 |
[α · softmax(QKᵀ/√dk) + β] · V
|
| 2247 |
+
= α · [softmax(QKᵀ/√dk) · V] ← term 1: standard backend output
|
| 2248 |
+
+ β · [Σ_{j∈A(i)} V_j] ← term 2: same valid keys A(i)
|
| 2249 |
|
| 2250 |
+
For global causal attention, A(i) is the prefix j≤i and term 2 is a
|
| 2251 |
+
cumsum. For sliding-window or packed/additive-mask paths, term 2 must use
|
| 2252 |
+
the corresponding windowed or explicit valid-key sum.
|
| 2253 |
|
| 2254 |
Dropout
|
| 2255 |
-------
|
|
|
|
| 2261 |
regularisation that achieves the same intent without the intermediate
|
| 2262 |
weight matrix. During inference dropout=0 so the paths are identical.
|
| 2263 |
|
| 2264 |
+
Mask handling
|
| 2265 |
+
-------------
|
| 2266 |
+
The β value-sum must respect the same valid keys as the backend attention.
|
| 2267 |
+
A 2D padding mask zeros invalid keys before prefix/window sums. A 4D
|
| 2268 |
+
additive mask is used directly to compute Σ_{j∈A(i)} V_j exactly.
|
|
|
|
|
|
|
|
|
|
| 2269 |
|
| 2270 |
Memory overhead vs standard flash call
|
| 2271 |
---------------------------------------
|
| 2272 |
+
Global causal attention adds one [B, H_q, S, d_head] value-sum tensor.
|
| 2273 |
+
Exact 4D-mask fallback may materialise a [B, H_q, S_q, S_k] boolean mask.
|
|
|
|
| 2274 |
|
| 2275 |
Attention-weight analysis fields (attn_weights_pre_affine,
|
| 2276 |
attn_weights_post_affine) remain None — those tensors are never
|
|
|
|
| 2292 |
)
|
| 2293 |
# flash_out: [B, S, H_q, d_head] — HF wrappers all return this layout
|
| 2294 |
|
| 2295 |
+
# ── Term 2: β · Σ_{j∈A(i)} V_j ──────────────────────────────────────
|
| 2296 |
+
# Expand V from KV heads to query heads for GQA, then sum only over the
|
| 2297 |
+
# same valid keys used by the backend attention path.
|
| 2298 |
+
value_expanded = repeat_kv(value, module.num_key_value_groups) # [B, H_q, S_k, d_head]
|
| 2299 |
+
value_sum = _affine_valid_value_sum(
|
| 2300 |
+
value_expanded,
|
| 2301 |
+
attention_mask=attention_mask,
|
| 2302 |
+
query_len=query.shape[-2],
|
| 2303 |
+
sliding_window=kwargs.get("sliding_window", None),
|
| 2304 |
+
) # [B, H_q, S_q, d_head]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2305 |
# Transpose to match flash_out layout.
|
| 2306 |
+
v_cumsum_t = value_sum.transpose(1, 2).contiguous() # [B, S_q, H_q, d_head]
|
| 2307 |
|
| 2308 |
# α, β: [B, H_q, S, 1] → [B, S, H_q, 1] to broadcast over d_head.
|
| 2309 |
alpha_t = alpha.permute(0, 2, 1, 3) # [B, S, H_q, 1]
|
|
|
|
| 3717 |
|
| 3718 |
Calls flash_attn_func (dense path) or flash_attn_varlen_func (packed
|
| 3719 |
path) directly. Affine-Scaled Attention is also computed inline here
|
| 3720 |
+
so that Term 2 sums V over the same valid keys as flash attention on
|
| 3721 |
+
the S·P expanded sequence.
|
| 3722 |
|
| 3723 |
Packing detection
|
| 3724 |
-----------------
|
|
|
|
| 3735 |
Affine-Scaled Attention (inline)
|
| 3736 |
---------------------------------
|
| 3737 |
When use_affine=True:
|
| 3738 |
+
output = α · flash_out + β · Σ_{j∈A(i)} V_j
|
| 3739 |
|
| 3740 |
α, β are [B, H_q, S·P, 1] (already expanded over the seq dim before
|
| 3741 |
+
this call). The V sum is causal prefix-sum for global IHA, windowed
|
| 3742 |
+
for local IHA, and segmented when packed position_ids reset.
|
|
|
|
| 3743 |
|
| 3744 |
Args
|
| 3745 |
----
|
|
|
|
| 3855 |
) # [B, S·P, H_q, d]
|
| 3856 |
|
| 3857 |
# ── Affine-Scaled Attention inline (flash path) ───────────────────────
|
| 3858 |
+
# Decomposition of [α·softmax(QKᵀ) + β]·V (Bae et al. 2026):
|
| 3859 |
# Term 1: α · flash_out (standard flash output)
|
| 3860 |
+
# Term 2: β · Σ_{j∈A(i)} V_j (same valid keys as flash)
|
| 3861 |
+
# For local IHA, A(i) is the flash sliding window; for packed IHA it is
|
| 3862 |
+
# additionally reset at each document boundary.
|
| 3863 |
# α, β arrive pre-expanded to [B, H_q, S·P, 1]; permute to [B,S·P,H,1]
|
| 3864 |
# to match the flash output layout [B, S·P, H_q, d].
|
| 3865 |
if use_affine:
|
| 3866 |
value_expanded = repeat_kv(v, self.num_key_value_groups) # [B,H_q,S·P,d]
|
| 3867 |
+
window_left = window[0] if isinstance(window, tuple) else None
|
| 3868 |
+
if is_packed:
|
| 3869 |
+
value_sum = torch.empty_like(value_expanded)
|
| 3870 |
+
start = 0
|
| 3871 |
+
for _seg_len in doc_lengths_p.to(dtype=torch.long).tolist():
|
| 3872 |
+
end = start + int(_seg_len)
|
| 3873 |
+
value_sum[:, :, start:end, :] = _causal_value_sum(
|
| 3874 |
+
value_expanded[:, :, start:end, :],
|
| 3875 |
+
query_len=int(_seg_len),
|
| 3876 |
+
window_left=window_left,
|
| 3877 |
+
)
|
| 3878 |
+
start = end
|
| 3879 |
+
else:
|
| 3880 |
+
value_sum = _causal_value_sum(
|
| 3881 |
+
value_expanded,
|
| 3882 |
+
query_len=SP,
|
| 3883 |
+
window_left=window_left,
|
| 3884 |
+
)
|
| 3885 |
+
v_cumsum_t = value_sum.transpose(1, 2).contiguous() # [B,S·P,H_q,d]
|
| 3886 |
alpha_t = alpha.permute(0, 2, 1, 3) # [B,S·P,H_q,1]
|
| 3887 |
beta_t = beta.permute(0, 2, 1, 3) # [B,S·P,H_q,1]
|
| 3888 |
attn_out = alpha_t * flash_out + beta_t * v_cumsum_t # [B,S·P,H_q,d]
|
|
|
|
| 4233 |
# Active whenever use_affine_scaled_attention=True, regardless of
|
| 4234 |
# attention backend. Two code paths — same math, different execution:
|
| 4235 |
# eager : full weight access, attn_weights_pre/post_affine captured.
|
| 4236 |
+
# flash/sdpa: α·backend_out + β·Σ valid V, no weight tensors materialised.
|
| 4237 |
alpha = None
|
| 4238 |
beta = None
|
| 4239 |
use_affine = self.use_affine_scaled_attention
|
|
|
|
| 4269 |
if _iha_seq_expand:
|
| 4270 |
alpha = alpha.repeat_interleave(self.iha_P, dim=2) # [B,H_q,S·P,1]
|
| 4271 |
beta = beta.repeat_interleave(self.iha_P, dim=2) # [B,H_q,S·P,1]
|
| 4272 |
+
# Recompute beta with N = S·P for the expanded attention axis.
|
|
|
|
| 4273 |
# alpha_ma shape [1,H,1,1] broadcasts correctly against expanded alpha.
|
| 4274 |
N_expanded = k.shape[-2] # S·P after IHA expansion
|
| 4275 |
beta = (self.alpha_ma.to(alpha.dtype) - alpha) / max(N_expanded, 1)
|
|
|
|
| 4284 |
# 3. attention_mask shaped [B,S] is incompatible with S·P tokens
|
| 4285 |
# _iha_seq_expand_flash_forward calls flash_attn_func /
|
| 4286 |
# flash_attn_varlen_func directly, handling packing, sliding window,
|
| 4287 |
+
# and the Affine-Scaled inline decomposition (α·flash + β·Σ valid V).
|
| 4288 |
# alpha/beta are [B, H_q, S·P, 1] at this point (already expanded).
|
| 4289 |
attn_out, attn_weights = self._iha_seq_expand_flash_forward(
|
| 4290 |
q, k, v,
|
|
|
|
| 4306 |
**kwargs,
|
| 4307 |
)
|
| 4308 |
else:
|
| 4309 |
+
# Flash / SDPA: valid-key value sum, no weight tensors.
|
| 4310 |
# attn_weights_pre/post_affine remain None in AnalysisState.
|
| 4311 |
backend_kwargs = kwargs
|
| 4312 |
if flash_local_sliding_window is not None:
|