DeepCFD / scripts /train.py
OneScience's picture
Upload folder using huggingface_hub
8880eca verified
Raw
History Blame
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()