Download scripts/train.py from OneScience-Group/DeepCFD: direct link, hf CLI and curl.
- Browser
- Download file 6.07 kB
-
https://huggingface.co/OneScience-Group/DeepCFD/resolve/main/scripts/train.py
- Command line
-
hf download hf://OneScience-Group/DeepCFD/scripts/train.py
-
curl -L -o train.py https://huggingface.co/OneScience-Group/DeepCFD/resolve/main/scripts/train.py
6.07 kB
| import sys | |
| import importlib.util | |
| from pathlib import Path | |
| import torch | |
| from torch.nn.parallel import DistributedDataParallel as DDP | |
| from tqdm import tqdm | |
| PROJECT_ROOT = Path(__file__).resolve().parents[1] | |
| sys.path.insert(0, str(PROJECT_ROOT)) | |
| from model import build_model | |
| from onescience.distributed.manager import DistributedManager | |
| from onescience.utils.YParams import YParams | |
| import onescience | |
| def resolve_path(path_value): | |
| path = Path(path_value) | |
| return path if path.is_absolute() else PROJECT_ROOT / path | |
| def load_config(): | |
| cfg = YParams(str(PROJECT_ROOT / "config" / "config.yaml"), "root") | |
| cfg.datapipe.source.data_dir = str(resolve_path(cfg.datapipe.source.data_dir)) | |
| cfg.training.output_dir = str(resolve_path(cfg.training.output_dir)) | |
| return cfg | |
| def load_deepcfd_datapipe_class(): | |
| runtime_root = Path(onescience.__file__).resolve().parent | |
| datapipe_file = runtime_root / "datapipes" / "cfd" / "deepcfd.py" | |
| spec = importlib.util.spec_from_file_location("_onescience_deepcfd_datapipe", datapipe_file) | |
| if spec is None or spec.loader is None: | |
| raise ImportError(f"Cannot load DeepCFD datapipe from {datapipe_file}") | |
| module = importlib.util.module_from_spec(spec) | |
| sys.modules[spec.name] = module | |
| spec.loader.exec_module(module) | |
| return module.DeepCFDDatapipe | |
| def loss_func(output, target, weights): | |
| lossu = (output[:, 0] - target[:, 0]) ** 2 | |
| lossv = (output[:, 1] - target[:, 1]) ** 2 | |
| lossp = torch.abs(output[:, 2] - target[:, 2]) | |
| loss_stack = torch.stack([lossu, lossv, lossp], dim=1) | |
| return torch.sum(loss_stack / weights) | |
| def evaluate(model, loader, device, weights, dist): | |
| model.eval() | |
| total_loss = 0.0 | |
| total_ux_mse = 0.0 | |
| total_uy_mse = 0.0 | |
| total_p_mse = 0.0 | |
| num_batches = 0 | |
| with torch.no_grad(): | |
| iterator = tqdm(loader, desc="Evaluating", disable=(dist.rank != 0)) | |
| for batch in iterator: | |
| x = batch["x"].to(device) | |
| y = batch["y"].to(device) | |
| output = model(x) | |
| total_loss += loss_func(output, y, weights).item() | |
| total_ux_mse += torch.sum((output[:, 0] - y[:, 0]) ** 2).item() | |
| total_uy_mse += torch.sum((output[:, 1] - y[:, 1]) ** 2).item() | |
| total_p_mse += torch.sum((output[:, 2] - y[:, 2]) ** 2).item() | |
| num_batches += 1 | |
| if num_batches == 0: | |
| raise RuntimeError("Evaluation loader is empty. Check split_ratio and dataset size.") | |
| return total_loss / num_batches, total_ux_mse, total_uy_mse, total_p_mse | |
| def main(): | |
| DistributedManager.initialize() | |
| dist = DistributedManager() | |
| device = dist.device | |
| cfg = load_config() | |
| DeepCFDDatapipe = load_deepcfd_datapipe_class() | |
| output_dir = Path(cfg.training.output_dir) | |
| if dist.rank == 0: | |
| output_dir.mkdir(parents=True, exist_ok=True) | |
| print(f"Config: {PROJECT_ROOT / 'config' / 'config.yaml'}") | |
| print(f"Data: {cfg.datapipe.source.data_dir}") | |
| print(f"Checkpoint directory: {output_dir}") | |
| datapipe = DeepCFDDatapipe(cfg.datapipe, distributed=(dist.world_size > 1)) | |
| train_loader, train_sampler = datapipe.train_dataloader() | |
| test_loader, _ = datapipe.test_dataloader() | |
| loss_weights = datapipe.get_loss_weights().to(device) | |
| model = build_model(cfg.model).to(device) | |
| if dist.world_size > 1: | |
| device_ids = [dist.local_rank] if device.type == "cuda" else None | |
| model = DDP(model, device_ids=device_ids) | |
| optimizer = torch.optim.AdamW( | |
| model.parameters(), | |
| lr=cfg.training.lr, | |
| weight_decay=cfg.training.weight_decay, | |
| ) | |
| best_val_loss = float("inf") | |
| patience_counter = 0 | |
| for epoch in range(cfg.training.num_epochs): | |
| if train_sampler: | |
| train_sampler.set_epoch(epoch) | |
| model.train() | |
| train_loss = 0.0 | |
| iterator = tqdm(train_loader, desc=f"Epoch {epoch}", disable=(dist.rank != 0)) | |
| for batch in iterator: | |
| x = batch["x"].to(device) | |
| y = batch["y"].to(device) | |
| optimizer.zero_grad(set_to_none=True) | |
| output = model(x) | |
| loss = loss_func(output, y, loss_weights) | |
| loss.backward() | |
| optimizer.step() | |
| train_loss += loss.item() | |
| if dist.rank == 0: | |
| iterator.set_postfix({"loss": f"{loss.item():.4e}"}) | |
| if len(train_loader) == 0: | |
| raise RuntimeError("Training loader is empty. Check split_ratio and dataset size.") | |
| avg_train_loss = train_loss / len(train_loader) | |
| if (epoch + 1) % cfg.training.eval_interval == 0: | |
| val_loss, ux_err, uy_err, p_err = evaluate(model, test_loader, device, loss_weights, dist) | |
| if dist.rank == 0: | |
| print(f"Epoch {epoch} | Train Loss: {avg_train_loss:.4e} | Val Loss: {val_loss:.4e}") | |
| print(f"Metrics (Sum Sq Err): Ux={ux_err:.2e}, Uy={uy_err:.2e}, P={p_err:.2e}") | |
| if val_loss < best_val_loss: | |
| best_val_loss = val_loss | |
| patience_counter = 0 | |
| model_to_save = model.module if hasattr(model, "module") else model | |
| ckpt = { | |
| "model_state": model_to_save.state_dict(), | |
| "config": cfg.model.to_dict(), | |
| "epoch": epoch, | |
| "val_loss": val_loss, | |
| } | |
| torch.save(ckpt, output_dir / cfg.training.checkpoint_name) | |
| print(f"Saved best model to {output_dir / cfg.training.checkpoint_name}") | |
| else: | |
| patience_counter += 1 | |
| stop_flag = torch.tensor([0], device=device) | |
| if dist.rank == 0 and patience_counter >= cfg.training.patience: | |
| stop_flag += 1 | |
| if dist.world_size > 1: | |
| torch.distributed.broadcast(stop_flag, src=0) | |
| if stop_flag.item() > 0: | |
| break | |
| dist.cleanup() | |
| if __name__ == "__main__": | |
| main() | |