diff --git a/content/post/mini_projects/gpt2.md b/content/post/mini_projects/gpt2.md index 135273c..f335e3b 100644 --- a/content/post/mini_projects/gpt2.md +++ b/content/post/mini_projects/gpt2.md @@ -487,3 +487,196 @@ for i in (t := trange(100)): ![GPT2 initial dataloaderlite loss](/dataloaderlite_loss.png) Sweet! We now have a GPT2 model that we can run with pretrained weights and train. That's all for now. + +Here's the code for training in full. + +```python +from tinygrad import Tensor, dtypes +from tinygrad.nn import Embedding, Linear, LayerNorm +from tinygrad.nn.state import torch_load, load_state_dict, get_state_dict, get_parameters +from tqdm import tqdm, trange +from tinygrad.nn.optim import AdamW +from dataclasses import dataclass +from tinygrad.helpers import fetch +import tiktoken +import numpy as np +import os +import matplotlib.pyplot as plt + +@dataclass +class GPT2Config: + block_size: int = 1024 + vocab_size: int = 50257 + n_layer: int = 12 + n_head: int = 12 + n_embd: int = 768 + norm_eps: float = 1e-5 + +@dataclass +class GPT2Small(GPT2Config): + pass + +@dataclass +class GPT2Medium(GPT2Config): + n_layer: int = 24 + n_head: int = 16 + n_embd: int = 1024 + +@dataclass +class GPT2Large(GPT2Config): + n_layer: int = 36 + n_head: int = 20 + n_embd: int = 1280 + +@dataclass +class GPT2XL(GPT2Config): + n_layer: int = 48 + n_head: int = 25 + n_embd: int = 1600 + +MODEL_CONFIGS = { + 'gpt2': GPT2Small, + 'gpt2-medium': GPT2Medium, + 'gpt2-large': GPT2Large, + 'gpt2-xl': GPT2XL +} + +class MLP: + def __init__(self, config : GPT2Config): + self.c_fc = Linear(config.n_embd, config.n_embd*4) + self.c_proj = Linear(config.n_embd*4, config.n_embd) + def __call__(self, x): + x = self.c_fc(x).gelu() + x = self.c_proj(x) + return x + +class Attention: + def __init__(self, config: GPT2Config): + self.config = config + self.c_attn = Linear(config.n_embd, config.n_embd*3) + self.c_proj = Linear(config.n_embd, config.n_embd) + def __call__(self, x): + B,T,C = x.shape + + q, k, v = self.c_attn(x).split(C, dim=-1) #(B,T,3C) -> (B,T,C) x 3 + split_heads = lambda x: x.view(B, T, self.config.n_head, self.config.n_embd//self.config.n_head).transpose(1,2) + q, k, v = map(split_heads, (q,k,v)) + + y = q.scaled_dot_product_attention(k, v, is_causal=True) + y = y.transpose(1, 2).contiguous().view(B, T, C) + y = self.c_proj(y) + + return y + +class TransformerBlock: + def __init__(self, config : GPT2Config): + self.ln_1 = LayerNorm(config.n_embd, eps=config.norm_eps) + self.ln_2 = LayerNorm(config.n_embd, eps=config.norm_eps) + self.attn = Attention(config) + self.mlp = MLP(config) + def __call__(self, x): + x = x + self.attn(self.ln_1(x)) + x = x + self.mlp(self.ln_2(x)) + return x + +class GPT2: + def __init__(self, config : GPT2Config = GPT2Small): + self.config = config + + self.wte = Embedding(config.vocab_size, config.n_embd) + self.wpe = Embedding(config.block_size, config.n_embd) + self.h = [TransformerBlock(config) for _ in range(config.n_layer)] + self.ln_f = LayerNorm(config.n_embd, config.norm_eps) + self.lm_head = Linear(config.n_embd, config.vocab_size, bias=False) + + # tie weights - HUGE SAVINGS + self.lm_head.weight = self.wte.weight + + def __call__(self, idx, targets=None): + B,T = idx.shape + + assert T <= self.config.block_size, f"Cannot forward, model block size is {self.config.block_size} but got sequence of length {T}" + pos = Tensor.arange(0, T, dtype=dtypes.long) # (T,) + pos_emb = self.wpe(pos) # (T,) -> (T,C) + tok_emb = self.wte(idx) # (B,T) -> (B,T,C) + + x = tok_emb + pos_emb + x = x.sequential(self.h) + + x = self.ln_f(x) + logits = self.lm_head(x) # (B,T,C) -> (B,T,V) + + if targets is not None: + loss = logits.flatten(0,1).sparse_categorical_crossentropy(targets.flatten()) + return logits, loss.realize() + + return logits, None + + @staticmethod + def build(MODEL_NAME): + + weights = torch_load(fetch(f'https://huggingface.co/{MODEL_NAME}/resolve/main/pytorch_model.bin')) + + transposed = ('attn.c_attn.weight', 'attn.c_proj.weight', 'mlp.c_fc.weight', 'mlp.c_proj.weight') + for k in weights: + if k.endswith(transposed): + weights[k] = weights[k].T + + weights['lm_head.weight'] = weights['wte.weight'] + model = GPT2(MODEL_CONFIGS[MODEL_NAME]) + load_state_dict(model, weights) + + return model + +class DataLoaderLite: + def __init__(self, B, T, file_path): + self.B=B + self.T=T + + self.batch = lambda x: x.view(B,T) + + with open(file_path, 'r') as f: + text = f.read() + + enc = tiktoken.get_encoding('gpt2') + + tokens = enc.encode(text) + self.tokens = Tensor(tokens, dtype=dtypes.long) + + print(f"loaded {len(self.tokens)} tokens") + print(f"1 epoch = {len(self.tokens) // (B*T)} batches") + + self.current_position = 0 + + def next_batch(self): + B, T = self.B, self.T + + buf = self.tokens[self.current_position:self.current_position + B*T+1] + x = self.batch(buf[:-1]) + y = self.batch(buf[1:]) + self.current_position += B*T + + if self.current_position + (B*T+1) > len(self.tokens): + print("read entire document, resetting position...") + self.current_position = 0 + + return x,y + +Tensor.training = True +Tensor.no_grad = False +model = GPT2(GPT2Small) +optim = AdamW(get_parameters(model), lr=3e-4) +dl = DataLoaderLite(4, 32, "datasets/shake.txt") +losses = [] +for i in (t := trange(100)): + x, y = dl.next_batch() + optim.zero_grad() + logits, loss = model(x,y) + losses.append(loss.numpy()) + loss.backward() + optim.step() + + t.set_description( + f"train loss: {loss.numpy():.2f}" + ) +``` diff --git a/public/post/mini_projects/gpt2/index.html b/public/post/mini_projects/gpt2/index.html index f863ce6..99a4d12 100644 --- a/public/post/mini_projects/gpt2/index.html +++ b/public/post/mini_projects/gpt2/index.html @@ -462,7 +462,196 @@
MLP
Parameters:Sweet! We now have a GPT2 model that we can run with pretrained weights and train. That’s all for now.
- +Here’s the code for training in full.
+from tinygrad import Tensor, dtypes
+from tinygrad.nn import Embedding, Linear, LayerNorm
+from tinygrad.nn.state import torch_load, load_state_dict, get_state_dict, get_parameters
+from tqdm import tqdm, trange
+from tinygrad.nn.optim import AdamW
+from dataclasses import dataclass
+from tinygrad.helpers import fetch
+import tiktoken
+import numpy as np
+import os
+import matplotlib.pyplot as plt
+
+@dataclass
+class GPT2Config:
+ block_size: int = 1024
+ vocab_size: int = 50257
+ n_layer: int = 12
+ n_head: int = 12
+ n_embd: int = 768
+ norm_eps: float = 1e-5
+
+@dataclass
+class GPT2Small(GPT2Config):
+ pass
+
+@dataclass
+class GPT2Medium(GPT2Config):
+ n_layer: int = 24
+ n_head: int = 16
+ n_embd: int = 1024
+
+@dataclass
+class GPT2Large(GPT2Config):
+ n_layer: int = 36
+ n_head: int = 20
+ n_embd: int = 1280
+
+@dataclass
+class GPT2XL(GPT2Config):
+ n_layer: int = 48
+ n_head: int = 25
+ n_embd: int = 1600
+
+MODEL_CONFIGS = {
+ 'gpt2': GPT2Small,
+ 'gpt2-medium': GPT2Medium,
+ 'gpt2-large': GPT2Large,
+ 'gpt2-xl': GPT2XL
+}
+
+class MLP:
+ def __init__(self, config : GPT2Config):
+ self.c_fc = Linear(config.n_embd, config.n_embd*4)
+ self.c_proj = Linear(config.n_embd*4, config.n_embd)
+ def __call__(self, x):
+ x = self.c_fc(x).gelu()
+ x = self.c_proj(x)
+ return x
+
+class Attention:
+ def __init__(self, config: GPT2Config):
+ self.config = config
+ self.c_attn = Linear(config.n_embd, config.n_embd*3)
+ self.c_proj = Linear(config.n_embd, config.n_embd)
+ def __call__(self, x):
+ B,T,C = x.shape
+
+ q, k, v = self.c_attn(x).split(C, dim=-1) #(B,T,3C) -> (B,T,C) x 3
+ split_heads = lambda x: x.view(B, T, self.config.n_head, self.config.n_embd//self.config.n_head).transpose(1,2)
+ q, k, v = map(split_heads, (q,k,v))
+
+ y = q.scaled_dot_product_attention(k, v, is_causal=True)
+ y = y.transpose(1, 2).contiguous().view(B, T, C)
+ y = self.c_proj(y)
+
+ return y
+
+class TransformerBlock:
+ def __init__(self, config : GPT2Config):
+ self.ln_1 = LayerNorm(config.n_embd, eps=config.norm_eps)
+ self.ln_2 = LayerNorm(config.n_embd, eps=config.norm_eps)
+ self.attn = Attention(config)
+ self.mlp = MLP(config)
+ def __call__(self, x):
+ x = x + self.attn(self.ln_1(x))
+ x = x + self.mlp(self.ln_2(x))
+ return x
+
+class GPT2:
+ def __init__(self, config : GPT2Config = GPT2Small):
+ self.config = config
+
+ self.wte = Embedding(config.vocab_size, config.n_embd)
+ self.wpe = Embedding(config.block_size, config.n_embd)
+ self.h = [TransformerBlock(config) for _ in range(config.n_layer)]
+ self.ln_f = LayerNorm(config.n_embd, config.norm_eps)
+ self.lm_head = Linear(config.n_embd, config.vocab_size, bias=False)
+
+ # tie weights - HUGE SAVINGS
+ self.lm_head.weight = self.wte.weight
+
+ def __call__(self, idx, targets=None):
+ B,T = idx.shape
+
+ assert T <= self.config.block_size, f"Cannot forward, model block size is {self.config.block_size} but got sequence of length {T}"
+ pos = Tensor.arange(0, T, dtype=dtypes.long) # (T,)
+ pos_emb = self.wpe(pos) # (T,) -> (T,C)
+ tok_emb = self.wte(idx) # (B,T) -> (B,T,C)
+
+ x = tok_emb + pos_emb
+ x = x.sequential(self.h)
+
+ x = self.ln_f(x)
+ logits = self.lm_head(x) # (B,T,C) -> (B,T,V)
+
+ if targets is not None:
+ loss = logits.flatten(0,1).sparse_categorical_crossentropy(targets.flatten())
+ return logits, loss.realize()
+
+ return logits, None
+
+ @staticmethod
+ def build(MODEL_NAME):
+
+ weights = torch_load(fetch(f'https://huggingface.co/{MODEL_NAME}/resolve/main/pytorch_model.bin'))
+
+ transposed = ('attn.c_attn.weight', 'attn.c_proj.weight', 'mlp.c_fc.weight', 'mlp.c_proj.weight')
+ for k in weights:
+ if k.endswith(transposed):
+ weights[k] = weights[k].T
+
+ weights['lm_head.weight'] = weights['wte.weight']
+ model = GPT2(MODEL_CONFIGS[MODEL_NAME])
+ load_state_dict(model, weights)
+
+ return model
+
+class DataLoaderLite:
+ def __init__(self, B, T, file_path):
+ self.B=B
+ self.T=T
+
+ self.batch = lambda x: x.view(B,T)
+
+ with open(file_path, 'r') as f:
+ text = f.read()
+
+ enc = tiktoken.get_encoding('gpt2')
+
+ tokens = enc.encode(text)
+ self.tokens = Tensor(tokens, dtype=dtypes.long)
+
+ print(f"loaded {len(self.tokens)} tokens")
+ print(f"1 epoch = {len(self.tokens) // (B*T)} batches")
+
+ self.current_position = 0
+
+ def next_batch(self):
+ B, T = self.B, self.T
+
+ buf = self.tokens[self.current_position:self.current_position + B*T+1]
+ x = self.batch(buf[:-1])
+ y = self.batch(buf[1:])
+ self.current_position += B*T
+
+ if self.current_position + (B*T+1) > len(self.tokens):
+ print("read entire document, resetting position...")
+ self.current_position = 0
+
+ return x,y
+
+Tensor.training = True
+Tensor.no_grad = False
+model = GPT2(GPT2Small)
+optim = AdamW(get_parameters(model), lr=3e-4)
+dl = DataLoaderLite(4, 32, "datasets/shake.txt")
+losses = []
+for i in (t := trange(100)):
+ x, y = dl.next_batch()
+ optim.zero_grad()
+ logits, loss = model(x,y)
+ losses.append(loss.numpy())
+ loss.backward()
+ optim.step()
+
+ t.set_description(
+ f"train loss: {loss.numpy():.2f}"
+ )
+