thenukegun10x commited on
Commit
4656f95
·
verified ·
1 Parent(s): 5026df9

Remove src/models/moe_swiglu_v2.py

Browse files
Files changed (1) hide show
  1. src/models/moe_swiglu_v2.py +0 -182
src/models/moe_swiglu_v2.py DELETED
@@ -1,182 +0,0 @@
1
- """
2
- Streamlined 16-Expert SwiGLU Mixture-of-Experts (MoE) Layer for Vision Transformers.
3
- Uses pure SwiGLU FFN experts, Gating Residuals (cross-layer routing memory),
4
- and Context-Aware Global Projections.
5
- """
6
- import torch
7
- import torch.nn as nn
8
- import torch.nn.functional as F
9
-
10
- class SwiGLU(nn.Module):
11
- """3-Matrix SwiGLU Feed-Forward Network: SiLU(W1 * x) * (W3 * x) @ W2"""
12
- def __init__(self, d_model: int, d_ff: int):
13
- super().__init__()
14
- self.w1 = nn.Linear(d_model, d_ff, bias=False)
15
- self.w2 = nn.Linear(d_ff, d_model, bias=False)
16
- self.w3 = nn.Linear(d_model, d_ff, bias=False)
17
-
18
- def forward(self, x: torch.Tensor) -> torch.Tensor:
19
- return self.w2(F.silu(self.w1(x)) * self.w3(x))
20
-
21
- class MoESwiGLUv2(nn.Module):
22
- """
23
- Pure SwiGLU MoE FFN layer (Feb 2026 specialization-ready).
24
- - num_ffn: Number of SwiGLU experts (default 16, use 20 for 3k or 32 for fine granularity)
25
- - top_k: Active experts per token (default 2)
26
- - gating_residual: Linear projection of previous-layer gate logits
27
- - gate_ctx: Global CLS context projection into gate logits
28
- - shared_experts: Dense shared expert(s) always active (DeepSeekMoE-style, 0 = pure sparse)
29
- """
30
- def __init__(
31
- self,
32
- d_model: int,
33
- d_ff: int = 512,
34
- num_ffn: int = 16,
35
- top_k: int = 2,
36
- gating_residual: bool = True,
37
- gate_ctx: bool = True,
38
- shared_experts: int = 0,
39
- ):
40
- super().__init__()
41
- self.d_model = d_model
42
- self.d_ff = d_ff
43
- self.num_ffn = num_ffn
44
- self.top_k = min(top_k, num_ffn)
45
- self.gate_ctx = gate_ctx
46
- self.shared_experts = int(shared_experts)
47
-
48
- # Router linear layer: [d_model -> num_ffn] (routed only — shared are dense)
49
- self.router = nn.Linear(d_model, num_ffn, bias=False)
50
- self.experts = nn.ModuleList([SwiGLU(d_model, d_ff) for _ in range(num_ffn)])
51
- # Shared dense experts (always on, no routing) — DeepSeekMoE-style
52
- self.shared_mlp = nn.ModuleList([SwiGLU(d_model, d_ff) for _ in range(self.shared_experts)]) if self.shared_experts > 0 else None
53
-
54
- # Cross-layer gating residual projection
55
- self.gating_residual = None
56
- if gating_residual:
57
- self.gating_residual = nn.Linear(num_ffn, num_ffn, bias=False)
58
- nn.init.zeros_(self.gating_residual.weight)
59
-
60
- # Context-aware global projection
61
- self.ctx_proj = None
62
- if gate_ctx:
63
- self.ctx_proj = nn.Linear(d_model, num_ffn, bias=False)
64
- nn.init.zeros_(self.ctx_proj.weight)
65
-
66
- def forward(self, x: torch.Tensor, prev_gate: torch.Tensor = None, ctx: torch.Tensor = None):
67
- """
68
- x: [B, T, D] Token embeddings
69
- prev_gate: [B, T, num_ffn] Gate logits from previous layer (optional)
70
- ctx: [B, D] Global CLS context embedding (optional)
71
- Returns:
72
- output: [B, T, D]
73
- aux_loss: Scalar load balancing loss
74
- gate_logits: [B, T, num_ffn]
75
- """
76
- B, T, D = x.shape
77
- x_2d = x.reshape(-1, D)
78
- N = x_2d.size(0)
79
-
80
- # 1. Compute Gate Logits
81
- gate_logits = self.router(x_2d) # [N, num_ffn]
82
-
83
- if self.ctx_proj is not None and ctx is not None:
84
- # Broadcast global CLS context across all T tokens
85
- gate_logits = gate_logits + self.ctx_proj(ctx).repeat_interleave(T, dim=0)
86
-
87
- if self.gating_residual is not None and prev_gate is not None:
88
- prev_flat = prev_gate.reshape(-1, self.num_ffn)
89
- if prev_flat.size(0) == N:
90
- gate_logits = gate_logits + self.gating_residual(prev_flat)
91
-
92
- # 2. Softmax & Top-K Selection
93
- gate_weights = F.softmax(gate_logits, dim=-1)
94
- top_weights, top_indices = gate_weights.topk(self.top_k, dim=-1)
95
- top_weights = top_weights / top_weights.sum(dim=-1, keepdim=True).clamp(min=1e-9)
96
- route_weights = top_weights.to(dtype=x_2d.dtype)
97
-
98
- # 3. Flatten tokens and sort by expert index for batch GEMM efficiency
99
- flat_expert = top_indices.reshape(-1)
100
- flat_token = (
101
- torch.arange(N, device=x.device)
102
- .unsqueeze(1)
103
- .expand(-1, self.top_k)
104
- .reshape(-1)
105
- )
106
- flat_weight = route_weights.reshape(-1, 1)
107
-
108
- order = torch.argsort(flat_expert, stable=True)
109
- sorted_tokens = flat_token[order]
110
- sorted_weights = flat_weight[order]
111
- sorted_x = x_2d.index_select(0, sorted_tokens)
112
-
113
- counts = torch.bincount(flat_expert, minlength=self.num_ffn).to(torch.int64)
114
- offsets = torch.cat(
115
- [torch.zeros(1, dtype=torch.int64, device=x.device),
116
- torch.cumsum(counts, 0)[:-1]]
117
- )
118
-
119
- output = torch.zeros_like(x_2d)
120
-
121
- # 4. Dispatch each expert — also collect per-expert mean activation for intra-loss
122
- expert_mean_acts: list[torch.Tensor] = []
123
- for expert_idx in range(self.num_ffn):
124
- lo = int(offsets[expert_idx])
125
- hi = lo + int(counts[expert_idx])
126
- if lo == hi:
127
- continue
128
- tids = sorted_tokens[lo:hi]
129
- w = sorted_weights[lo:hi]
130
- # Keep raw expert act before weighting for specialization loss
131
- raw_out = self.experts[expert_idx](sorted_x[lo:hi])
132
- # For intra loss we store L2-normalized mean act per expert on this batch
133
- if counts[expert_idx] > 0:
134
- with torch.no_grad():
135
- # detached mean to avoid backprop through loss shaping the act itself too strongly
136
- pass
137
- mean_act = raw_out.mean(dim=0)
138
- expert_mean_acts.append((expert_idx, mean_act.float()))
139
- expert_out = raw_out * w
140
- output.index_add_(0, tids, expert_out.to(dtype=output.dtype))
141
-
142
- # 4b. Shared dense experts (always active, DeepSeekMoE-style) — add on top of sparse routed
143
- if self.shared_mlp is not None:
144
- shared_out = 0
145
- for mlp in self.shared_mlp:
146
- shared_out = shared_out + mlp(x_2d)
147
- # Average shared contribution (keeps scale comparable to sparse)
148
- output = output + (shared_out / max(1, len(self.shared_mlp))).to(dtype=output.dtype)
149
-
150
- # 5. Load-Balancing Auxiliary Loss
151
- fractions = counts.float() / counts.sum().clamp(min=1e-12).float()
152
- avg_prob = gate_weights.mean(dim=0)
153
- aux_loss = self.num_ffn * (fractions.to(dtype=gate_weights.dtype) * avg_prob).sum()
154
-
155
- # 6. Intra-layer SwiGLU specialization loss (Feb 2026)
156
- # Penalize cosine similarity between distinct routed experts' mean activations on same batch.
157
- # Encourages complementarity vs redundant Eucalyptus-venation experts. Cheap: [E, D] matmul.
158
- intra_loss = torch.tensor(0.0, device=x.device, dtype=x.dtype)
159
- if len(expert_mean_acts) >= 2:
160
- # Stack available experts: [E_active, D]
161
- idxs, acts = zip(*expert_mean_acts)
162
- acts_t = torch.stack(list(acts), dim=0) # [Ea, D]
163
- acts_n = F.normalize(acts_t, dim=1, p=2, eps=1e-6)
164
- cos = acts_n @ acts_n.T # [Ea, Ea]
165
- # Mask diagonal, penalize mean absolute cosine (want orthogonal / complementary)
166
- mask = torch.eye(cos.size(0), device=cos.device, dtype=torch.bool)
167
- # Use upper triangle only
168
- cos_off = cos.masked_select(~mask)
169
- if cos_off.numel() > 0:
170
- # Hinge: only penalize >0.1 similarity, allow slight negative correlation
171
- intra_loss = F.relu(cos_off.abs() - 0.1).mean()
172
- # Keep in FP32 for stability, then cast back
173
- intra_loss = intra_loss.to(dtype=x.dtype)
174
-
175
- # Return intra_loss as 4th element (backward compat: caller can ignore)
176
- return (
177
- output.view(B, T, D),
178
- aux_loss,
179
- gate_logits.view(B, T, self.num_ffn),
180
- intra_loss,
181
- gate_weights.view(B, T, self.num_ffn),
182
- )