Quark-50m-v2 / model.py
ThingsAI's picture
Quark-50M-v2: 43.8M Italian-first LM (loss 0.9409551193519028)
52135a8 verified
Raw
History Blame Contribute Delete
4.58 kB
"""Quark-50M model definition — standalone."""
import torch
import torch.nn as nn
import torch.nn.functional as F
from dataclasses import dataclass
@dataclass
class ModelConfig:
vocab_size: int = 16384; d_model: int = 512; n_heads: int = 8
n_kv_heads: int = 4; n_layers: int = 12; d_ff: int = 1408
head_dim: int = 64; max_seq_len: int = 2048; rope_theta: float = 10000.0
rms_eps: float = 1e-5; qkv_bias: bool = False; dropout: float = 0.0
class RMSNorm(nn.Module):
def __init__(self, dim, eps=1e-5):
super().__init__(); self.eps = eps; self.scale = nn.Parameter(torch.ones(dim))
def forward(self, x):
return (x.float() * x.float().pow(2).mean(-1, keepdim=True).add(self.eps).rsqrt()).to(x.dtype) * self.scale
class RotaryEmbedding(nn.Module):
def __init__(self, head_dim, max_seq_len, theta=10000.0):
super().__init__()
inv_freq = 1.0 / (theta ** (torch.arange(0, head_dim, 2).float() / head_dim))
self.register_buffer("inv_freq", inv_freq, persistent=False); self._build(max_seq_len)
def _build(self, seq_len):
t = torch.arange(seq_len, device=self.inv_freq.device).float()
freqs = torch.outer(t, self.inv_freq); emb = torch.cat([freqs, freqs], dim=-1)
self.register_buffer("cos_cache", emb.cos()[None, None], persistent=False)
self.register_buffer("sin_cache", emb.sin()[None, None], persistent=False); self._max = seq_len
@staticmethod
def _rot(x):
x1, x2 = x.chunk(2, dim=-1); return torch.cat([-x2, x1], dim=-1)
def forward(self, q, k):
T = q.size(2)
if T > self._max: self._build(T)
c, s = self.cos_cache[:,:,:T], self.sin_cache[:,:,:T]
return q*c + self._rot(q)*s, k*c + self._rot(k)*s
class GQA(nn.Module):
def __init__(self, cfg):
super().__init__()
self.n_heads, self.n_kv_heads = cfg.n_heads, cfg.n_kv_heads
self.n_groups, self.head_dim = cfg.n_heads // cfg.n_kv_heads, cfg.head_dim
self.q_proj = nn.Linear(cfg.d_model, cfg.n_heads * cfg.head_dim, bias=cfg.qkv_bias)
self.k_proj = nn.Linear(cfg.d_model, cfg.n_kv_heads * cfg.head_dim, bias=cfg.qkv_bias)
self.v_proj = nn.Linear(cfg.d_model, cfg.n_kv_heads * cfg.head_dim, bias=cfg.qkv_bias)
self.o_proj = nn.Linear(cfg.n_heads * cfg.head_dim, cfg.d_model, bias=False)
self.rope = RotaryEmbedding(cfg.head_dim, cfg.max_seq_len, cfg.rope_theta)
def forward(self, x):
B, T, _ = x.shape
q = self.q_proj(x).view(B, T, self.n_heads, self.head_dim).transpose(1, 2)
k = self.k_proj(x).view(B, T, self.n_kv_heads, self.head_dim).transpose(1, 2)
v = self.v_proj(x).view(B, T, self.n_kv_heads, self.head_dim).transpose(1, 2)
q, k = self.rope(q, k)
if self.n_groups > 1:
B_r, _, T_r, D_r = k.shape
k = k[:,:,None,:,:].expand(B_r, self.n_kv_heads, self.n_groups, T_r, D_r).reshape(B_r, self.n_heads, T_r, D_r)
v = v[:,:,None,:,:].expand(B_r, self.n_kv_heads, self.n_groups, T_r, D_r).reshape(B_r, self.n_heads, T_r, D_r)
return self.o_proj(F.scaled_dot_product_attention(q, k, v, is_causal=True).transpose(1, 2).contiguous().view(B, T, -1))
class Block(nn.Module):
def __init__(self, cfg):
super().__init__()
self.norm_attn = RMSNorm(cfg.d_model, cfg.rms_eps); self.attn = GQA(cfg)
self.norm_ffn = RMSNorm(cfg.d_model, cfg.rms_eps)
self.gate = nn.Linear(cfg.d_model, cfg.d_ff, bias=False)
self.up = nn.Linear(cfg.d_model, cfg.d_ff, bias=False)
self.down = nn.Linear(cfg.d_ff, cfg.d_model, bias=False)
def forward(self, x):
x = x + self.attn(self.norm_attn(x))
h = self.norm_ffn(x); x = x + self.down(F.silu(self.gate(h)) * self.up(h))
return x
class Quark(nn.Module):
def __init__(self, cfg):
super().__init__(); self.cfg = cfg
self.embed_tokens = nn.Embedding(cfg.vocab_size, cfg.d_model)
self.layers = nn.ModuleList([Block(cfg) for _ in range(cfg.n_layers)])
self.norm = RMSNorm(cfg.d_model, cfg.rms_eps)
self.lm_head = nn.Linear(cfg.d_model, cfg.vocab_size, bias=False)
self.lm_head.weight = self.embed_tokens.weight
def forward(self, ids, labels=None):
x = self.embed_tokens(ids)
for layer in self.layers: x = layer(x)
logits = self.lm_head(self.norm(x))
loss = None
if labels is not None:
loss = F.cross_entropy(logits.view(-1, self.cfg.vocab_size), labels.view(-1), ignore_index=-100)
return loss, logits