Text Generation
Safetensors
Swedish
qwen3
Swedish1M / train.py
qwrt's picture
Upload 16 files
97d0285 verified
Raw History Blame Contribute Delete
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.")