model / model.py
jyhptr's picture
Upload model.py with huggingface_hub
7368724 verified
Raw
History Blame Contribute Delete
12.9 kB
#!/home/jyhs/cse251b/.venv/bin/python3
# run := python3 test.py
# dir := .
# kid :=
from dataclasses import dataclass
import inspect
import math
import torch
import torch.nn as nn
import torch.nn.functional as F
class CombinedOptimizer:
def __init__(self, *optimizers):
self.optimizers = [opt for opt in optimizers if opt is not None]
self.param_groups = []
for opt in self.optimizers:
self.param_groups.extend(opt.param_groups)
def step(self, *args, **kwargs):
out = None
for opt in self.optimizers:
out = opt.step(*args, **kwargs)
return out
def zero_grad(self, *args, **kwargs):
for opt in self.optimizers:
opt.zero_grad(*args, **kwargs)
def state_dict(self):
return {"optimizers": [opt.state_dict() for opt in self.optimizers]}
def load_state_dict(self, state_dict):
states = state_dict["optimizers"]
if len(states) != len(self.optimizers):
raise ValueError(f"optimizer count mismatch: {len(states)} != {len(self.optimizers)}")
for opt, state in zip(self.optimizers, states):
opt.load_state_dict(state)
@dataclass
class LyraConfig:
block_size: int = 1024
vocab_size: int = 50304
n_layer: int = 12
n_head: int = 10
n_embd: int = 640
rope_base: float = 10000.0
use_qk_norm: bool = True
logit_softcap: float = 0.0
mlp_hidden: int = 2048
dropout: float = 0.0
bias: bool = False
class ChannelRMSNorm(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):
x_fp = x.float()
rms = x_fp.pow(2).mean(-1, keepdim=True).add(self.eps).rsqrt()
return (x_fp * rms).to(x.dtype) * self.weight
def build_rotary_cache(head_dim: int, max_seq_len: int, base: float = 10000.0):
inv_freq = 1.0 / (base ** (torch.arange(0, head_dim, 2, dtype=torch.float32) / head_dim))
t = torch.arange(max_seq_len, dtype=torch.float32)
freqs = torch.outer(t, inv_freq)
return freqs.cos(), freqs.sin()
def apply_rotary_embedding(x: torch.Tensor, cos: torch.Tensor, sin: torch.Tensor) -> torch.Tensor:
x1, x2 = x.chunk(2, dim=-1)
cos = cos[None, None, :, :].to(x.dtype)
sin = sin[None, None, :, :].to(x.dtype)
return torch.cat([x1 * cos - x2 * sin, x1 * sin + x2 * cos], dim=-1)
class RotarySelfAttention(nn.Module):
def __init__(self, config: LyraConfig):
super().__init__()
assert config.n_embd % config.n_head == 0
self.n_head = config.n_head
self.n_embd = config.n_embd
self.head_dim = config.n_embd // config.n_head
assert self.head_dim % 2 == 0, "head_dim must be even for RoPE"
self.c_attn = nn.Linear(config.n_embd, 3 * config.n_embd, bias=False)
self.c_proj = nn.Linear(config.n_embd, config.n_embd, bias=False)
self.c_proj.NANOGPT_SCALE_INIT = 1
self.use_qk_norm = config.use_qk_norm
if self.use_qk_norm:
self.q_norm = ChannelRMSNorm(self.head_dim)
self.k_norm = ChannelRMSNorm(self.head_dim)
def forward(self, x, cos, sin):
B, T, C = x.size()
qkv = self.c_attn(x)
q, k, v = qkv.split(self.n_embd, dim=2)
q = q.view(B, T, self.n_head, self.head_dim).transpose(1, 2)
k = k.view(B, T, self.n_head, self.head_dim).transpose(1, 2)
v = v.view(B, T, self.n_head, self.head_dim).transpose(1, 2)
if self.use_qk_norm:
q = self.q_norm(q)
k = self.k_norm(k)
q = apply_rotary_embedding(q, cos[:T], sin[:T])
k = apply_rotary_embedding(k, cos[:T], sin[:T])
y = F.scaled_dot_product_attention(q, k, v, is_causal=True)
y = y.transpose(1, 2).contiguous().view(B, T, C)
return self.c_proj(y)
class GatedFeedForward(nn.Module):
def __init__(self, config: LyraConfig):
super().__init__()
hidden = config.mlp_hidden
self.c_gate = nn.Linear(config.n_embd, hidden, bias=False)
self.c_up = nn.Linear(config.n_embd, hidden, bias=False)
self.c_proj = nn.Linear(hidden, config.n_embd, bias=False)
self.c_proj.NANOGPT_SCALE_INIT = 1
def forward(self, x):
return self.c_proj(F.silu(self.c_gate(x)) * self.c_up(x))
class DecoderLayer(nn.Module):
def __init__(self, config: LyraConfig):
super().__init__()
self.ln_1 = ChannelRMSNorm(config.n_embd)
self.attn = RotarySelfAttention(config)
self.ln_2 = ChannelRMSNorm(config.n_embd)
self.mlp = GatedFeedForward(config)
def forward(self, x, cos, sin):
x = x + self.attn(self.ln_1(x), cos, sin)
x = x + self.mlp(self.ln_2(x))
return x
class LyraLM(nn.Module):
def __init__(self, config: LyraConfig):
super().__init__()
self.config = config
self.transformer = nn.ModuleDict(dict(
wte=nn.Embedding(config.vocab_size, config.n_embd),
h=nn.ModuleList([DecoderLayer(config) for _ in range(config.n_layer)]),
ln_f=ChannelRMSNorm(config.n_embd),
))
self.lm_head = nn.Linear(config.n_embd, config.vocab_size, bias=False)
self.transformer.wte.weight = self.lm_head.weight
head_dim = config.n_embd // config.n_head
cos, sin = build_rotary_cache(head_dim, config.block_size, config.rope_base)
self.register_buffer("rope_cos", cos, persistent=False)
self.register_buffer("rope_sin", sin, persistent=False)
self.apply(self._init_weights)
def _init_weights(self, module):
if isinstance(module, nn.Linear):
std = 0.02
if hasattr(module, "NANOGPT_SCALE_INIT"):
std *= (2 * self.config.n_layer) ** -0.5
torch.nn.init.normal_(module.weight, mean=0.0, std=std)
if module.bias is not None:
torch.nn.init.zeros_(module.bias)
elif isinstance(module, nn.Embedding):
torch.nn.init.normal_(module.weight, mean=0.0, std=0.02)
def forward(self, idx, targets=None):
B, T = idx.size()
assert T <= self.config.block_size, (
f"sequence length {T} > block_size {self.config.block_size}"
)
x = self.transformer.wte(idx)
for block in self.transformer.h:
x = block(x, self.rope_cos, self.rope_sin)
x = self.transformer.ln_f(x)
logits = self.lm_head(x)
if self.config.logit_softcap > 0:
cap = self.config.logit_softcap
logits = cap * torch.tanh(logits / cap)
logits = logits[:, :, :50257]
if targets is None:
return logits
loss = F.cross_entropy(logits.reshape(-1, logits.size(-1)), targets.reshape(-1))
return logits, loss
def configure_optimizers(
self,
weight_decay,
learning_rate,
betas,
device_type,
use_muon=False,
muon_lr=4e-4,
muon_momentum=0.95,
muon_ns_steps=5,
muon_nesterov=True,
muon_adjust_lr_fn="original",
embed_lr=0.0,
scalar_lr=0.0,
):
param_dict = {pn: p for pn, p in self.named_parameters() if p.requires_grad}
if use_muon:
embed_names = ("transformer.wte", "lm_head")
muon_items = [
(n, p) for n, p in param_dict.items()
if p.dim() == 2 and not any(name in n for name in embed_names)
]
muon_ids = {id(p) for _, p in muon_items}
adamw_items = [(n, p) for n, p in param_dict.items() if id(p) not in muon_ids]
embed_params = [p for n, p in adamw_items if p.dim() >= 2]
scalar_params = [p for n, p in adamw_items if p.dim() < 2]
print(
f"Muon parameter tensors: {len(muon_items)}, "
f"with {sum(p.numel() for _, p in muon_items):,} parameters"
)
print(
f"AdamW embed tensors: {len(embed_params)}, "
f"with {sum(p.numel() for p in embed_params):,} parameters"
)
print(
f"AdamW scalar tensors: {len(scalar_params)}, "
f"with {sum(p.numel() for p in scalar_params):,} parameters"
)
muon_optimizer = torch.optim.Muon(
[{"params": [p for _, p in muon_items], "lr": muon_lr, "initial_lr": muon_lr}],
lr=muon_lr,
weight_decay=weight_decay,
momentum=muon_momentum,
nesterov=muon_nesterov,
ns_steps=muon_ns_steps,
adjust_lr_fn=muon_adjust_lr_fn,
)
adamw_groups = []
if embed_params:
elr = embed_lr if embed_lr > 0 else learning_rate
adamw_groups.append({
"params": embed_params,
"lr": elr,
"initial_lr": elr,
"weight_decay": 0.0,
})
if scalar_params:
slr = scalar_lr if scalar_lr > 0 else learning_rate
adamw_groups.append({
"params": scalar_params,
"lr": slr,
"initial_lr": slr,
"weight_decay": 0.0,
})
fused_available = "fused" in inspect.signature(torch.optim.AdamW).parameters
use_fused = fused_available and device_type == "cuda"
adamw_optimizer = torch.optim.AdamW(
adamw_groups,
lr=learning_rate,
betas=betas,
**(dict(fused=True) if use_fused else {}),
)
print(
"using Muon + AdamW: "
f"muon_lr={muon_lr}, adamw_lr={learning_rate}, fused AdamW={use_fused}"
)
return CombinedOptimizer(muon_optimizer, adamw_optimizer)
decay_params = [p for p in param_dict.values() if p.dim() >= 2]
nodecay_params = [p for p in param_dict.values() if p.dim() < 2]
optim_groups = [
{"params": decay_params, "weight_decay": weight_decay},
{"params": nodecay_params, "weight_decay": 0.0},
]
num_decay_params = sum(p.numel() for p in decay_params)
num_nodecay_params = sum(p.numel() for p in nodecay_params)
print(f"num decayed parameter tensors: {len(decay_params)}, with {num_decay_params:,} parameters")
print(f"num non-decayed parameter tensors: {len(nodecay_params)}, with {num_nodecay_params:,} parameters")
fused_available = "fused" in inspect.signature(torch.optim.AdamW).parameters
use_fused = fused_available and device_type == "cuda"
extra_args = dict(fused=True) if use_fused else dict()
optimizer = torch.optim.AdamW(optim_groups, lr=learning_rate, betas=betas, **extra_args)
print(f"using fused AdamW: {use_fused}")
return optimizer
def estimate_mfu(self, fwdbwd_per_iter, dt):
N = num_params(self)
cfg = self.config
L, H, Q, T = cfg.n_layer, cfg.n_head, cfg.n_embd // cfg.n_head, cfg.block_size
flops_per_token = 6 * N + 12 * L * H * Q * T
flops_per_fwdbwd = flops_per_token * T
flops_per_iter = flops_per_fwdbwd * fwdbwd_per_iter
flops_achieved = flops_per_iter * (1.0 / dt)
flops_promised = 312e12
return flops_achieved / flops_promised
def num_params(model: LyraLM) -> int:
seen = set()
total = 0
for p in model.parameters():
if id(p) in seen:
continue
seen.add(id(p))
total += p.numel()
return total
def strip_runtime_prefixes(state_dict):
clean = {}
for key, value in state_dict.items():
for prefix in ("_orig_mod.", "module."):
if key.startswith(prefix):
key = key[len(prefix):]
clean[key] = value
return clean
def load_model(checkpoint_path: str, device: str = "cuda") -> nn.Module:
cfg = LyraConfig()
model = LyraLM(cfg)
checkpoint = torch.load(checkpoint_path, map_location=device, weights_only=False)
state_dict = checkpoint["model"] if isinstance(checkpoint, dict) and "model" in checkpoint else checkpoint
model.load_state_dict(strip_runtime_prefixes(state_dict), strict=True)
model.to(device)
model.eval()
return model
if __name__ == "__main__":
cfg = LyraConfig()
m = LyraLM(cfg)
n = num_params(m)
print(f"LyraConfig = {cfg}")
print(f"params: {n:,} ({n/1e6:.2f} M)")
assert n < 100_000_000, "OVER 100M PARAM CAP"
x = torch.randint(0, 50257, (2, 64))
logits, loss = m(x, x)
print(f"in: {tuple(x.shape)} out: {tuple(logits.shape)} loss: {loss.item():.3f}")
print("ok")