File size: 8,452 Bytes
df43f42 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 | """
============================================================================
clankerDiffusion — Colab A100-80GB training script
============================================================================
HOW TO RUN IN COLAB (A100 80GB):
[Cell 1 — setup, run once]
!pip install -q torch==2.11.0+cu124 -f https://download.pytorch.org/whl/cu124
!pip install -q transformers tokenizers datasets accelerate safetensors huggingface_hub numpy
import os
os.environ["HF_TOKEN"] = "hf_xxx" # your token (also set in Secrets)
os.environ["CODE_REPO"] = "clankerDiffusion/base" # from upload_artifacts.py
os.environ["CKPT_REPO"] = "clankerDiffusion/checkpoints"
[Cell 2 — launch in BACKGROUND, then disconnect safely]
!nohup python colab_train.py > colab_train.log 2>&1 &
# check later with: !tail -n 30 colab_train.log
# it checkpoints + uploads to HF every 250 steps until the runtime dies
The script trains the SAME from-scratch hybrid model (AR + masked
diffusion) on FineWeb-edu, resuming if a checkpoint exists, and pushes
a checkpoint to HuggingFace Hub every 250 steps in a background thread.
============================================================================
"""
import os, sys, json, time, threading, argparse
import numpy as np
import torch
import torch.nn as nn
import torch.nn.functional as F
from huggingface_hub import snapshot_download, HfApi
CODE_REPO = os.environ.get("CODE_REPO", "clankerDiffusion/base")
CKPT_REPO = os.environ.get("CKPT_REPO", "clankerDiffusion/checkpoints")
HF_TOKEN = os.environ.get("HF_TOKEN")
api = HfApi(token=HF_TOKEN)
# ---- pull our model code + tokenizer from HF -------------------------------
print(f"[colab] downloading code from {CODE_REPO} ...")
local = snapshot_download(CODE_REPO, repo_type="model")
sys.path.insert(0, local)
from model import YKDiff
from tokenizer import YKTokenizer
# ---- big architecture for A100 80GB -------------------------------------
CFG = dict(
d_model=2048, n_layers=24, n_heads=16, d_ff=5504,
max_len=2048, vocab_size=32768,
)
tok = YKTokenizer.load(os.path.join(local, "tokenizer.json"))
CFG["vocab_size"] = tok.vocab_size
V = CFG["vocab_size"]
mask_id, pad_id = tok.mask_id, tok.pad_id
print(f"[colab] vocab={V}")
# ---- streaming fineweb into a rolling token buffer -------------------------
from datasets import load_dataset
ds = load_dataset("HuggingFaceFW/fineweb-edu", "sample-10BT",
streaming=True, split="train")
BUF_CAP = 60_000_000
buf = []
_buf_lock = threading.Lock()
def _refill():
for ex in ds:
ids = tok.encode(ex["text"])
with _buf_lock:
buf.extend(ids)
if len(buf) > BUF_CAP:
del buf[: len(buf) - BUF_CAP]
threading.Thread(target=_refill, daemon=True).start()
def sample_batch(batch, seq_len):
with _buf_lock:
if len(buf) < seq_len + 1:
return None
N = len(buf)
starts = np.random.randint(0, N - seq_len, size=batch)
return torch.tensor(
[buf[s:s + seq_len] for s in starts], dtype=torch.long)
# ---- model -----------------------------------------------------------------
model = YKDiff(CFG).cuda()
n_params = sum(p.numel() for p in model.parameters())
print(f"[colab] params = {n_params/1e9:.2f}B")
optim = torch.optim.AdamW(model.parameters(), lr=1e-4, betas=(0.9, 0.95),
weight_decay=0.1)
# ---- resume ----------------------------------------------------------------
CKPT_LOCAL = "/content/clanker_ckpts"
os.makedirs(CKPT_LOCAL, exist_ok=True)
step0 = 0
existing = sorted(f for f in os.listdir(CKPT_LOCAL) if f.endswith(".pt"))
if existing:
sd = torch.load(os.path.join(CKPT_LOCAL, existing[-1]), map_location="cuda")
model.load_state_dict(sd["model"]); optim.load_state_dict(sd["optim"])
step0 = sd["step"]
print(f"[colab] resumed step={step0}")
else:
# try pulling latest from HF
try:
api.create_repo(CKPT_REPO, repo_type="model", exist_ok=True)
except Exception:
pass
def _upload(path):
def _u():
try:
api.upload_file(repo_id=CKPT_REPO,
path_in_repo=os.path.basename(path),
path_or_fileobj=path, repo_type="model")
print(f"[colab] uploaded {os.path.basename(path)} -> {CKPT_REPO}",
flush=True)
except Exception as e:
print(f"[colab] upload failed: {e}", flush=True)
threading.Thread(target=_u, daemon=True).start()
# ---- training loop (hybrid) ----------------------------------------------
@torch.no_grad()
def _cosine_lr(step, warmup, total, base, minlr):
if step < warmup:
return base * step / warmup
p = (step - warmup) / max(total - warmup, 1)
return minlr + 0.5 * (base - minlr) * (1 + np.cos(np.pi * min(p, 1.0)))
BATCH, SEQ, GRAD_ACCUM = 16, 2048, 4
WARMUP, TOTAL_STEPS = 500, 200_000
BASE_LR, MIN_LR = 1e-4, 1e-5
CKPT_EVERY = 250
amp = torch.cuda.amp.autocast(dtype=torch.bfloat16)
step = step0
model.train()
t0 = time.time()
print("[colab] training started.", flush=True)
while True: # run as long as possible
try:
optim.zero_grad(set_to_none=True)
for micro in range(GRAD_ACCUM):
idx = None
while idx is None:
idx = sample_batch(BATCH, SEQ)
time.sleep(0.02)
idx = idx.cuda()
mode_ar = (torch.rand(1).item() < 0.5)
with amp:
if mode_ar:
m = torch.zeros(BATCH, dtype=torch.long, device="cuda")
logits = model(idx, m, t=None)
loss = F.cross_entropy(
logits[:, :-1].reshape(-1, V),
idx[:, 1:].reshape(-1), ignore_index=pad_id)
else:
m = torch.ones(BATCH, dtype=torch.long, device="cuda")
r = torch.rand(BATCH, device="cuda")
is_mask = torch.rand(BATCH, SEQ, device="cuda") < r[:, None]
not_pad = idx != pad_id
masked = idx.clone(); masked[is_mask] = mask_id
logits = model(masked, m, t=r)
ce = F.cross_entropy(logits.reshape(-1, V),
idx.reshape(-1), reduction="none",
ignore_index=-100)
ce = ce * is_mask.reshape(-1) * not_pad.reshape(-1)
denom = (is_mask & not_pad).reshape(-1).sum().clamp(min=1)
loss = ce.sum() / denom
(loss / GRAD_ACCUM).backward()
nn.utils.clip_grad_norm_(model.parameters(), 1.0)
lr = _cosine_lr(step, WARMUP, TOTAL_STEPS, BASE_LR, MIN_LR)
for g in optim.param_groups:
g["lr"] = lr
optim.step()
step += 1
if step % 25 == 0:
print(f"[colab] step {step} loss~{loss.item():.3f} "
f"lr={lr:.2e} t={(time.time()-t0)/60:.1f}m", flush=True)
if step % CKPT_EVERY == 0:
# full local ckpt (for resume)
full = os.path.join(CKPT_LOCAL, f"clanker_{step:07d}.pt")
torch.save({"model": model.state_dict(),
"optim": optim.state_dict(),
"step": step, "cfg": CFG, "vocab": V}, full)
# light bf16 model-only for HF upload
lite = os.path.join(CKPT_LOCAL, f"clanker_{step:07d}_lite.pt")
torch.save({"model": {k: v.to(torch.bfloat16)
for k, v in model.state_dict().items()},
"cfg": CFG, "vocab": V, "step": step}, lite)
print(f"[colab] checkpoint {step}", flush=True)
_upload(lite)
# keep only last 2 local full ckpts to save disk
for old in sorted(f for f in os.listdir(CKPT_LOCAL)
if f.endswith(".pt") and "lite" not in f)[:-2]:
os.remove(os.path.join(CKPT_LOCAL, old))
except torch.cuda.OutOfMemoryError:
print("[colab] OOM — skipping step", flush=True)
optim.zero_grad(set_to_none=True)
torch.cuda.empty_cache()
except Exception as e:
print(f"[colab] step error (continuing): {e}", flush=True)
torch.cuda.empty_cache()
print("[colab] loop ended.")
|