nano-gemini / src /model.py
SNAPKITTYWEST's picture
push from SNAPKITTYWEST/nano-gemini
2dce10c verified
Raw
History Blame Contribute Delete
4.61 kB
from __future__ import annotations
import math
import torch
import torch.nn as nn
import torch.nn.functional as F
from .config import MeridianConfig, NanoConfig
class RMSNorm(nn.Module):
def __init__(self, dim, eps=1e-6):
super().__init__()
self.eps = eps
self.weight = nn.Parameter(torch.zeros(dim))
def forward(self, x):
return x * torch.rsqrt(x.pow(2).mean(-1, keepdim=True) + self.eps) * (1 + self.weight)
class GeGLU(nn.Module):
def __init__(self, dim, intermediate):
super().__init__()
self.gate = nn.Linear(dim, intermediate, bias=False)
self.up = nn.Linear(dim, intermediate, bias=False)
self.down = nn.Linear(intermediate, dim, bias=False)
def forward(self, x):
return self.down(F.gelu(self.gate(x), approximate='tanh') * self.up(x))
class GQA(nn.Module):
def __init__(self, cfg: MeridianConfig):
super().__init__()
d = cfg.gemma.model_dims
hd = cfg.gemma.head_dim
n_heads = cfg.gemma.n_heads
n_kv = cfg.gemma.n_kv_heads
self.n_heads = n_heads
self.n_kv = n_kv
self.head_dim = hd
self.q = nn.Linear(d, n_heads * hd, bias=False)
self.k = nn.Linear(d, n_kv * hd, bias=False)
self.v = nn.Linear(d, n_kv * hd, bias=False)
self.o = nn.Linear(n_heads * hd, d, bias=False)
self.q_norm = RMSNorm(hd) if cfg.gemma.qk_norm else nn.Identity()
self.k_norm = RMSNorm(hd) if cfg.gemma.qk_norm else nn.Identity()
def forward(self, x, causal=True):
B, S, D = x.shape
q = self.q(x).view(B, S, self.n_heads, self.head_dim).transpose(1, 2)
k = self.k(x).view(B, S, self.n_kv, self.head_dim).transpose(1, 2)
v = self.v(x).view(B, S, self.n_kv, self.head_dim).transpose(1, 2)
q = self.q_norm(q)
k = self.k_norm(k)
if self.n_heads != self.n_kv:
k = k.repeat_interleave(self.n_heads // self.n_kv, dim=1)
v = v.repeat_interleave(self.n_heads // self.n_kv, dim=1)
scores = torch.matmul(q, k.transpose(-2, -1)) / math.sqrt(self.head_dim)
if causal:
mask = torch.tril(torch.ones(S, S, device=x.device, dtype=torch.bool))
scores = scores.masked_fill(~mask, float('-inf'))
attn = F.softmax(scores, dim=-1)
out = torch.matmul(attn, v).transpose(1, 2).contiguous().view(B, S, D)
return self.o(out)
class GemmaMixingBlock(nn.Module):
def __init__(self, cfg: MeridianConfig, is_global=False):
super().__init__()
self.attn_norm = RMSNorm(cfg.gemma.model_dims)
self.attn = GQA(cfg)
self.ffn_norm = RMSNorm(cfg.gemma.model_dims)
self.mlp = GeGLU(cfg.gemma.model_dims, cfg.gemma.intermediate_size)
self.is_global = is_global
def forward(self, x):
x = x + self.attn(self.attn_norm(x), causal=not self.is_global)
x = x + self.mlp(self.ffn_norm(x))
return x
class ResidualAdapter(nn.Module):
def __init__(self, cfg: MeridianConfig):
super().__init__()
in_f = cfg.input_feature_dim
h = cfg.residual.hidden_dims
out = cfg.residual.output_dims
self.fc1 = nn.Linear(in_f, h)
self.fc2 = nn.Linear(h, out)
self.skip = nn.Linear(in_f, out)
def forward(self, x):
return self.fc2(F.relu(self.fc1(x))) + self.skip(x)
class GemmaTimeSeriesTorch(nn.Module):
def __init__(self, cfg: MeridianConfig = NanoConfig):
super().__init__()
self.cfg = cfg
self.adapter = ResidualAdapter(cfg)
self.layers = nn.ModuleList([GemmaMixingBlock(cfg, is_global=(i % cfg.gemma.global_every == 0)) for i in range(cfg.n_layers)])
self.norm = RMSNorm(cfg.gemma.model_dims)
self.head = nn.Linear(cfg.gemma.model_dims, cfg.output_patch_len * len(cfg.quantiles))
self._init_weights()
def _init_weights(self):
for m in self.modules():
if isinstance(m, nn.Linear):
nn.init.normal_(m.weight, std=0.02)
if m.bias is not None:
nn.init.zeros_(m.bias)
def forward(self, x):
# x: (B, V, N, 192) patches + covariates
B, V, N, F = x.shape
h = self.adapter(x)
for layer in self.layers:
h = layer(h.view(B*V, N, -1)).view(B, V, N, -1)
h = self.norm(h)
logits = self.head(h)
return logits.view(B, V, N, self.cfg.output_patch_len, len(self.cfg.quantiles))