Download flux/modules/moe.py from val-utehy/RotEx: direct link, hf CLI and curl.
- Browser
- Download file 5.9 kB
-
https://huggingface.co/val-utehy/RotEx/resolve/main/flux/modules/moe.py
- Command line
-
hf download hf://val-utehy/RotEx/flux/modules/moe.py
-
curl -L -o moe.py https://huggingface.co/val-utehy/RotEx/resolve/main/flux/modules/moe.py
5.9 kB
| """ | |
| Mixture-of-Experts (MoE) module for time-based style control. | |
| Implements sparse expert selection based on time intervals. | |
| Author: https://github.com/datnguyen-tien204 | |
| """ | |
| import torch | |
| import torch.nn as nn | |
| import torch.nn.functional as F | |
| from typing import Tuple, Optional, List | |
| import math | |
| class Expert(nn.Module): | |
| def __init__(self, input_dim: int, hidden_dim: int, output_dim: int): | |
| super().__init__() | |
| self.network = nn.Sequential( | |
| nn.Linear(input_dim, hidden_dim), | |
| nn.SiLU(), | |
| nn.Linear(hidden_dim, hidden_dim), | |
| nn.SiLU(), | |
| nn.Linear(hidden_dim, output_dim) | |
| ) | |
| def forward(self, x: torch.Tensor) -> torch.Tensor: | |
| return self.network(x) | |
| class TimeBasedGatingNetwork(nn.Module): | |
| def __init__(self, num_experts: int, input_dim: int = 2, temperature: float = 0.2, | |
| custom_centers: Optional[List[float]] = None, | |
| custom_widths: Optional[List[float]] = None): | |
| super().__init__() | |
| self.num_experts = num_experts | |
| self.temperature = temperature | |
| if custom_centers is not None: | |
| centers_rad = [c * (2 * math.pi / 24.0) for c in custom_centers] | |
| self.register_buffer('expert_centers', torch.tensor(centers_rad)) | |
| else: | |
| centers = [i * (2 * math.pi / num_experts) for i in range(num_experts)] | |
| self.register_buffer('expert_centers', torch.tensor(centers)) | |
| if custom_widths is not None: | |
| widths_rad = [(w * (2 * math.pi / 24.0)) / 2.0 for w in custom_widths] | |
| init_log_widths = torch.log(torch.tensor(widths_rad) + 1e-5) | |
| self.expert_log_widths = nn.Parameter(init_log_widths) | |
| else: | |
| self.expert_log_widths = nn.Parameter(torch.zeros(num_experts)) | |
| self.min_width = 0.1 | |
| def forward(self, phi: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor]: | |
| phi = phi.view(-1, 1) | |
| centers = self.expert_centers.view(1, -1) | |
| diff = torch.remainder(phi - centers + math.pi, 2 * math.pi) - math.pi | |
| widths = F.softplus(self.expert_log_widths).view(1, -1) + self.min_width | |
| logits = -diff ** 2 / (2 * widths ** 2) | |
| weights = F.softmax(logits / self.temperature, dim=-1) | |
| return weights, logits | |
| class MixtureOfExperts(nn.Module): | |
| def __init__(self, input_dim, hidden_dim, output_dim, num_experts, k=2, temperature=0.2, noise_std=0.1, | |
| custom_centers=None, custom_widths=None): | |
| super().__init__() | |
| self.num_experts = num_experts | |
| self.k = k | |
| self.noise_std = noise_std | |
| self.experts = nn.ModuleList([ | |
| Expert(input_dim, hidden_dim, output_dim) for _ in range(num_experts) | |
| ]) | |
| self.gate = TimeBasedGatingNetwork( | |
| num_experts, input_dim=2, temperature=temperature, | |
| custom_centers=custom_centers, custom_widths=custom_widths | |
| ) | |
| self.register_buffer('load_balance_loss_coef', torch.tensor(0.01)) | |
| def _add_noise(self, logits): | |
| if self.training and self.noise_std > 0: | |
| noise = torch.randn_like(logits) * self.noise_std | |
| return logits + noise | |
| return logits | |
| def _compute_load_balance_loss(self, weights): | |
| mean_weights = weights.mean(dim=0) | |
| target = torch.ones_like(mean_weights) / self.num_experts | |
| loss = F.mse_loss(mean_weights, target) | |
| return loss * self.load_balance_loss_coef | |
| def forward(self, x, phi, return_aux=False): | |
| is_sequence = x.dim() == 3 | |
| if is_sequence: | |
| B, L, C = x.shape | |
| x = x.reshape(B * L, C) | |
| phi = phi.unsqueeze(1).expand(B, L).reshape(B * L) | |
| weights, gate_logits = self.gate(phi) | |
| if self.training: | |
| gate_logits = self._add_noise(gate_logits) | |
| weights = F.softmax(gate_logits / self.gate.temperature, dim=-1) | |
| if self.k < self.num_experts: | |
| top_k_weights, top_k_indices = torch.topk(weights, self.k, dim=-1) | |
| top_k_weights = top_k_weights / top_k_weights.sum(dim=-1, keepdim=True) | |
| sparse_weights = torch.zeros_like(weights) | |
| sparse_weights.scatter_(1, top_k_indices, top_k_weights) | |
| weights = sparse_weights | |
| expert_outputs = torch.stack([e(x) for e in self.experts], dim=1) | |
| output = torch.einsum('be,beo->bo', weights, expert_outputs) | |
| if is_sequence: | |
| output = output.reshape(B, L, -1) | |
| weights = weights.reshape(B, L, -1).mean(dim=1) | |
| aux = None | |
| if return_aux: | |
| aux = {'load_balance_loss': self._compute_load_balance_loss(weights)} | |
| return output, aux | |
| class MoEStyleAdapter(nn.Module): | |
| def __init__(self, style_dim, hidden_dim, num_experts, custom_centers=None, custom_widths=None): | |
| super().__init__() | |
| self.moe = MixtureOfExperts( | |
| style_dim, hidden_dim, style_dim, num_experts, k=2, temperature=0.2, noise_std=0.1, | |
| custom_centers=custom_centers, custom_widths=custom_widths | |
| ) | |
| def forward(self, style, phi, return_aux=False): | |
| adapted, aux = self.moe(style, phi, return_aux=return_aux) | |
| return adapted + style, aux | |
| class MoEFeatureProcessor(nn.Module): | |
| def __init__(self, feature_dim, num_experts, expand_ratio=4, custom_centers=None, custom_widths=None): | |
| super().__init__() | |
| self.moe = MixtureOfExperts( | |
| feature_dim, feature_dim * expand_ratio, feature_dim, num_experts, k=2, temperature=0.2, noise_std=0.1, | |
| custom_centers=custom_centers, custom_widths=custom_widths | |
| ) | |
| self.norm = nn.LayerNorm(feature_dim) | |
| def forward(self, x, phi, return_aux=False): | |
| normed = self.norm(x) | |
| processed, aux = self.moe(normed, phi, return_aux=return_aux) | |
| return x + processed, aux |