Download train.py from qwrt/Swedish1M: direct link, hf CLI and curl.
- Browser
- Download file 4.88 kB
-
https://huggingface.co/qwrt/Swedish1M/resolve/main/train.py
- Command line
-
hf download hf://qwrt/Swedish1M/train.py
-
curl -L -o train.py https://huggingface.co/qwrt/Swedish1M/resolve/main/train.py
4.88 kB
| """ | |
| Byte-nivå LLM med färdiga Qwen3-arkitekturen. Kräver: pip install transformers torch numpy | |
| Förberedelse: se tidigare — train.bin/val.bin som råa uint8-bytes. | |
| """ | |
| import math, os, time | |
| import numpy as np | |
| import torch | |
| from transformers import Qwen3Config, Qwen3ForCausalLM | |
| device = "cuda" if torch.cuda.is_available() else "cpu" | |
| print(f"device: {device}") | |
| os.makedirs("models", exist_ok=True) | |
| # --- modell: Qwen3, rakt av, fast liten och med vocab=256 ------------- | |
| config = Qwen3Config( | |
| vocab_size=256, | |
| hidden_size=160, | |
| head_dim=24, | |
| num_attention_heads=4, | |
| num_key_value_heads=4, | |
| num_hidden_layers=5, | |
| intermediate_size=320, | |
| max_position_embeddings=1024, | |
| tie_word_embeddings=True, | |
| attention_dropout=0.0, | |
| ) | |
| raw_model = Qwen3ForCausalLM(config).to(device) | |
| raw_model.set_attn_implementation("sdpa") | |
| print(f"parametrar: {sum(p.numel() for p in raw_model.parameters())/1e3:.1f}K") | |
| print(f"attention: {raw_model.config._attn_implementation}") | |
| # kompilerad variant för träning; raw_model för generate/checkpoint | |
| model = raw_model | |
| # --- sekventiell loader ------------------------------------------------ | |
| class SequentialLoader: | |
| def __init__(self, path, B, T): | |
| self.data = np.memmap(path, dtype=np.uint8, mode="r") | |
| self.B, self.T = B, T | |
| self.seg = (len(self.data) - 1) // B | |
| self.pos, self.epoch = 0, 0 | |
| def next_batch(self): | |
| starts = np.arange(self.B) * self.seg + self.pos | |
| x = np.stack([self.data[s:s+self.T] for s in starts]).astype(np.int64) | |
| y = np.stack([self.data[s+1:s+1+self.T] for s in starts]).astype(np.int64) | |
| self.pos += self.T | |
| if self.pos + self.T + 1 > self.seg: | |
| self.pos, self.epoch = 0, self.epoch + 1 | |
| return torch.from_numpy(x), torch.from_numpy(y) | |
| B, T = 128, 1024 | |
| accum_steps = 2 | |
| base_lr = 3e-3 | |
| epochs = 10 | |
| train = SequentialLoader("data/train.bin", B, T) | |
| steps_per_epoch = (train.seg // T) // accum_steps # räknar OPTIMIZER-steg, inte mikrosteg | |
| max_steps = epochs * steps_per_epoch | |
| print(f"steg per epok: {steps_per_epoch:,} (effektiv batch: {B*accum_steps})") | |
| # --- optimizer + lr-schema --------------------------------------------- | |
| opt = torch.optim.AdamW(raw_model.parameters(), lr=base_lr, | |
| betas=(0.9, 0.95), weight_decay=0.1, | |
| fused=(device == "cuda")) | |
| warmup = 1000 | |
| def lr_at(step): | |
| if step < warmup: | |
| return base_lr * (step + 1) / warmup | |
| t = (step - warmup) / max(1, max_steps - warmup) | |
| return base_lr / 10 + 0.5 * (base_lr - base_lr / 10) * (1 + math.cos(math.pi * min(t, 1.0))) | |
| import json | |
| import matplotlib.pyplot as plt | |
| def save_losses(losses, path="models/losses"): | |
| # rådatan till fil | |
| with open(f"{path}.json", "w") as f: | |
| json.dump(losses, f) | |
| # --- träningsloop -------------------------------------------------------- | |
| model.train() | |
| t0 = time.time() | |
| losses = [] | |
| opt.zero_grad(set_to_none=True) | |
| for step in range(max_steps): | |
| for g in opt.param_groups: | |
| g["lr"] = lr_at(step) | |
| # --- ackumulera gradienter över accum_steps mikrobatcher --- | |
| for micro in range(accum_steps): | |
| x, y = train.next_batch() | |
| x = x.pin_memory().to(device, non_blocking=True) | |
| y = y.pin_memory().to(device, non_blocking=True) | |
| with torch.autocast(device_type=device, dtype=torch.bfloat16, enabled=device == "cuda"): | |
| out = model(input_ids=x, labels=x) # HF räknar cross-entropy åt oss | |
| loss = out.loss / accum_steps # skala ner så gradienterna summeras rätt | |
| loss.backward() | |
| losses.append(out.loss.item()) # logga oskalad loss | |
| torch.nn.utils.clip_grad_norm_(raw_model.parameters(), 1.0) | |
| opt.step() | |
| opt.zero_grad(set_to_none=True) | |
| if step % 50 == 0: | |
| tok_s = 50 * accum_steps * B * T / max(time.time() - t0, 1e-9) | |
| print(f"steg {step:6d} | epok {train.epoch} | loss {losses[-1]:.4f} " | |
| f"| lr {lr_at(step):.2e} | {tok_s/1e3:.0f}k tok/s") | |
| t0 = time.time() | |
| if step % 1000 == 0 and step > 0: | |
| save_losses(losses) | |
| torch.save({"model": raw_model.state_dict(), "step": step, | |
| "pos": train.pos, "epoch": train.epoch}, "models/ckpt.pt") | |
| # smakprov — på raw_model, inte den kompilerade | |
| seed = torch.tensor(list("Det var en gång".encode()), dtype=torch.long)[None].to(device) | |
| with torch.no_grad(): | |
| gen = raw_model.generate(seed, max_new_tokens=200, | |
| do_sample=True, temperature=0.8) | |
| print(bytes(gen[0].tolist()).decode("utf-8", errors="replace")) | |
| t0 = time.time() # räkna inte in generate-tiden i tok/s | |
| print("klart.") |