SeaWolf-AI commited on
Commit
1e67d89
·
verified ·
1 Parent(s): 4d073e1

fix: make model loadable via AutoModelForCausalLM (add auto_map, flatten module files to repo root, inherit GenerationMixin)

Browse files
Files changed (1) hide show
  1. differential.py +135 -0
differential.py ADDED
@@ -0,0 +1,135 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Differential Transformer (PRODUCTION - causal-safe)
3
+ ====================================================
4
+
5
+ Microsoft 2410.05258 (ICLR 2025)
6
+ Cancels noise via dual-softmax attention map subtraction.
7
+
8
+ Core formula:
9
+ DiffAttn(X) = (softmax(Q1 K1^T / sqrt(d)) - λ × softmax(Q2 K2^T / sqrt(d))) V
10
+ λ = exp(λ_q1 · λ_k1) - exp(λ_q2 · λ_k2) + λ_init
11
+
12
+ Implementation notes:
13
+ - GroupNorm (sequence-spanning) → LayerNorm(head_dim) (per-token, causal-safe)
14
+ - Causal triu mask explicit (was relying on SDPA, but split + softmax direct now)
15
+ - forward signature: layer_idx kwarg + (Tensor, past_kv) tuple return for modeling compat
16
+
17
+ Hyperparameter (configuration_aether_v2_7way.py):
18
+ - diff_lambda_init = 0.8
19
+ """
20
+ from __future__ import annotations
21
+ import math
22
+ from typing import Optional, Tuple
23
+
24
+ import torch
25
+ import torch.nn as nn
26
+ import torch.nn.functional as F
27
+
28
+
29
+ class DifferentialAttention(nn.Module):
30
+ """λ-gated dual-softmax differential attention with causal mask + LayerNorm."""
31
+
32
+ def __init__(self, config, layer_idx: int = 0):
33
+ super().__init__()
34
+ self.layer_idx = layer_idx
35
+ self.h = config.hidden_size
36
+ self.num_heads = config.num_attention_heads
37
+ self.num_kv_heads = config.num_key_value_heads
38
+ self.head_dim = config.head_dim
39
+ # Differential splits head_dim in half
40
+ self.diff_head_dim = self.head_dim // 2
41
+
42
+ # Layer-depth lambda init (paper Eq. 3): λ_init = 0.8 - 0.6 * exp(-0.3 * (depth - 1))
43
+ # Use config base + depth decay if provided.
44
+ base = getattr(config, "diff_lambda_init", 0.8)
45
+ decay = getattr(config, "diff_lambda_layer_decay", 0.6)
46
+ # Simple per-layer init
47
+ self.lambda_init = base - decay * math.exp(-0.3 * max(layer_idx - 1, 0))
48
+
49
+ # Q/K/V/O projections
50
+ self.q_proj = nn.Linear(self.h, self.num_heads * self.head_dim, bias=False)
51
+ self.k_proj = nn.Linear(self.h, self.num_kv_heads * self.head_dim, bias=False)
52
+ self.v_proj = nn.Linear(self.h, self.num_kv_heads * self.head_dim, bias=False)
53
+ self.o_proj = nn.Linear(self.num_heads * self.head_dim, self.h, bias=False)
54
+
55
+ # Lambda parameters per-head
56
+ self.lambda_q1 = nn.Parameter(torch.zeros(self.num_heads, self.diff_head_dim).normal_(0, 0.1))
57
+ self.lambda_k1 = nn.Parameter(torch.zeros(self.num_heads, self.diff_head_dim).normal_(0, 0.1))
58
+ self.lambda_q2 = nn.Parameter(torch.zeros(self.num_heads, self.diff_head_dim).normal_(0, 0.1))
59
+ self.lambda_k2 = nn.Parameter(torch.zeros(self.num_heads, self.diff_head_dim).normal_(0, 0.1))
60
+
61
+ # ★ FIX 5/7: LayerNorm(head_dim) — per-token norm, causal-safe.
62
+ # Was nn.GroupNorm(num_groups=num_heads, num_channels=num_heads*head_dim) which
63
+ # normalized over sequence dim and leaked future info.
64
+ self.subln = nn.LayerNorm(self.head_dim, eps=getattr(config, "rms_norm_eps", 1e-6))
65
+
66
+ def forward(
67
+ self,
68
+ hidden_states: torch.Tensor,
69
+ attention_mask: Optional[torch.Tensor] = None,
70
+ position_ids: Optional[torch.LongTensor] = None,
71
+ past_key_value=None,
72
+ use_cache: bool = False,
73
+ **kwargs,
74
+ ) -> Tuple[torch.Tensor, Optional[object]]:
75
+ B, S, _ = hidden_states.shape
76
+
77
+ q = self.q_proj(hidden_states).view(B, S, self.num_heads, self.head_dim)
78
+ k = self.k_proj(hidden_states).view(B, S, self.num_kv_heads, self.head_dim)
79
+ v = self.v_proj(hidden_states).view(B, S, self.num_kv_heads, self.head_dim)
80
+
81
+ # AetherCache: cache full k,v (pre-split) in HF layout [B, kv_h, S, D]
82
+ if past_key_value is not None:
83
+ kc, vc = past_key_value.update(k.transpose(1, 2), v.transpose(1, 2), getattr(self, "cache_idx", self.layer_idx))
84
+ k = kc.transpose(1, 2)
85
+ v = vc.transpose(1, 2)
86
+ S_kv = k.shape[1]
87
+
88
+ # Split Q, K halves
89
+ q1, q2 = q.split(self.diff_head_dim, dim=-1) # [B, S, H, d/2]
90
+ k1, k2 = k.split(self.diff_head_dim, dim=-1) # [B, S_kv, kv_h, d/2]
91
+
92
+ # GQA repeat
93
+ repeat = self.num_heads // self.num_kv_heads
94
+ k1 = k1.repeat_interleave(repeat, dim=2)
95
+ k2 = k2.repeat_interleave(repeat, dim=2)
96
+ v = v.repeat_interleave(repeat, dim=2)
97
+
98
+ # Transpose to [B, H, S, d]
99
+ q1 = q1.transpose(1, 2)
100
+ q2 = q2.transpose(1, 2)
101
+ k1 = k1.transpose(1, 2)
102
+ k2 = k2.transpose(1, 2)
103
+ v = v.transpose(1, 2)
104
+
105
+ scale = 1.0 / math.sqrt(self.diff_head_dim)
106
+ scores1 = torch.matmul(q1, k1.transpose(-2, -1)) * scale
107
+ scores2 = torch.matmul(q2, k2.transpose(-2, -1)) * scale
108
+
109
+ # ★ FIX 5/7: causal mask (triu(1) blocks future)
110
+ # AetherCache: query i is at absolute pos (S_kv - S + i) -> shift the diagonal
111
+ causal = torch.ones(S, S_kv, device=q1.device, dtype=torch.bool).triu(1 + S_kv - S)
112
+ scores1 = scores1.masked_fill(causal, float("-inf"))
113
+ scores2 = scores2.masked_fill(causal, float("-inf"))
114
+
115
+ attn1 = F.softmax(scores1, dim=-1)
116
+ attn2 = F.softmax(scores2, dim=-1)
117
+
118
+ # Lambda per-head (paper Eq. 3)
119
+ lam_q1k1 = (self.lambda_q1 * self.lambda_k1).sum(dim=-1) # [H]
120
+ lam_q2k2 = (self.lambda_q2 * self.lambda_k2).sum(dim=-1)
121
+ lam = torch.exp(lam_q1k1) - torch.exp(lam_q2k2) + self.lambda_init # [H]
122
+ lam = lam.view(1, -1, 1, 1)
123
+
124
+ diff_attn = attn1 - lam * attn2
125
+
126
+ out = torch.matmul(diff_attn, v) # [B, H, S, head_dim]
127
+ # ★ FIX 5/7: per-token LayerNorm (was sequence-spanning GroupNorm = leak)
128
+ out = self.subln(out)
129
+ out = out * (1 - self.lambda_init)
130
+
131
+ out = out.transpose(1, 2).reshape(B, S, -1)
132
+ return self.o_proj(out), past_key_value
133
+
134
+
135
+ __all__ = ["DifferentialAttention"]