Image Classification
LiteRT
LiteRT
ONNX
English
vision
botany
western-australia
dinov3
mixture-of-experts
adaround
fp8
int8
android
biodiversity
flora
Instructions to use thenukegun10x/PLantDetect-WA with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- LiteRT
How to use thenukegun10x/PLantDetect-WA with LiteRT:
# No code snippets available yet for this library. # To use this model, check the repository files and the library's documentation. # Want to help? PRs adding snippets are welcome at: # https://github.com/huggingface/huggingface.js
- Notebooks
- Google Colab
- Kaggle
Remove src/models/moe_swiglu_v2.py
Browse files- 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 |
-
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|