Self-contained LDT model definition

#2
by Compactbot - opened
Files changed (1) hide show
  1. model.py +159 -0
model.py ADDED
@@ -0,0 +1,159 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """LDT-10M — 10M-param LLaMA-style prose LM, trained from scratch.
2
+
3
+ Self-contained model definition (no transformers dependency). Load the
4
+ safetensors weights with `LDT.from_pretrained` (below) or the snippet in the
5
+ README.
6
+
7
+ Architecture (10,284,480 params, tied embeddings):
8
+ - vocab 12288 (gollem_eval byte-level BPE)
9
+ - d_model 320, n_layers 5, n_heads 5 (head_dim 64), SwiGLU FFN inter 896
10
+ - RMSNorm pre-norm, RoPE (base 10000), causal attention, ctx 512
11
+ - Standard LLaMA (no sliding window)
12
+ """
13
+ import math
14
+ import torch
15
+ import torch.nn as nn
16
+ import torch.nn.functional as F
17
+
18
+
19
+ def precompute_rope(dim, max_pos, base=10000.0):
20
+ freqs = 1.0 / (base ** (torch.arange(0, dim, 2).float() / dim))
21
+ t = torch.arange(max_pos).float()
22
+ angles = torch.outer(t, freqs)
23
+ return torch.polar(torch.ones_like(angles), angles) # complex
24
+
25
+
26
+ def apply_rope(x, freqs_cis, offset=0):
27
+ B, nh, S, hd = x.shape
28
+ x = x.view(B, nh, S, hd // 2, 2)
29
+ xr = x[..., 0].float()
30
+ xi = x[..., 1].float()
31
+ fc = freqs_cis[offset:offset + S].to(x.device)
32
+ fr, fi = fc.real, fc.imag
33
+ out_r = xr * fr - xi * fi
34
+ out_i = xr * fi + xi * fr
35
+ out = torch.stack([out_r, out_i], dim=-1).view(B, nh, S, hd)
36
+ return out.to(x.dtype)
37
+
38
+
39
+ class RMSNorm(nn.Module):
40
+ def __init__(self, dim, eps=1e-5):
41
+ super().__init__()
42
+ self.eps = eps
43
+ self.weight = nn.Parameter(torch.ones(dim))
44
+
45
+ def forward(self, x):
46
+ norm = x.float().pow(2).mean(-1, keepdim=True).add(self.eps).rsqrt()
47
+ return (x.float() * norm).to(x.dtype) * self.weight
48
+
49
+
50
+ class Attention(nn.Module):
51
+ def __init__(self, d, n_heads):
52
+ super().__init__()
53
+ self.n_heads = n_heads
54
+ self.head_dim = d // n_heads
55
+ self.wq = nn.Linear(d, d, bias=False)
56
+ self.wk = nn.Linear(d, d, bias=False)
57
+ self.wv = nn.Linear(d, d, bias=False)
58
+ self.wo = nn.Linear(d, d, bias=False)
59
+
60
+ def forward(self, x, freqs_cis, offset=0):
61
+ B, S, _ = x.shape
62
+ q = self.wq(x).view(B, S, self.n_heads, self.head_dim).transpose(1, 2)
63
+ k = self.wk(x).view(B, S, self.n_heads, self.head_dim).transpose(1, 2)
64
+ v = self.wv(x).view(B, S, self.n_heads, self.head_dim).transpose(1, 2)
65
+ q = apply_rope(q, freqs_cis, offset)
66
+ k = apply_rope(k, freqs_cis, offset)
67
+ out = F.scaled_dot_product_attention(q, k, v)
68
+ out = out.transpose(1, 2).contiguous().view(B, S, -1)
69
+ return self.wo(out)
70
+
71
+
72
+ class MLP(nn.Module):
73
+ def __init__(self, d, ff):
74
+ super().__init__()
75
+ self.w1 = nn.Linear(d, ff, bias=False) # gate
76
+ self.w2 = nn.Linear(d, ff, bias=False) # up
77
+ self.w3 = nn.Linear(ff, d, bias=False) # down
78
+
79
+ def forward(self, x):
80
+ return self.w3(F.silu(self.w1(x)) * self.w2(x))
81
+
82
+
83
+ class Block(nn.Module):
84
+ def __init__(self, d, n_heads, ff):
85
+ super().__init__()
86
+ self.ln1 = RMSNorm(d)
87
+ self.attn = Attention(d, n_heads)
88
+ self.ln2 = RMSNorm(d)
89
+ self.ffn = MLP(d, ff)
90
+
91
+ def forward(self, x, freqs_cis, offset=0):
92
+ x = x + self.attn(self.ln1(x), freqs_cis, offset)
93
+ x = x + self.ffn(self.ln2(x))
94
+ return x
95
+
96
+
97
+ class LDT(nn.Module):
98
+ def __init__(self, vocab=12288, d=320, n_layers=5, n_heads=5, ff=896, ctx=512):
99
+ super().__init__()
100
+ self.vocab = vocab
101
+ self.ctx = ctx
102
+ self.d = d
103
+ self.tok = nn.Embedding(vocab, d)
104
+ self.blocks = nn.ModuleList([Block(d, n_heads, ff) for _ in range(n_layers)])
105
+ self.ln_f = RMSNorm(d)
106
+ self.head = nn.Linear(d, vocab, bias=False)
107
+ self.head.weight = self.tok.weight # tied
108
+ self.freqs_cis = precompute_rope(d // n_heads, ctx)
109
+
110
+ @property
111
+ def tied_weights(self):
112
+ return ["lm_head"]
113
+
114
+ def forward(self, idx, targets=None):
115
+ B, S = idx.shape
116
+ h = self.tok(idx)
117
+ for b in self.blocks:
118
+ h = b(h, self.freqs_cis)
119
+ h = self.ln_f(h)
120
+ logits = self.head(h)
121
+ if targets is not None:
122
+ loss = F.cross_entropy(
123
+ logits[:, :-1].reshape(-1, logits.size(-1)),
124
+ targets[:, 1:].reshape(-1),
125
+ ignore_index=-1,
126
+ )
127
+ return loss
128
+ return logits
129
+
130
+ @torch.no_grad()
131
+ def generate(self, idx, max_new_tokens=128, temperature=0.8, top_k=40, seed=0):
132
+ g = torch.Generator(device=idx.device).manual_seed(seed)
133
+ for _ in range(max_new_tokens):
134
+ ctx_in = idx[:, -self.ctx:]
135
+ logits = self(ctx_in)[:, -1]
136
+ if temperature and temperature > 0:
137
+ logits = logits / temperature
138
+ if top_k:
139
+ v, _ = torch.topk(logits, top_k, dim=-1)
140
+ logits[logits < v[:, -1, None]] = float("-inf")
141
+ p = torch.softmax(logits, dim=-1)
142
+ nxt = torch.multinomial(p, 1, generator=g)
143
+ idx = torch.cat([idx, nxt], dim=1)
144
+ return idx
145
+
146
+ @classmethod
147
+ def from_pretrained(cls, path, device="cpu"):
148
+ """Load the safetensors weights (tied lm_head re-bound to tok.weight)."""
149
+ from safetensors.torch import load_file
150
+
151
+ m = cls()
152
+ sd = load_file(path, device=device)
153
+ # head.weight is tied to tok.weight and is NOT stored separately.
154
+ sd.pop("head.weight", None)
155
+ missing, unexpected = m.load_state_dict(sd, strict=False)
156
+ assert "head.weight" in missing, f"unexpected missing keys: {missing}"
157
+ assert not unexpected, f"unexpected keys: {unexpected}"
158
+ m.head.weight = m.tok.weight # restore the tie
159
+ return m.to(device).eval()