| from __future__ import annotations |
|
|
| import math |
|
|
| import torch |
| import torch.nn as nn |
| import torch.nn.functional as F |
|
|
| from .aurora_config import AuroraConfig |
|
|
| try: |
| from cut_cross_entropy import linear_cross_entropy |
| except ImportError: |
| linear_cross_entropy = None |
|
|
|
|
| class RMSNorm(nn.Module): |
| def __init__(self, dim: int, eps: float) -> None: |
| super().__init__() |
| self.weight = nn.Parameter(torch.ones(dim)) |
| self.eps = eps |
|
|
| def forward(self, x: torch.Tensor) -> torch.Tensor: |
| scale = torch.rsqrt(x.pow(2).mean(dim=-1, keepdim=True) + self.eps) |
| return self.weight * x * scale |
|
|
|
|
| def precompute_rope_frequencies( |
| seq_len: int, head_dim: int, theta: float, device: torch.device, dtype: torch.dtype |
| ) -> tuple[torch.Tensor, torch.Tensor]: |
| inv_freq = 1.0 / (theta ** (torch.arange(0, head_dim, 2, device=device).float() / head_dim)) |
| positions = torch.arange(seq_len, device=device).float() |
| freqs = torch.outer(positions, inv_freq) |
| return freqs.cos().to(dtype=dtype), freqs.sin().to(dtype=dtype) |
|
|
|
|
| def apply_rope(x: torch.Tensor, cos: torch.Tensor, sin: torch.Tensor) -> torch.Tensor: |
| cos = cos[None, :, None, :] |
| sin = sin[None, :, None, :] |
| x_even = x[..., 0::2] |
| x_odd = x[..., 1::2] |
| out = torch.empty_like(x) |
| out[..., 0::2] = x_even * cos - x_odd * sin |
| out[..., 1::2] = x_even * sin + x_odd * cos |
| return out |
|
|
|
|
| class CausalSelfAttention(nn.Module): |
| def __init__(self, cfg: AuroraConfig) -> None: |
| super().__init__() |
| self.cfg = cfg |
| self.num_heads = cfg.num_attention_heads |
| self.num_kv_heads = cfg.num_key_value_heads |
| self.head_dim = cfg.head_dim |
| self.kv_repeat = self.num_heads // self.num_kv_heads |
|
|
| self.q_proj = nn.Linear(cfg.hidden_size, cfg.num_attention_heads * self.head_dim, bias=cfg.attention_bias) |
| self.k_proj = nn.Linear(cfg.hidden_size, cfg.num_key_value_heads * self.head_dim, bias=cfg.attention_bias) |
| self.v_proj = nn.Linear(cfg.hidden_size, cfg.num_key_value_heads * self.head_dim, bias=cfg.attention_bias) |
| self.o_proj = nn.Linear(cfg.hidden_size, cfg.hidden_size, bias=cfg.attention_bias) |
| self.q_norm = RMSNorm(self.head_dim, cfg.rms_norm_eps) if cfg.qk_norm else nn.Identity() |
| self.k_norm = RMSNorm(self.head_dim, cfg.rms_norm_eps) if cfg.qk_norm else nn.Identity() |
| self.dropout_p = cfg.dropout |
|
|
| def forward(self, x: torch.Tensor, cos: torch.Tensor, sin: torch.Tensor) -> torch.Tensor: |
| batch, seq_len, _ = x.shape |
| q = self.q_proj(x).view(batch, seq_len, self.num_heads, self.head_dim) |
| k = self.k_proj(x).view(batch, seq_len, self.num_kv_heads, self.head_dim) |
| v = self.v_proj(x).view(batch, seq_len, self.num_kv_heads, self.head_dim) |
|
|
| q = self.q_norm(q) |
| k = self.k_norm(k) |
| q = apply_rope(q, cos, sin).transpose(1, 2) |
| k = apply_rope(k, cos, sin).transpose(1, 2) |
| v = v.transpose(1, 2) |
|
|
| |
| |
| if self.kv_repeat > 1: |
| k = k.repeat_interleave(self.kv_repeat, dim=1) |
| v = v.repeat_interleave(self.kv_repeat, dim=1) |
| y = F.scaled_dot_product_attention( |
| q, |
| k, |
| v, |
| attn_mask=None, |
| dropout_p=self.dropout_p if self.training else 0.0, |
| is_causal=True, |
| ) |
| y = y.transpose(1, 2).contiguous().view(batch, seq_len, self.cfg.hidden_size) |
| return self.o_proj(y) |
|
|
|
|
| class SwiGLU(nn.Module): |
| def __init__(self, cfg: AuroraConfig, intermediate_size: int | None = None) -> None: |
| super().__init__() |
| intermediate_size = intermediate_size or cfg.intermediate_size |
| self.gate_proj = nn.Linear(cfg.hidden_size, intermediate_size, bias=cfg.mlp_bias) |
| self.up_proj = nn.Linear(cfg.hidden_size, intermediate_size, bias=cfg.mlp_bias) |
| self.down_proj = nn.Linear(intermediate_size, cfg.hidden_size, bias=cfg.mlp_bias) |
|
|
| def forward(self, x: torch.Tensor) -> torch.Tensor: |
| return self.down_proj(F.silu(self.gate_proj(x)) * self.up_proj(x)) |
|
|
|
|
| class Top1MoE(nn.Module): |
| """Top-1 routed SwiGLU experts with Switch-style router regularization.""" |
|
|
| def __init__(self, cfg: AuroraConfig) -> None: |
| super().__init__() |
| self.num_experts = cfg.num_experts |
| self.router_aux_loss_coef = cfg.router_aux_loss_coef |
| self.router_z_loss_coef = cfg.router_z_loss_coef |
| self.router_noise_scale = cfg.router_noise_scale |
| self.capacity_factor = cfg.moe_capacity_factor |
| self.use_gate_weight = cfg.router_use_gate_weight |
| |
| self.router = nn.Linear(cfg.hidden_size, cfg.num_experts, bias=False) |
| self.experts = nn.ModuleList([SwiGLU(cfg) for _ in range(cfg.num_experts)]) |
| |
| |
| self.last_expert_fraction: torch.Tensor | None = None |
| self.last_preferred_expert_fraction: torch.Tensor | None = None |
| self.last_forced_fraction: torch.Tensor | None = None |
| self.last_selected_gate_probability: torch.Tensor | None = None |
|
|
| def _capacity_constrained_route( |
| self, scores: torch.Tensor, preferred_index: torch.Tensor |
| ) -> torch.Tensor: |
| """Assign exactly one expert/token while bounding every expert load. |
| |
| Experts keep their highest-scoring first-choice tokens. Overflow is |
| deterministically retried against each token's next preference. With |
| five experts this small eager-only matching pass is far cheaper than |
| an expert MLP and prevents a collapsed router from starving experts. |
| """ |
| token_count = scores.size(0) |
| capacity = max( |
| math.ceil(token_count / self.num_experts), |
| math.ceil(token_count * self.capacity_factor / self.num_experts), |
| ) |
| rankings = torch.argsort(scores, dim=-1, descending=True) |
| assigned = torch.full_like(preferred_index, -1) |
| remaining = [capacity for _ in range(self.num_experts)] |
|
|
| for rank in range(self.num_experts): |
| for expert_index in range(self.num_experts): |
| slots = remaining[expert_index] |
| if slots <= 0: |
| continue |
| candidates = torch.nonzero( |
| (assigned < 0) & (rankings[:, rank] == expert_index), as_tuple=False |
| ).flatten() |
| candidate_count = candidates.numel() |
| if candidate_count == 0: |
| continue |
| if candidate_count > slots: |
| candidate_scores = scores.index_select(0, candidates)[:, expert_index] |
| best_positions = torch.topk(candidate_scores, k=slots, sorted=False).indices |
| candidates = candidates.index_select(0, best_positions) |
| candidate_count = slots |
| assigned.index_fill_(0, candidates, expert_index) |
| remaining[expert_index] -= candidate_count |
|
|
| |
| |
| |
| if bool(torch.any(assigned < 0)): |
| raise RuntimeError("capacity-constrained MoE routing left tokens unassigned") |
| return assigned |
|
|
| def forward(self, x: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]: |
| original_shape = x.shape |
| flat_x = x.reshape(-1, original_shape[-1]) |
| router_logits = self.router(flat_x).float() |
| router_probs = torch.softmax(router_logits, dim=-1) |
| |
| |
| |
| |
| with torch.no_grad(): |
| routing_logits = router_logits |
| if self.training and self.router_noise_scale > 0: |
| |
| |
| |
| gumbel_noise = -torch.empty_like(router_logits).exponential_().log() |
| routing_logits = router_logits + self.router_noise_scale * gumbel_noise |
| preferred_index = torch.argmax(routing_logits, dim=-1) |
| route_index = ( |
| self._capacity_constrained_route(routing_logits, preferred_index) |
| if self.capacity_factor |
| else preferred_index |
| ) |
| selected_router_probability = router_probs.gather(1, route_index.unsqueeze(1)).squeeze(1) |
| route_weight = ( |
| selected_router_probability |
| if self.use_gate_weight |
| else torch.ones_like(selected_router_probability) |
| ) |
|
|
| output = torch.zeros_like(flat_x) |
| for expert_index, expert in enumerate(self.experts): |
| token_indices = torch.nonzero(route_index == expert_index, as_tuple=False).flatten() |
| if token_indices.numel() == 0: |
| continue |
| expert_input = flat_x.index_select(0, token_indices) |
| real_token_count = expert_input.size(0) |
| |
| |
| |
| |
| fp8_padding = (-real_token_count) % 16 |
| if fp8_padding: |
| expert_input = torch.cat( |
| (expert_input, expert_input.new_zeros((fp8_padding, expert_input.size(-1)))), dim=0 |
| ) |
| expert_output = expert(expert_input)[:real_token_count] |
| routed_output = expert_output * route_weight.index_select(0, token_indices).to(expert_output.dtype).unsqueeze(-1) |
| |
| |
| |
| output = output.index_copy(0, token_indices, routed_output.to(output.dtype)) |
|
|
| expert_fraction = F.one_hot(route_index, num_classes=self.num_experts).to(router_probs.dtype).mean(dim=0) |
| preferred_fraction = F.one_hot(preferred_index, num_classes=self.num_experts).to(router_probs.dtype).mean(dim=0) |
| mean_router_prob = router_probs.mean(dim=0) |
| self.last_expert_fraction = expert_fraction.detach() |
| self.last_preferred_expert_fraction = preferred_fraction.detach() |
| self.last_forced_fraction = (route_index != preferred_index).float().mean().detach() |
| self.last_selected_gate_probability = selected_router_probability.mean().detach() |
| |
| |
| aux_loss = self.router_aux_loss_coef * self.num_experts * torch.sum( |
| preferred_fraction * mean_router_prob |
| ) |
| z_loss = self.router_z_loss_coef * torch.logsumexp(router_logits, dim=-1).square().mean() |
| return output.reshape(original_shape), aux_loss + z_loss |
|
|
|
|
| class DecoderBlock(nn.Module): |
| def __init__(self, cfg: AuroraConfig) -> None: |
| super().__init__() |
| self.input_layernorm = RMSNorm(cfg.hidden_size, cfg.rms_norm_eps) |
| self.self_attn = CausalSelfAttention(cfg) |
| self.post_attention_layernorm = RMSNorm(cfg.hidden_size, cfg.rms_norm_eps) |
| self.mlp: nn.Module = Top1MoE(cfg) if cfg.num_experts > 1 else SwiGLU(cfg) |
|
|
| def forward(self, x: torch.Tensor, cos: torch.Tensor, sin: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]: |
| x = x + self.self_attn(self.input_layernorm(x), cos, sin) |
| mlp_input = self.post_attention_layernorm(x) |
| if isinstance(self.mlp, Top1MoE): |
| mlp_output, router_loss = self.mlp(mlp_input) |
| else: |
| mlp_output = self.mlp(mlp_input) |
| router_loss = x.new_zeros((), dtype=torch.float32) |
| return x + mlp_output, router_loss |
|
|
|
|
| class AuroraForCausalLM(nn.Module): |
| def __init__(self, cfg: AuroraConfig) -> None: |
| super().__init__() |
| cfg.validate() |
| self.cfg = cfg |
| self.embed_tokens = nn.Embedding(cfg.vocab_size, cfg.hidden_size) |
| self.layers = nn.ModuleList([DecoderBlock(cfg) for _ in range(cfg.num_layers)]) |
| self.norm = RMSNorm(cfg.hidden_size, cfg.rms_norm_eps) |
| self.lm_head = nn.Linear(cfg.hidden_size, cfg.vocab_size, bias=False) |
| self.last_router_loss: torch.Tensor | None = None |
| self.register_buffer("rope_cos_cached", torch.empty(0), persistent=False) |
| self.register_buffer("rope_sin_cached", torch.empty(0), persistent=False) |
| if cfg.tie_word_embeddings: |
| self.lm_head.weight = self.embed_tokens.weight |
| self.apply(self._init_weights) |
|
|
| def _init_weights(self, module: nn.Module) -> None: |
| if isinstance(module, nn.Linear): |
| nn.init.normal_(module.weight, mean=0.0, std=0.02) |
| if module.bias is not None: |
| nn.init.zeros_(module.bias) |
| elif isinstance(module, nn.Embedding): |
| nn.init.normal_(module.weight, mean=0.0, std=0.02) |
|
|
| def _rope_cache( |
| self, seq_len: int, device: torch.device, dtype: torch.dtype |
| ) -> tuple[torch.Tensor, torch.Tensor]: |
| cache_miss = ( |
| self.rope_cos_cached.numel() == 0 |
| or self.rope_cos_cached.size(0) < seq_len |
| or self.rope_cos_cached.device != device |
| or self.rope_cos_cached.dtype != dtype |
| ) |
| if cache_miss: |
| cos, sin = precompute_rope_frequencies( |
| self.cfg.context_length, |
| self.cfg.head_dim, |
| self.cfg.rope_theta, |
| device, |
| dtype, |
| ) |
| self.rope_cos_cached = cos |
| self.rope_sin_cached = sin |
| return self.rope_cos_cached[:seq_len], self.rope_sin_cached[:seq_len] |
|
|
| def _rope_dtype(self, x: torch.Tensor) -> torch.dtype: |
| if x.device.type == "cuda" and torch.is_autocast_enabled("cuda"): |
| return torch.get_autocast_dtype("cuda") |
| if x.device.type == "cpu" and torch.is_autocast_enabled("cpu"): |
| return torch.get_autocast_dtype("cpu") |
| return x.dtype |
|
|
| def forward( |
| self, input_ids: torch.Tensor, labels: torch.Tensor | None = None |
| ) -> tuple[torch.Tensor, torch.Tensor | None]: |
| x = self.embed_tokens(input_ids) |
| cos, sin = self._rope_cache(x.size(1), x.device, self._rope_dtype(x)) |
| router_loss = torch.zeros((), device=x.device, dtype=torch.float32) |
| for layer in self.layers: |
| x, layer_router_loss = layer(x, cos, sin) |
| router_loss = router_loss + layer_router_loss |
| |
| |
| |
| router_loss = router_loss / max(1, len(self.layers)) |
| self.last_router_loss = router_loss.detach() |
| x = self.norm(x) |
| if labels is not None and linear_cross_entropy is not None: |
| |
| |
| loss = linear_cross_entropy(x.to(self.lm_head.weight.dtype), self.lm_head.weight, labels, shift=True) |
| logits = x.new_empty(0) |
| else: |
| logits = self.lm_head(x) |
| loss = None |
| if labels is not None: |
| loss = F.cross_entropy( |
| logits[:, :-1].contiguous().view(-1, logits.size(-1)), |
| labels[:, 1:].contiguous().view(-1), |
| ) |
| |
| |
| if loss is not None and self.training: |
| loss = loss + router_loss |
| return logits, loss |
|
|
|
|
| def count_parameters(model: nn.Module) -> int: |
| seen: set[int] = set() |
| total = 0 |
| for param in model.parameters(): |
| ident = id(param) |
| if ident not in seen: |
| seen.add(ident) |
| total += param.numel() |
| return total |
|
|
|
|
| def count_active_parameters(model: nn.Module) -> int: |
| """Count parameters used by one top-1 path, without double-counting ties.""" |
| seen: set[int] = set() |
| total = 0 |
| for name, param in model.named_parameters(): |
| if ".mlp.experts." in name: |
| expert_index = name.split(".mlp.experts.", 1)[1].split(".", 1)[0] |
| if expert_index != "0": |
| continue |
| ident = id(param) |
| if ident not in seen: |
| seen.add(ident) |
| total += param.numel() |
| return total |
|
|
|
|
| def estimate_parameter_count(cfg: AuroraConfig) -> int: |
| model = AuroraForCausalLM(cfg) |
| return count_parameters(model) |
|
|