| """ |
| ============================================================================ |
| 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) |
|
|
| |
| 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 |
|
|
| |
| 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}") |
|
|
| |
| 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 = 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) |
|
|
| |
| 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: |
| 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() |
|
|
|
|
| |
| @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: |
| 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 = 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) |
| |
| 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) |
| |
| 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.") |
|
|