Second5M / model.py
kaafivikrant's picture
Publish Second5M model
a8e5aec verified
Raw History Blame Contribute Delete
6.1 kB
"""Compact decoder-only Transformer for the Part 2 chat experiment."""
from dataclasses import asdict, dataclass
import math
import torch
from torch import nn
from torch.nn import functional as F
@dataclass
class ModelConfig:
vocab_size: int = 8192
d_model: int = 192
n_layers: int = 6
n_heads: int = 6
n_kv_heads: int = 2
d_ff: int = 512
max_seq_len: int = 512
dropout: float = 0.0
def parameter_count(config: ModelConfig) -> int:
# tied token/output embedding; Q and output projections; grouped K/V.
d, h, kv, f, v = config.d_model, config.n_heads, config.n_kv_heads, config.d_ff, config.vocab_size
attn = d * d + 2 * d * (d * kv // h) + d * d
# SwiGLU has gate/up/down projections. RMSNorm has one scale per norm.
layer = attn + 3 * d * f + 2 * d
return v * d + config.n_layers * layer + d
class RMSNorm(nn.Module):
def __init__(self, dim: int, eps: float = 1e-6):
super().__init__()
self.weight = nn.Parameter(torch.ones(dim))
self.eps = eps
def forward(self, x):
return F.rms_norm(x, (x.shape[-1],), self.weight, self.eps)
class Attention(nn.Module):
def __init__(self, c: ModelConfig):
super().__init__()
assert c.d_model % c.n_heads == 0 and c.n_heads % c.n_kv_heads == 0
self.head_dim = c.d_model // c.n_heads
self.n_heads, self.n_kv_heads = c.n_heads, c.n_kv_heads
self.q = nn.Linear(c.d_model, c.d_model, bias=False)
kv_dim = c.n_kv_heads * self.head_dim
self.k = nn.Linear(c.d_model, kv_dim, bias=False)
self.v = nn.Linear(c.d_model, kv_dim, bias=False)
self.out = nn.Linear(c.d_model, c.d_model, bias=False)
self.dropout = c.dropout
def forward(self, x):
b, t, _ = x.shape
q = self.q(x).view(b, t, self.n_heads, self.head_dim).transpose(1, 2)
k = self.k(x).view(b, t, self.n_kv_heads, self.head_dim).transpose(1, 2)
v = self.v(x).view(b, t, self.n_kv_heads, self.head_dim).transpose(1, 2)
if self.n_kv_heads != self.n_heads:
repeat = self.n_heads // self.n_kv_heads
k, v = k.repeat_interleave(repeat, dim=1), v.repeat_interleave(repeat, dim=1)
# RoPE, with interleaved even/odd pairs.
half = self.head_dim // 2
freq = torch.arange(half, device=x.device, dtype=torch.float32)
inv_freq = 1.0 / (10000 ** (freq / half))
pos = torch.arange(t, device=x.device, dtype=torch.float32)
angle = torch.outer(pos, inv_freq)
cos, sin = angle.cos()[None, None], angle.sin()[None, None]
def rotate(z):
z1, z2 = z[..., :half], z[..., half:2 * half]
return torch.cat((z1 * cos - z2 * sin, z1 * sin + z2 * cos, z[..., 2 * half:]), dim=-1).to(z.dtype)
q, k = rotate(q), rotate(k)
y = F.scaled_dot_product_attention(q, k, v, is_causal=True,
dropout_p=self.dropout if self.training else 0.0)
return self.out(y.transpose(1, 2).contiguous().view(b, t, -1))
class Block(nn.Module):
def __init__(self, c):
super().__init__()
self.attn_norm, self.ffn_norm = RMSNorm(c.d_model), RMSNorm(c.d_model)
self.attn = Attention(c)
self.gate = nn.Linear(c.d_model, c.d_ff, bias=False)
self.up = nn.Linear(c.d_model, c.d_ff, bias=False)
self.down = nn.Linear(c.d_ff, c.d_model, bias=False)
self.dropout = nn.Dropout(c.dropout)
def forward(self, x):
x = x + self.dropout(self.attn(self.attn_norm(x)))
x = x + self.dropout(self.down(F.silu(self.gate(self.ffn_norm(x))) * self.up(self.ffn_norm(x))))
return x
class TinyChatLM(nn.Module):
def __init__(self, config: ModelConfig):
super().__init__()
self.config = config
self.token_embedding = nn.Embedding(config.vocab_size, config.d_model)
self.blocks = nn.ModuleList([Block(config) for _ in range(config.n_layers)])
self.norm = RMSNorm(config.d_model)
self.lm_head = nn.Linear(config.d_model, config.vocab_size, bias=False)
self.lm_head.weight = self.token_embedding.weight
self.apply(self._init_weights)
for name, p in self.named_parameters():
if name.endswith("out.weight") or name.endswith("down.weight"):
nn.init.normal_(p, mean=0.0, std=0.02 / math.sqrt(2 * config.n_layers))
@staticmethod
def _init_weights(module):
if isinstance(module, nn.Linear):
nn.init.normal_(module.weight, mean=0.0, std=0.02)
elif isinstance(module, nn.Embedding):
nn.init.normal_(module.weight, mean=0.0, std=0.02)
def forward(self, ids, targets=None, loss_mask=None):
x = self.token_embedding(ids)
for block in self.blocks:
x = block(x)
logits = self.lm_head(self.norm(x))
loss = None
if targets is not None:
per_token = F.cross_entropy(logits.reshape(-1, logits.size(-1)), targets.reshape(-1), reduction="none")
if loss_mask is not None:
weights = loss_mask.reshape(-1).to(per_token.dtype)
loss = (per_token * weights).sum() / weights.sum().clamp_min(1)
else:
loss = per_token.mean()
return logits, loss
@torch.no_grad()
def generate(self, ids, max_new_tokens=100, temperature=0.8, top_k=40, eos_id=None):
self.eval()
for _ in range(max_new_tokens):
ctx = ids[:, -self.config.max_seq_len:]
logits, _ = self(ctx)
logits = logits[:, -1, :] / max(temperature, 1e-5)
if top_k:
cutoff = torch.topk(logits, min(top_k, logits.size(-1))).values[:, -1:]
logits = logits.masked_fill(logits < cutoff, -float("inf"))
nxt = torch.multinomial(F.softmax(logits, dim=-1), 1)
ids = torch.cat((ids, nxt), dim=1)
if eos_id is not None and (nxt == eos_id).all():
break
return ids
def config_dict(config):
return asdict(config)