clankerDiffusion-base / modal_train.py
coderofpears's picture
Upload modal_train.py with huggingface_hub
9bf246c verified
Raw
History Blame Contribute Delete
4.73 kB
"""
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)