File size: 4,726 Bytes
626bca9 9d7b292 626bca9 9d7b292 626bca9 9d7b292 626bca9 9d7b292 626bca9 9d7b292 626bca9 9bf246c 626bca9 8481d22 626bca9 9d7b292 626bca9 359f2c4 626bca9 ed0dcee 9d7b292 ed0dcee 626bca9 9d7b292 626bca9 359f2c4 9d7b292 020a3e3 9d7b292 626bca9 9bf246c 626bca9 9d7b292 | 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 | """
clankerDiffusion — Modal L4 training.
Builds a CUDA image, pulls the latest code from the HF code repo at runtime,
keeps data + checkpoints on a persistent Modal Volume (/vol), injects HF_TOKEN
from .env, trains a big hybrid model on an L4 (24 GB), and pushes every
checkpoint to the HF repo:
coderofpears/clankerDiffusion-checkpoints
Data is generated in-container by prep.py (fast HF egress), scaled by --scale,
so no large file transfer is needed.
Launch (after `modal token new` on this machine):
modal run modal_train.py::train_on_l4 --hours 20 --size large --scale 3
"""
import os
from modal import App, Image, Volume, Secret
HERE = os.path.dirname(os.path.abspath(__file__))
CODE_REPO = "coderofpears/clankerDiffusion-base"
DATA_REPO = "coderofpears/clankerDiffusion-data"
CKPT_REPO = "coderofpears/clankerDiffusion-checkpoints"
DOTENV = os.path.join(HERE, ".env")
image = (
Image.debian_slim()
.pip_install("torch==2.11.0", index_url="https://download.pytorch.org/whl/cu128")
.pip_install("numpy", "tokenizers", "datasets", "safetensors",
"huggingface_hub", "hf-transfer")
.env({"HF_HUB_ENABLE_HF_TRANSFER": "1", "TOKENIZERS_PARALLELISM": "false"})
)
app = App("clanker-diffusion", image=image)
volume = Volume.from_name("clanker-vol", create_if_missing=True)
SIZES = {
"base": dict(d_model=768, n_layers=12, n_heads=12, d_ff=2048, batch=24),
"large": dict(d_model=2048, n_layers=24, n_heads=16, d_ff=5504, batch=8),
"xl": dict(d_model=2560, n_layers=28, n_heads=20, d_ff=6912, batch=4),
# ~0.8B: fits L4 comfortably, more throughput than the 1.35B "large"
"08b": dict(d_model=1536, n_layers=20, n_heads=16, d_ff=4096, batch=12,
rope_scale=16.0),
}
@app.function(
image=image,
gpu="L4",
timeout=82800, # 23h, under Modal's 24h cap
volumes={"/vol": volume},
secrets=[Secret.from_dotenv(DOTENV)],
)
def train_on_l4(hours: float = 20.0, ckpt_every: int = 250,
size: str = "large", batch: int = None, scale: float = 3.0):
import subprocess
import sys
import shutil
# ---- pull latest code from HF (use huggingface_hub, not the `hf` CLI) ----
from huggingface_hub import snapshot_download
os.makedirs("/root/clanker", exist_ok=True)
snapshot_download(repo_id=CODE_REPO, repo_type="model",
local_dir="/root/clanker",
token=os.environ.get("HF_TOKEN"))
os.chdir("/root/clanker")
data_dir, ckpt_dir = "/vol/data", "/vol/checkpoints"
os.makedirs(data_dir, exist_ok=True)
os.makedirs(ckpt_dir, exist_ok=True)
# reuse the canonical tokenizer / RAG corpus already fetched with the code
for name in ("tokenizer.json", "meta.json", "rag_corpus.txt"):
src = os.path.join("/root/clanker/data", name)
if os.path.exists(src):
shutil.copy(src, os.path.join(data_dir, name))
os.makedirs(data_dir, exist_ok=True)
os.makedirs(ckpt_dir, exist_ok=True)
# ---- ensure data (generate in-container; fast HF egress) ----
# Only treat the dataset as complete if meta.json exists (a partial
# train.bin from a killed run must be regenerated).
meta_path = os.path.join(data_dir, "meta.json")
if not os.path.exists(meta_path):
stale = os.path.join(data_dir, "train.bin")
if os.path.exists(stale):
print("[modal] found partial train.bin without meta.json; discarding.")
os.remove(stale)
print(f"[modal] generating data locally (scale={scale}) ...")
subprocess.run([sys.executable, "prep.py", "--out-dir", data_dir,
"--scale", str(scale)], check=True)
spec = dict(SIZES.get(size, SIZES["large"]))
if batch:
spec["batch"] = batch
cmd = [
sys.executable, "train.py",
"--hours", str(hours),
"--batch", str(spec["batch"]),
"--ckpt-every", str(ckpt_every),
"--data-dir", data_dir,
"--ckpt-dir", ckpt_dir,
"--hf-repo", CKPT_REPO,
"--d-model", str(spec["d_model"]),
"--n-layers", str(spec["n_layers"]),
"--n-heads", str(spec["n_heads"]),
"--d-ff", str(spec["d_ff"]),
]
if "rope_scale" in spec:
cmd += ["--rope-scale", str(spec["rope_scale"])]
print("[modal] launching:", " ".join(cmd), flush=True)
subprocess.run(cmd, check=True)
print("[modal] training finished", flush=True)
@app.local_entrypoint()
def main(hours: float = 20.0, ckpt_every: int = 250, size: str = "large",
batch: int = None, scale: float = 3.0):
train_on_l4.remote(hours=hours, ckpt_every=ckpt_every, size=size,
batch=batch, scale=scale)
|