Dory-Beam-4B / code /modal_app /train_dory.py
ProCreations's picture
Dory-Beam-4B weights, config, tokenizer, license and code
f0975ca verified
Raw History Blame Contribute Delete
3.4 kB
"""Train Dory1.0-4B on Beam traces on Modal (torchrun + DDP on B200s).
modal run modal_app/train_dory.py::download_dory # one-time: base weights -> Volume
modal run modal_app/train_dory.py --gpus 1 --run smoke --extra "--max-steps 20"
modal run modal_app/train_dory.py --gpus 8 --run r1 --data sft/sft_verified.jsonl
Data files are paths inside the `beam-dory-data` Volume (upload with `modal volume put`).
Checkpoints land in the `beam-dory-ckpt` Volume under runs/<run>/.
"""
import shlex
import subprocess
import time
import modal
APP = modal.App("beam-dory-train")
weights = modal.Volume.from_name("beam-weights", create_if_missing=True, version=2)
data = modal.Volume.from_name("beam-dory-data", create_if_missing=True, version=2)
ckpt = modal.Volume.from_name("beam-dory-ckpt", create_if_missing=True, version=2)
W, D, C = "/weights", "/data", "/ckpt"
DORY_REPO = "nvidia/NVIDIA-Nemotron-Labs-Dory1.0-4B-BF16"
image = (
modal.Image.debian_slim(python_version="3.12")
.pip_install("torch==2.14.1", "safetensors", "transformers>=5.20", "jinja2", "numpy",
"huggingface_hub[hf_xet]>=0.34")
.env({"PYTHONUNBUFFERED": "1", "TOKENIZERS_PARALLELISM": "false"})
.add_local_dir("dory", "/proj/dory")
.add_local_dir("scripts", "/proj/scripts")
)
@APP.function(image=image, volumes={W: weights}, cpu=4, memory=16384, timeout=3600)
def download_dory():
from huggingface_hub import snapshot_download
snapshot_download(DORY_REPO, local_dir=f"{W}/dory-4b", allow_patterns=["*.json", "*.jinja", "*.safetensors"])
weights.commit()
print(subprocess.run(["ls", "-la", f"{W}/dory-4b"], capture_output=True, text=True).stdout)
@APP.function(image=image, gpu=["B200:8", "H200:8", "B200:4"], volumes={W: weights, D: data, C: ckpt},
timeout=12 * 3600, cpu=32, memory=262144)
def train(run: str, data_files: list, nproc: int = 0, extra: str = ""):
"""nproc=0 uses every GPU in the container (whichever fallback config was allocated)."""
if not nproc:
out_ = subprocess.run(["nvidia-smi", "--query-gpu=name", "--format=csv,noheader"], capture_output=True, text=True)
names = [l for l in out_.stdout.splitlines() if l.strip()]
nproc = len(names)
print(f"got {nproc}x {names[0]}", flush=True)
if "--grad-accum" not in extra: # keep the global batch of 8 GPUs x 1 sequence on smaller fallbacks
extra += f" --grad-accum {max(1, 8 // nproc)}"
out = f"{C}/runs/{run}"
cmd = ["torchrun", "--standalone", f"--nproc_per_node={nproc}", "/proj/scripts/train_dory.py",
"--model", f"{W}/dory-4b", "--out", out, "--data", *[f"{D}/{f}" for f in data_files],
*shlex.split(extra)]
print("cmd:", " ".join(cmd), flush=True)
t0 = time.time()
proc = subprocess.run(cmd, cwd="/proj")
ckpt.commit()
print(f"exit {proc.returncode} after {(time.time() - t0) / 60:.1f} min", flush=True)
if proc.returncode != 0:
raise RuntimeError(f"training failed with exit code {proc.returncode}")
@APP.local_entrypoint()
def main(run: str, data: str = "sft/sft_verified.jsonl", gpus: int = 8, extra: str = ""):
"""Interactive runs (smoke tests). Long runs: deploy, then spawn via scripts/spawn_train.py."""
files = [f.strip() for f in data.split(",") if f.strip()]
train.with_options(gpu=f"B200:{gpus}").remote(run, files, gpus, extra)