| 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())) |
|
|