Upload folder using huggingface_hub
Browse files- README.md +145 -0
- config/config.yaml +45 -0
- configuration.json +1 -0
- model/__init__.py +23 -0
- model/unet.py +44 -0
- model/unetex.py +59 -0
- scripts/fake_data.py +75 -0
- scripts/inference.py +103 -0
- scripts/result.py +41 -0
- scripts/train.py +170 -0
- weight/.gitkeep +1 -0
README.md
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: apache-2.0
|
| 3 |
+
language:
|
| 4 |
+
- en
|
| 5 |
+
tags:
|
| 6 |
+
- OneScience
|
| 7 |
+
- fluid dynamics
|
| 8 |
+
- steady-state laminar flow prediction
|
| 9 |
+
- CFD surrogate modeling
|
| 10 |
+
frameworks: PyTorch
|
| 11 |
+
---
|
| 12 |
+
<p align="center">
|
| 13 |
+
<strong>
|
| 14 |
+
<span style="font-size: 30px;">DeepCFD</span>
|
| 15 |
+
</strong>
|
| 16 |
+
</p>
|
| 17 |
+
|
| 18 |
+
# Model Overview
|
| 19 |
+
DeepCFD is a U-Net-based surrogate model developed by the German Aerospace Center for two-dimensional steady-state laminar flows. It uses geometric information to rapidly predict velocity and pressure fields, accelerating the evaluation of channel flows, flows around obstacles, and aerodynamic designs.
|
| 20 |
+
|
| 21 |
+
Paper: [DeepCFD: Efficient Steady-State Laminar Flow
|
| 22 |
+
Approximation with Deep Convolutional Neural Networks](https://arxiv.org/abs/2004.08826).
|
| 23 |
+
|
| 24 |
+
# Model Description
|
| 25 |
+
DeepCFD uses a U-Net deep convolutional architecture that takes a signed distance field (SDF) and flow-region mask as inputs to rapidly predict velocity and pressure fields for two-dimensional, nonuniform, steady-state laminar flows.
|
| 26 |
+
|
| 27 |
+
## Use Cases
|
| 28 |
+
|
| 29 |
+
| Use Case | Description |
|
| 30 |
+
| ---------- | ---------------------------------- |
|
| 31 |
+
| Steady-state laminar flow prediction | Approximate velocity and pressure fields in two-dimensional, nonuniform, steady-state laminar flows |
|
| 32 |
+
| Channel-flow simulation | Predict flow distributions around randomly shaped obstacles in a channel |
|
| 33 |
+
| CFD surrogate modeling | Replace conventional CFD workflows such as OpenFOAM to improve inference efficiency |
|
| 34 |
+
| Aerodynamic and fluid-shape optimization | Evaluate large numbers of candidate geometries for low-speed laminar-flow design |
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
# Usage
|
| 38 |
+
|
| 39 |
+
## 1. OneCode
|
| 40 |
+
|
| 41 |
+
Use the online OneCode environment for an intelligent, one-click AI for Science (AI4S) programming experience:
|
| 42 |
+
|
| 43 |
+
[Launch OneCode for one-click AI4S programming](https://web-2069360198568017922-iaaj.ksai.scnet.cn:58043/home)
|
| 44 |
+
|
| 45 |
+
## 2. Manual Setup
|
| 46 |
+
|
| 47 |
+
**Hardware Requirements**
|
| 48 |
+
|
| 49 |
+
- A GPU or DCU is recommended.
|
| 50 |
+
- A CPU can be used for import checks and small-scale pipeline validation, but full training and inference will be slow.
|
| 51 |
+
- DCU users must install DTK in advance. DTK 25.04.2 or later, or the OneScience-recommended version for the target cluster, is recommended.
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
### Download the Model Package
|
| 56 |
+
|
| 57 |
+
```bash
|
| 58 |
+
modelscope download --model OneScience/DeepCFD --local_dir ./DeepCFD
|
| 59 |
+
cd DeepCFD
|
| 60 |
+
```
|
| 61 |
+
|
| 62 |
+
### Set Up the Runtime Environment
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
**DCU Environment**
|
| 66 |
+
|
| 67 |
+
```bash
|
| 68 |
+
# Activate DTK and Conda first
|
| 69 |
+
conda create -n onescience311 python=3.11 -y
|
| 70 |
+
conda activate onescience311
|
| 71 |
+
# Installation with uv is also supported
|
| 72 |
+
pip install onescience[cfd-dcu] -i http://mirrors.onescience.ai:3141/pypi/simple/ --trusted-host mirrors.onescience.ai
|
| 73 |
+
```
|
| 74 |
+
|
| 75 |
+
**GPU Environment**
|
| 76 |
+
```bash
|
| 77 |
+
# Activate Conda first
|
| 78 |
+
conda create -n onescience311 python=3.11 -y libstdcxx-ng=12 libgcc-ng=12 gcc_linux-64=12 gxx_linux-64=12
|
| 79 |
+
conda activate onescience311
|
| 80 |
+
# Installation with uv is also supported
|
| 81 |
+
pip install onescience[cfd-gpu] -i http://mirrors.onescience.ai:3141/pypi/simple/ --trusted-host mirrors.onescience.ai
|
| 82 |
+
```
|
| 83 |
+
|
| 84 |
+
### Training Data
|
| 85 |
+
The OneScience community provides the `deepcfd` dataset for training. Download it with the command below and verify that the data path in `conf/config.yaml` is configured correctly:
|
| 86 |
+
|
| 87 |
+
```bash
|
| 88 |
+
modelscope download --dataset OneScience/deepcfd --local_dir ./data
|
| 89 |
+
```
|
| 90 |
+
|
| 91 |
+
The project dataset can also be [downloaded from Zenodo](https://zenodo.org/record/3666056/files/DeepCFD.zip?download=1).
|
| 92 |
+
|
| 93 |
+
The dataset contains two files:
|
| 94 |
+
- `dataX.pkl`: Geometric input data for 981 channel-flow samples
|
| 95 |
+
- `dataY.pkl`: Ground-truth CFD solutions for the corresponding samples, computed with the `simpleFOAM` solver
|
| 96 |
+
|
| 97 |
+
### Training
|
| 98 |
+
|
| 99 |
+
Single GPU:
|
| 100 |
+
|
| 101 |
+
- Training parameters are read from the `root.datapipe`, `root.model`, and `root.training` sections of `config/config.yaml`. The default configuration is intended for a minimal smoke test.
|
| 102 |
+
- For full training, set `root.datapipe.source.data_dir` to the actual DeepCFD dataset directory and increase the model size, batch size, and number of epochs as needed.
|
| 103 |
+
|
| 104 |
+
```bash
|
| 105 |
+
python scripts/train.py
|
| 106 |
+
```
|
| 107 |
+
Multiple GPUs:
|
| 108 |
+
|
| 109 |
+
```bash
|
| 110 |
+
torchrun --standalone --nproc_per_node=<num_GPUs> scripts/train.py
|
| 111 |
+
```
|
| 112 |
+
By default, training saves the best checkpoint to:
|
| 113 |
+
|
| 114 |
+
```text
|
| 115 |
+
./weight/best_model.pt
|
| 116 |
+
```
|
| 117 |
+
|
| 118 |
+
### Model Weights
|
| 119 |
+
|
| 120 |
+
This repository will provide pretrained DeepCFD weights in the `weights/` directory. The weights will be uploaded soon.
|
| 121 |
+
|
| 122 |
+
### Inference
|
| 123 |
+
|
| 124 |
+
```bash
|
| 125 |
+
python scripts/inference.py
|
| 126 |
+
```
|
| 127 |
+
|
| 128 |
+
### Evaluation and Visualization
|
| 129 |
+
|
| 130 |
+
```bash
|
| 131 |
+
python scripts/result.py
|
| 132 |
+
```
|
| 133 |
+
|
| 134 |
+
|
| 135 |
+
# Official OneScience Resources
|
| 136 |
+
|
| 137 |
+
| Platform | OneScience Repository | Skills Repository |
|
| 138 |
+
| --- | --- | --- |
|
| 139 |
+
| Gitee | https://gitee.com/onescience-ai/onescience | https://gitee.com/onescience-ai/oneskills |
|
| 140 |
+
| GitHub | https://github.com/onescience-ai/OneScience | https://github.com/onescience-ai/oneskills |
|
| 141 |
+
|
| 142 |
+
# Citations and License
|
| 143 |
+
|
| 144 |
+
- Original DeepCFD paper: [DeepCFD: Efficient Steady-State Laminar Flow Approximation with Deep Convolutional Neural Networks](https://arxiv.org/abs/2004.08826)
|
| 145 |
+
- This repository retains source attribution and has been adapted for automated execution through OneScience and ModelScope.
|
config/config.yaml
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
root:
|
| 2 |
+
datapipe:
|
| 3 |
+
verbose: false
|
| 4 |
+
source:
|
| 5 |
+
data_dir: "./data/deepcfd"
|
| 6 |
+
data_x_name: "dataX.pkl"
|
| 7 |
+
data_y_name: "dataY.pkl"
|
| 8 |
+
|
| 9 |
+
data:
|
| 10 |
+
split_ratio: 0.75
|
| 11 |
+
seed: 0
|
| 12 |
+
|
| 13 |
+
dataloader:
|
| 14 |
+
batch_size: 1
|
| 15 |
+
num_workers: 0
|
| 16 |
+
|
| 17 |
+
model:
|
| 18 |
+
name: "UNetEx"
|
| 19 |
+
in_channels: 3
|
| 20 |
+
out_channels: 3
|
| 21 |
+
base_channels: 2
|
| 22 |
+
num_stages: 1
|
| 23 |
+
kernel_size: 3
|
| 24 |
+
bilinear: true
|
| 25 |
+
normtype: "none"
|
| 26 |
+
|
| 27 |
+
training:
|
| 28 |
+
output_dir: "./weight"
|
| 29 |
+
checkpoint_name: "best_model.pt"
|
| 30 |
+
num_epochs: 1
|
| 31 |
+
lr: 0.001
|
| 32 |
+
weight_decay: 0.005
|
| 33 |
+
patience: 20
|
| 34 |
+
eval_interval: 1
|
| 35 |
+
|
| 36 |
+
inference:
|
| 37 |
+
checkpoint_path: "./weight/best_model.pt"
|
| 38 |
+
result_dir: "./result/deepcfd"
|
| 39 |
+
num_visualize: 2
|
| 40 |
+
|
| 41 |
+
fake_data:
|
| 42 |
+
num_samples: 4
|
| 43 |
+
height: 16
|
| 44 |
+
width: 16
|
| 45 |
+
seed: 42
|
configuration.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
{"framework":"Pytorch","task":"other"}
|
model/__init__.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .unet import UNet
|
| 2 |
+
from .unetex import UNetEx
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
def build_model(config):
|
| 6 |
+
name = config["name"] if isinstance(config, dict) else config.name
|
| 7 |
+
model_map = {
|
| 8 |
+
"UNet": UNet,
|
| 9 |
+
"UNetEx": UNetEx,
|
| 10 |
+
}
|
| 11 |
+
if name not in model_map:
|
| 12 |
+
raise ValueError(f"Unknown DeepCFD model: {name}")
|
| 13 |
+
|
| 14 |
+
get = config.get if isinstance(config, dict) else lambda key, default=None: getattr(config, key, default)
|
| 15 |
+
return model_map[name](
|
| 16 |
+
in_channels=get("in_channels"),
|
| 17 |
+
out_channels=get("out_channels"),
|
| 18 |
+
base_channels=get("base_channels", 16),
|
| 19 |
+
num_stages=get("num_stages", 2),
|
| 20 |
+
bilinear=get("bilinear", True),
|
| 21 |
+
normtype=get("normtype", "bn"),
|
| 22 |
+
kernel_size=get("kernel_size", 3),
|
| 23 |
+
)
|
model/unet.py
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch.nn as nn
|
| 2 |
+
|
| 3 |
+
from onescience.modules.decoder.unet_decoder import UNetDecoder2D
|
| 4 |
+
from onescience.modules.encoder.unet_encoder import UNetEncoder2D
|
| 5 |
+
from onescience.modules.head.unet_head import UNetHead2D
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
class UNet(nn.Module):
|
| 9 |
+
def __init__(
|
| 10 |
+
self,
|
| 11 |
+
in_channels: int,
|
| 12 |
+
out_channels: int,
|
| 13 |
+
base_channels: int = 16,
|
| 14 |
+
num_stages: int = 2,
|
| 15 |
+
bilinear: bool = True,
|
| 16 |
+
normtype: str = "bn",
|
| 17 |
+
kernel_size: int = 3,
|
| 18 |
+
):
|
| 19 |
+
super().__init__()
|
| 20 |
+
self.encoder = UNetEncoder2D(
|
| 21 |
+
in_channels=in_channels,
|
| 22 |
+
base_channels=base_channels,
|
| 23 |
+
num_stages=num_stages,
|
| 24 |
+
bilinear=bilinear,
|
| 25 |
+
normtype=normtype,
|
| 26 |
+
kernel_size=kernel_size,
|
| 27 |
+
)
|
| 28 |
+
self.decoder = UNetDecoder2D(
|
| 29 |
+
base_channels=base_channels,
|
| 30 |
+
num_stages=num_stages,
|
| 31 |
+
bilinear=bilinear,
|
| 32 |
+
normtype=normtype,
|
| 33 |
+
kernel_size=kernel_size,
|
| 34 |
+
)
|
| 35 |
+
self.head = UNetHead2D(
|
| 36 |
+
in_channels=base_channels,
|
| 37 |
+
out_channels=out_channels,
|
| 38 |
+
kernel_size=1,
|
| 39 |
+
)
|
| 40 |
+
|
| 41 |
+
def forward(self, x):
|
| 42 |
+
features = self.encoder(x)
|
| 43 |
+
decoded = self.decoder(features)
|
| 44 |
+
return self.head(decoded)
|
model/unetex.py
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import torch.nn as nn
|
| 3 |
+
|
| 4 |
+
from onescience.modules.decoder.unet_decoder import UNetDecoder2D
|
| 5 |
+
from onescience.modules.encoder.unet_encoder import UNetEncoder2D
|
| 6 |
+
from onescience.modules.head.unet_head import UNetHead2D
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
class DecoderPath(nn.Module):
|
| 10 |
+
def __init__(self, base_channels, num_stages, bilinear, normtype, kernel_size):
|
| 11 |
+
super().__init__()
|
| 12 |
+
self.decoder = UNetDecoder2D(
|
| 13 |
+
base_channels=base_channels,
|
| 14 |
+
num_stages=num_stages,
|
| 15 |
+
bilinear=bilinear,
|
| 16 |
+
normtype=normtype,
|
| 17 |
+
kernel_size=kernel_size,
|
| 18 |
+
)
|
| 19 |
+
self.head = UNetHead2D(
|
| 20 |
+
in_channels=base_channels,
|
| 21 |
+
out_channels=1,
|
| 22 |
+
kernel_size=1,
|
| 23 |
+
)
|
| 24 |
+
|
| 25 |
+
def forward(self, features):
|
| 26 |
+
return self.head(self.decoder(features))
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
class UNetEx(nn.Module):
|
| 30 |
+
def __init__(
|
| 31 |
+
self,
|
| 32 |
+
in_channels: int,
|
| 33 |
+
out_channels: int,
|
| 34 |
+
base_channels: int = 16,
|
| 35 |
+
num_stages: int = 2,
|
| 36 |
+
bilinear: bool = True,
|
| 37 |
+
normtype: str = "bn",
|
| 38 |
+
kernel_size: int = 3,
|
| 39 |
+
):
|
| 40 |
+
super().__init__()
|
| 41 |
+
self.encoder = UNetEncoder2D(
|
| 42 |
+
in_channels=in_channels,
|
| 43 |
+
base_channels=base_channels,
|
| 44 |
+
num_stages=num_stages,
|
| 45 |
+
bilinear=bilinear,
|
| 46 |
+
normtype=normtype,
|
| 47 |
+
kernel_size=kernel_size,
|
| 48 |
+
)
|
| 49 |
+
self.decoders = nn.ModuleList(
|
| 50 |
+
[
|
| 51 |
+
DecoderPath(base_channels, num_stages, bilinear, normtype, kernel_size)
|
| 52 |
+
for _ in range(out_channels)
|
| 53 |
+
]
|
| 54 |
+
)
|
| 55 |
+
|
| 56 |
+
def forward(self, x):
|
| 57 |
+
features = self.encoder(x)
|
| 58 |
+
outputs = [decoder_path(features) for decoder_path in self.decoders]
|
| 59 |
+
return torch.cat(outputs, dim=1)
|
scripts/fake_data.py
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pickle
|
| 2 |
+
import sys
|
| 3 |
+
from pathlib import Path
|
| 4 |
+
|
| 5 |
+
import numpy as np
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
PROJECT_ROOT = Path(__file__).resolve().parents[1]
|
| 9 |
+
sys.path.insert(0, str(PROJECT_ROOT))
|
| 10 |
+
|
| 11 |
+
from onescience.utils.YParams import YParams
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def resolve_path(path_value):
|
| 15 |
+
path = Path(path_value)
|
| 16 |
+
return path if path.is_absolute() else PROJECT_ROOT / path
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
def make_fields(num_samples, height, width, seed):
|
| 20 |
+
rng = np.random.default_rng(seed)
|
| 21 |
+
y_axis = np.linspace(-1.0, 1.0, height, dtype=np.float32)
|
| 22 |
+
x_axis = np.linspace(-1.0, 1.0, width, dtype=np.float32)
|
| 23 |
+
yy, xx = np.meshgrid(y_axis, x_axis, indexing="ij")
|
| 24 |
+
|
| 25 |
+
x = np.empty((num_samples, 3, height, width), dtype=np.float32)
|
| 26 |
+
target = np.empty_like(x)
|
| 27 |
+
|
| 28 |
+
for i in range(num_samples):
|
| 29 |
+
cx = 0.2 * np.sin(i)
|
| 30 |
+
cy = 0.2 * np.cos(i * 0.7)
|
| 31 |
+
radius = 0.25 + 0.03 * (i % 3)
|
| 32 |
+
distance = np.sqrt((xx - cx) ** 2 + (yy - cy) ** 2) - radius
|
| 33 |
+
obstacle = (distance < 0.0).astype(np.float32)
|
| 34 |
+
inlet = np.ones_like(xx, dtype=np.float32) * (1.0 + 0.05 * i)
|
| 35 |
+
|
| 36 |
+
x[i, 0] = obstacle
|
| 37 |
+
x[i, 1] = distance
|
| 38 |
+
x[i, 2] = inlet
|
| 39 |
+
|
| 40 |
+
ux = inlet * (1.0 - obstacle) + 0.1 * np.sin(np.pi * yy)
|
| 41 |
+
uy = -0.2 * yy * np.exp(-4.0 * np.maximum(distance, 0.0))
|
| 42 |
+
pressure = 0.5 * (1.0 - xx) + 0.1 * obstacle
|
| 43 |
+
|
| 44 |
+
noise = 0.005 * rng.standard_normal((3, height, width)).astype(np.float32)
|
| 45 |
+
target[i, 0] = ux
|
| 46 |
+
target[i, 1] = uy
|
| 47 |
+
target[i, 2] = pressure
|
| 48 |
+
target[i] += noise
|
| 49 |
+
|
| 50 |
+
return x, target
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
def main():
|
| 54 |
+
cfg = YParams(str(PROJECT_ROOT / "config" / "config.yaml"), "root")
|
| 55 |
+
data_dir = resolve_path(cfg.datapipe.source.data_dir)
|
| 56 |
+
data_dir.mkdir(parents=True, exist_ok=True)
|
| 57 |
+
|
| 58 |
+
x, y = make_fields(
|
| 59 |
+
num_samples=cfg.fake_data.num_samples,
|
| 60 |
+
height=cfg.fake_data.height,
|
| 61 |
+
width=cfg.fake_data.width,
|
| 62 |
+
seed=cfg.fake_data.seed,
|
| 63 |
+
)
|
| 64 |
+
|
| 65 |
+
with open(data_dir / cfg.datapipe.source.data_x_name, "wb") as f:
|
| 66 |
+
pickle.dump(x, f)
|
| 67 |
+
with open(data_dir / cfg.datapipe.source.data_y_name, "wb") as f:
|
| 68 |
+
pickle.dump(y, f)
|
| 69 |
+
|
| 70 |
+
print(f"Fake DeepCFD data written to {data_dir}")
|
| 71 |
+
print(f"x shape={x.shape}, y shape={y.shape}, dtype={x.dtype}")
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
if __name__ == "__main__":
|
| 75 |
+
main()
|
scripts/inference.py
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import sys
|
| 3 |
+
import importlib.util
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
|
| 6 |
+
os.environ.setdefault("MPLBACKEND", "Agg")
|
| 7 |
+
|
| 8 |
+
import numpy as np
|
| 9 |
+
import torch
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
PROJECT_ROOT = Path(__file__).resolve().parents[1]
|
| 13 |
+
sys.path.insert(0, str(PROJECT_ROOT))
|
| 14 |
+
|
| 15 |
+
from model import build_model
|
| 16 |
+
from onescience.distributed.manager import DistributedManager
|
| 17 |
+
from onescience.utils.YParams import YParams
|
| 18 |
+
from onescience.utils.deepcfd.functions import visualize
|
| 19 |
+
import onescience
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def resolve_path(path_value):
|
| 23 |
+
path = Path(path_value)
|
| 24 |
+
return path if path.is_absolute() else PROJECT_ROOT / path
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
def load_config():
|
| 28 |
+
cfg = YParams(str(PROJECT_ROOT / "config" / "config.yaml"), "root")
|
| 29 |
+
cfg.datapipe.source.data_dir = str(resolve_path(cfg.datapipe.source.data_dir))
|
| 30 |
+
cfg.inference.checkpoint_path = str(resolve_path(cfg.inference.checkpoint_path))
|
| 31 |
+
cfg.inference.result_dir = str(resolve_path(cfg.inference.result_dir))
|
| 32 |
+
return cfg
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
def load_deepcfd_datapipe_class():
|
| 36 |
+
runtime_root = Path(onescience.__file__).resolve().parent
|
| 37 |
+
datapipe_file = runtime_root / "datapipes" / "cfd" / "deepcfd.py"
|
| 38 |
+
spec = importlib.util.spec_from_file_location("_onescience_deepcfd_datapipe", datapipe_file)
|
| 39 |
+
if spec is None or spec.loader is None:
|
| 40 |
+
raise ImportError(f"Cannot load DeepCFD datapipe from {datapipe_file}")
|
| 41 |
+
module = importlib.util.module_from_spec(spec)
|
| 42 |
+
sys.modules[spec.name] = module
|
| 43 |
+
spec.loader.exec_module(module)
|
| 44 |
+
return module.DeepCFDDatapipe
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
def main():
|
| 48 |
+
DistributedManager.initialize()
|
| 49 |
+
dist = DistributedManager()
|
| 50 |
+
device = dist.device
|
| 51 |
+
cfg = load_config()
|
| 52 |
+
DeepCFDDatapipe = load_deepcfd_datapipe_class()
|
| 53 |
+
|
| 54 |
+
checkpoint_path = Path(cfg.inference.checkpoint_path)
|
| 55 |
+
if not checkpoint_path.exists():
|
| 56 |
+
raise FileNotFoundError(f"Checkpoint not found: {checkpoint_path}")
|
| 57 |
+
|
| 58 |
+
checkpoint = torch.load(checkpoint_path, map_location=device)
|
| 59 |
+
model_config = checkpoint.get("config", cfg.model.to_dict())
|
| 60 |
+
model = build_model(model_config).to(device)
|
| 61 |
+
model.load_state_dict(checkpoint["model_state"])
|
| 62 |
+
model.eval()
|
| 63 |
+
|
| 64 |
+
datapipe = DeepCFDDatapipe(cfg.datapipe, distributed=False)
|
| 65 |
+
test_loader, _ = datapipe.test_dataloader()
|
| 66 |
+
batch = next(iter(test_loader))
|
| 67 |
+
|
| 68 |
+
x = batch["x"].to(device)
|
| 69 |
+
y = batch["y"].to(device)
|
| 70 |
+
with torch.no_grad():
|
| 71 |
+
out = model(x)
|
| 72 |
+
|
| 73 |
+
error = torch.abs(out.cpu() - y.cpu())
|
| 74 |
+
mse = torch.mean((out.cpu() - y.cpu()) ** 2, dim=(0, 2, 3)).numpy()
|
| 75 |
+
mae = torch.mean(error, dim=(0, 2, 3)).numpy()
|
| 76 |
+
|
| 77 |
+
result_dir = Path(cfg.inference.result_dir)
|
| 78 |
+
vis_dir = result_dir / "vis_results"
|
| 79 |
+
pred_dir = result_dir / "predictions"
|
| 80 |
+
vis_dir.mkdir(parents=True, exist_ok=True)
|
| 81 |
+
pred_dir.mkdir(parents=True, exist_ok=True)
|
| 82 |
+
|
| 83 |
+
np.save(pred_dir / "prediction_batch.npy", out.cpu().numpy())
|
| 84 |
+
np.save(pred_dir / "target_batch.npy", y.cpu().numpy())
|
| 85 |
+
np.save(pred_dir / "absolute_error_batch.npy", error.numpy())
|
| 86 |
+
|
| 87 |
+
y_np = y.cpu().numpy()
|
| 88 |
+
out_np = out.cpu().numpy()
|
| 89 |
+
err_np = error.numpy()
|
| 90 |
+
for i in range(min(cfg.inference.num_visualize, x.shape[0])):
|
| 91 |
+
visualize(y_np, out_np, err_np, i, save_dir=str(vis_dir))
|
| 92 |
+
|
| 93 |
+
if dist.rank == 0:
|
| 94 |
+
print(f"Checkpoint: {checkpoint_path}")
|
| 95 |
+
print(f"MSE per channel [Ux, Uy, p]: {mse}")
|
| 96 |
+
print(f"MAE per channel [Ux, Uy, p]: {mae}")
|
| 97 |
+
print(f"Results saved to {result_dir}")
|
| 98 |
+
|
| 99 |
+
dist.cleanup()
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
if __name__ == "__main__":
|
| 103 |
+
main()
|
scripts/result.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import sys
|
| 2 |
+
from pathlib import Path
|
| 3 |
+
|
| 4 |
+
import numpy as np
|
| 5 |
+
import torch
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
PROJECT_ROOT = Path(__file__).resolve().parents[1]
|
| 9 |
+
sys.path.insert(0, str(PROJECT_ROOT))
|
| 10 |
+
|
| 11 |
+
from onescience.utils.YParams import YParams
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def resolve_path(path_value):
|
| 15 |
+
path = Path(path_value)
|
| 16 |
+
return path if path.is_absolute() else PROJECT_ROOT / path
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
def main():
|
| 20 |
+
cfg = YParams(str(PROJECT_ROOT / "config" / "config.yaml"), "root")
|
| 21 |
+
checkpoint_path = resolve_path(cfg.inference.checkpoint_path)
|
| 22 |
+
pred_dir = resolve_path(cfg.inference.result_dir) / "predictions"
|
| 23 |
+
|
| 24 |
+
if checkpoint_path.exists():
|
| 25 |
+
ckpt = torch.load(checkpoint_path, map_location="cpu")
|
| 26 |
+
print(f"Checkpoint: {checkpoint_path}")
|
| 27 |
+
print(f"Epoch: {ckpt.get('epoch')}, val_loss: {ckpt.get('val_loss')}")
|
| 28 |
+
print(f"Model config: {ckpt.get('config')}")
|
| 29 |
+
else:
|
| 30 |
+
print(f"Checkpoint not found: {checkpoint_path}")
|
| 31 |
+
|
| 32 |
+
pred_path = pred_dir / "prediction_batch.npy"
|
| 33 |
+
if pred_path.exists():
|
| 34 |
+
pred = np.load(pred_path)
|
| 35 |
+
print(f"Prediction batch: shape={pred.shape}, dtype={pred.dtype}, mean={pred.mean():.6f}")
|
| 36 |
+
else:
|
| 37 |
+
print(f"Prediction batch not found: {pred_path}")
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
if __name__ == "__main__":
|
| 41 |
+
main()
|
scripts/train.py
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import sys
|
| 2 |
+
import importlib.util
|
| 3 |
+
from pathlib import Path
|
| 4 |
+
|
| 5 |
+
import torch
|
| 6 |
+
from torch.nn.parallel import DistributedDataParallel as DDP
|
| 7 |
+
from tqdm import tqdm
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
PROJECT_ROOT = Path(__file__).resolve().parents[1]
|
| 11 |
+
sys.path.insert(0, str(PROJECT_ROOT))
|
| 12 |
+
|
| 13 |
+
from model import build_model
|
| 14 |
+
from onescience.distributed.manager import DistributedManager
|
| 15 |
+
from onescience.utils.YParams import YParams
|
| 16 |
+
import onescience
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
def resolve_path(path_value):
|
| 20 |
+
path = Path(path_value)
|
| 21 |
+
return path if path.is_absolute() else PROJECT_ROOT / path
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
def load_config():
|
| 25 |
+
cfg = YParams(str(PROJECT_ROOT / "config" / "config.yaml"), "root")
|
| 26 |
+
cfg.datapipe.source.data_dir = str(resolve_path(cfg.datapipe.source.data_dir))
|
| 27 |
+
cfg.training.output_dir = str(resolve_path(cfg.training.output_dir))
|
| 28 |
+
return cfg
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def load_deepcfd_datapipe_class():
|
| 32 |
+
runtime_root = Path(onescience.__file__).resolve().parent
|
| 33 |
+
datapipe_file = runtime_root / "datapipes" / "cfd" / "deepcfd.py"
|
| 34 |
+
spec = importlib.util.spec_from_file_location("_onescience_deepcfd_datapipe", datapipe_file)
|
| 35 |
+
if spec is None or spec.loader is None:
|
| 36 |
+
raise ImportError(f"Cannot load DeepCFD datapipe from {datapipe_file}")
|
| 37 |
+
module = importlib.util.module_from_spec(spec)
|
| 38 |
+
sys.modules[spec.name] = module
|
| 39 |
+
spec.loader.exec_module(module)
|
| 40 |
+
return module.DeepCFDDatapipe
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
def loss_func(output, target, weights):
|
| 44 |
+
lossu = (output[:, 0] - target[:, 0]) ** 2
|
| 45 |
+
lossv = (output[:, 1] - target[:, 1]) ** 2
|
| 46 |
+
lossp = torch.abs(output[:, 2] - target[:, 2])
|
| 47 |
+
loss_stack = torch.stack([lossu, lossv, lossp], dim=1)
|
| 48 |
+
return torch.sum(loss_stack / weights)
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
def evaluate(model, loader, device, weights, dist):
|
| 52 |
+
model.eval()
|
| 53 |
+
total_loss = 0.0
|
| 54 |
+
total_ux_mse = 0.0
|
| 55 |
+
total_uy_mse = 0.0
|
| 56 |
+
total_p_mse = 0.0
|
| 57 |
+
num_batches = 0
|
| 58 |
+
|
| 59 |
+
with torch.no_grad():
|
| 60 |
+
iterator = tqdm(loader, desc="Evaluating", disable=(dist.rank != 0))
|
| 61 |
+
for batch in iterator:
|
| 62 |
+
x = batch["x"].to(device)
|
| 63 |
+
y = batch["y"].to(device)
|
| 64 |
+
output = model(x)
|
| 65 |
+
|
| 66 |
+
total_loss += loss_func(output, y, weights).item()
|
| 67 |
+
total_ux_mse += torch.sum((output[:, 0] - y[:, 0]) ** 2).item()
|
| 68 |
+
total_uy_mse += torch.sum((output[:, 1] - y[:, 1]) ** 2).item()
|
| 69 |
+
total_p_mse += torch.sum((output[:, 2] - y[:, 2]) ** 2).item()
|
| 70 |
+
num_batches += 1
|
| 71 |
+
|
| 72 |
+
if num_batches == 0:
|
| 73 |
+
raise RuntimeError("Evaluation loader is empty. Check split_ratio and dataset size.")
|
| 74 |
+
return total_loss / num_batches, total_ux_mse, total_uy_mse, total_p_mse
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
def main():
|
| 78 |
+
DistributedManager.initialize()
|
| 79 |
+
dist = DistributedManager()
|
| 80 |
+
device = dist.device
|
| 81 |
+
cfg = load_config()
|
| 82 |
+
DeepCFDDatapipe = load_deepcfd_datapipe_class()
|
| 83 |
+
|
| 84 |
+
output_dir = Path(cfg.training.output_dir)
|
| 85 |
+
if dist.rank == 0:
|
| 86 |
+
output_dir.mkdir(parents=True, exist_ok=True)
|
| 87 |
+
print(f"Config: {PROJECT_ROOT / 'config' / 'config.yaml'}")
|
| 88 |
+
print(f"Data: {cfg.datapipe.source.data_dir}")
|
| 89 |
+
print(f"Checkpoint directory: {output_dir}")
|
| 90 |
+
|
| 91 |
+
datapipe = DeepCFDDatapipe(cfg.datapipe, distributed=(dist.world_size > 1))
|
| 92 |
+
train_loader, train_sampler = datapipe.train_dataloader()
|
| 93 |
+
test_loader, _ = datapipe.test_dataloader()
|
| 94 |
+
loss_weights = datapipe.get_loss_weights().to(device)
|
| 95 |
+
|
| 96 |
+
model = build_model(cfg.model).to(device)
|
| 97 |
+
if dist.world_size > 1:
|
| 98 |
+
device_ids = [dist.local_rank] if device.type == "cuda" else None
|
| 99 |
+
model = DDP(model, device_ids=device_ids)
|
| 100 |
+
|
| 101 |
+
optimizer = torch.optim.AdamW(
|
| 102 |
+
model.parameters(),
|
| 103 |
+
lr=cfg.training.lr,
|
| 104 |
+
weight_decay=cfg.training.weight_decay,
|
| 105 |
+
)
|
| 106 |
+
|
| 107 |
+
best_val_loss = float("inf")
|
| 108 |
+
patience_counter = 0
|
| 109 |
+
|
| 110 |
+
for epoch in range(cfg.training.num_epochs):
|
| 111 |
+
if train_sampler:
|
| 112 |
+
train_sampler.set_epoch(epoch)
|
| 113 |
+
|
| 114 |
+
model.train()
|
| 115 |
+
train_loss = 0.0
|
| 116 |
+
iterator = tqdm(train_loader, desc=f"Epoch {epoch}", disable=(dist.rank != 0))
|
| 117 |
+
|
| 118 |
+
for batch in iterator:
|
| 119 |
+
x = batch["x"].to(device)
|
| 120 |
+
y = batch["y"].to(device)
|
| 121 |
+
|
| 122 |
+
optimizer.zero_grad(set_to_none=True)
|
| 123 |
+
output = model(x)
|
| 124 |
+
loss = loss_func(output, y, loss_weights)
|
| 125 |
+
loss.backward()
|
| 126 |
+
optimizer.step()
|
| 127 |
+
|
| 128 |
+
train_loss += loss.item()
|
| 129 |
+
if dist.rank == 0:
|
| 130 |
+
iterator.set_postfix({"loss": f"{loss.item():.4e}"})
|
| 131 |
+
|
| 132 |
+
if len(train_loader) == 0:
|
| 133 |
+
raise RuntimeError("Training loader is empty. Check split_ratio and dataset size.")
|
| 134 |
+
avg_train_loss = train_loss / len(train_loader)
|
| 135 |
+
|
| 136 |
+
if (epoch + 1) % cfg.training.eval_interval == 0:
|
| 137 |
+
val_loss, ux_err, uy_err, p_err = evaluate(model, test_loader, device, loss_weights, dist)
|
| 138 |
+
|
| 139 |
+
if dist.rank == 0:
|
| 140 |
+
print(f"Epoch {epoch} | Train Loss: {avg_train_loss:.4e} | Val Loss: {val_loss:.4e}")
|
| 141 |
+
print(f"Metrics (Sum Sq Err): Ux={ux_err:.2e}, Uy={uy_err:.2e}, P={p_err:.2e}")
|
| 142 |
+
|
| 143 |
+
if val_loss < best_val_loss:
|
| 144 |
+
best_val_loss = val_loss
|
| 145 |
+
patience_counter = 0
|
| 146 |
+
model_to_save = model.module if hasattr(model, "module") else model
|
| 147 |
+
ckpt = {
|
| 148 |
+
"model_state": model_to_save.state_dict(),
|
| 149 |
+
"config": cfg.model.to_dict(),
|
| 150 |
+
"epoch": epoch,
|
| 151 |
+
"val_loss": val_loss,
|
| 152 |
+
}
|
| 153 |
+
torch.save(ckpt, output_dir / cfg.training.checkpoint_name)
|
| 154 |
+
print(f"Saved best model to {output_dir / cfg.training.checkpoint_name}")
|
| 155 |
+
else:
|
| 156 |
+
patience_counter += 1
|
| 157 |
+
|
| 158 |
+
stop_flag = torch.tensor([0], device=device)
|
| 159 |
+
if dist.rank == 0 and patience_counter >= cfg.training.patience:
|
| 160 |
+
stop_flag += 1
|
| 161 |
+
if dist.world_size > 1:
|
| 162 |
+
torch.distributed.broadcast(stop_flag, src=0)
|
| 163 |
+
if stop_flag.item() > 0:
|
| 164 |
+
break
|
| 165 |
+
|
| 166 |
+
dist.cleanup()
|
| 167 |
+
|
| 168 |
+
|
| 169 |
+
if __name__ == "__main__":
|
| 170 |
+
main()
|
weight/.gitkeep
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
|