Small-LLM1 / train.py
aaaaaaaieuyd's picture
Upload 7 files
16dce9d verified
Raw History Blame Contribute Delete
3.94 kB
"""Small LLM1: ~5M parameter Llama-style model trained from scratch on TinyStories."""
import math, os, time
import torch
from datasets import load_dataset
from tokenizers import Tokenizer, models, pre_tokenizers, decoders, trainers, processors
from transformers import LlamaConfig, LlamaForCausalLM, PreTrainedTokenizerFast
OUT = "small-llm1"
N_STORIES = 400_000
VOCAB = 4096
SEQ = 256
BATCH = 64
STEPS = 6000
LR = 2e-3
torch.manual_seed(0)
# --- data ---
print("loading data...")
ds = load_dataset("roneneldan/TinyStories", split="train", streaming=True)
texts = [ex["text"].strip() for _, ex in zip(range(N_STORIES), ds) if ex["text"].strip()]
print(f"{len(texts)} stories")
# --- tokenizer (byte-level BPE) ---
tok = Tokenizer(models.BPE())
tok.pre_tokenizer = pre_tokenizers.ByteLevel(add_prefix_space=False)
tok.decoder = decoders.ByteLevel()
trainer = trainers.BpeTrainer(vocab_size=VOCAB, special_tokens=["<|endoftext|>"],
initial_alphabet=pre_tokenizers.ByteLevel.alphabet())
tok.train_from_iterator(texts[:100_000], trainer)
tok.post_processor = processors.ByteLevel(trim_offsets=False)
hf_tok = PreTrainedTokenizerFast(tokenizer_object=tok, bos_token="<|endoftext|>",
eos_token="<|endoftext|>", unk_token="<|endoftext|>",
pad_token="<|endoftext|>")
eos = hf_tok.eos_token_id
ids = []
for i in range(0, len(texts), 10_000):
for seq in hf_tok(texts[i:i + 10_000])["input_ids"]:
ids.extend(seq + [eos])
data = torch.tensor(ids, dtype=torch.long)
n_val = SEQ * 2000
train_data, val_data = data[:-n_val], data[-n_val:]
print(f"{len(data)/1e6:.1f}M tokens")
# --- model ---
config = LlamaConfig(vocab_size=VOCAB, hidden_size=256, intermediate_size=512,
num_hidden_layers=6, num_attention_heads=8, num_key_value_heads=8,
max_position_embeddings=SEQ, tie_word_embeddings=True,
bos_token_id=eos, eos_token_id=eos, pad_token_id=eos)
dev = "cuda"
model = LlamaForCausalLM(config).to(dev)
n_params = sum(p.numel() for p in model.parameters())
print(f"params: {n_params/1e6:.2f}M")
opt = torch.optim.AdamW(model.parameters(), lr=LR, weight_decay=0.1, betas=(0.9, 0.95), fused=True)
warmup = 200
sched = torch.optim.lr_scheduler.LambdaLR(opt, lambda s: min(1, (s + 1) / warmup) *
(0.1 + 0.9 * 0.5 * (1 + math.cos(math.pi * min(s, STEPS) / STEPS))))
def batch(src):
ix = torch.randint(len(src) - SEQ - 1, (BATCH,))
x = torch.stack([src[i:i + SEQ] for i in ix]).to(dev, non_blocking=True)
return x
@torch.no_grad()
def evaluate():
model.eval()
losses = []
for i in range(0, len(val_data) - SEQ, SEQ * BATCH):
x = torch.stack([val_data[j:j + SEQ] for j in range(i, min(i + SEQ * BATCH, len(val_data) - SEQ), SEQ)]).to(dev)
with torch.autocast("cuda", dtype=torch.bfloat16):
losses.append(model(input_ids=x, labels=x).loss.item())
model.train()
return sum(losses) / len(losses)
t0 = time.time()
model.train()
for step in range(STEPS):
x = batch(train_data)
with torch.autocast("cuda", dtype=torch.bfloat16):
loss = model(input_ids=x, labels=x).loss
loss.backward()
torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)
opt.step(); sched.step(); opt.zero_grad(set_to_none=True)
if step % 500 == 0 or step == STEPS - 1:
print(f"step {step} loss {loss.item():.3f} lr {sched.get_last_lr()[0]:.2e} {time.time()-t0:.0f}s", flush=True)
val_loss = evaluate()
print(f"val loss {val_loss:.3f} ppl {math.exp(val_loss):.2f} time {time.time()-t0:.0f}s")
model.generation_config.pad_token_id = eos
model.save_pretrained(OUT)
hf_tok.save_pretrained(OUT)
with open(os.path.join(OUT, "train_stats.txt"), "w") as f:
f.write(f"params={n_params}\ntokens={len(data)}\nsteps={STEPS}\nval_loss={val_loss:.4f}\n")
print("saved to", OUT)