import numpy as np from .backend import xp from .model import rope_tables, rotate_matrix def _rmsnorm(x, w, eps=1e-5): ms = (x * x).mean(axis=-1, keepdims=True) return x * (1.0 / xp.sqrt(ms + eps)) * w def _silu(z): return z / (1.0 + xp.exp(-z)) def _softmax(z): z = z - z.max(axis=-1, keepdims=True) e = xp.exp(z) return e / e.sum(axis=-1, keepdims=True) def _rope(x, cos, sin, M, offset): T = x.shape[-2] c = cos[offset:offset + T] s = sin[offset:offset + T] return x * c + (x @ M) * s def _repeat_kv(x, n_rep): if n_rep == 1: return x B, nKV, T, dh = x.shape x = x.reshape(B, nKV, 1, T, dh) x = xp.broadcast_to(x, (B, nKV, n_rep, T, dh)) return x.reshape(B, nKV * n_rep, T, dh) class KVCache: def __init__(self, n_layers): self.k = [None] * n_layers self.v = [None] * n_layers def length(self): return 0 if self.k[0] is None else self.k[0].shape[2] def append(self, layer, k, v): if self.k[layer] is None: self.k[layer] = k self.v[layer] = v else: self.k[layer] = xp.concatenate([self.k[layer], k], axis=2) self.v[layer] = xp.concatenate([self.v[layer], v], axis=2) return self.k[layer], self.v[layer] def forward_cached(model, ids, cache): cfg = model.cfg nH, nKV, dh = cfg.n_heads, cfg.n_kv_heads, cfg.head_dim scale = 1.0 / (dh ** 0.5) cos, sin = rope_tables(cfg.seq_len, dh) M = rotate_matrix(dh) offset = cache.length() x = model.embed.weight.data[xp.asarray(ids, dtype=xp.int64)] B, T, _ = x.shape ii = xp.arange(T) q_abs = (offset + ii).reshape(T, 1) for li, blk in enumerate(model.blocks): a = blk.attn h = _rmsnorm(x, blk.attn_norm.weight.data) q = (h @ a.wq.data).reshape(B, T, nH, dh).transpose(0, 2, 1, 3) k = (h @ a.wk.data).reshape(B, T, nKV, dh).transpose(0, 2, 1, 3) v = (h @ a.wv.data).reshape(B, T, nKV, dh).transpose(0, 2, 1, 3) q = _rmsnorm(q, a.q_norm.weight.data) k = _rmsnorm(k, a.k_norm.weight.data) q = _rope(q, cos, sin, M, offset) k = _rope(k, cos, sin, M, offset) kf, vf = cache.append(li, k, v) kf = _repeat_kv(kf, nH // nKV) vf = _repeat_kv(vf, nH // nKV) scores = (q @ kf.transpose(0, 1, 3, 2)) * scale Tk = kf.shape[2] key_pos = xp.arange(Tk).reshape(1, Tk) mask = xp.where(key_pos > q_abs, -1e9, 0.0) scores = scores + mask attn = _softmax(scores) o = (attn @ vf).transpose(0, 2, 1, 3).reshape(B, T, nH * dh) x = x + o @ a.wo.data hn = _rmsnorm(x, blk.mlp_norm.weight.data) g = _silu(hn @ blk.mlp.wg.data) u = hn @ blk.mlp.wu.data x = x + (g * u) @ blk.mlp.wd.data x = _rmsnorm(x, model.final_norm.weight.data) return x @ model.embed.weight.data.T