"""Baseline decoder-only transformer with shared building blocks (RMSNorm, SwiGLU, MoE, RoPE, sink softmax) reused across every other variant.""" import math from dataclasses import dataclass from typing import List, Optional, Tuple, Dict, Any import torch import torch.nn as nn import torch.nn.functional as F from .lm_loss import ( lm_cross_entropy_from_logits, token_superposition_attention_mask, token_superposition_embeddings, ) def _maybe_all_reduce_load(load: torch.Tensor) -> torch.Tensor: """Centralized expert-load accounting for bias updates. CUDA/CPU distributed runs use ``torch.distributed`` directly. XLA patches this hook from ``scripts/train_xla.py`` because its collectives live outside ``torch.distributed``. """ if torch.distributed.is_available() and torch.distributed.is_initialized(): load = load.clone() torch.distributed.all_reduce( load, op=torch.distributed.ReduceOp.SUM, ) return load def _expert_load_from_topk( topk_indices: torch.Tensor, num_experts: int, ) -> torch.Tensor: """Count top-k expert assignments without materialising one-hot tensors.""" return torch.bincount( topk_indices.reshape(-1), minlength=num_experts, ).to(torch.float32) def combine_lm_and_aux_loss( lm_loss: Optional[torch.Tensor], aux_loss: Optional[torch.Tensor], training: bool, ) -> Optional[torch.Tensor]: """Train on auxiliary regularization while reporting LM loss separately.""" if lm_loss is None: return None if training and aux_loss is not None: return lm_loss + aux_loss return lm_loss def _validate_moe_config(config) -> None: if not config.use_moe: return if config.num_shared_experts < 1: raise ValueError("num_shared_experts must be >= 1 when use_moe=True") if config.num_sparse_experts < 1: raise ValueError("num_sparse_experts must be >= 1 when use_moe=True") if not (1 <= config.top_k <= config.num_sparse_experts): raise ValueError( f"top_k ({config.top_k}) must be in [1, num_sparse_experts=" f"{config.num_sparse_experts}] when use_moe=True" ) if config.expert_d_ff < 1: raise ValueError("expert_d_ff must be >= 1 when use_moe=True") if config.capacity_factor <= 0: raise ValueError("capacity_factor must be > 0 when use_moe=True") if getattr(config, "router_logit_noise_std", 0.0) < 0: raise ValueError("router_logit_noise_std must be >= 0 when use_moe=True") if getattr(config, "router_logit_noise_decay_steps", 0) < 0: raise ValueError("router_logit_noise_decay_steps must be >= 0 when use_moe=True") if getattr(config, "router_init_std", 0.0) <= 0: raise ValueError("router_init_std must be > 0 when use_moe=True") if getattr(config, "router_bias_error_clip", 0.0) <= 0: raise ValueError("router_bias_error_clip must be > 0 when use_moe=True") if getattr(config, "router_bias_clip", 0.0) <= 0: raise ValueError("router_bias_clip must be > 0 when use_moe=True") if getattr(config, "moe_aux_loss_weight", 0.0) < 0: raise ValueError("moe_aux_loss_weight must be >= 0 when use_moe=True") if getattr(config, "moe_aux_loss_decay_steps", 0) < 0: raise ValueError("moe_aux_loss_decay_steps must be >= 0 when use_moe=True") @dataclass class BaselineConfig: vocab_size: int = 32000 d_model: int = 512 max_seq_len: int = 2048 num_layers: int = 12 num_heads: int = 8 norm_eps: float = 1e-6 # SwiGLU has 3 matmuls; ~(8/3) * d_model matches a 4*d_model 2-matmul FFN. d_ff: int = 1364 use_moe: bool = True num_shared_experts: int = 2 num_sparse_experts: int = 64 top_k: int = 6 expert_d_ff: int = 256 bias_update_rate: float = 0.01 capacity_factor: float = 2.0 router_logit_noise_std: float = 0.1 router_logit_noise_decay_steps: int = 2000 router_init_std: float = 0.002 router_bias_error_clip: float = 1.0 router_bias_clip: float = 1.0 moe_aux_loss_weight: float = 1e-3 moe_aux_loss_decay_steps: int = 2000 # 0 keeps the standard full-logits CE. Positive values enable the # memory-efficient chunked LM-head CE in models that support it. lm_head_chunk_size: int = 0 # Cross-loop expert-diversity weight; only acts when an MoE layer is # reused across loop iterations (recursive / logos body stack). moe_diversity_factor: float = 0.0 rope_base: float = 10000.0 qk_norm: bool = True partial_rope_dim: Optional[int] = None attention_sink: bool = True # When True, route the BlockAttentionResidual depth-softmax + weighted # sum through an opaque torch.library.custom_op so torch.compile can't # fuse softmax_backward with the upstream stack / RMSNorm / dot-product # chain. Needed on SMEM-constrained GPUs (sm_120 / Ada-class consumer # cards, ~99 KB SMEM/SM) where Inductor's persistent-reduction fused # backward exceeds the per-block shared-memory cap. Adds ~one graph # break per BlockAttentionResidual call (~97 in Logos at default # depth) — typically <2% throughput on cards where the unfused path # compiles fine. block_residual_isolate_softmax: bool = False def __post_init__(self): if self.d_model % self.num_heads != 0: raise ValueError("d_model must be divisible by num_heads") head_dim = self.d_model // self.num_heads if self.partial_rope_dim is not None: if self.partial_rope_dim > head_dim: raise ValueError( f"partial_rope_dim ({self.partial_rope_dim}) must be " f"<= head_dim ({head_dim})" ) if self.partial_rope_dim % 2 != 0: raise ValueError( f"partial_rope_dim ({self.partial_rope_dim}) must be even" ) _validate_moe_config(self) # Fused C++ kernel (PyTorch >= 2.4) — pow+mean+rsqrt+mul in a single pass, # vs the previous 3-kernel python implementation. Same ``.weight`` parameter # so existing checkpoints load unchanged. Note that nn.RMSNorm puts eps # inside the sqrt (1/sqrt(mean(x^2)+eps)) where the old impl had it outside # (1/(sqrt(mean(x^2))+eps)) — a tiny numerical difference, not a behavior # change at training scale. RMSNorm = nn.RMSNorm class RotaryEmbedding(nn.Module): def __init__(self, head_dim: int, max_seq_len: int = 2048, base: float = 10000.0): super().__init__() self.head_dim = head_dim self.max_seq_len = max_seq_len self.base = base inv_freq = 1.0 / (base ** (torch.arange(0, head_dim, 2).float() / head_dim)) t = torch.arange(max_seq_len, dtype=torch.float32) freqs = torch.einsum("i,j->ij", t, inv_freq) emb = torch.cat((freqs, freqs), dim=-1) self.register_buffer("cos", emb.cos().unsqueeze(0).unsqueeze(0), persistent=False) self.register_buffer("sin", emb.sin().unsqueeze(0).unsqueeze(0), persistent=False) @staticmethod def rotate_half(x: torch.Tensor) -> torch.Tensor: x1, x2 = x.chunk(2, dim=-1) return torch.cat((-x2, x1), dim=-1) def forward(self, x: torch.Tensor, seq_len: int) -> torch.Tensor: # Fail loudly when slicing the cos/sin table would silently truncate. if seq_len > self.max_seq_len: raise ValueError( f"RotaryEmbedding: seq_len ({seq_len}) exceeds the " f"precomputed max_seq_len ({self.max_seq_len})." ) cos = self.cos[:, :, :seq_len, :].to(x.device) sin = self.sin[:, :, :seq_len, :].to(x.device) return x * cos + self.rotate_half(x) * sin def forward_at_positions( self, x: torch.Tensor, positions: torch.Tensor ) -> torch.Tensor: """Rotate ``x`` using cos/sin gathered at arbitrary integer positions. ``positions`` is a 1-D ``long`` tensor of length ``x.shape[-2]`` (the sequence axis), broadcast across batch/head dims. Used by compressed attention to rotate pooled keys at a per-group representative position rather than the dense ``0..seq_len`` grid. Indexing (not slicing) the precomputed table keeps this torch.compile/XLA-safe. Positions are clamped into ``[0, max_seq_len)`` so an out-of-range index can't silently wrap or fault the gather — the same guard ``forward()`` gives via its ``ValueError``, expressed as a clamp here to stay free of the data-dependent host sync a bound check would force. """ positions = positions.clamp(0, self.max_seq_len - 1) # cos/sin are [1, 1, max_seq_len, dim]; gather the seq axis -> [L, dim]. cos = self.cos[0, 0].to(x.device).index_select(0, positions) sin = self.sin[0, 0].to(x.device).index_select(0, positions) return x * cos + self.rotate_half(x) * sin class SwiGLU(nn.Module): def __init__(self, d_model: int, d_ff: int): super().__init__() self.w_gate = nn.Linear(d_model, d_ff, bias=False) self.w_up = nn.Linear(d_model, d_ff, bias=False) self.w_down = nn.Linear(d_ff, d_model, bias=False) def forward(self, x: torch.Tensor) -> torch.Tensor: return self.w_down(F.silu(self.w_gate(x)) * self.w_up(x)) class Expert(nn.Module): def __init__(self, d_model: int, d_ff: int): super().__init__() self.ffn = SwiGLU(d_model, d_ff) def forward(self, x: torch.Tensor) -> torch.Tensor: return self.ffn(x) class SparseExpertBank(nn.Module): """Packed sparse-expert SwiGLU weights. The master parameters stay 2D so the existing Muon parameter split still picks them up. ``packed_weights`` returns zero-copy 3D views grouped by expert. """ def __init__( self, num_experts: int, d_model: int, d_ff: int, ): super().__init__() self.num_experts = num_experts self.d_model = d_model self.d_ff = d_ff self.w_gate = nn.Parameter(torch.empty(num_experts * d_ff, d_model)) self.w_up = nn.Parameter(torch.empty(num_experts * d_ff, d_model)) self.w_down = nn.Parameter(torch.empty(num_experts * d_model, d_ff)) self.reset_parameters() def reset_parameters(self) -> None: nn.init.normal_(self.w_gate, mean=0.0, std=0.02) nn.init.normal_(self.w_up, mean=0.0, std=0.02) nn.init.normal_(self.w_down, mean=0.0, std=0.02) def packed_weights( self, ) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]: return ( self.w_gate.view(self.num_experts, self.d_ff, self.d_model), self.w_up.view(self.num_experts, self.d_ff, self.d_model), self.w_down.view(self.num_experts, self.d_model, self.d_ff), ) def forward_batched(self, expert_in: torch.Tensor) -> torch.Tensor: """SwiGLU over a static-shape ``(E, C, d_model)`` expert-grouped batch. Three batched GEMMs replace ``E`` Python iterations of three small ``F.linear`` calls — fewer kernel launches and better SM utilisation when per-expert capacity ``C`` is small. """ w_gate, w_up, w_down = self.packed_weights() h_gate = torch.bmm(expert_in, w_gate.transpose(-1, -2)) h_up = torch.bmm(expert_in, w_up.transpose(-1, -2)) hidden = F.silu(h_gate) * h_up return torch.bmm(hidden, w_down.transpose(-1, -2)) class Router(nn.Module): def __init__(self, d_model: int, num_experts: int): super().__init__() self.linear = nn.Linear(d_model, num_experts, bias=False) def forward(self, x: torch.Tensor) -> torch.Tensor: return self.linear(x) class MoELayer(nn.Module): """Shared experts + top-k sparse experts with bounded bias balancing. Static-shape dispatch keeps it torch.compile-clean. The router combines a post-step bias controller with optional warmup-only selection noise and a tiny differentiable importance/load regularizer. ``num_loops`` > 1 gives the bias buffer a row per loop iteration so the same weights can specialise differently when reused across loops. """ def __init__( self, config: BaselineConfig, num_loops: int = 1, top_k: Optional[int] = None, ): super().__init__() self.d_model = config.d_model self.num_shared_experts = config.num_shared_experts self.num_sparse_experts = config.num_sparse_experts # Per-instance ``top_k`` override lets boundary stacks (entry/exit) # request more experts per token without touching the body's value. # Validation mirrors ``_validate_moe_config``. if top_k is None: top_k = config.top_k if not (1 <= top_k <= config.num_sparse_experts): raise ValueError( f"MoELayer top_k ({top_k}) must be in " f"[1, num_sparse_experts={config.num_sparse_experts}]" ) self.top_k = top_k self.bias_update_rate = config.bias_update_rate self.capacity_factor = config.capacity_factor self.router_logit_noise_std = float(getattr(config, "router_logit_noise_std", 0.0)) self.router_logit_noise_decay_steps = int( getattr(config, "router_logit_noise_decay_steps", 0) ) self.router_bias_error_clip = float( getattr(config, "router_bias_error_clip", 1.0) ) self.router_bias_clip = float(getattr(config, "router_bias_clip", 1.0)) self.moe_aux_loss_weight = float(getattr(config, "moe_aux_loss_weight", 0.0)) self.moe_aux_loss_decay_steps = int( getattr(config, "moe_aux_loss_decay_steps", 0) ) self.num_loops = num_loops self.diversity_factor = float(getattr(config, "moe_diversity_factor", 0.0)) self.router = Router(config.d_model, config.num_sparse_experts) self.register_buffer( "bias", torch.zeros(num_loops, config.num_sparse_experts), persistent=True, ) self.register_buffer( "router_noise_scale", torch.tensor(self.router_logit_noise_std, dtype=torch.float32), persistent=False, ) self.register_buffer( "moe_aux_loss_scale", torch.tensor(self.moe_aux_loss_weight, dtype=torch.float32), persistent=False, ) self.shared_experts = nn.ModuleList([ Expert(config.d_model, config.expert_d_ff) for _ in range(config.num_shared_experts) ]) self.sparse_experts = SparseExpertBank( config.num_sparse_experts, config.d_model, config.expert_d_ff, ) @staticmethod def _decayed_scale(base: float, decay_steps: int, step: int) -> float: if base <= 0: return 0.0 if decay_steps <= 0: return base progress = min(1.0, max(0.0, step / max(1, decay_steps))) return base * (1.0 - progress) def set_training_step(self, step: int) -> None: """Update warmup-only router controls outside the compiled forward.""" self.router_noise_scale.fill_( self._decayed_scale( self.router_logit_noise_std, self.router_logit_noise_decay_steps, step, ) ) self.moe_aux_loss_scale.fill_( self._decayed_scale( self.moe_aux_loss_weight, self.moe_aux_loss_decay_steps, step, ) ) def _router_aux_loss( self, router_scores: torch.Tensor, topk_indices: torch.Tensor, ) -> torch.Tensor: if not self.training or self.moe_aux_loss_weight <= 0: return router_scores.new_zeros(()) E = self.num_sparse_experts scores = router_scores.float().reshape(-1, E) importance = scores.mean(dim=0) importance = importance / importance.sum().clamp_min(1e-9) with torch.no_grad(): load = _expert_load_from_topk(topk_indices.detach(), E).to( importance.device, ) load = load / load.sum().clamp_min(1.0) # Switch-style load/importance term gives router weights gradient even # when hard top-k collapses while soft scores still look near-uniform. switch = E * (load * importance).sum() target = 1.0 / E kl_uniform = ( importance * (torch.log(importance.clamp_min(1e-9)) - math.log(target)) ).sum() aux = switch + kl_uniform scale = self.moe_aux_loss_scale.to(device=aux.device, dtype=aux.dtype) return aux * scale def _balance_update(self, load_fraction: torch.Tensor) -> torch.Tensor: target = 1.0 / self.num_sparse_experts update = (target - load_fraction) / target return update.clamp( -self.router_bias_error_clip, self.router_bias_error_clip, ) def _renormalize_bias(self) -> None: self.bias.sub_(self.bias.mean(dim=1, keepdim=True)) self.bias.clamp_(-self.router_bias_clip, self.router_bias_clip) def forward( self, x: torch.Tensor, loop_idx: int = 0, ) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]: batch, seq_len, d_model = x.shape N = batch * seq_len device = x.device dtype = x.dtype E = self.num_sparse_experts K = self.top_k # Canonical DeepSeek-V3 routing: bounded sigmoid affinities drive # top-k selection, while the balance bias only steers selection. # Gates come from the clean affinities so lowering bias[e] for an # over-used expert does not also suppress e's gradient signal. raw_logits = self.router(x) router_scores = raw_logits.sigmoid() biased_scores = router_scores + self.bias[loop_idx] shared_out = sum(expert(x) for expert in self.shared_experts) / self.num_shared_experts capacity = max(1, int(N * K * self.capacity_factor / E)) C = capacity x_flat = x.view(-1, d_model) # Top-K selection from biased scores. Optional training-only noisy # top-k breaks whole-layer ties when compressed/global attention # produces low-diversity early router inputs; gates below still use # clean scores so selected experts receive stable gradients. selection_scores = biased_scores if self.training and self.router_logit_noise_std > 0: selection_scores = selection_scores + ( torch.randn_like(selection_scores) * self.router_noise_scale.to(selection_scores.dtype) ) _, topk_indices = torch.topk(selection_scores, K, dim=-1) aux_loss = self._router_aux_loss(router_scores, topk_indices) # Gates from clean bounded scores at the selected experts. selected_scores = router_scores.gather(-1, topk_indices) topk_probs = selected_scores / selected_scores.sum( dim=-1, keepdim=True, ).clamp_min(1e-9) topk_indices_flat = topk_indices.view(-1) topk_probs_flat = topk_probs.view(-1) token_ids = torch.arange( N, device=device, ).unsqueeze(1).expand(-1, K).reshape(-1) sorted_expert_ids, sort_idx = torch.sort(topk_indices_flat) sorted_token_ids = token_ids[sort_idx] sorted_gates = topk_probs_flat[sort_idx].to(dtype=dtype) # Per-expert slot index via cummax over (position * is_first); # avoids the dynamic-shape ``nonzero`` that would graph-break # under compile. M = sorted_expert_ids.size(0) positions = torch.arange(M, device=device) diff = sorted_expert_ids[1:] != sorted_expert_ids[:-1] is_first = torch.cat( [torch.ones(1, dtype=torch.bool, device=device), diff] ) group_starts = (positions * is_first.long()).cummax(dim=0).values slot_indices = positions - group_starts sorted_x = x_flat[sorted_token_ids] # Over-capacity tokens are routed to a sentinel slot C and trimmed. valid = slot_indices < C safe_slot = torch.where( valid, slot_indices, torch.full_like(slot_indices, C) ) flat_slot = sorted_expert_ids * (C + 1) + safe_slot flat_size = E * (C + 1) valid_f = valid.to(dtype) invalid_tok = torch.full_like(sorted_token_ids, N) safe_token_ids = torch.where(valid, sorted_token_ids, invalid_tok) expert_in = torch.zeros(flat_size, d_model, device=device, dtype=dtype) expert_gate = torch.zeros(flat_size, device=device, dtype=dtype) expert_tok = torch.full((flat_size,), N, dtype=torch.long, device=device) expert_mask_i32 = torch.zeros(flat_size, dtype=torch.int32, device=device) expert_in = expert_in.index_add(0, flat_slot, sorted_x * valid_f.unsqueeze(-1)) expert_gate = expert_gate.index_add(0, flat_slot, sorted_gates * valid_f) expert_tok = expert_tok.scatter(0, flat_slot, safe_token_ids) expert_mask_i32 = expert_mask_i32.scatter(0, flat_slot, valid.to(torch.int32)) expert_in = expert_in.view(E, C + 1, d_model) expert_gate = expert_gate.view(E, C + 1) expert_tok = expert_tok.view(E, C + 1) expert_mask = expert_mask_i32.view(E, C + 1).bool() expert_in = expert_in[:, :C].contiguous() expert_gate = expert_gate[:, :C].contiguous() expert_tok = expert_tok[:, :C].contiguous() expert_mask = expert_mask[:, :C].contiguous() expert_out = self.sparse_experts.forward_batched(expert_in) # Static-shape scatter: invalid slots route to sentinel index N and # are trimmed off after the index_add_. flat_mask = expert_mask.view(-1) flat_tok = expert_tok.view(-1) flat_gate = expert_gate.view(-1) flat_src = expert_out.view(-1, d_model) safe_dst = torch.where( flat_mask, flat_tok, torch.full_like(flat_tok, N) ) safe_gate = torch.where( flat_mask, flat_gate, torch.zeros_like(flat_gate) ) sparse_out_ext = torch.zeros( N + 1, d_model, device=device, dtype=dtype ).index_add( 0, safe_dst, safe_gate.unsqueeze(-1) * flat_src ) sparse_out = sparse_out_ext[:N].view(batch, seq_len, d_model) return shared_out + sparse_out, aux_loss, topk_indices def update_bias(self, topk_indices: torch.Tensor, loop_idx: int = 0) -> None: """Per-row balance update for one loop iteration. Call after ``optimizer.step()``.""" with torch.no_grad(): load = _expert_load_from_topk( topk_indices, self.num_sparse_experts ) load = _maybe_all_reduce_load(load) total = load.sum() + 1e-9 load_fraction = load / total self.bias[loop_idx] += self.bias_update_rate * self._balance_update( load_fraction, ) self._renormalize_bias() def update_bias_per_loop( self, topk_per_loop: List[torch.Tensor], ) -> None: """Combined balance + cross-loop diversity update. With ``diversity_factor > 0`` and ``num_loops > 1``, swaps per-row balance for an aggregate balance plus a diversity term that pushes each row away from experts the other loops over-use. The specialisation mode has growth coefficient ``+beta / (num_loops-1)`` — unstable for any beta > 0, so any starting asymmetry amplifies. """ if len(topk_per_loop) != self.num_loops: raise ValueError( f"update_bias_per_loop expected {self.num_loops} loop " f"entries, got {len(topk_per_loop)}" ) with torch.no_grad(): loads = torch.stack([ _expert_load_from_topk(topk, self.num_sparse_experts) for topk in topk_per_loop ], dim=0) loads = _maybe_all_reduce_load(loads) loads = loads / (loads.sum(dim=1, keepdim=True) + 1e-9) if self.num_loops > 1 and self.diversity_factor > 0: agg_load = loads.mean(dim=0) agg_term = self._balance_update(agg_load).unsqueeze(0).expand_as(loads) other_mean = (loads.sum(dim=0, keepdim=True) - loads) / ( self.num_loops - 1 ) target = 1.0 / self.num_sparse_experts diversity_term = -self.diversity_factor * ( (other_mean - target) / target ).clamp( -self.router_bias_error_clip, self.router_bias_error_clip, ) update = agg_term + diversity_term else: update = self._balance_update(loads) update = update.clamp( -self.router_bias_error_clip, self.router_bias_error_clip, ) self.bias += self.bias_update_rate * update self._renormalize_bias() def init_moe_router_weights(module: nn.Module, std: float) -> None: """Initialize MoE routers after generic Linear initialization. Router projections should start much smaller than content projections: otherwise early low-diversity hidden states can make one random top-k expert set win globally before the bias balancer has enough authority. """ for child in module.modules(): if isinstance(child, MoELayer): nn.init.normal_(child.router.linear.weight, mean=0.0, std=std) def set_moe_training_step(module: nn.Module, step: int) -> None: """Apply step-scheduled MoE controls without mutating state in forward.""" for child in module.modules(): if isinstance(child, MoELayer): child.set_training_step(step) def softmax_with_sink(scores: torch.Tensor, sink_logit: torch.Tensor) -> torch.Tensor: """Softmax with a per-head learnable sink logit appended to the denominator; weights sum to <= 1 (StreamingLLM / GPT-OSS-style).""" B, H, T_q, T_k = scores.shape out_dtype = scores.dtype sink = sink_logit.to(torch.float32).view(1, H, 1, 1).expand(B, H, T_q, 1) aug = torch.cat([scores.to(torch.float32), sink], dim=-1) weights = F.softmax(aug, dim=-1)[..., :T_k] return weights.to(out_dtype) def manual_attention( q: torch.Tensor, k: torch.Tensor, v: torch.Tensor, mask: Optional[torch.Tensor] = None, sink_logit: Optional[torch.Tensor] = None, ) -> torch.Tensor: D = q.shape[-1] scale = D ** -0.5 scores = (q @ k.transpose(-2, -1)) * scale if mask is not None: scores = scores.masked_fill(~mask, float("-inf")) if sink_logit is not None: weights = softmax_with_sink(scores, sink_logit) else: weights = F.softmax(scores, dim=-1) return (weights @ v.to(weights.dtype)).to(v.dtype) class Attention(nn.Module): """Rotary MHA with optional Q/K RMSNorm, partial RoPE, and a per-head learnable attention sink. Falls back to SDPA when sink is disabled.""" def __init__(self, config: BaselineConfig): super().__init__() self.d_model = config.d_model self.num_heads = config.num_heads self.head_dim = config.d_model // config.num_heads self.q_proj = nn.Linear(config.d_model, config.d_model, bias=False) self.k_proj = nn.Linear(config.d_model, config.d_model, bias=False) self.v_proj = nn.Linear(config.d_model, config.d_model, bias=False) self.out_proj = nn.Linear(config.d_model, config.d_model, bias=False) self.qk_norm = config.qk_norm if self.qk_norm: self.q_norm = RMSNorm(self.head_dim, eps=config.norm_eps) self.k_norm = RMSNorm(self.head_dim, eps=config.norm_eps) rope_dim = config.partial_rope_dim if config.partial_rope_dim is not None else self.head_dim self.rope_dim = rope_dim self.rotary = RotaryEmbedding(rope_dim, config.max_seq_len, config.rope_base) self.attention_sink = config.attention_sink if self.attention_sink: self.sink_logit = nn.Parameter(torch.zeros(self.num_heads)) def _apply_rope(self, x: torch.Tensor, seq_len: int) -> torch.Tensor: if self.rope_dim >= x.shape[-1]: return self.rotary(x, seq_len) no_rope = x[..., :-self.rope_dim] rope = x[..., -self.rope_dim:] rope = self.rotary(rope, seq_len) return torch.cat([no_rope, rope], dim=-1) def _build_mask( self, batch: int, seq_len: int, device: torch.device, attention_mask: Optional[torch.Tensor], is_causal: bool, ) -> Optional[torch.Tensor]: if attention_mask is not None: key_mask = attention_mask.unsqueeze(1).unsqueeze(2).bool() key_mask = key_mask.expand(batch, 1, seq_len, seq_len) if is_causal: causal_mask = torch.tril( torch.ones(seq_len, seq_len, device=device, dtype=torch.bool) ) return key_mask & causal_mask.unsqueeze(0).unsqueeze(0) return key_mask if is_causal and self.attention_sink: # The manual sink path needs an explicit causal mask. causal_mask = torch.tril( torch.ones(seq_len, seq_len, device=device, dtype=torch.bool) ) return causal_mask.unsqueeze(0).unsqueeze(0) return None def forward( self, x: torch.Tensor, attention_mask: Optional[torch.Tensor] = None, is_causal: bool = True, ) -> 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_heads, self.head_dim) v = self.v_proj(x).view(batch, seq_len, self.num_heads, self.head_dim) if self.qk_norm: q = self.q_norm(q) k = self.k_norm(k) q = q.transpose(1, 2) k = k.transpose(1, 2) v = v.transpose(1, 2) q = self._apply_rope(q, seq_len) k = self._apply_rope(k, seq_len) mask = self._build_mask(batch, seq_len, x.device, attention_mask, is_causal) if self.attention_sink: out = manual_attention( q, k, v, mask=mask, sink_logit=self.sink_logit, ) else: out = F.scaled_dot_product_attention( q, k, v, attn_mask=mask, is_causal=(mask is None and is_causal), ) out = out.transpose(1, 2).contiguous().view(batch, seq_len, self.d_model) return self.out_proj(out) class TransformerBlock(nn.Module): """Pre-norm Transformer block: RMSNorm -> Attn -> RMSNorm -> FFN/MoE. ``num_loops`` is forwarded to the optional MoE layer for weight-shared body stacks (recursive / logos). """ def __init__(self, config: BaselineConfig, num_loops: int = 1): super().__init__() self.use_moe = config.use_moe self.attn_norm = RMSNorm(config.d_model, eps=config.norm_eps) self.attn = Attention(config) self.ffn_norm = RMSNorm(config.d_model, eps=config.norm_eps) if config.use_moe: self.ffn = MoELayer(config, num_loops=num_loops) else: self.ffn = SwiGLU(config.d_model, config.d_ff) def forward( self, x: torch.Tensor, attention_mask: Optional[torch.Tensor] = None, is_causal: bool = True, loop_idx: int = 0, ) -> Tuple[torch.Tensor, torch.Tensor, Optional[torch.Tensor]]: x = x + self.attn(self.attn_norm(x), attention_mask=attention_mask, is_causal=is_causal) if self.use_moe: ffn_out, aux_loss, topk_indices = self.ffn(self.ffn_norm(x), loop_idx=loop_idx) x = x + ffn_out return x, aux_loss, topk_indices else: x = x + self.ffn(self.ffn_norm(x)) return x, torch.zeros((), device=x.device, dtype=x.dtype), None class BaselineTransformer(nn.Module): def __init__(self, config: BaselineConfig): super().__init__() self.config = config self.token_emb = nn.Embedding(config.vocab_size, config.d_model) self.layers = nn.ModuleList([ TransformerBlock(config) for _ in range(config.num_layers) ]) self.final_norm = RMSNorm(config.d_model, eps=config.norm_eps) self.lm_head = nn.Linear(config.d_model, config.vocab_size, bias=False) self.lm_head.weight = self.token_emb.weight self._init_weights() def _init_weights(self): for module in self.modules(): if isinstance(module, nn.Linear): torch.nn.init.normal_(module.weight, mean=0.0, std=0.02) if module.bias is not None: torch.nn.init.zeros_(module.bias) elif isinstance(module, nn.Embedding): torch.nn.init.normal_(module.weight, mean=0.0, std=0.02) init_moe_router_weights(self, self.config.router_init_std) def forward( self, input_ids: torch.Tensor, attention_mask: Optional[torch.Tensor] = None, labels: Optional[torch.Tensor] = None, is_causal: bool = True, token_superposition_bag_size: int = 1, ) -> Dict[str, Any]: x = token_superposition_embeddings( self.token_emb, input_ids, token_superposition_bag_size, ) attention_mask = token_superposition_attention_mask( attention_mask, token_superposition_bag_size, ) aux_loss = torch.zeros((), device=input_ids.device, dtype=x.dtype) topk_indices_list: List[Optional[torch.Tensor]] = [] for layer in self.layers: x, layer_aux, layer_topk = layer(x, attention_mask=attention_mask, is_causal=is_causal) aux_loss = aux_loss + layer_aux topk_indices_list.append(layer_topk) x = self.final_norm(x) logits = self.lm_head(x) lm_loss: Optional[torch.Tensor] = None if labels is not None: lm_loss = lm_cross_entropy_from_logits( logits, labels, token_superposition_bag_size=token_superposition_bag_size, ignore_index=-100, ) loss = combine_lm_and_aux_loss( lm_loss, aux_loss if self.config.use_moe else None, self.training, ) return { "logits": logits, "loss": loss, "lm_loss": lm_loss, "aux_loss": aux_loss if self.config.use_moe else None, "topk_indices": topk_indices_list if self.config.use_moe else None, } def update_router_biases(self, topk_indices_list: List[Optional[torch.Tensor]]) -> None: if not self.config.use_moe: return for layer, topk_indices in zip(self.layers, topk_indices_list): if topk_indices is not None and isinstance(layer.ffn, MoELayer): layer.ffn.update_bias(topk_indices) @torch.no_grad() def get_balance_stats(self) -> Dict[str, float]: if not self.config.use_moe: return {} stats = {} for idx, layer in enumerate(self.layers): if hasattr(layer.ffn, "bias"): bias = layer.ffn.bias stats[f"layer{idx}_bias_mean"] = bias.abs().mean().item() stats[f"layer{idx}_bias_max"] = bias.abs().max().item() return stats @torch.no_grad() def generate( self, input_ids: torch.Tensor, max_new_tokens: int = 100, temperature: float = 1.0, top_k: Optional[int] = None, attention_mask: Optional[torch.Tensor] = None, eos_token_id: Optional[int] = None, ) -> torch.Tensor: self.eval() batch_size = input_ids.size(0) for _ in range(max_new_tokens): outputs = self.forward( input_ids, attention_mask=attention_mask, is_causal=True, ) logits = outputs["logits"][:, -1, :] / temperature if top_k is not None: v, _ = torch.topk(logits, min(top_k, logits.size(-1))) logits[logits < v[:, [-1]]] = -float("Inf") probs = F.softmax(logits, dim=-1) next_token = torch.multinomial(probs, num_samples=1) input_ids = torch.cat([input_ids, next_token], dim=-1) if attention_mask is not None: attention_mask = torch.cat([ attention_mask, torch.ones((batch_size, 1), device=attention_mask.device, dtype=attention_mask.dtype), ], dim=-1) if eos_token_id is not None and (next_token == eos_token_id).all(): break return input_ids def count_parameters(model: nn.Module) -> int: return sum(p.numel() for p in model.parameters() if p.requires_grad) def model_summary(model: nn.Module) -> str: lines = ["Model Summary", "=" * 50] total = 0 for name, module in model.named_children(): n = sum(p.numel() for p in module.parameters()) total += n lines.append(f"{name:25s} {n:>15,} params") lines.append("-" * 50) lines.append(f"{'Total':25s} {total:>15,} params") return "\n".join(lines)