model-a-scratch / mla /model.py
karthik-2905's picture
Upload folder using huggingface_hub
ae9e4fe verified
Raw
History Blame Contribute Delete
5.71 kB
from dataclasses import dataclass
from .backend import xp
from .tensor import Tensor
from .functional import rsqrt, silu, softmax
@dataclass
class Config:
vocab_size: int = 4096
d_model: int = 256
n_layers: int = 4
n_heads: int = 8
n_kv_heads: int = 2
head_dim: int = 32
swiglu_hidden: int = 704
seq_len: int = 256
def normal(shape, std):
return Tensor(xp.random.randn(*shape) * std)
def rope_tables(seq_len, head_dim, base=10000.0):
h = head_dim // 2
i = xp.arange(h)
theta = base ** (-2.0 * i / head_dim)
m = xp.arange(seq_len)
freqs = xp.outer(m, theta)
emb = xp.concatenate([freqs, freqs], axis=-1)
return xp.cos(emb), xp.sin(emb)
def rotate_matrix(head_dim):
h = head_dim // 2
M = xp.zeros((head_dim, head_dim))
for j in range(head_dim):
if j < h:
M[j + h, j] = -1.0
else:
M[j - h, j] = 1.0
return M
class RMSNorm:
def __init__(self, dim, eps=1e-5):
self.eps = eps
self.weight = Tensor(xp.ones(dim))
def __call__(self, x):
d = x.shape[-1]
ms = x.mul(x).sum(axis=-1, keepdims=True).mul(1.0 / d)
inv = rsqrt(ms.add(self.eps))
return x.mul(inv).mul(self.weight)
def parameters(self):
return [self.weight]
class RoPE:
def __init__(self, cfg, base=10000.0):
self.cfg = cfg
cos, sin = rope_tables(cfg.seq_len, cfg.head_dim, base)
self.cos = cos
self.sin = sin
self.M = Tensor(rotate_matrix(cfg.head_dim))
def __call__(self, x, offset=0):
T = x.shape[-2]
cos = Tensor(self.cos[offset:offset + T])
sin = Tensor(self.sin[offset:offset + T])
rot = x.matmul(self.M)
return x.mul(cos).add(rot.mul(sin))
class TiedEmbedding:
def __init__(self, cfg):
self.cfg = cfg
self.weight = normal((cfg.vocab_size, cfg.d_model), 0.02)
def embed(self, ids):
return self.weight.gather(ids)
def project(self, h):
return h.matmul(self.weight.transpose())
def parameters(self):
return [self.weight]
def repeat_kv(x, n_rep):
if n_rep == 1:
return x
B, n_kv, T, hd = x.shape
x = x.reshape(B, n_kv, 1, T, hd)
ones = Tensor(xp.ones((1, 1, n_rep, 1, 1)))
x = x.mul(ones)
return x.reshape(B, n_kv * n_rep, T, hd)
def causal_mask(T_q, T_k, offset=0):
m = xp.zeros((T_q, T_k))
for i in range(T_q):
m[i, offset + i + 1:] = -1e9
return Tensor(m)
class Attention:
def __init__(self, cfg):
self.cfg = cfg
dh = cfg.head_dim
self.wq = normal((cfg.d_model, cfg.n_heads * dh), 0.02)
self.wk = normal((cfg.d_model, cfg.n_kv_heads * dh), 0.02)
self.wv = normal((cfg.d_model, cfg.n_kv_heads * dh), 0.02)
self.wo = normal((cfg.n_heads * dh, cfg.d_model), 0.02)
self.q_norm = RMSNorm(dh)
self.k_norm = RMSNorm(dh)
self.rope = RoPE(cfg)
self.scale = 1.0 / (dh ** 0.5)
def __call__(self, x, offset=0):
cfg = self.cfg
B, T, _ = x.shape
nH, nKV, dh = cfg.n_heads, cfg.n_kv_heads, cfg.head_dim
q = x.matmul(self.wq).reshape(B, T, nH, dh).transpose((0, 2, 1, 3))
k = x.matmul(self.wk).reshape(B, T, nKV, dh).transpose((0, 2, 1, 3))
v = x.matmul(self.wv).reshape(B, T, nKV, dh).transpose((0, 2, 1, 3))
q = self.q_norm(q)
k = self.k_norm(k)
q = self.rope(q, offset)
k = self.rope(k, offset)
k = repeat_kv(k, nH // nKV)
v = repeat_kv(v, nH // nKV)
kt = k.transpose((0, 1, 3, 2))
scores = q.matmul(kt).mul(self.scale)
scores = scores.add(causal_mask(T, T, offset))
attn = softmax(scores, axis=-1)
out = attn.matmul(v).transpose((0, 2, 1, 3)).reshape(B, T, nH * dh)
return out.matmul(self.wo)
def parameters(self):
return [self.wq, self.wk, self.wv, self.wo,
self.q_norm.weight, self.k_norm.weight]
class SwiGLU:
def __init__(self, cfg):
self.cfg = cfg
self.wg = normal((cfg.d_model, cfg.swiglu_hidden), 0.02)
self.wu = normal((cfg.d_model, cfg.swiglu_hidden), 0.02)
self.wd = normal((cfg.swiglu_hidden, cfg.d_model), 0.02)
def __call__(self, x):
g = silu(x.matmul(self.wg))
u = x.matmul(self.wu)
return g.mul(u).matmul(self.wd)
def parameters(self):
return [self.wg, self.wu, self.wd]
class Block:
def __init__(self, cfg):
self.attn_norm = RMSNorm(cfg.d_model)
self.attn = Attention(cfg)
self.mlp_norm = RMSNorm(cfg.d_model)
self.mlp = SwiGLU(cfg)
def __call__(self, x, offset=0):
x = x.add(self.attn(self.attn_norm(x), offset))
x = x.add(self.mlp(self.mlp_norm(x)))
return x
def parameters(self):
return (self.attn_norm.parameters() + self.attn.parameters()
+ self.mlp_norm.parameters() + self.mlp.parameters())
class Model:
def __init__(self, cfg):
self.cfg = cfg
self.embed = TiedEmbedding(cfg)
self.blocks = [Block(cfg) for _ in range(cfg.n_layers)]
self.final_norm = RMSNorm(cfg.d_model)
def __call__(self, ids, offset=0):
x = self.embed.embed(ids)
for b in self.blocks:
x = b(x, offset)
x = self.final_norm(x)
return self.embed.project(x)
def parameters(self):
ps = self.embed.parameters()
for b in self.blocks:
ps = ps + b.parameters()
ps = ps + self.final_norm.parameters()
return ps
def n_params(self):
return int(sum(p.data.size for p in self.parameters()))