vanshnawander's picture
Publish validated one-epoch assignment model and evaluation results
b7751e5 verified
Raw History Blame Contribute Delete
6.14 kB
"""Pre-normalized decoder-only transformer with interchangeable FFNs."""
import torch
from torch import Tensor, nn
import torch.nn.functional as F
from moe import TransformerConfig, MLP, MoE
class Block(nn.Module):
"""Apply pre-normalized causal attention and the selected FFN."""
def __init__(self, config):
"""Create attention projections, normalization layers and the FFN."""
super().__init__()
self.norm1 = nn.LayerNorm(config.d_model)
self.norm2 = nn.LayerNorm(config.d_model)
self.qkv = nn.Linear(config.d_model, 3 * config.d_model, bias=False)
self.proj = nn.Linear(config.d_model, config.d_model, bias=False)
self.ffn = MLP(config) if config.num_experts == 1 else MoE(config)
self.heads = config.n_heads
self.dropout = config.dropout
def forward(self, x, attention_mask, routing_mask=None):
"""Apply causal attention and the FFN to (batch, tokens, hidden) states."""
b, t, d = x.shape
q, k, v = self.qkv(self.norm1(x)).chunk(3, -1)
q, k, v = [
z.reshape(b, t, self.heads, d // self.heads).transpose(1, 2)
for z in (q, k, v)
]
mask = None
if attention_mask is not None and not bool(attention_mask.all()):
mask = (
torch.ones(t, t, dtype=torch.bool, device=x.device).tril()[None, None]
& attention_mask[:, None, None, :].bool()
)
attended = F.scaled_dot_product_attention(
q,
k,
v,
attn_mask=mask,
is_causal=mask is None,
dropout_p=self.dropout if self.training else 0.0,
)
x = x + F.dropout(
self.proj(attended.transpose(1, 2).reshape(b, t, d)),
self.dropout,
self.training,
)
if isinstance(self.ffn, MoE):
self.ffn.tracking_mask = (
attention_mask if routing_mask is None else routing_mask
)
return x + F.dropout(self.ffn(self.norm2(x)), self.dropout, self.training)
class CausalLM(nn.Module):
"""Decode tokens using tied embeddings and dense or MoE blocks."""
def __init__(self, config: TransformerConfig):
"""Build the decoder, initialize its weights and tie the output embedding."""
super().__init__()
if config.d_model % config.n_heads:
raise ValueError("d_model must be divisible by n_heads")
self.config = config
self.context_length = config.context_length
self.usage_mask = None
self.token_embedding = nn.Embedding(config.vocab_size, config.d_model)
self.position_embedding = nn.Embedding(config.context_length, config.d_model)
self.blocks = nn.ModuleList([Block(config) for _ in range(config.n_layers)])
self.norm = nn.LayerNorm(config.d_model)
self.lm_head = nn.Linear(config.d_model, config.vocab_size, bias=False)
self.apply(self._initialize)
self.lm_head.weight = self.token_embedding.weight
@staticmethod
def _initialize(module):
"""Initialize projection and embedding weights normally, with zero biases."""
if isinstance(module, (nn.Linear, nn.Embedding)):
nn.init.normal_(module.weight, std=0.02)
if getattr(module, "bias", None) is not None:
nn.init.zeros_(module.bias)
@property
def aux_loss(self):
"""Return the mean balancing loss, or zero for a dense model."""
losses = [
block.ffn.aux_loss for block in self.blocks if isinstance(block.ffn, MoE)
]
return (
torch.stack(losses).mean()
if losses
else self.token_embedding.weight.new_zeros(())
)
def forward(
self, input_ids: Tensor, attention_mask: Tensor | None = None
) -> Tensor:
"""Return logits of shape (batch, tokens, vocabulary) for a token sequence.
Use learned absolute positions and causal attention. The optional mask
marks valid input positions; sequences must fit the configured context.
"""
if input_ids.ndim != 2 or input_ids.shape[1] > self.context_length:
raise ValueError("Expected (batch, sequence) within context_length")
positions = torch.arange(input_ids.shape[1], device=input_ids.device)
hidden = (
self.token_embedding(input_ids) + self.position_embedding(positions)[None]
)
for block in self.blocks:
hidden = block(hidden, attention_mask, self.usage_mask)
return self.lm_head(self.norm(hidden))
def parameter_counts(self):
"""Count stored and active parameters, counting tied weights once."""
total = sum(p.numel() for p in self.parameters())
inactive = sum(
sum(p.numel() for p in b.ffn.parameters()) - b.ffn.active_parameter_count()
for b in self.blocks
if isinstance(b.ffn, MoE)
)
return {"total": total, "active": total - inactive}
class DenseCausalLM(CausalLM):
"""Provide the dense decoder through the earlier constructor interface."""
def __init__(
self, vocab_size, d_model, n_layers, n_heads, d_ff, context_length, dropout
):
"""Create a decoder configuration with one dense feedforward expert."""
super().__init__(
TransformerConfig(
d_model,
d_ff,
vocab_size,
context_length,
n_layers=n_layers,
n_heads=n_heads,
dropout=dropout,
)
)
def build_model(config):
"""Construct the configured dense or MoE causal language model."""
m = config.model
f = m.ffn
return CausalLM(
TransformerConfig(
m.d_model,
m.d_ff,
m.vocab_size,
m.context_length,
f.num_experts,
f.top_k,
f.shared_expert,
f.match_active_parameters,
m.n_layers,
m.n_heads,
m.dropout,
)
)