Download scripts/train.py from OneScience-Group/AURORA: direct link, hf CLI and curl.
- Browser
- Download file 24.1 kB
-
https://huggingface.co/OneScience-Group/AURORA/resolve/main/scripts/train.py
- Command line
-
hf download hf://OneScience-Group/AURORA/scripts/train.py
-
curl -L -o train.py https://huggingface.co/OneScience-Group/AURORA/resolve/main/scripts/train.py
24.1 kB
| """Train or resume the OneScience Aurora adapter. | |
| The official Aurora repository does not publish a complete pretraining recipe. This entry point | |
| therefore implements an explicit engineering contract: AdamW, optional bfloat16 autocast, | |
| channel-scale-balanced MSE, periodic validation, and rank-zero checkpoints. | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import copy | |
| import json | |
| import logging | |
| import os | |
| import random | |
| import sys | |
| from pathlib import Path | |
| from typing import Any, Mapping, Sequence | |
| import numpy as np | |
| import h5py | |
| import torch | |
| import torch.distributed as dist | |
| import yaml | |
| from torch import nn | |
| from torch.nn.parallel import DistributedDataParallel as DDP | |
| from torch.utils.data import DataLoader | |
| from torch.utils.data.distributed import DistributedSampler | |
| PROJECT_ROOT = Path(__file__).resolve().parents[1] | |
| DEFAULT_CONFIG = PROJECT_ROOT / "conf" / "config.yaml" | |
| if str(PROJECT_ROOT) not in sys.path: | |
| sys.path.insert(0, str(PROJECT_ROOT)) | |
| def load_config(path: Path) -> dict[str, Any]: | |
| with path.open(encoding="utf-8") as handle: | |
| return yaml.safe_load(handle) | |
| def resolve_path(value: str | Path, *, config_path: Path, project_root: Path) -> Path: | |
| path = Path(value).expanduser() | |
| if path.is_absolute(): | |
| return path | |
| return (config_path.resolve().parents[1] / path).resolve() | |
| def parse_args(argv: Sequence[str] | None = None) -> argparse.Namespace: | |
| parser = argparse.ArgumentParser(description=__doc__) | |
| parser.add_argument("--config", type=Path, default=DEFAULT_CONFIG) | |
| parser.add_argument("--data-dir", type=Path, default=None) | |
| parser.add_argument("--static-file", type=Path, default=None) | |
| parser.add_argument("--output-dir", type=Path, default=None) | |
| parser.add_argument("--checkpoint-dir", type=Path, default=None) | |
| parser.add_argument("--mode", choices=("train_from_scratch", "finetune", "resume"), default=None) | |
| parser.add_argument("--checkpoint", type=Path, default=None) | |
| parser.add_argument( | |
| "--checkpoint-type", | |
| choices=("official", "training"), | |
| default=None, | |
| help="Checkpoint schema used by finetune: official Aurora .ckpt or project training .pt", | |
| ) | |
| parser.add_argument("--device", default="auto", help="auto, cpu, cuda, or cuda:N") | |
| parser.add_argument("--backend", default=None, help="DDP backend; defaults to config") | |
| parser.add_argument("--max-steps", type=int, default=None) | |
| parser.add_argument("--validation-interval", type=int, default=None) | |
| parser.add_argument("--checkpoint-interval", type=int, default=None) | |
| parser.add_argument("--batch-size", type=int, default=None) | |
| parser.add_argument("--num-workers", type=int, default=None) | |
| parser.add_argument("--seed", type=int, default=None) | |
| parser.add_argument( | |
| "--distributed", | |
| action="store_true", | |
| help="Require a torchrun DDP environment (also inferred from WORLD_SIZE)", | |
| ) | |
| parser.add_argument("--dry-run", action="store_true", help="Build the loader/model and inspect one batch") | |
| parser.add_argument("--no-gradient-checkpointing", action="store_true") | |
| return parser.parse_args(argv) | |
| def is_distributed_requested(args: argparse.Namespace) -> bool: | |
| return args.distributed or int(os.environ.get("WORLD_SIZE", "1")) > 1 | |
| def setup_distributed(args: argparse.Namespace, cfg: Mapping[str, Any]) -> dict[str, Any]: | |
| distributed = is_distributed_requested(args) | |
| world_size = int(os.environ.get("WORLD_SIZE", "1")) if distributed else 1 | |
| rank = int(os.environ.get("RANK", "0")) if distributed else 0 | |
| local_rank = int(os.environ.get("LOCAL_RANK", "0")) if distributed else 0 | |
| backend = args.backend or str(cfg["distributed"].get("backend", "nccl")) | |
| if distributed and world_size <= 1 and "MASTER_ADDR" not in os.environ: | |
| raise RuntimeError("Distributed training must be launched with torchrun (MASTER_ADDR is missing)") | |
| if distributed and not dist.is_initialized(): | |
| dist.init_process_group(backend=backend, init_method="env://") | |
| return { | |
| "distributed": distributed, | |
| "world_size": world_size, | |
| "rank": rank, | |
| "local_rank": local_rank, | |
| "backend": backend, | |
| "is_main": rank == 0, | |
| } | |
| def select_device(args: argparse.Namespace, runtime: Mapping[str, Any]) -> torch.device: | |
| requested = args.device | |
| if requested == "auto": | |
| if torch.cuda.is_available(): | |
| requested = f"cuda:{runtime['local_rank']}" | |
| else: | |
| requested = "cpu" | |
| device = torch.device(requested) | |
| if device.type == "cuda": | |
| if not torch.cuda.is_available(): | |
| raise RuntimeError("CUDA/DCU device requested but torch.cuda.is_available() is false") | |
| if device.index is None: | |
| device = torch.device(device.type, runtime["local_rank"]) | |
| return device | |
| def seed_everything(seed: int, rank: int) -> None: | |
| value = seed + rank | |
| random.seed(value) | |
| np.random.seed(value) | |
| torch.manual_seed(value) | |
| if torch.cuda.is_available(): | |
| torch.cuda.manual_seed_all(value) | |
| def configure_logging(output_dir: Path, is_main: bool) -> logging.Logger: | |
| logger = logging.getLogger("aurora.train") | |
| logger.handlers.clear() | |
| logger.setLevel(logging.INFO if is_main else logging.WARNING) | |
| formatter = logging.Formatter("%(asctime)s %(levelname)s %(message)s") | |
| stream = logging.StreamHandler(sys.stdout) | |
| stream.setFormatter(formatter) | |
| logger.addHandler(stream) | |
| if is_main: | |
| output_dir.mkdir(parents=True, exist_ok=True) | |
| file_handler = logging.FileHandler(output_dir / "train.log", encoding="utf-8") | |
| file_handler.setFormatter(formatter) | |
| logger.addHandler(file_handler) | |
| return logger | |
| def extract_latest_history_times(time_index: Any, batch_size: int, history_steps: int) -> list[str]: | |
| """Handle the nested list/tuple form produced by PyTorch's default collate.""" | |
| if isinstance(time_index, (list, tuple)) and len(time_index) > history_steps - 1: | |
| value = time_index[history_steps - 1] | |
| else: | |
| value = time_index | |
| if isinstance(value, torch.Tensor): | |
| value = value.tolist() | |
| if isinstance(value, (list, tuple)): | |
| values = [str(item) for item in value] | |
| else: | |
| values = [str(value)] | |
| if len(values) == 1 and batch_size > 1: | |
| values *= batch_size | |
| if len(values) != batch_size: | |
| raise ValueError(f"Could not extract {batch_size} latest-history timestamps from {time_index!r}") | |
| return values | |
| def make_loader( | |
| dataset_dir: Path, | |
| years: Sequence[int], | |
| channels: Sequence[str], | |
| *, | |
| mode: str, | |
| input_steps: int, | |
| output_steps: int, | |
| normalize: bool, | |
| batch_size: int, | |
| num_workers: int, | |
| distributed: bool, | |
| ) -> tuple[DataLoader, DistributedSampler | None]: | |
| from onescience.datapipes.climate import ERA5Dataset | |
| dataset = ERA5Dataset( | |
| dataset_dir=str(dataset_dir), | |
| used_years=list(years), | |
| used_variables=list(channels), | |
| mode=mode, | |
| input_steps=input_steps, | |
| output_steps=output_steps, | |
| normalize=normalize, | |
| ) | |
| sampler = DistributedSampler(dataset, shuffle=mode == "train") if distributed else None | |
| loader = DataLoader( | |
| dataset, | |
| batch_size=batch_size, | |
| shuffle=(sampler is None and mode == "train"), | |
| sampler=sampler, | |
| num_workers=num_workers, | |
| pin_memory=True, | |
| drop_last=distributed and mode == "train", | |
| ) | |
| return loader, sampler | |
| def build_model( | |
| cfg: Mapping[str, Any], | |
| config_path: Path, | |
| mode: str, | |
| checkpoint: Path | None, | |
| static_file: Path, | |
| checkpoint_type: str, | |
| ): | |
| from model.aurora import build_aurora_model | |
| project_root = config_path.resolve().parents[1] | |
| model_cfg = copy.deepcopy(cfg) | |
| model_cfg["data"]["static_file"] = str(static_file) | |
| model = build_aurora_model(model_cfg, project_root=project_root, load_pretrained=False) | |
| if mode in {"train_from_scratch", "finetune"} and checkpoint is not None: | |
| if checkpoint_type == "official": | |
| model.load_checkpoint_local(checkpoint, strict=True) | |
| else: | |
| payload = torch.load(checkpoint, map_location="cpu", weights_only=False) | |
| if not isinstance(payload, Mapping) or payload.get("schema_version") != "aurora-training-checkpoint-v1": | |
| raise ValueError( | |
| "--checkpoint-type training requires an aurora-training-checkpoint-v1 checkpoint" | |
| ) | |
| state = payload.get("model_state") | |
| if not isinstance(state, Mapping): | |
| raise ValueError("Training checkpoint does not contain a model_state mapping") | |
| model.load_state_dict(state, strict=True) | |
| elif mode == "finetune": | |
| raise ValueError( | |
| "No fine-tuning checkpoint was configured. Provide --checkpoint or set " | |
| "training.finetune.checkpoint." | |
| ) | |
| return model | |
| def make_autocast(device: torch.device, cfg: Mapping[str, Any]): | |
| precision = str(cfg["training"].get("precision", "float32")) | |
| enabled = precision == "bfloat16_autocast" and device.type == "cuda" | |
| if not enabled: | |
| return torch.autocast(device_type=device.type, enabled=False) | |
| return torch.autocast(device_type=device.type, dtype=torch.bfloat16, enabled=True) | |
| def load_channel_scales( | |
| dataset_dir: Path, | |
| years: Sequence[int], | |
| channels: Sequence[str], | |
| *, | |
| enabled: bool, | |
| epsilon: float, | |
| ) -> torch.Tensor | None: | |
| """Load per-channel standard deviations used to balance physical-space errors.""" | |
| if not enabled: | |
| return None | |
| yearly_stds = [] | |
| for year in years: | |
| path = dataset_dir / "data" / f"{year}.h5" | |
| with h5py.File(path, "r") as handle: | |
| values = handle["fields"].attrs["variables"] | |
| names = [value.decode() if isinstance(value, bytes) else str(value) for value in values] | |
| if names != list(channels): | |
| raise ValueError(f"{path}: channel order does not match training configuration") | |
| yearly_stds.append(np.asarray(handle["global_stds"][:], dtype=np.float32).reshape(-1)) | |
| scales = np.sqrt(np.mean(np.square(np.stack(yearly_stds, axis=0)), axis=0)) | |
| return torch.from_numpy(np.maximum(scales, epsilon)).reshape(1, -1, 1, 1) | |
| def mse_assumption( | |
| prediction: torch.Tensor, | |
| target: torch.Tensor, | |
| channel_scales: torch.Tensor | None = None, | |
| ) -> torch.Tensor: | |
| """Compute balanced MSE; this is an engineering objective, not the unpublished official loss.""" | |
| error = prediction.float() - target.float() | |
| if channel_scales is not None: | |
| error = error / channel_scales.to(device=error.device, dtype=error.dtype) | |
| return torch.mean(error**2) | |
| def save_checkpoint( | |
| path: Path, | |
| model: nn.Module, | |
| optimizer: torch.optim.Optimizer, | |
| scheduler: Any, | |
| *, | |
| step: int, | |
| mode: str, | |
| config_path: Path, | |
| ) -> None: | |
| module = model.module if isinstance(model, DDP) else model | |
| payload = { | |
| "schema_version": "aurora-training-checkpoint-v1", | |
| "step": step, | |
| "mode": mode, | |
| "config": str(config_path), | |
| "model_state": module.state_dict(), | |
| "optimizer_state": optimizer.state_dict(), | |
| "scheduler_state": scheduler.state_dict() if scheduler is not None else None, | |
| } | |
| path.parent.mkdir(parents=True, exist_ok=True) | |
| torch.save(payload, path) | |
| def load_resume_checkpoint( | |
| path: Path, | |
| model: nn.Module, | |
| optimizer: torch.optim.Optimizer, | |
| scheduler: Any, | |
| device: torch.device, | |
| ) -> int: | |
| payload = torch.load(path, map_location=device, weights_only=False) | |
| if payload.get("schema_version") != "aurora-training-checkpoint-v1": | |
| raise ValueError("--mode resume requires an Aurora training checkpoint produced by this entry point") | |
| module = model.module if isinstance(model, DDP) else model | |
| module.load_state_dict(payload["model_state"]) | |
| optimizer.load_state_dict(payload["optimizer_state"]) | |
| if scheduler is not None and payload.get("scheduler_state") is not None: | |
| scheduler.load_state_dict(payload["scheduler_state"]) | |
| return int(payload["step"]) | |
| def evaluate( | |
| model: nn.Module, | |
| loader: DataLoader, | |
| device: torch.device, | |
| *, | |
| history_steps: int, | |
| patch_size: int, | |
| channel_scales: torch.Tensor | None, | |
| ) -> float: | |
| model.eval() | |
| total = torch.zeros(1, device=device) | |
| count = torch.zeros(1, device=device) | |
| with torch.no_grad(): | |
| for invar, outvar, _, _, time_index in loader: | |
| invar = invar.to(device=device, dtype=torch.float32, non_blocking=True) | |
| outvar = outvar.to(device=device, dtype=torch.float32, non_blocking=True) | |
| times = extract_latest_history_times(time_index, invar.shape[0], history_steps) | |
| with make_autocast(device, {"training": {"precision": "float32"}}): | |
| prediction = model(invar, times=times) | |
| target = model.module.crop_target(outvar, patch_size) if isinstance(model, DDP) else model.crop_target(outvar, patch_size) | |
| total += mse_assumption(prediction, target, channel_scales).detach() | |
| count += 1 | |
| if dist.is_initialized(): | |
| dist.all_reduce(total, op=dist.ReduceOp.SUM) | |
| dist.all_reduce(count, op=dist.ReduceOp.SUM) | |
| return float((total / count.clamp_min(1)).item()) | |
| def run_training(args: argparse.Namespace) -> int: | |
| config_path = args.config.resolve() | |
| cfg = load_config(config_path) | |
| mode = args.mode or str(cfg["training"].get("mode", "train_from_scratch")) | |
| runtime = setup_distributed(args, cfg) | |
| device = select_device(args, runtime) | |
| seed = int(args.seed if args.seed is not None else cfg["project"]["seed"]) | |
| seed_everything(seed, runtime["rank"]) | |
| data_cfg = cfg["data"] | |
| model_cfg = cfg["model"] | |
| train_cfg = cfg["training"] | |
| data_dir = args.data_dir or resolve_path(data_cfg["virtual_dir"], config_path=config_path, project_root=PROJECT_ROOT) | |
| if args.static_file is not None: | |
| static_file = args.static_file.resolve() | |
| elif args.data_dir is not None: | |
| static_file = (data_dir / "static" / "static_vars.npz").resolve() | |
| else: | |
| static_file = resolve_path(data_cfg["static_file"], config_path=config_path, project_root=PROJECT_ROOT) | |
| configured_log_dir = resolve_path( | |
| train_cfg["log_dir"], config_path=config_path, project_root=PROJECT_ROOT | |
| ) | |
| output_dir = (args.output_dir or configured_log_dir.parent).resolve() | |
| checkpoint_dir = ( | |
| args.checkpoint_dir.resolve() | |
| if args.checkpoint_dir is not None | |
| else resolve_path( | |
| train_cfg["checkpoint_dir"], config_path=config_path, project_root=PROJECT_ROOT | |
| ) | |
| ) | |
| log_dir = ( | |
| output_dir / "logs" | |
| if args.output_dir is not None | |
| else configured_log_dir | |
| ) | |
| logger = configure_logging(log_dir, runtime["is_main"]) | |
| logger.info("runtime=%s device=%s mode=%s data=%s", runtime, device, mode, data_dir) | |
| batch_size = int(args.batch_size if args.batch_size is not None else data_cfg["batch_size"]) | |
| num_workers = int(args.num_workers if args.num_workers is not None else data_cfg["num_workers"]) | |
| max_steps = int(args.max_steps if args.max_steps is not None else train_cfg["max_steps"]) | |
| validation_interval = int( | |
| args.validation_interval | |
| if args.validation_interval is not None | |
| else train_cfg.get("validation_interval", max_steps) | |
| ) | |
| checkpoint_interval = int( | |
| args.checkpoint_interval | |
| if args.checkpoint_interval is not None | |
| else train_cfg.get("checkpoint_interval", max_steps) | |
| ) | |
| if max_steps < 1 or validation_interval < 1 or checkpoint_interval < 1: | |
| raise ValueError("max_steps, validation_interval and checkpoint_interval must be positive") | |
| input_steps = int(data_cfg["input_steps"]) | |
| output_steps = int(data_cfg["output_steps"]) | |
| patch_size = int(model_cfg["patch_size"]) | |
| channels = list(data_cfg["channel_order"]) | |
| train_loader, train_sampler = make_loader( | |
| data_dir, | |
| data_cfg["train_years"], | |
| channels, | |
| mode="train", | |
| input_steps=input_steps, | |
| output_steps=output_steps, | |
| normalize=bool(data_cfg["normalize_in_onescience"]), | |
| batch_size=batch_size, | |
| num_workers=num_workers, | |
| distributed=runtime["distributed"], | |
| ) | |
| val_loader, _ = make_loader( | |
| data_dir, | |
| data_cfg["val_years"], | |
| channels, | |
| mode="val", | |
| input_steps=input_steps, | |
| output_steps=output_steps, | |
| normalize=bool(data_cfg["normalize_in_onescience"]), | |
| batch_size=batch_size, | |
| num_workers=num_workers, | |
| distributed=runtime["distributed"], | |
| ) | |
| scratch_cfg = train_cfg.get("scratch", {}) | |
| finetune_cfg = train_cfg.get("finetune", {}) | |
| checkpoint = args.checkpoint | |
| if checkpoint is None and mode == "train_from_scratch" and scratch_cfg.get("checkpoint"): | |
| checkpoint = Path(str(scratch_cfg["checkpoint"])) | |
| if checkpoint is None and mode == "finetune" and finetune_cfg.get("checkpoint"): | |
| checkpoint = Path(str(finetune_cfg["checkpoint"])) | |
| if checkpoint is not None and not checkpoint.is_absolute(): | |
| checkpoint = (config_path.resolve().parents[1] / checkpoint).resolve() | |
| checkpoint_type = str( | |
| args.checkpoint_type | |
| or ( | |
| finetune_cfg.get("checkpoint_type") if mode == "finetune" | |
| else scratch_cfg.get("checkpoint_type") if mode == "train_from_scratch" | |
| else None | |
| ) | |
| or ("training" if mode == "resume" else "official") | |
| ) | |
| if mode == "resume": | |
| if checkpoint_type != "training": | |
| raise ValueError("--mode resume requires --checkpoint-type training") | |
| if checkpoint is None: | |
| raise ValueError("--checkpoint is required for --mode resume") | |
| model = build_model(cfg, config_path, mode, checkpoint, static_file, checkpoint_type) | |
| model.to(device) | |
| if bool(train_cfg.get("gradient_checkpointing", False)) and not args.no_gradient_checkpointing: | |
| model.configure_activation_checkpointing() | |
| if runtime["distributed"]: | |
| device_ids = [device.index] if device.type == "cuda" else None | |
| model = DDP(model, device_ids=device_ids, find_unused_parameters=False) | |
| learning_rate = float( | |
| finetune_cfg.get("learning_rate", train_cfg["learning_rate"]) | |
| if mode == "finetune" | |
| else train_cfg["learning_rate"] | |
| ) | |
| optimizer = torch.optim.AdamW( | |
| model.parameters(), | |
| lr=learning_rate, | |
| weight_decay=float(train_cfg["weight_decay"]), | |
| ) | |
| scheduler = None | |
| if str(train_cfg.get("scheduler", "none")).lower() not in {"none", "null", ""}: | |
| raise ValueError("Only scheduler=none is implemented because the official scheduler is unpublished") | |
| start_step = 0 | |
| if mode == "resume": | |
| start_step = load_resume_checkpoint(checkpoint, model, optimizer, scheduler, device) | |
| loss_cfg = train_cfg.get("loss", {}) | |
| channel_scales = load_channel_scales( | |
| data_dir, | |
| data_cfg["train_years"], | |
| channels, | |
| enabled=bool(loss_cfg.get("scale_by_channel_std", True)), | |
| epsilon=float(loss_cfg.get("scale_epsilon", 1.0e-6)), | |
| ) | |
| logger.info( | |
| "initialization=%s checkpoint=%s lr=%.3e loss=%s", | |
| "random" if checkpoint is None else checkpoint_type, | |
| checkpoint, | |
| learning_rate, | |
| loss_cfg.get("name", "channel_std_normalized_mse"), | |
| ) | |
| if args.dry_run: | |
| first = next(iter(train_loader)) | |
| invar, outvar, _, _, time_index = first | |
| times = extract_latest_history_times(time_index, invar.shape[0], input_steps) | |
| logger.info("dry_run input=%s target=%s times=%s", tuple(invar.shape), tuple(outvar.shape), times) | |
| if runtime["distributed"]: | |
| dist.barrier() | |
| dist.destroy_process_group() | |
| return 0 | |
| metrics_path = log_dir / "metrics.jsonl" | |
| step = start_step | |
| epoch = 0 | |
| while step < max_steps: | |
| if train_sampler is not None: | |
| train_sampler.set_epoch(epoch) | |
| epoch += 1 | |
| model.train() | |
| for invar, outvar, _, _, time_index in train_loader: | |
| if step >= max_steps: | |
| break | |
| invar = invar.to(device=device, dtype=torch.float32, non_blocking=True) | |
| outvar = outvar.to(device=device, dtype=torch.float32, non_blocking=True) | |
| times = extract_latest_history_times(time_index, invar.shape[0], input_steps) | |
| optimizer.zero_grad(set_to_none=True) | |
| with make_autocast(device, cfg): | |
| prediction = model(invar, times=times) | |
| base_model = model.module if isinstance(model, DDP) else model | |
| target = base_model.crop_target(outvar, patch_size) | |
| loss = mse_assumption(prediction, target, channel_scales) | |
| physical_mse = mse_assumption(prediction, target) | |
| loss.backward() | |
| clip_norm = float(train_cfg.get("gradient_clip_norm", 0.0)) | |
| if clip_norm > 0: | |
| torch.nn.utils.clip_grad_norm_(model.parameters(), clip_norm) | |
| optimizer.step() | |
| step += 1 | |
| if runtime["is_main"]: | |
| record = { | |
| "step": step, | |
| "train_loss": float(loss.detach().item()), | |
| "physical_mse": float(physical_mse.detach().item()), | |
| "lr": optimizer.param_groups[0]["lr"], | |
| } | |
| with metrics_path.open("a", encoding="utf-8") as handle: | |
| handle.write(json.dumps(record) + "\n") | |
| logger.info( | |
| "step=%d loss=%.6e physical_mse=%.6e", | |
| step, | |
| record["train_loss"], | |
| record["physical_mse"], | |
| ) | |
| if step % validation_interval == 0 or step == max_steps: | |
| val_loss = evaluate( | |
| model, | |
| val_loader, | |
| device, | |
| history_steps=input_steps, | |
| patch_size=patch_size, | |
| channel_scales=channel_scales, | |
| ) | |
| if runtime["is_main"]: | |
| record = {"step": step, "val_loss": val_loss} | |
| with metrics_path.open("a", encoding="utf-8") as handle: | |
| handle.write(json.dumps(record) + "\n") | |
| logger.info("step=%d val_loss=%.6e", step, val_loss) | |
| model.train() | |
| if runtime["is_main"] and (step % checkpoint_interval == 0 or step == max_steps): | |
| checkpoint_name = "model_finetune.pt" if mode == "finetune" else "model_bak.pt" | |
| save_checkpoint( | |
| checkpoint_dir / checkpoint_name, | |
| model, | |
| optimizer, | |
| scheduler, | |
| step=step, | |
| mode=mode, | |
| config_path=config_path, | |
| ) | |
| if runtime["distributed"]: | |
| dist.barrier() | |
| if runtime["is_main"]: | |
| summary = { | |
| "schema_version": "aurora-training-summary-v1", | |
| "status": "completed", | |
| "mode": mode, | |
| "steps": step, | |
| "output_dir": str(output_dir), | |
| "loss": loss_cfg.get("name", "channel_std_normalized_mse_assumption"), | |
| "loss_note": train_cfg.get("loss_note", ""), | |
| } | |
| with (output_dir / "run_summary.json").open("w", encoding="utf-8") as handle: | |
| json.dump(summary, handle, indent=2) | |
| if runtime["distributed"]: | |
| dist.destroy_process_group() | |
| return 0 | |
| def main(argv: Sequence[str] | None = None) -> int: | |
| args = parse_args(argv) | |
| return run_training(args) | |
| if __name__ == "__main__": | |
| raise SystemExit(main()) | |