LatentRoute / model /__init__.py
Vchaudhari17's picture
Upload folder using huggingface_hub
b4f623e verified
Raw
History Blame Contribute Delete
38.6 kB
from __future__ import annotations
import os
import json
import math
from dataclasses import dataclass
from typing import Any, Optional, Sequence, Union
import torch
import torch.nn as nn
import torch.nn.functional as F
from ..embedding.factory import create_embedding_module
from ..embedding.rope import ARFSRoPEEmbedding, RoPEEmbedding, apply_rope
from ..embedding.token_embedding import PositionalEmbedding
from ..embedding.config import EmbeddingConfig
@dataclass
class ModelConfig:
vocab_size: int = 50_000
d_model: int = 4096
n_layers: int = 32
n_heads: int = 32
max_seq_length: int = 2048
mlp_ratio: float = 4.0
dropout: float = 0.1
attn_dropout: float = 0.0
norm_eps: float = 1e-5
use_bias: bool = False
padding_idx: int = 0
embedding_mode: str = "plain"
pos_embedding_type: str = "learned"
rope_base: float = 10000.0
rope_n_domains: int = 4
k: int = 256
k_min: int = 64
k_max: int = 512
alpha: float = 1.0
attention_type: str = "mha" # "mha" or "mla"
mla_d_c: int = 512
mla_d_rope: int = 64
use_hlcr: bool = False
hlcr_c1: int = 1024
hlcr_c2: int = 256
ffn_type: str = "gelu" # "gelu", "swiglu", or "moe"
moe_num_experts: int = 64
moe_top_k: int = 2
moe_d_expert: int = 2048
moe_aux_loss_weight: float = 0.01
moe_entropy_weight: float = 0.01
moe_hierarchical: bool = False
moe_num_groups: int = 8
moe_group_top_k: int = 1
def validate(self) -> None:
if self.vocab_size <= 0:
raise ValueError(f"vocab_size must be positive, got {self.vocab_size}")
if self.d_model <= 0 or self.n_layers <= 0 or self.n_heads <= 0:
raise ValueError("d_model, n_layers, and n_heads must be positive")
if self.max_seq_length <= 0:
raise ValueError(f"max_seq_length must be positive, got {self.max_seq_length}")
if self.mlp_ratio <= 0:
raise ValueError(f"mlp_ratio must be positive, got {self.mlp_ratio}")
if not 0.0 <= self.dropout < 1.0:
raise ValueError(f"dropout must be in [0, 1), got {self.dropout}")
if not 0.0 <= self.attn_dropout < 1.0:
raise ValueError(f"attn_dropout must be in [0, 1), got {self.attn_dropout}")
if self.norm_eps <= 0:
raise ValueError(f"norm_eps must be positive, got {self.norm_eps}")
if self.d_model % self.n_heads != 0:
raise ValueError(f"d_model must be divisible by n_heads, got d_model={self.d_model}, n_heads={self.n_heads}")
if self.embedding_mode not in {"plain", "fed", "fed_dk"}:
raise ValueError(f"embedding_mode must be one of {{'plain', 'fed', 'fed_dk'}}, got {self.embedding_mode}")
if self.pos_embedding_type not in {"learned", "rope", "arfs"}:
raise ValueError(f"pos_embedding_type must be one of {{'learned', 'rope', 'arfs'}}, got {self.pos_embedding_type}")
if self.embedding_mode in {"fed", "fed_dk"}:
if self.k <= 0:
raise ValueError(f"k must be positive, got {self.k}")
if self.k_min <= 0 or self.k_max <= 0 or self.k_min > self.k_max:
raise ValueError(f"k_min/k_max invalid: k_min={self.k_min}, k_max={self.k_max}")
if self.pos_embedding_type in {"rope", "arfs"}:
head_dim = self.d_model // self.n_heads
if head_dim % 2 != 0:
raise ValueError(f"head_dim must be even for RoPE/ARFS, got {head_dim}")
if self.rope_n_domains <= 0:
raise ValueError(f"rope_n_domains must be positive, got {self.rope_n_domains}")
if self.padding_idx < 0 or self.padding_idx >= self.vocab_size:
raise ValueError(f"padding_idx must be in [0, vocab_size), got {self.padding_idx}")
if self.attention_type not in {"mha", "mla"}:
raise ValueError(f"attention_type must be one of {{'mha', 'mla'}}, got {self.attention_type}")
if self.mla_d_c <= 0:
raise ValueError(f"mla_d_c must be positive, got {self.mla_d_c}")
if self.mla_d_rope < 0:
raise ValueError(f"mla_d_rope must be non-negative, got {self.mla_d_rope}")
if self.mla_d_rope > 0 and self.mla_d_rope % 2 != 0:
raise ValueError(f"mla_d_rope must be even, got {self.mla_d_rope}")
if self.use_hlcr:
if self.hlcr_c1 <= 0 or self.hlcr_c2 <= 0:
raise ValueError(f"hlcr_c1 and hlcr_c2 must be positive, got hlcr_c1={self.hlcr_c1}, hlcr_c2={self.hlcr_c2}")
if self.ffn_type not in {"gelu", "swiglu", "moe"}:
raise ValueError(f"ffn_type must be one of {{'gelu', 'swiglu', 'moe'}}, got {self.ffn_type}")
if self.moe_num_experts <= 0:
raise ValueError(f"moe_num_experts must be positive, got {self.moe_num_experts}")
if self.moe_top_k <= 0 or self.moe_top_k > self.moe_num_experts:
raise ValueError(f"moe_top_k must be in [1, moe_num_experts], got moe_top_k={self.moe_top_k}")
if self.moe_d_expert <= 0:
raise ValueError(f"moe_d_expert must be positive, got {self.moe_d_expert}")
if self.moe_num_groups <= 0:
raise ValueError(f"moe_num_groups must be positive, got {self.moe_num_groups}")
if self.moe_hierarchical and self.moe_num_experts % self.moe_num_groups != 0:
raise ValueError("moe_num_experts must be divisible by moe_num_groups for hierarchical routing")
if self.moe_hierarchical:
experts_per_group = self.moe_num_experts // self.moe_num_groups
if self.moe_top_k > experts_per_group:
raise ValueError(
"moe_top_k must be <= experts_per_group for hierarchical routing, "
f"got moe_top_k={self.moe_top_k}, experts_per_group={experts_per_group}"
)
if self.moe_group_top_k != 1:
raise ValueError("moe_group_top_k currently supports only 1")
def __post_init__(self) -> None:
self.validate()
def to_embedding_config(self) -> EmbeddingConfig:
return EmbeddingConfig(
vocab_size=self.vocab_size,
d_model=self.d_model,
max_seq_length=self.max_seq_length,
dropout=self.dropout,
padding_idx=self.padding_idx,
mode=self.embedding_mode,
k=self.k,
k_min=self.k_min,
k_max=self.k_max,
alpha=self.alpha,
pos_embedding_type=self.pos_embedding_type,
rope_base=self.rope_base,
rope_n_domains=self.rope_n_domains,
)
@dataclass
class KVCache:
key: Optional[torch.Tensor] = None
value: Optional[torch.Tensor] = None
seq_len: int = 0
def is_empty(self) -> bool:
return self.key is None or self.value is None or self.seq_len == 0
def append(self, key: torch.Tensor, value: torch.Tensor) -> KVCache:
if self.is_empty():
self.key = key
self.value = value
else:
assert self.key is not None and self.value is not None
self.key = torch.cat([self.key, key], dim=2)
self.value = torch.cat([self.value, value], dim=2)
assert self.key is not None
self.seq_len = self.key.shape[2]
return self
def get(self) -> tuple[Optional[torch.Tensor], Optional[torch.Tensor]]:
if self.is_empty():
return None, None
return self.key, self.value
def reset(self) -> KVCache:
self.key = None
self.value = None
self.seq_len = 0
return self
def to(self, *args, **kwargs) -> KVCache:
if self.key is not None:
self.key = self.key.to(*args, **kwargs)
if self.value is not None:
self.value = self.value.to(*args, **kwargs)
return self
@dataclass
class LatentKVCache:
c_kv: Optional[torch.Tensor] = None
seq_len: int = 0
def is_empty(self) -> bool:
return self.c_kv is None or self.seq_len == 0
def append(self, c_kv: torch.Tensor) -> LatentKVCache:
if self.is_empty():
self.c_kv = c_kv
else:
assert self.c_kv is not None
self.c_kv = torch.cat([self.c_kv, c_kv], dim=1)
assert self.c_kv is not None
self.seq_len = self.c_kv.shape[1]
return self
def get(self) -> Optional[torch.Tensor]:
if self.is_empty():
return None
return self.c_kv
def reset(self) -> LatentKVCache:
self.c_kv = None
self.seq_len = 0
return self
def to(self, *args, **kwargs) -> LatentKVCache:
if self.c_kv is not None:
self.c_kv = self.c_kv.to(*args, **kwargs)
return self
class RMSNorm(nn.Module):
def __init__(self, d_model: int, eps: float = 1e-6):
super().__init__()
self.eps = eps
self.weight = nn.Parameter(torch.ones(d_model))
def forward(self, x: torch.Tensor) -> torch.Tensor:
rms = torch.sqrt(torch.mean(x * x, dim=-1, keepdim=True) + self.eps)
return (x / rms) * self.weight
def scaled_dot_product_attention(
query: torch.Tensor,
key: torch.Tensor,
value: torch.Tensor,
attn_mask: Optional[torch.Tensor] = None,
dropout_p: float = 0.0,
training: bool = False,
is_causal: bool = True,
causal_offset: int = 0,
) -> torch.Tensor:
scale = 1.0 / math.sqrt(query.size(-1))
scores = torch.matmul(query, key.transpose(-2, -1)) * scale
if is_causal:
q_len = query.size(-2)
k_len = key.size(-2)
query_positions = torch.arange(q_len, device=query.device).unsqueeze(-1) + causal_offset
key_positions = torch.arange(k_len, device=query.device).unsqueeze(0)
causal_mask = key_positions <= query_positions
scores = scores.masked_fill(~causal_mask.unsqueeze(0).unsqueeze(0), torch.finfo(scores.dtype).min)
if attn_mask is not None:
mask = attn_mask
while mask.dim() < scores.dim():
mask = mask.unsqueeze(1)
if mask.dtype == torch.bool:
scores = scores.masked_fill(~mask, torch.finfo(scores.dtype).min)
else:
scores = scores + mask
attn = torch.softmax(scores.float(), dim=-1).to(value.dtype)
if dropout_p > 0.0:
attn = F.dropout(attn, p=dropout_p, training=training)
return torch.matmul(attn, value)
class MultiHeadAttention(nn.Module):
def __init__(
self,
d_model: int,
n_heads: int,
dropout: float = 0.1,
bias: bool = True,
pos_embedding_type: str = "learned",
max_seq_length: int = 2048,
rope_base: float = 10000.0,
rope_n_domains: int = 4,
):
super().__init__()
if d_model % n_heads != 0:
raise ValueError(f"d_model must be divisible by n_heads, got d_model={d_model}, n_heads={n_heads}")
self.d_model = d_model
self.n_heads = n_heads
self.head_dim = d_model // n_heads
self.dropout = dropout
self.pos_embedding_type = pos_embedding_type
if pos_embedding_type in {"rope", "arfs"} and self.head_dim % 2 != 0:
raise ValueError(f"head_dim must be even for RoPE/ARFS, got {self.head_dim}")
self.q_proj = nn.Linear(d_model, d_model, bias=bias)
self.k_proj = nn.Linear(d_model, d_model, bias=bias)
self.v_proj = nn.Linear(d_model, d_model, bias=bias)
self.out_proj = nn.Linear(d_model, d_model, bias=bias)
if pos_embedding_type == "rope":
self.rope: Optional[nn.Module] = RoPEEmbedding(
d_model=self.head_dim,
max_seq_len=max_seq_length,
base=rope_base,
)
elif pos_embedding_type == "arfs":
self.rope = ARFSRoPEEmbedding(
d_model=self.head_dim,
max_seq_len=max_seq_length,
base=rope_base,
n_domains=rope_n_domains,
)
else:
self.rope = None
def _shape(self, x: torch.Tensor) -> torch.Tensor:
batch_size, seq_len, _ = x.shape
return x.view(batch_size, seq_len, self.n_heads, self.head_dim).transpose(1, 2)
def _apply_rope(
self,
x: torch.Tensor,
position_offset: int = 0,
domain_id: int = 0,
) -> torch.Tensor:
if self.rope is None:
return x
seq_len = x.size(-2)
rope = self.rope
if isinstance(rope, RoPEEmbedding):
cos_buf: torch.Tensor = rope.freqs_cos # type: ignore[assignment]
sin_buf: torch.Tensor = rope.freqs_sin # type: ignore[assignment]
freqs_cos = cos_buf[position_offset : position_offset + seq_len].to(device=x.device, dtype=x.dtype)
freqs_sin = sin_buf[position_offset : position_offset + seq_len].to(device=x.device, dtype=x.dtype)
elif isinstance(rope, ARFSRoPEEmbedding):
gamma: torch.Tensor = rope.gamma # type: ignore[assignment]
domain_tensor = torch.tensor(domain_id, device=gamma.device, dtype=torch.long)
domain_embed = rope.domain_embed(domain_tensor)
scaling = torch.exp(gamma * domain_embed)
cos_base: torch.Tensor = rope.freqs_cos_base # type: ignore[assignment]
sin_base: torch.Tensor = rope.freqs_sin_base # type: ignore[assignment]
freqs_cos = (cos_base[position_offset : position_offset + seq_len] * scaling.unsqueeze(0)).to(
device=x.device,
dtype=x.dtype,
)
freqs_sin = (sin_base[position_offset : position_offset + seq_len] * scaling.unsqueeze(0)).to(
device=x.device,
dtype=x.dtype,
)
else:
return x
x = x.transpose(1, 2)
x = apply_rope(x, freqs_cos, freqs_sin)
return x.transpose(1, 2)
def forward(
self,
x: torch.Tensor,
attn_mask: Optional[torch.Tensor] = None,
cache: Optional[KVCache] = None,
domain_id: int = 0,
position_offset: Optional[int] = None,
) -> torch.Tensor:
batch_size, seq_len, _ = x.shape
past_len = 0 if cache is None else cache.seq_len
if position_offset is None:
position_offset = past_len
query = self._shape(self.q_proj(x))
key = self._shape(self.k_proj(x))
value = self._shape(self.v_proj(x))
query = self._apply_rope(query, position_offset=position_offset, domain_id=domain_id)
key = self._apply_rope(key, position_offset=position_offset, domain_id=domain_id)
if cache is not None:
cache.append(key, value)
cached_key, cached_value = cache.get()
assert cached_key is not None and cached_value is not None
key, value = cached_key, cached_value
output = scaled_dot_product_attention(
query=query,
key=key,
value=value,
attn_mask=attn_mask,
dropout_p=self.dropout,
training=self.training,
is_causal=True,
causal_offset=position_offset,
)
output = output.transpose(1, 2).contiguous().view(batch_size, seq_len, self.d_model)
return self.out_proj(output)
class MLAAttention(nn.Module):
"""Multi-head Latent Attention (MLA) with optional HLCR gating."""
def __init__(
self,
d_model: int,
n_heads: int,
d_c: int = 512,
d_rope: int = 64,
dropout: float = 0.1,
bias: bool = False,
max_seq_length: int = 2048,
rope_base: float = 10000.0,
use_hlcr: bool = False,
hlcr_c1: int = 1024,
hlcr_c2: int = 256,
):
super().__init__()
if d_model % n_heads != 0:
raise ValueError(f"d_model must be divisible by n_heads, got d_model={d_model}, n_heads={n_heads}")
if d_rope % 2 != 0:
raise ValueError(f"d_rope must be even for RoPE, got {d_rope}")
self.d_model = d_model
self.n_heads = n_heads
self.d_head = d_model // n_heads
self.d_c = d_c
self.d_rope = d_rope
self.dropout = dropout
self.use_hlcr = use_hlcr
if use_hlcr:
self.W_DKV_1 = nn.Linear(d_model, hlcr_c1, bias=False)
self.W_DKV_2 = nn.Linear(d_model, hlcr_c2, bias=False)
self.W_C2_PROJ = nn.Linear(hlcr_c2, hlcr_c1, bias=False)
self.W_G = nn.Linear(d_model + hlcr_c1 + hlcr_c2, hlcr_c1, bias=True)
self.W_MERGE = nn.Linear(hlcr_c1, d_c, bias=False)
else:
self.W_DKV = nn.Linear(d_model, d_c, bias=False)
self.W_UK = nn.Linear(d_c, n_heads * self.d_head, bias=False)
self.W_UV = nn.Linear(d_c, n_heads * self.d_head, bias=False)
self.W_DQ = nn.Linear(d_model, d_c, bias=False)
self.W_UQ = nn.Linear(d_c, n_heads * self.d_head, bias=False)
self.W_REC = nn.Linear(d_c, d_model, bias=False)
self.last_recon_loss = torch.tensor(0.0)
self.W_QR = nn.Linear(d_model, n_heads * d_rope, bias=False) if d_rope > 0 else None
self.W_KR = nn.Linear(d_c, d_rope, bias=False) if d_rope > 0 else None
self.rope = RoPEEmbedding(d_model=d_rope, max_seq_len=max_seq_length, base=rope_base) if d_rope > 0 else None
self.W_O = nn.Linear(n_heads * self.d_head, d_model, bias=bias)
def _compress_kv(self, x: torch.Tensor) -> torch.Tensor:
if not self.use_hlcr:
return self.W_DKV(x)
c1 = self.W_DKV_1(x)
c2 = self.W_DKV_2(x)
c2_proj = self.W_C2_PROJ(c2)
g_in = torch.cat([c1, c2, x], dim=-1)
g = torch.sigmoid(self.W_G(g_in))
c_final = g * c1 + (1.0 - g) * c2_proj
return self.W_MERGE(c_final)
def _shape_heads(self, x: torch.Tensor, head_dim: int) -> torch.Tensor:
bsz, seq_len, _ = x.shape
return x.view(bsz, seq_len, self.n_heads, head_dim)
def _apply_rope(self, x: torch.Tensor, position_offset: int = 0) -> torch.Tensor:
if self.rope is None or self.d_rope == 0:
return x
rope = self.rope
assert isinstance(rope, RoPEEmbedding)
cos_buf: torch.Tensor = rope.freqs_cos # type: ignore[assignment]
sin_buf: torch.Tensor = rope.freqs_sin # type: ignore[assignment]
seq_len = x.size(1)
freqs_cos = cos_buf[position_offset : position_offset + seq_len].to(device=x.device, dtype=x.dtype)
freqs_sin = sin_buf[position_offset : position_offset + seq_len].to(device=x.device, dtype=x.dtype)
return apply_rope(x, freqs_cos, freqs_sin)
def forward(
self,
x: torch.Tensor,
attn_mask: Optional[torch.Tensor] = None,
cache: Optional[LatentKVCache] = None,
domain_id: int = 0,
position_offset: Optional[int] = None,
) -> torch.Tensor:
del domain_id
bsz, seq_len, _ = x.shape
past_len = 0 if cache is None else cache.seq_len
if position_offset is None:
position_offset = past_len
c_kv_new = self._compress_kv(x)
recon = self.W_REC(c_kv_new)
self.last_recon_loss = F.mse_loss(recon, x)
c_q = self.W_DQ(x)
if cache is not None:
cache.append(c_kv_new)
c_kv_all = cache.get()
assert c_kv_all is not None
else:
c_kv_all = c_kv_new
k_content = self.W_UK(c_kv_all).view(bsz, c_kv_all.size(1), self.n_heads, self.d_head)
v = self.W_UV(c_kv_all).view(bsz, c_kv_all.size(1), self.n_heads, self.d_head)
q_content = self.W_UQ(c_q).view(bsz, seq_len, self.n_heads, self.d_head)
if self.d_rope > 0 and self.W_QR is not None and self.W_KR is not None:
q_r = self._shape_heads(self.W_QR(x), self.d_rope)
k_r = self.W_KR(c_kv_all).view(bsz, c_kv_all.size(1), 1, self.d_rope).expand(-1, -1, self.n_heads, -1)
q_r = self._apply_rope(q_r, position_offset=position_offset)
k_r = self._apply_rope(k_r, position_offset=0)
q = torch.cat([q_content, q_r], dim=-1)
k = torch.cat([k_content, k_r], dim=-1)
else:
q = q_content
k = k_content
q = q.transpose(1, 2)
k = k.transpose(1, 2)
v = v.transpose(1, 2)
out = scaled_dot_product_attention(
query=q,
key=k,
value=v,
attn_mask=attn_mask,
dropout_p=self.dropout,
training=self.training,
is_causal=True,
causal_offset=position_offset,
)
out = out.transpose(1, 2).contiguous().view(bsz, seq_len, self.n_heads * self.d_head)
return self.W_O(out)
class SwiGLUFFN(nn.Module):
"""Position-wise FFN using SwiGLU: (SiLU(xW_gate) ⊙ xW_up)W_down."""
def __init__(self, d_model: int, d_ffn: int, bias: bool = True, dropout: float = 0.0):
super().__init__()
self.w_gate = nn.Linear(d_model, d_ffn, bias=bias)
self.w_up = nn.Linear(d_model, d_ffn, bias=bias)
self.w_down = nn.Linear(d_ffn, d_model, bias=bias)
self.dropout = nn.Dropout(dropout)
def forward(self, x: torch.Tensor) -> torch.Tensor:
gated = F.silu(self.w_gate(x)) * self.w_up(x)
return self.w_down(self.dropout(gated))
class TopKRouter(nn.Module):
def __init__(self, d_model: int, n_experts: int, top_k: int):
super().__init__()
self.n_experts = n_experts
self.top_k = top_k
self.router = nn.Linear(d_model, n_experts, bias=False)
def forward(self, x_flat: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
logits = self.router(x_flat).float()
probs = torch.softmax(logits, dim=-1)
top_k_probs, top_k_idx = torch.topk(probs, self.top_k, dim=-1)
top_k_probs = top_k_probs / (top_k_probs.sum(dim=-1, keepdim=True) + 1e-9)
return top_k_probs, top_k_idx, probs
class HierarchicalRouter(nn.Module):
"""Coarse group routing, then fine routing among experts inside the selected group."""
def __init__(self, d_model: int, n_experts: int, top_k: int, n_groups: int):
super().__init__()
if n_experts % n_groups != 0:
raise ValueError("n_experts must be divisible by n_groups")
self.n_experts = n_experts
self.top_k = top_k
self.n_groups = n_groups
self.experts_per_group = n_experts // n_groups
self.group_router = nn.Linear(d_model, n_groups, bias=False)
self.expert_routers = nn.ModuleList(
[nn.Linear(d_model, self.experts_per_group, bias=False) for _ in range(n_groups)]
)
def forward(self, x_flat: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
num_tokens = x_flat.size(0)
k = min(self.top_k, self.experts_per_group)
group_logits = self.group_router(x_flat).float()
group_probs = torch.softmax(group_logits, dim=-1)
selected_group = torch.argmax(group_probs, dim=-1)
top_k_idx = torch.zeros(num_tokens, k, device=x_flat.device, dtype=torch.long)
top_k_probs = torch.zeros(num_tokens, k, device=x_flat.device, dtype=x_flat.dtype)
full_probs = torch.zeros(num_tokens, self.n_experts, device=x_flat.device, dtype=x_flat.dtype)
for g in range(self.n_groups):
token_mask = selected_group == g
if not token_mask.any():
continue
token_ids = token_mask.nonzero(as_tuple=False).squeeze(-1)
x_g = x_flat[token_ids]
expert_logits = self.expert_routers[g](x_g).float()
expert_probs_local = torch.softmax(expert_logits, dim=-1)
weighted_local = expert_probs_local * group_probs[token_ids, g].unsqueeze(-1)
top_local_probs, top_local_idx = torch.topk(weighted_local, k, dim=-1)
top_local_probs = top_local_probs / (top_local_probs.sum(dim=-1, keepdim=True) + 1e-9)
global_idx = top_local_idx + g * self.experts_per_group
top_k_idx[token_ids] = global_idx
top_k_probs[token_ids] = top_local_probs.to(top_k_probs.dtype)
full_probs[token_ids, g * self.experts_per_group : (g + 1) * self.experts_per_group] = weighted_local.to(full_probs.dtype)
full_probs = full_probs / (full_probs.sum(dim=-1, keepdim=True) + 1e-9)
return top_k_probs, top_k_idx, full_probs
class MoELayer(nn.Module):
def __init__(
self,
d_model: int,
n_experts: int = 64,
n_active: int = 2,
d_expert: int = 2048,
bias: bool = True,
dropout: float = 0.0,
hierarchical: bool = False,
n_groups: int = 8,
aux_loss_weight: float = 0.01,
entropy_weight: float = 0.01,
):
super().__init__()
self.n_experts = n_experts
self.n_active = n_active
self.aux_loss_weight = aux_loss_weight
self.entropy_weight = entropy_weight
self.router: Union[HierarchicalRouter, TopKRouter]
if hierarchical:
self.router = HierarchicalRouter(d_model=d_model, n_experts=n_experts, top_k=n_active, n_groups=n_groups)
else:
self.router = TopKRouter(d_model=d_model, n_experts=n_experts, top_k=n_active)
self.experts = nn.ModuleList(
[SwiGLUFFN(d_model=d_model, d_ffn=d_expert, bias=bias, dropout=dropout) for _ in range(n_experts)]
)
self.load_balance_loss = torch.tensor(0.0)
self.entropy_loss = torch.tensor(0.0)
self.aux_loss = torch.tensor(0.0)
def forward(self, x: torch.Tensor, return_aux: bool = False) -> Union[torch.Tensor, tuple[torch.Tensor, torch.Tensor]]:
bsz, seq_len, dim = x.shape
x_flat = x.reshape(-1, dim)
top_k_probs, top_k_idx, router_probs = self.router(x_flat)
top_k_probs = top_k_probs.to(x_flat.dtype)
output = torch.zeros_like(x_flat)
for i in range(self.n_experts):
assigned = (top_k_idx == i).any(dim=-1)
if not assigned.any():
continue
tokens_i = x_flat[assigned]
expert_out = self.experts[i](tokens_i)
gate_w = (top_k_probs * (top_k_idx == i).float()).sum(dim=-1, keepdim=True)
output[assigned] += gate_w[assigned] * expert_out
with torch.no_grad():
token_expert_count = F.one_hot(top_k_idx, num_classes=self.n_experts).float().sum(dim=1)
f_i = token_expert_count.sum(dim=0) / max(float(x_flat.size(0) * self.n_active), 1.0)
p_i = router_probs.mean(dim=0)
self.load_balance_loss = self.n_experts * torch.sum(f_i * p_i)
self.entropy_loss = -(p_i * torch.log(p_i + 1e-8)).sum()
self.aux_loss = self.aux_loss_weight * self.load_balance_loss - self.entropy_weight * self.entropy_loss
out = output.view(bsz, seq_len, dim)
if return_aux:
return out, self.aux_loss
return out
class DecoderBlock(nn.Module):
def __init__(
self,
d_model: int,
n_heads: int,
mlp_ratio: float = 4.0,
dropout: float = 0.1,
attn_dropout: float = 0.1,
norm_eps: float = 1e-5,
bias: bool = True,
pos_embedding_type: str = "learned",
max_seq_length: int = 2048,
rope_base: float = 10000.0,
rope_n_domains: int = 4,
attention_type: str = "mha",
mla_d_c: int = 512,
mla_d_rope: int = 64,
use_hlcr: bool = False,
hlcr_c1: int = 1024,
hlcr_c2: int = 256,
ffn_type: str = "gelu",
moe_num_experts: int = 64,
moe_top_k: int = 2,
moe_d_expert: int = 2048,
moe_aux_loss_weight: float = 0.01,
moe_entropy_weight: float = 0.01,
moe_hierarchical: bool = False,
moe_num_groups: int = 8,
):
super().__init__()
hidden_dim = round(d_model * mlp_ratio)
if hidden_dim <= 0:
raise ValueError(f"mlp_ratio produced invalid hidden_dim={hidden_dim}")
self.norm1 = RMSNorm(d_model, eps=norm_eps)
self.attn: Union[MLAAttention, MultiHeadAttention]
if attention_type == "mla":
self.attn = MLAAttention(
d_model=d_model,
n_heads=n_heads,
d_c=mla_d_c,
d_rope=mla_d_rope,
dropout=attn_dropout,
bias=bias,
max_seq_length=max_seq_length,
rope_base=rope_base,
use_hlcr=use_hlcr,
hlcr_c1=hlcr_c1,
hlcr_c2=hlcr_c2,
)
else:
self.attn = MultiHeadAttention(
d_model=d_model,
n_heads=n_heads,
dropout=attn_dropout,
bias=bias,
pos_embedding_type=pos_embedding_type,
max_seq_length=max_seq_length,
rope_base=rope_base,
rope_n_domains=rope_n_domains,
)
self.norm2 = RMSNorm(d_model, eps=norm_eps)
self.ffn_type = ffn_type
self.mlp: Union[SwiGLUFFN, MoELayer, nn.Sequential]
if ffn_type == "swiglu":
self.mlp = SwiGLUFFN(d_model=d_model, d_ffn=hidden_dim, bias=bias, dropout=dropout)
elif ffn_type == "moe":
self.mlp = MoELayer(
d_model=d_model,
n_experts=moe_num_experts,
n_active=moe_top_k,
d_expert=moe_d_expert,
bias=bias,
dropout=dropout,
hierarchical=moe_hierarchical,
n_groups=moe_num_groups,
aux_loss_weight=moe_aux_loss_weight,
entropy_weight=moe_entropy_weight,
)
else:
self.mlp = nn.Sequential(
nn.Linear(d_model, hidden_dim, bias=bias),
nn.GELU(),
nn.Dropout(dropout),
nn.Linear(hidden_dim, d_model, bias=bias),
nn.Dropout(dropout),
)
self.last_aux_loss: Optional[torch.Tensor] = None
def forward(
self,
x: torch.Tensor,
attn_mask: Optional[torch.Tensor] = None,
cache: Optional[Union[KVCache, LatentKVCache]] = None,
domain_id: int = 0,
position_offset: Optional[int] = None,
return_aux: bool = False,
) -> Union[torch.Tensor, tuple[torch.Tensor, torch.Tensor]]:
x = x + self.attn(self.norm1(x), attn_mask=attn_mask, cache=cache, domain_id=domain_id, position_offset=position_offset)
ffn_in = self.norm2(x)
aux_loss = x.new_zeros(())
if isinstance(self.mlp, MoELayer):
ffn_out, aux_loss = self.mlp(ffn_in, return_aux=True)
self.last_aux_loss = aux_loss
else:
ffn_out = self.mlp(ffn_in)
self.last_aux_loss = None
x = x + ffn_out
if return_aux:
return x, aux_loss
return x
class TransformerLM(nn.Module):
def __init__(self, config: ModelConfig):
super().__init__()
config.validate()
self.config = config
self.token_embedding = create_embedding_module(config.to_embedding_config())
self.positional_embedding = PositionalEmbedding(config.d_model, config.max_seq_length) if config.pos_embedding_type == "learned" else None
self.emb_dropout = nn.Dropout(config.dropout)
self.layers = nn.ModuleList(
[
DecoderBlock(
d_model=config.d_model,
n_heads=config.n_heads,
mlp_ratio=config.mlp_ratio,
dropout=config.dropout,
attn_dropout=config.attn_dropout,
norm_eps=config.norm_eps,
bias=config.use_bias,
pos_embedding_type=config.pos_embedding_type,
max_seq_length=config.max_seq_length,
rope_base=config.rope_base,
rope_n_domains=config.rope_n_domains,
attention_type=config.attention_type,
mla_d_c=config.mla_d_c,
mla_d_rope=config.mla_d_rope,
use_hlcr=config.use_hlcr,
hlcr_c1=config.hlcr_c1,
hlcr_c2=config.hlcr_c2,
ffn_type=config.ffn_type,
moe_num_experts=config.moe_num_experts,
moe_top_k=config.moe_top_k,
moe_d_expert=config.moe_d_expert,
moe_aux_loss_weight=config.moe_aux_loss_weight,
moe_entropy_weight=config.moe_entropy_weight,
moe_hierarchical=config.moe_hierarchical,
moe_num_groups=config.moe_num_groups,
)
for _ in range(config.n_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)
def forward(
self,
input_ids: torch.Tensor,
attn_mask: Optional[torch.Tensor] = None,
caches: Optional[list[Union[KVCache, LatentKVCache, None]]] = None,
domain_id: int = 0,
use_cache: bool = False,
return_aux: bool = False,
) -> Any:
x = self.token_embedding(input_ids)
if self.positional_embedding is not None:
cache_offset = 0
if caches is not None and len(caches) > 0 and caches[0] is not None:
cache_offset = caches[0].seq_len
pos = self.positional_embedding(cache_offset + input_ids.size(1)).to(device=x.device, dtype=x.dtype)
pos = pos[cache_offset:cache_offset + input_ids.size(1)]
x = x + pos.unsqueeze(0)
x = self.emb_dropout(x)
cache_cls = LatentKVCache if self.config.attention_type == "mla" else KVCache
if caches is None:
caches = [cache_cls() if use_cache else None for _ in range(len(self.layers))]
elif len(caches) != len(self.layers):
raise ValueError(f"caches must have length {len(self.layers)}, got {len(caches)}")
elif use_cache:
caches = [cache if cache is not None else cache_cls() for cache in caches]
aux_total = x.new_zeros(())
for layer, cache in zip(self.layers, caches):
if return_aux:
x, layer_aux = layer(x, attn_mask=attn_mask, cache=cache, domain_id=domain_id, return_aux=True)
aux_total = aux_total + layer_aux
else:
x = layer(x, attn_mask=attn_mask, cache=cache, domain_id=domain_id)
x = self.final_norm(x)
logits = self.lm_head(x)
if use_cache and return_aux:
return logits, caches, aux_total
if use_cache:
return logits, caches
if return_aux:
return logits, aux_total
return logits
class TransformerBlock(DecoderBlock):
"""Alias wrapper for DecoderBlock."""
class LLM(nn.Module):
def __init__(
self,
vocab_size: int = 50_000,
d_model: int = 4096,
n_layers: int = 32,
n_heads: int = 32,
d_c: int = 512,
n_experts: int = 64,
max_seq_len: int = 8192,
d_rope: int = 64,
embedding_mode: str = "fed_dk",
tie_weights: bool = False,
):
super().__init__()
moe_num_groups = max(1, min(8, n_experts // 2))
config = ModelConfig(
vocab_size=vocab_size,
d_model=d_model,
n_layers=n_layers,
n_heads=n_heads,
max_seq_length=max_seq_len,
attention_type="mla",
mla_d_c=d_c,
mla_d_rope=d_rope,
use_hlcr=True,
ffn_type="moe",
moe_num_experts=n_experts,
moe_hierarchical=True,
moe_num_groups=moe_num_groups,
pos_embedding_type="arfs",
embedding_mode=embedding_mode,
)
self.model = TransformerLM(config)
token_embed_weight = None
if hasattr(self.model.token_embedding, "embed") and hasattr(self.model.token_embedding.embed, "weight"):
token_embed_weight = self.model.token_embedding.embed.weight
if tie_weights and token_embed_weight is not None and self.model.lm_head.weight.shape == token_embed_weight.shape:
self.model.lm_head.weight = token_embed_weight # type: ignore[assignment]
def forward(self, *args, **kwargs):
return self.model(*args, **kwargs)
def save_pretrained(self, save_directory: str):
"""Save the model state and config to a directory."""
os.makedirs(save_directory, exist_ok=True)
# Save state dict
torch.save(self.state_dict(), os.path.join(save_directory, "pytorch_model.bin"))
# Save config
config_dict = {
"vocab_size": self.model.config.vocab_size,
"d_model": self.model.config.d_model,
"n_layers": self.model.config.n_layers,
"n_heads": self.model.config.n_heads,
"max_seq_len": self.model.config.max_seq_length,
"n_experts": self.model.config.moe_num_experts,
"d_c": self.model.config.mla_d_c,
"d_rope": self.model.config.mla_d_rope,
"embedding_mode": self.model.config.embedding_mode,
}
with open(os.path.join(save_directory, "config.json"), "w") as f:
json.dump(config_dict, f, indent=2)
print(f"Model saved to {save_directory}")
@classmethod
def from_pretrained(cls, load_directory: str):
"""Load the model from a directory."""
config_path = os.path.join(load_directory, "config.json")
with open(config_path, "r") as f:
config_dict = json.load(f)
model = cls(**config_dict)
state_dict_path = os.path.join(load_directory, "pytorch_model.bin")
model.load_state_dict(torch.load(state_dict_path, map_location="cpu"))
return model
__all__ = [
"ModelConfig",
"KVCache",
"LatentKVCache",
"RMSNorm",
"scaled_dot_product_attention",
"MultiHeadAttention",
"MLAAttention",
"SwiGLUFFN",
"TopKRouter",
"HierarchicalRouter",
"MoELayer",
"DecoderBlock",
"TransformerBlock",
"TransformerLM",
"LLM",
]