File size: 5,909 Bytes
1e67d89
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
"""

Differential Transformer (PRODUCTION - causal-safe)

====================================================



Microsoft 2410.05258 (ICLR 2025)

Cancels noise via dual-softmax attention map subtraction.



Core formula:

  DiffAttn(X) = (softmax(Q1 K1^T / sqrt(d)) - λ × softmax(Q2 K2^T / sqrt(d))) V

  λ = exp(λ_q1 · λ_k1) - exp(λ_q2 · λ_k2) + λ_init



Implementation notes:

  - GroupNorm (sequence-spanning) → LayerNorm(head_dim) (per-token, causal-safe)

  - Causal triu mask explicit (was relying on SDPA, but split + softmax direct now)

  - forward signature: layer_idx kwarg + (Tensor, past_kv) tuple return for modeling compat



Hyperparameter (configuration_aether_v2_7way.py):

  - diff_lambda_init = 0.8

"""
from __future__ import annotations
import math
from typing import Optional, Tuple

import torch
import torch.nn as nn
import torch.nn.functional as F


class DifferentialAttention(nn.Module):
    """λ-gated dual-softmax differential attention with causal mask + LayerNorm."""

    def __init__(self, config, layer_idx: int = 0):
        super().__init__()
        self.layer_idx = layer_idx
        self.h = config.hidden_size
        self.num_heads = config.num_attention_heads
        self.num_kv_heads = config.num_key_value_heads
        self.head_dim = config.head_dim
        # Differential splits head_dim in half
        self.diff_head_dim = self.head_dim // 2

        # Layer-depth lambda init (paper Eq. 3): λ_init = 0.8 - 0.6 * exp(-0.3 * (depth - 1))
        # Use config base + depth decay if provided.
        base = getattr(config, "diff_lambda_init", 0.8)
        decay = getattr(config, "diff_lambda_layer_decay", 0.6)
        # Simple per-layer init
        self.lambda_init = base - decay * math.exp(-0.3 * max(layer_idx - 1, 0))

        # Q/K/V/O projections
        self.q_proj = nn.Linear(self.h, self.num_heads * self.head_dim, bias=False)
        self.k_proj = nn.Linear(self.h, self.num_kv_heads * self.head_dim, bias=False)
        self.v_proj = nn.Linear(self.h, self.num_kv_heads * self.head_dim, bias=False)
        self.o_proj = nn.Linear(self.num_heads * self.head_dim, self.h, bias=False)

        # Lambda parameters per-head
        self.lambda_q1 = nn.Parameter(torch.zeros(self.num_heads, self.diff_head_dim).normal_(0, 0.1))
        self.lambda_k1 = nn.Parameter(torch.zeros(self.num_heads, self.diff_head_dim).normal_(0, 0.1))
        self.lambda_q2 = nn.Parameter(torch.zeros(self.num_heads, self.diff_head_dim).normal_(0, 0.1))
        self.lambda_k2 = nn.Parameter(torch.zeros(self.num_heads, self.diff_head_dim).normal_(0, 0.1))

        # ★ FIX 5/7: LayerNorm(head_dim) — per-token norm, causal-safe.
        # Was nn.GroupNorm(num_groups=num_heads, num_channels=num_heads*head_dim) which
        # normalized over sequence dim and leaked future info.
        self.subln = nn.LayerNorm(self.head_dim, eps=getattr(config, "rms_norm_eps", 1e-6))

    def forward(

        self,

        hidden_states: torch.Tensor,

        attention_mask: Optional[torch.Tensor] = None,

        position_ids: Optional[torch.LongTensor] = None,

        past_key_value=None,

        use_cache: bool = False,

        **kwargs,

    ) -> Tuple[torch.Tensor, Optional[object]]:
        B, S, _ = hidden_states.shape

        q = self.q_proj(hidden_states).view(B, S, self.num_heads, self.head_dim)
        k = self.k_proj(hidden_states).view(B, S, self.num_kv_heads, self.head_dim)
        v = self.v_proj(hidden_states).view(B, S, self.num_kv_heads, self.head_dim)

        # AetherCache: cache full k,v (pre-split) in HF layout [B, kv_h, S, D]
        if past_key_value is not None:
            kc, vc = past_key_value.update(k.transpose(1, 2), v.transpose(1, 2), getattr(self, "cache_idx", self.layer_idx))
            k = kc.transpose(1, 2)
            v = vc.transpose(1, 2)
        S_kv = k.shape[1]

        # Split Q, K halves
        q1, q2 = q.split(self.diff_head_dim, dim=-1)  # [B, S, H, d/2]
        k1, k2 = k.split(self.diff_head_dim, dim=-1)  # [B, S_kv, kv_h, d/2]

        # GQA repeat
        repeat = self.num_heads // self.num_kv_heads
        k1 = k1.repeat_interleave(repeat, dim=2)
        k2 = k2.repeat_interleave(repeat, dim=2)
        v = v.repeat_interleave(repeat, dim=2)

        # Transpose to [B, H, S, d]
        q1 = q1.transpose(1, 2)
        q2 = q2.transpose(1, 2)
        k1 = k1.transpose(1, 2)
        k2 = k2.transpose(1, 2)
        v = v.transpose(1, 2)

        scale = 1.0 / math.sqrt(self.diff_head_dim)
        scores1 = torch.matmul(q1, k1.transpose(-2, -1)) * scale
        scores2 = torch.matmul(q2, k2.transpose(-2, -1)) * scale

        # ★ FIX 5/7: causal mask (triu(1) blocks future)
        # AetherCache: query i is at absolute pos (S_kv - S + i) -> shift the diagonal
        causal = torch.ones(S, S_kv, device=q1.device, dtype=torch.bool).triu(1 + S_kv - S)
        scores1 = scores1.masked_fill(causal, float("-inf"))
        scores2 = scores2.masked_fill(causal, float("-inf"))

        attn1 = F.softmax(scores1, dim=-1)
        attn2 = F.softmax(scores2, dim=-1)

        # Lambda per-head (paper Eq. 3)
        lam_q1k1 = (self.lambda_q1 * self.lambda_k1).sum(dim=-1)  # [H]
        lam_q2k2 = (self.lambda_q2 * self.lambda_k2).sum(dim=-1)
        lam = torch.exp(lam_q1k1) - torch.exp(lam_q2k2) + self.lambda_init  # [H]
        lam = lam.view(1, -1, 1, 1)

        diff_attn = attn1 - lam * attn2

        out = torch.matmul(diff_attn, v)  # [B, H, S, head_dim]
        # ★ FIX 5/7: per-token LayerNorm (was sequence-spanning GroupNorm = leak)
        out = self.subln(out)
        out = out * (1 - self.lambda_init)

        out = out.transpose(1, 2).reshape(B, S, -1)
        return self.o_proj(out), past_key_value


__all__ = ["DifferentialAttention"]