Files
2026-07-13 12:31:40 +08:00

245 lines
7.2 KiB
Python

from __future__ import annotations
import os
import subprocess
import time
from pathlib import Path
from typing import Any
import click
import modal
from ..resources import (
FOUROVERSIX_CACHE_PATH,
FOUROVERSIX_INSTALL_PATH,
Dependency,
app,
cache_volume,
get_image,
wandb_secret,
)
img = get_image(
dependencies=[Dependency.flash_attention, Dependency.flame, Dependency.fouroversix],
extra_pip_dependencies=["datasets<4.6"],
)
def train(
*,
batch_size: int,
checkpoint_interval: int,
checkpoint_keep_latest_k: int,
checkpoint_load_step: int,
context_length: int,
dataset: str,
dataset_name: str,
dataset_split: str,
exp_folder: str,
gradient_accumulation_steps: int,
initial_load_path: str | None,
job_config_file: str,
lr: float,
lr_decay_type: str,
model_config: str,
model_name: str,
no_torch_compile: bool,
seed: int,
tokenizer: str,
training_steps: int | None,
) -> None:
import torch
# Cache activations and gradients and set dump folder
os.environ["CACHE_ACTIVATIONS"] = "1"
os.environ["CACHE_GRADIENTS"] = "1"
os.environ["DUMP_FOLDER"] = f"{exp_folder}/{model_name}"
# Set MODEL_NAME for wandb
os.environ["MODEL_NAME"] = model_name
# Set NGPU for flame
os.environ["NGPU"] = str(torch.cuda.device_count())
if training_steps is None:
if dataset_name == "sample-10BT":
num_tokens = 10_000_000_000
elif dataset_name == "sample-100BT":
num_tokens = 100_000_000_000
else:
msg = (
"You must provide the number of training steps if not using the "
"sample-10BT or sample-100BT datasets"
)
raise ValueError(msg)
training_steps = num_tokens // int(
context_length * batch_size * torch.cuda.device_count(),
)
# Start training
args = [
"bash",
"train.sh",
"--job.config_file",
job_config_file,
"--job.dump_folder",
f"{exp_folder}/{model_name}",
"--model.config",
model_config,
"--model.tokenizer_path",
tokenizer,
"--optimizer.name",
"AdamW",
"--optimizer.lr",
str(lr),
"--lr_scheduler.warmup_steps",
"0",
"--lr_scheduler.decay_ratio",
"0.15",
"--lr_scheduler.decay_type",
lr_decay_type,
"--lr_scheduler.lr_min",
"0.01",
"--training.batch_size",
"1",
"--training.seq_len",
str(int(context_length * batch_size)),
"--training.context_len",
str(context_length),
"--training.varlen",
"--training.gradient_accumulation_steps",
str(gradient_accumulation_steps),
"--training.steps",
str(training_steps),
"--training.max_norm",
"1.0",
"--training.skip_nan_inf",
"--training.dataset",
dataset,
"--training.dataset_name",
dataset_name,
"--training.dataset_split",
dataset_split,
"--training.num_workers",
"32",
"--training.prefetch_factor",
"2",
"--training.seed",
str(seed),
"--checkpoint.interval",
str(checkpoint_interval),
"--checkpoint.load_step",
str(checkpoint_load_step),
"--checkpoint.keep_latest_k",
str(checkpoint_keep_latest_k),
"--metrics.log_freq",
"1",
]
if not no_torch_compile:
args.append("--training.compile")
if initial_load_path is not None:
args.extend(
[
"--checkpoint.initial_load_path",
initial_load_path,
"--checkpoint.no_initial_load_model_weights_only",
],
)
subprocess.run(args, check=True)
@app.cls(
image=img,
gpu="B200:8",
timeout=24 * 60 * 60,
cpu=64,
memory=8 * 64 * 1024,
volumes={FOUROVERSIX_CACHE_PATH: cache_volume},
secrets=[wandb_secret],
)
class ModalTrainer:
"""Run training jobs on Modal."""
@modal.method()
def train(self, **kwargs: dict[str, Any]) -> None:
"""Start a training job on Modal."""
os.chdir(FOUROVERSIX_INSTALL_PATH / "third_party" / "flame")
train(**kwargs)
@click.command()
@click.option("--batch-size", type=float, default=16)
@click.option("--checkpoint-interval", type=int, default=1000)
@click.option("--checkpoint-keep-latest-k", type=int, default=0)
@click.option("--checkpoint-load-step", type=int, default=-1)
@click.option("--context-length", type=int, default=8192)
@click.option("--dataset", type=str, default="HuggingFaceFW/fineweb-edu")
@click.option("--dataset-name", type=str, default="sample-100BT")
@click.option("--dataset-split", type=str, default="train")
@click.option("--detach", is_flag=True)
@click.option("--exp-folder", type=str, default="exp")
@click.option("--gradient-accumulation-steps", type=int, default=1)
@click.option("--initial-load-path", type=str)
@click.option("--job-config-file", type=str, default="flame/models/fla.toml")
@click.option("--lr", type=float, default=1.2e-3)
@click.option("--lr-decay-type", type=str, default="linear")
@click.option("--modal", is_flag=True)
@click.option("--modal-gpu", type=str, default="B200:8")
@click.option("--model-config", type=str, required=True)
@click.option("--model-name", type=str, required=True)
@click.option("--no-torch-compile", is_flag=True)
@click.option("--seed", type=int, default=42)
@click.option("--tokenizer", type=str, default="fla-hub/transformer-1.3B-100B")
@click.option("--training-steps", type=int, default=None)
@click.option("--wait-for-pid", type=int, default=None)
def cli(**kwargs: dict[str, Any]) -> None:
# Options that are not passed to the train function
detach = kwargs.pop("detach", False)
modal_gpu = kwargs.pop("modal_gpu", "B200:8")
use_modal = kwargs.pop("modal", False)
wait_for_pid = kwargs.pop("wait_for_pid", None)
# Wait for the previous training job to finish
if wait_for_pid is not None:
while (
subprocess.run(["kill", "-0", str(wait_for_pid)], check=False).returncode
== 0
):
time.sleep(1)
time.sleep(60)
if not Path(kwargs["model_config"]).exists():
kwargs["model_config"] = (
Path(__file__).parent.parent.parent / kwargs["model_config"]
)
if not Path(kwargs["model_config"]).exists():
msg = f"Model config file not found: {kwargs['model_config']}"
raise FileNotFoundError(msg)
# Set exp folder on Modal
if use_modal:
with modal.enable_output(), app.run(detach=detach):
kwargs["exp_folder"] = (FOUROVERSIX_CACHE_PATH / "exp").as_posix()
kwargs["model_config"] = (
(FOUROVERSIX_INSTALL_PATH / kwargs["model_config"])
.absolute()
.as_posix()
)
ModalTrainer.with_options(gpu=modal_gpu)().train.remote(**kwargs)
else:
kwargs["model_config"] = Path(kwargs["model_config"]).absolute().as_posix()
os.chdir(Path(__file__).parent.parent.parent / "third_party" / "flame")
train(**kwargs)
if __name__ == "__main__":
cli()