Upload folder using huggingface_hub
Browse files- README.md +136 -0
- config/config.yaml +63 -0
- configuration.json +1 -0
- model/meshgraphnet.py +306 -0
- scripts/fake_data.py +167 -0
- scripts/inference.py +92 -0
- scripts/result.py +30 -0
- scripts/train.py +154 -0
README.md
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
frameworks:
|
| 3 |
+
- ""
|
| 4 |
+
language:
|
| 5 |
+
- en
|
| 6 |
+
license: apache-2.0
|
| 7 |
+
tags:
|
| 8 |
+
- OneScience
|
| 9 |
+
- fluid dynamics
|
| 10 |
+
- external flow prediction
|
| 11 |
+
- unstructured-mesh simulation
|
| 12 |
+
|
| 13 |
+
---
|
| 14 |
+
<p align="center">
|
| 15 |
+
<strong>
|
| 16 |
+
<span style="font-size: 30px;">MeshGraphNet</span>
|
| 17 |
+
</strong>
|
| 18 |
+
</p>
|
| 19 |
+
|
| 20 |
+
# Model Overview
|
| 21 |
+
|
| 22 |
+
MeshGraphNets is a graph neural network developed by DeepMind for mesh-based physical simulation. It rapidly predicts the dynamics of complex physical systems, including fluids, structures, and cloth.
|
| 23 |
+
|
| 24 |
+
Paper: Learning Mesh-Based Simulation with Graph Networks
|
| 25 |
+
https://arxiv.org/abs/2010.03409
|
| 26 |
+
|
| 27 |
+
# Model Description
|
| 28 |
+
MeshGraphNets uses an encoder–processor–decoder graph-network architecture trained on trajectories from fluid, structural, and cloth simulations to perform long-horizon dynamical simulation of complex physical systems.
|
| 29 |
+
|
| 30 |
+
## Use Cases
|
| 31 |
+
|
| 32 |
+
| Use Case | Description |
|
| 33 |
+
|---|---|
|
| 34 |
+
| External flow prediction | Predict velocity, pressure, and other flow variables at mesh nodes |
|
| 35 |
+
| Structural deformation simulation | Predict the displacement, stress, and deformation of loaded structures |
|
| 36 |
+
| Cloth dynamics | Simulate the motion of deformable objects such as flexible membranes and cloth |
|
| 37 |
+
| ModelScope/OneCode execution | Download the standalone model package, install its dependencies, and run the provided scripts |
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
# Usage
|
| 41 |
+
|
| 42 |
+
## 1. OneCode
|
| 43 |
+
|
| 44 |
+
Use the online OneCode environment for an intelligent, one-click AI for Science (AI4S) programming experience:
|
| 45 |
+
|
| 46 |
+
[Launch OneCode for one-click AI4S programming](https://web-2069360198568017922-iaaj.ksai.scnet.cn:58043/home)
|
| 47 |
+
|
| 48 |
+
## 2. Manual Setup
|
| 49 |
+
|
| 50 |
+
**Hardware Requirements**
|
| 51 |
+
|
| 52 |
+
- A GPU or DCU is recommended.
|
| 53 |
+
- DCU users must install DTK in advance. DTK 25.04.2 or later, or the OneScience-recommended version for the target cluster, is recommended.
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
### Download the Model Package
|
| 57 |
+
|
| 58 |
+
```bash
|
| 59 |
+
modelscope download --model OneScience/MeshGraphNet --local_dir ./MeshGraphNet
|
| 60 |
+
cd MeshGraphNet
|
| 61 |
+
```
|
| 62 |
+
|
| 63 |
+
### Set Up the Runtime Environment
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
**DCU Environment**
|
| 67 |
+
|
| 68 |
+
```bash
|
| 69 |
+
# Activate DTK and Conda first
|
| 70 |
+
conda create -n onescience311 python=3.11 -y
|
| 71 |
+
conda activate onescience311
|
| 72 |
+
# Installation with uv is also supported
|
| 73 |
+
pip install onescience[cfd-dcu] -i http://mirrors.onescience.ai:3141/pypi/simple/ --trusted-host mirrors.onescience.ai
|
| 74 |
+
```
|
| 75 |
+
|
| 76 |
+
**GPU Environment**
|
| 77 |
+
```bash
|
| 78 |
+
# Activate Conda first
|
| 79 |
+
conda create -n onescience311 python=3.11 -y libstdcxx-ng=12 libgcc-ng=12 gcc_linux-64=12 gxx_linux-64=12
|
| 80 |
+
conda activate onescience311
|
| 81 |
+
# Installation with uv is also supported
|
| 82 |
+
pip install onescience[cfd-gpu] -i http://mirrors.onescience.ai:3141/pypi/simple/ --trusted-host mirrors.onescience.ai
|
| 83 |
+
```
|
| 84 |
+
|
| 85 |
+
### Training Data
|
| 86 |
+
|
| 87 |
+
The OneScience community provides the `cylinder_flow` dataset for training. Download it with the command below and verify that the data path in `config/config.yaml` is configured correctly:
|
| 88 |
+
|
| 89 |
+
```bash
|
| 90 |
+
modelscope download --dataset OneScience/cylinder_flow --local_dir ./data
|
| 91 |
+
```
|
| 92 |
+
|
| 93 |
+
### Training
|
| 94 |
+
|
| 95 |
+
Single GPU:
|
| 96 |
+
|
| 97 |
+
```bash
|
| 98 |
+
python scripts/train.py
|
| 99 |
+
```
|
| 100 |
+
|
| 101 |
+
Multiple GPUs:
|
| 102 |
+
|
| 103 |
+
```bash
|
| 104 |
+
torchrun --nproc_per_node=8 --nnodes=1 --rdzv_id=1000 --rdzv_backend=c10d --max_restarts=0 --master_addr="localhost" --master_port=29500 scripts/train.py
|
| 105 |
+
```
|
| 106 |
+
|
| 107 |
+
Training saves `.pth` files under `weight/checkpoints`.
|
| 108 |
+
|
| 109 |
+
### Model Weights
|
| 110 |
+
This repository will provide weights trained on the `cylinder_flow` dataset in the `weights/` directory. The weights will be uploaded soon.
|
| 111 |
+
|
| 112 |
+
### Inference
|
| 113 |
+
|
| 114 |
+
```bash
|
| 115 |
+
python scripts/inference.py
|
| 116 |
+
```
|
| 117 |
+
|
| 118 |
+
Inference results are saved to `result/output/`.
|
| 119 |
+
|
| 120 |
+
### Evaluation and Visualization
|
| 121 |
+
|
| 122 |
+
```bash
|
| 123 |
+
python scripts/result.py
|
| 124 |
+
```
|
| 125 |
+
|
| 126 |
+
|
| 127 |
+
# Official OneScience Resources
|
| 128 |
+
|
| 129 |
+
| Platform | OneScience Repository | Skills Repository |
|
| 130 |
+
| --- | --- | --- |
|
| 131 |
+
| Gitee | https://gitee.com/onescience-ai/onescience | https://gitee.com/onescience-ai/oneskills |
|
| 132 |
+
| GitHub | https://github.com/onescience-ai/OneScience | https://github.com/onescience-ai/oneskills |
|
| 133 |
+
|
| 134 |
+
# Citations and License
|
| 135 |
+
- Original MeshGraphNet paper: [Learning Mesh-Based Simulation with Graph Networks](https://arxiv.org/abs/2010.03409).
|
| 136 |
+
- This repository retains source attribution and has been adapted for automated execution through OneScience and ModelScope.
|
config/config.yaml
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
paths:
|
| 2 |
+
data_dir: data/cylinder_flow
|
| 3 |
+
fake_data_path: data/cylinder_flow/fake_cylinder_flow.pt
|
| 4 |
+
checkpoint_dir: weight/checkpoints
|
| 5 |
+
result_dir: results
|
| 6 |
+
|
| 7 |
+
datapipe:
|
| 8 |
+
source:
|
| 9 |
+
data_dir: data/cylinder_flow
|
| 10 |
+
stats_dir: data/cylinder_flow/stats
|
| 11 |
+
fake_data: true
|
| 12 |
+
fake_data_path: data/cylinder_flow/fake_cylinder_flow.pt
|
| 13 |
+
verbose: true
|
| 14 |
+
data:
|
| 15 |
+
train_samples: 4
|
| 16 |
+
train_steps: 5
|
| 17 |
+
val_samples: 2
|
| 18 |
+
val_steps: 5
|
| 19 |
+
test_samples: 2
|
| 20 |
+
test_steps: 5
|
| 21 |
+
noise_std: 0.0
|
| 22 |
+
dataloader:
|
| 23 |
+
batch_size: 1
|
| 24 |
+
num_workers: 0
|
| 25 |
+
|
| 26 |
+
model:
|
| 27 |
+
name: MeshGraphNet
|
| 28 |
+
specific_params:
|
| 29 |
+
MeshGraphNet:
|
| 30 |
+
num_input_features: 6
|
| 31 |
+
num_edge_features: 3
|
| 32 |
+
num_output_features: 3
|
| 33 |
+
processor_size: 2
|
| 34 |
+
hidden_dim_processor: 32
|
| 35 |
+
num_layers_node_processor: 2
|
| 36 |
+
num_layers_edge_processor: 2
|
| 37 |
+
hidden_dim_node_encoder: 32
|
| 38 |
+
hidden_dim_edge_encoder: 32
|
| 39 |
+
hidden_dim_node_decoder: 32
|
| 40 |
+
do_concat_trick: false
|
| 41 |
+
num_processor_checkpoint_segments: 0
|
| 42 |
+
recompute_activation: false
|
| 43 |
+
|
| 44 |
+
training:
|
| 45 |
+
device: cpu
|
| 46 |
+
max_epoch: 1
|
| 47 |
+
lr: 0.0001
|
| 48 |
+
lr_decay_rate: 0.999
|
| 49 |
+
amp: false
|
| 50 |
+
jit: false
|
| 51 |
+
checkpoint_dir: weight/checkpoints
|
| 52 |
+
loss_criterion: MSE
|
| 53 |
+
patience: 10
|
| 54 |
+
log_interval: 1
|
| 55 |
+
gpuid: 0
|
| 56 |
+
|
| 57 |
+
inference:
|
| 58 |
+
device: cpu
|
| 59 |
+
checkpoint_dir: weight/checkpoints
|
| 60 |
+
output_path: results/inference_results.npz
|
| 61 |
+
viz_vars: ["u", "v", "p"]
|
| 62 |
+
frame_skip: 1
|
| 63 |
+
frame_interval: 1
|
configuration.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
{"framework":"Pytorch","task":"other","model":"MeshGraphNet","domain":"cfd"}
|
model/meshgraphnet.py
ADDED
|
@@ -0,0 +1,306 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import torch.nn as nn
|
| 3 |
+
from torch import Tensor
|
| 4 |
+
|
| 5 |
+
try:
|
| 6 |
+
import dgl # noqa: F401 for docs
|
| 7 |
+
from dgl import DGLGraph
|
| 8 |
+
except ImportError:
|
| 9 |
+
raise ImportError(
|
| 10 |
+
"Mesh Graph Net requires the DGL library. Install the "
|
| 11 |
+
)
|
| 12 |
+
from dataclasses import dataclass
|
| 13 |
+
from itertools import chain
|
| 14 |
+
from typing import Callable, List, Tuple, Union
|
| 15 |
+
|
| 16 |
+
import onescience # noqa: F401 for docs
|
| 17 |
+
from onescience.modules.edge.mesh_edge_block import MeshEdgeBlock
|
| 18 |
+
from onescience.modules.mlp.mesh_graph_mlp import MeshGraphMLP
|
| 19 |
+
from onescience.modules.node.mesh_node_block import MeshNodeBlock
|
| 20 |
+
|
| 21 |
+
from onescience.modules.utils.gnnlayer_utils import CuGraphCSC, set_checkpoint_fn
|
| 22 |
+
from onescience.modules.layer.activations import get_activation
|
| 23 |
+
from onescience.modules.meta import ModelMetaData
|
| 24 |
+
from onescience.modules.module import Module
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
@dataclass
|
| 28 |
+
class MetaData(ModelMetaData):
|
| 29 |
+
name: str = "MeshGraphNet"
|
| 30 |
+
# Optimization, no JIT as DGLGraph causes trouble
|
| 31 |
+
jit: bool = False
|
| 32 |
+
cuda_graphs: bool = False
|
| 33 |
+
amp_cpu: bool = False
|
| 34 |
+
amp_gpu: bool = True
|
| 35 |
+
torch_fx: bool = False
|
| 36 |
+
# Inference
|
| 37 |
+
onnx: bool = False
|
| 38 |
+
# Physics informed
|
| 39 |
+
func_torch: bool = True
|
| 40 |
+
auto_grad: bool = True
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
class MeshGraphNet(Module):
|
| 44 |
+
"""
|
| 45 |
+
MeshGraphNet 网络架构。
|
| 46 |
+
|
| 47 |
+
该模型基于 "Learning mesh-based simulation with graph networks" (Pfaff et al., 2020) 实现。
|
| 48 |
+
它采用 Encode-Process-Decode 架构:
|
| 49 |
+
1. **Encoder**: 将节点和边的物理特征映射到高维隐空间。
|
| 50 |
+
2. **Processor**: 通过多层消息传递(Message Passing)在图中传播信息,更新节点和边的隐状态。
|
| 51 |
+
3. **Decoder**: 将处理后的节点特征解码回物理空间(例如加速度或速度增量)。
|
| 52 |
+
|
| 53 |
+
本实现使用 MeshGraphMLP、MeshEdgeBlock 和 MeshNodeBlock 构建。
|
| 54 |
+
|
| 55 |
+
Args:
|
| 56 |
+
input_dim_nodes (int): 输入节点特征的维度。
|
| 57 |
+
input_dim_edges (int): 输入边特征的维度。
|
| 58 |
+
output_dim (int): 输出特征的维度(通常是节点状态的更新量)。
|
| 59 |
+
processor_size (int, optional): 消息传递块(Processor Block)的数量。默认值: 15。
|
| 60 |
+
mlp_activation_fn (Union[str, List[str]], optional): MLP 中使用的激活函数。默认值: 'relu'。
|
| 61 |
+
num_layers_node_processor (int, optional): 处理器中节点更新 MLP 的层数。默认值: 2。
|
| 62 |
+
num_layers_edge_processor (int, optional): 处理器中边更新 MLP 的层数。默认值: 2。
|
| 63 |
+
hidden_dim_processor (int, optional): 处理器中隐层的特征维度。默认值: 128。
|
| 64 |
+
hidden_dim_node_encoder (int, optional): 节点编码器的隐层维度。默认值: 128。
|
| 65 |
+
num_layers_node_encoder (Union[int, None], optional): 节点编码器的层数。如果为 None,则不使用编码器。默认值: 2。
|
| 66 |
+
hidden_dim_edge_encoder (int, optional): 边编码器的隐层维度。默认值: 128。
|
| 67 |
+
num_layers_edge_encoder (Union[int, None], optional): 边编码器的层数。如果为 None,则不使用编码器。默认值: 2。
|
| 68 |
+
hidden_dim_node_decoder (int, optional): 节点解码器的隐层维度。默认值: 128。
|
| 69 |
+
num_layers_node_decoder (Union[int, None], optional): 节点解码器的层数。如果为 None,则不使用解码器。默认值: 2。
|
| 70 |
+
aggregation (str, optional): 消息聚合方式,可选 "sum", "mean" 等。默认值: "sum"。
|
| 71 |
+
do_concat_trick (bool, optional): 是否使用拼接优化技巧 (MLP+idx+sum) 以节省显存。默认值: False。
|
| 72 |
+
num_processor_checkpoint_segments (int, optional): 梯度检查点 (Gradient Checkpointing) 的分段数。0 表示禁用。默认值: 0。
|
| 73 |
+
recompute_activation (bool, optional): 是否重计算激活函数以节省显存。默认值: False。
|
| 74 |
+
|
| 75 |
+
形状:
|
| 76 |
+
输入 node_features: (N, input_dim_nodes),其中 N 为节点总数。
|
| 77 |
+
输入 edge_features: (M, input_dim_edges),其中 M 为边总数。
|
| 78 |
+
输入 graph: DGLGraph 或 CuGraphCSC,定义图拓扑结构。
|
| 79 |
+
输出: (N, output_dim),解码后的节点物理量。
|
| 80 |
+
|
| 81 |
+
"""
|
| 82 |
+
|
| 83 |
+
def __init__(
|
| 84 |
+
self,
|
| 85 |
+
input_dim_nodes: int,
|
| 86 |
+
input_dim_edges: int,
|
| 87 |
+
output_dim: int,
|
| 88 |
+
processor_size: int = 15,
|
| 89 |
+
mlp_activation_fn: Union[str, List[str]] = "relu",
|
| 90 |
+
num_layers_node_processor: int = 2,
|
| 91 |
+
num_layers_edge_processor: int = 2,
|
| 92 |
+
hidden_dim_processor: int = 128,
|
| 93 |
+
hidden_dim_node_encoder: int = 128,
|
| 94 |
+
num_layers_node_encoder: Union[int, None] = 2,
|
| 95 |
+
hidden_dim_edge_encoder: int = 128,
|
| 96 |
+
num_layers_edge_encoder: Union[int, None] = 2,
|
| 97 |
+
hidden_dim_node_decoder: int = 128,
|
| 98 |
+
num_layers_node_decoder: Union[int, None] = 2,
|
| 99 |
+
aggregation: str = "sum",
|
| 100 |
+
do_concat_trick: bool = False,
|
| 101 |
+
num_processor_checkpoint_segments: int = 0,
|
| 102 |
+
recompute_activation: bool = False,
|
| 103 |
+
):
|
| 104 |
+
super().__init__(meta=MetaData())
|
| 105 |
+
|
| 106 |
+
activation_fn = get_activation(mlp_activation_fn)
|
| 107 |
+
|
| 108 |
+
# 1. Edge Encoder
|
| 109 |
+
self.edge_encoder = MeshGraphMLP(
|
| 110 |
+
input_dim=input_dim_edges,
|
| 111 |
+
output_dim=hidden_dim_processor,
|
| 112 |
+
hidden_dim=hidden_dim_edge_encoder,
|
| 113 |
+
hidden_layers=num_layers_edge_encoder,
|
| 114 |
+
activation_fn=activation_fn,
|
| 115 |
+
norm_type="LayerNorm",
|
| 116 |
+
recompute_activation=recompute_activation,
|
| 117 |
+
)
|
| 118 |
+
|
| 119 |
+
# 2. Node Encoder
|
| 120 |
+
self.node_encoder = MeshGraphMLP(
|
| 121 |
+
input_dim=input_dim_nodes,
|
| 122 |
+
output_dim=hidden_dim_processor,
|
| 123 |
+
hidden_dim=hidden_dim_node_encoder,
|
| 124 |
+
hidden_layers=num_layers_node_encoder,
|
| 125 |
+
activation_fn=activation_fn,
|
| 126 |
+
norm_type="LayerNorm",
|
| 127 |
+
recompute_activation=recompute_activation,
|
| 128 |
+
)
|
| 129 |
+
|
| 130 |
+
# 3. Node Decoder
|
| 131 |
+
self.node_decoder = MeshGraphMLP(
|
| 132 |
+
input_dim=hidden_dim_processor,
|
| 133 |
+
output_dim=output_dim,
|
| 134 |
+
hidden_dim=hidden_dim_node_decoder,
|
| 135 |
+
hidden_layers=num_layers_node_decoder,
|
| 136 |
+
activation_fn=activation_fn,
|
| 137 |
+
norm_type=None,
|
| 138 |
+
recompute_activation=recompute_activation,
|
| 139 |
+
)
|
| 140 |
+
|
| 141 |
+
# 4. Processor (Core GNN)
|
| 142 |
+
self.processor = MeshGraphNetProcessor(
|
| 143 |
+
processor_size=processor_size,
|
| 144 |
+
input_dim_node=hidden_dim_processor,
|
| 145 |
+
input_dim_edge=hidden_dim_processor,
|
| 146 |
+
num_layers_node=num_layers_node_processor,
|
| 147 |
+
num_layers_edge=num_layers_edge_processor,
|
| 148 |
+
aggregation=aggregation,
|
| 149 |
+
norm_type="LayerNorm",
|
| 150 |
+
activation_fn=activation_fn,
|
| 151 |
+
do_concat_trick=do_concat_trick,
|
| 152 |
+
num_processor_checkpoint_segments=num_processor_checkpoint_segments,
|
| 153 |
+
)
|
| 154 |
+
|
| 155 |
+
def forward(
|
| 156 |
+
self,
|
| 157 |
+
node_features: Tensor,
|
| 158 |
+
edge_features: Tensor,
|
| 159 |
+
graph: Union[DGLGraph, List[DGLGraph], CuGraphCSC],
|
| 160 |
+
) -> Tensor:
|
| 161 |
+
edge_features = self.edge_encoder(edge_features)
|
| 162 |
+
node_features = self.node_encoder(node_features)
|
| 163 |
+
x = self.processor(node_features, edge_features, graph)
|
| 164 |
+
x = self.node_decoder(x)
|
| 165 |
+
return x
|
| 166 |
+
|
| 167 |
+
|
| 168 |
+
class MeshGraphNetProcessor(nn.Module):
|
| 169 |
+
"""
|
| 170 |
+
MeshGraphNet 核心处理器 (Processor)。
|
| 171 |
+
|
| 172 |
+
该模块由一系列堆叠的消息传递块 (Message Passing Blocks) 组成。
|
| 173 |
+
每个块包含两个步骤:
|
| 174 |
+
1. **Edge Block**: 使用 MeshEdgeBlock 更新边特征。
|
| 175 |
+
2. **Node Block**: 使用 MeshNodeBlock 聚合边信息并更新节点特征。
|
| 176 |
+
|
| 177 |
+
支持梯度检查点 (Gradient Checkpointing) 以减少大规模图训练时的显存占用。
|
| 178 |
+
|
| 179 |
+
Args:
|
| 180 |
+
processor_size (int, optional): 处理器包含的消息传递层数。默认值: 15。
|
| 181 |
+
input_dim_node (int, optional): 输入节点特征维度。默认值: 128。
|
| 182 |
+
input_dim_edge (int, optional): 输入边特征维度。默认值: 128。
|
| 183 |
+
num_layers_node (int, optional): 节点更新 MLP 的层数。默认值: 2。
|
| 184 |
+
num_layers_edge (int, optional): 边更新 MLP 的层数。默认值: 2。
|
| 185 |
+
aggregation (str, optional): 消息聚合方式 ("sum", "mean" 等)。默认值: "sum"。
|
| 186 |
+
norm_type (str, optional): 归一化类型。默认值: "LayerNorm"。
|
| 187 |
+
activation_fn (nn.Module, optional): 激活函数。默认值: nn.ReLU()。
|
| 188 |
+
do_concat_trick (bool, optional): 是否启用显存优化技巧。默认值: False。
|
| 189 |
+
num_processor_checkpoint_segments (int, optional): 梯度检查点分段数。默认值: 0 (禁用)。
|
| 190 |
+
|
| 191 |
+
形状:
|
| 192 |
+
输入 node_features: (N, input_dim_node)
|
| 193 |
+
输入 edge_features: (M, input_dim_edge)
|
| 194 |
+
输入 graph: DGLGraph
|
| 195 |
+
输出: (N, input_dim_node) - 仅返回更新后的节点特征。
|
| 196 |
+
|
| 197 |
+
"""
|
| 198 |
+
|
| 199 |
+
def __init__(
|
| 200 |
+
self,
|
| 201 |
+
processor_size: int = 15,
|
| 202 |
+
input_dim_node: int = 128,
|
| 203 |
+
input_dim_edge: int = 128,
|
| 204 |
+
num_layers_node: int = 2,
|
| 205 |
+
num_layers_edge: int = 2,
|
| 206 |
+
aggregation: str = "sum",
|
| 207 |
+
norm_type: str = "LayerNorm",
|
| 208 |
+
activation_fn: nn.Module = nn.ReLU(),
|
| 209 |
+
do_concat_trick: bool = False,
|
| 210 |
+
num_processor_checkpoint_segments: int = 0,
|
| 211 |
+
):
|
| 212 |
+
super().__init__()
|
| 213 |
+
self.processor_size = processor_size
|
| 214 |
+
self.num_processor_checkpoint_segments = num_processor_checkpoint_segments
|
| 215 |
+
|
| 216 |
+
edge_blocks = []
|
| 217 |
+
node_blocks = []
|
| 218 |
+
|
| 219 |
+
for _ in range(self.processor_size):
|
| 220 |
+
edge_blocks.append(
|
| 221 |
+
MeshEdgeBlock(
|
| 222 |
+
input_dim_nodes=input_dim_node,
|
| 223 |
+
input_dim_edges=input_dim_edge,
|
| 224 |
+
output_dim=input_dim_edge,
|
| 225 |
+
hidden_dim=input_dim_edge,
|
| 226 |
+
hidden_layers=num_layers_edge,
|
| 227 |
+
activation_fn=activation_fn,
|
| 228 |
+
norm_type=norm_type,
|
| 229 |
+
do_concat_trick=do_concat_trick,
|
| 230 |
+
recompute_activation=False
|
| 231 |
+
)
|
| 232 |
+
)
|
| 233 |
+
node_blocks.append(
|
| 234 |
+
MeshNodeBlock(
|
| 235 |
+
aggregation=aggregation,
|
| 236 |
+
input_dim_nodes=input_dim_node,
|
| 237 |
+
input_dim_edges=input_dim_edge,
|
| 238 |
+
output_dim=input_dim_node,
|
| 239 |
+
hidden_dim=input_dim_node,
|
| 240 |
+
hidden_layers=num_layers_node,
|
| 241 |
+
activation_fn=activation_fn,
|
| 242 |
+
norm_type=norm_type,
|
| 243 |
+
recompute_activation=False
|
| 244 |
+
)
|
| 245 |
+
)
|
| 246 |
+
|
| 247 |
+
# 按照 Edge -> Node 的顺序交替排列
|
| 248 |
+
layers = list(chain(*zip(edge_blocks, node_blocks)))
|
| 249 |
+
|
| 250 |
+
self.processor_layers = nn.ModuleList(layers)
|
| 251 |
+
self.num_processor_layers = len(self.processor_layers)
|
| 252 |
+
self.set_checkpoint_segments(self.num_processor_checkpoint_segments)
|
| 253 |
+
|
| 254 |
+
def set_checkpoint_segments(self, checkpoint_segments: int):
|
| 255 |
+
if checkpoint_segments > 0:
|
| 256 |
+
if self.num_processor_layers % checkpoint_segments != 0:
|
| 257 |
+
raise ValueError(
|
| 258 |
+
"Processor layers must be a multiple of checkpoint_segments"
|
| 259 |
+
)
|
| 260 |
+
segment_size = self.num_processor_layers // checkpoint_segments
|
| 261 |
+
self.checkpoint_segments = []
|
| 262 |
+
for i in range(0, self.num_processor_layers, segment_size):
|
| 263 |
+
self.checkpoint_segments.append((i, i + segment_size))
|
| 264 |
+
self.checkpoint_fn = set_checkpoint_fn(True)
|
| 265 |
+
else:
|
| 266 |
+
self.checkpoint_fn = set_checkpoint_fn(False)
|
| 267 |
+
self.checkpoint_segments = [(0, self.num_processor_layers)]
|
| 268 |
+
|
| 269 |
+
def run_function(
|
| 270 |
+
self, segment_start: int, segment_end: int
|
| 271 |
+
) -> Callable[
|
| 272 |
+
[Tensor, Tensor, Union[DGLGraph, List[DGLGraph]]], Tuple[Tensor, Tensor]
|
| 273 |
+
]:
|
| 274 |
+
segment = self.processor_layers[segment_start:segment_end]
|
| 275 |
+
|
| 276 |
+
def custom_forward(
|
| 277 |
+
node_features: Tensor,
|
| 278 |
+
edge_features: Tensor,
|
| 279 |
+
graph: Union[DGLGraph, List[DGLGraph]],
|
| 280 |
+
) -> Tuple[Tensor, Tensor]:
|
| 281 |
+
for module in segment:
|
| 282 |
+
edge_features, node_features = module(
|
| 283 |
+
edge_features, node_features, graph
|
| 284 |
+
)
|
| 285 |
+
return edge_features, node_features
|
| 286 |
+
|
| 287 |
+
return custom_forward
|
| 288 |
+
|
| 289 |
+
@torch.jit.unused
|
| 290 |
+
def forward(
|
| 291 |
+
self,
|
| 292 |
+
node_features: Tensor,
|
| 293 |
+
edge_features: Tensor,
|
| 294 |
+
graph: Union[DGLGraph, List[DGLGraph], CuGraphCSC],
|
| 295 |
+
) -> Tensor:
|
| 296 |
+
for segment_start, segment_end in self.checkpoint_segments:
|
| 297 |
+
edge_features, node_features = self.checkpoint_fn(
|
| 298 |
+
self.run_function(segment_start, segment_end),
|
| 299 |
+
node_features,
|
| 300 |
+
edge_features,
|
| 301 |
+
graph,
|
| 302 |
+
use_reentrant=False,
|
| 303 |
+
preserve_rng_state=False,
|
| 304 |
+
)
|
| 305 |
+
|
| 306 |
+
return node_features
|
scripts/fake_data.py
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import sys
|
| 3 |
+
from pathlib import Path
|
| 4 |
+
|
| 5 |
+
import dgl
|
| 6 |
+
import torch
|
| 7 |
+
from dgl.dataloading import GraphDataLoader
|
| 8 |
+
from torch.utils.data import Dataset
|
| 9 |
+
|
| 10 |
+
PROJECT_ROOT = Path(__file__).resolve().parents[1]
|
| 11 |
+
sys.path.insert(0, str(PROJECT_ROOT / "model"))
|
| 12 |
+
|
| 13 |
+
from onescience.utils.YParams import YParams
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
def make_graph(num_nodes: int = 12):
|
| 17 |
+
src = torch.arange(num_nodes, dtype=torch.int32)
|
| 18 |
+
dst = torch.roll(src, shifts=-1)
|
| 19 |
+
graph = dgl.to_bidirected(dgl.graph((src, dst), num_nodes=num_nodes, idtype=torch.int32))
|
| 20 |
+
|
| 21 |
+
pos = torch.stack(
|
| 22 |
+
(
|
| 23 |
+
torch.linspace(0.0, 1.0, num_nodes),
|
| 24 |
+
torch.sin(torch.linspace(0.0, 3.14159, num_nodes)) * 0.2,
|
| 25 |
+
),
|
| 26 |
+
dim=1,
|
| 27 |
+
)
|
| 28 |
+
row, col = graph.edges()
|
| 29 |
+
disp = pos[row.long()] - pos[col.long()]
|
| 30 |
+
graph.edata["x"] = torch.cat(
|
| 31 |
+
(disp, torch.linalg.norm(disp, dim=-1, keepdim=True)),
|
| 32 |
+
dim=1,
|
| 33 |
+
)
|
| 34 |
+
|
| 35 |
+
velocity = torch.randn(num_nodes, 2) * 0.1
|
| 36 |
+
node_type = torch.zeros(num_nodes, 4)
|
| 37 |
+
node_type[:, 0] = 1.0
|
| 38 |
+
graph.ndata["x"] = torch.cat((velocity, node_type), dim=1)
|
| 39 |
+
graph.ndata["y"] = torch.cat(
|
| 40 |
+
(torch.randn(num_nodes, 2) * 0.01, torch.randn(num_nodes, 1) * 0.01),
|
| 41 |
+
dim=1,
|
| 42 |
+
)
|
| 43 |
+
graph.ndata["mesh_pos"] = pos
|
| 44 |
+
|
| 45 |
+
cells = torch.tensor(
|
| 46 |
+
[[i, i + 1, min(i + 2, num_nodes - 1)] for i in range(num_nodes - 2)],
|
| 47 |
+
dtype=torch.int64,
|
| 48 |
+
)
|
| 49 |
+
mask = torch.ones(num_nodes, 1, dtype=torch.bool)
|
| 50 |
+
return {"graph": graph, "cells": cells, "mask": mask}
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
class FakeGraphDataset(Dataset):
|
| 54 |
+
def __init__(self, samples):
|
| 55 |
+
self.samples = samples
|
| 56 |
+
|
| 57 |
+
def __len__(self):
|
| 58 |
+
return len(self.samples)
|
| 59 |
+
|
| 60 |
+
def __getitem__(self, index):
|
| 61 |
+
sample = self.samples[index]
|
| 62 |
+
if isinstance(sample, dict) and "graph" in sample:
|
| 63 |
+
return sample["graph"]
|
| 64 |
+
return sample
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
def _resolve_path(project_root: Path, path):
|
| 68 |
+
path = Path(path)
|
| 69 |
+
return path if path.is_absolute() else project_root / path
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
def _torch_load(path: Path):
|
| 73 |
+
try:
|
| 74 |
+
return torch.load(path, map_location="cpu", weights_only=False)
|
| 75 |
+
except TypeError:
|
| 76 |
+
return torch.load(path, map_location="cpu")
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
class FakeCylinderFlowDatapipe:
|
| 80 |
+
def __init__(self, params, project_root: Path):
|
| 81 |
+
self.params = params
|
| 82 |
+
fake_data_path = _resolve_path(project_root, params.source.fake_data_path)
|
| 83 |
+
if not fake_data_path.exists():
|
| 84 |
+
raise FileNotFoundError(
|
| 85 |
+
f"Fake data file not found: {fake_data_path}. Run scripts/fake_data.py first."
|
| 86 |
+
)
|
| 87 |
+
|
| 88 |
+
payload = _torch_load(fake_data_path)
|
| 89 |
+
self.train_dataset = FakeGraphDataset(payload["train"])
|
| 90 |
+
self.val_dataset = FakeGraphDataset(payload["val"])
|
| 91 |
+
self.test_dataset = FakeGraphDataset(payload["test"])
|
| 92 |
+
self.stats = payload.get("stats", {})
|
| 93 |
+
|
| 94 |
+
def _loader(self, dataset, shuffle=False, drop_last=False):
|
| 95 |
+
return GraphDataLoader(
|
| 96 |
+
dataset,
|
| 97 |
+
batch_size=self.params.dataloader.batch_size,
|
| 98 |
+
drop_last=drop_last,
|
| 99 |
+
num_workers=self.params.dataloader.num_workers,
|
| 100 |
+
pin_memory=True,
|
| 101 |
+
shuffle=shuffle,
|
| 102 |
+
)
|
| 103 |
+
|
| 104 |
+
def train_dataloader(self):
|
| 105 |
+
return self._loader(self.train_dataset, shuffle=True), None
|
| 106 |
+
|
| 107 |
+
def val_dataloader(self):
|
| 108 |
+
return self._loader(self.val_dataset), None
|
| 109 |
+
|
| 110 |
+
def test_dataloader(self):
|
| 111 |
+
return self._loader(self.test_dataset)
|
| 112 |
+
|
| 113 |
+
|
| 114 |
+
def use_fake_data(params):
|
| 115 |
+
return bool(getattr(params.source, "fake_data", False))
|
| 116 |
+
|
| 117 |
+
|
| 118 |
+
def build_cylinder_flow_datapipe(params, distributed: bool, project_root: Path):
|
| 119 |
+
if use_fake_data(params):
|
| 120 |
+
return FakeCylinderFlowDatapipe(params=params, project_root=project_root)
|
| 121 |
+
|
| 122 |
+
from onescience.datapipes.cfd import DeepMind_CylinderFlowDatapipe
|
| 123 |
+
|
| 124 |
+
return DeepMind_CylinderFlowDatapipe(params=params, distributed=distributed)
|
| 125 |
+
|
| 126 |
+
|
| 127 |
+
def main():
|
| 128 |
+
os.chdir(PROJECT_ROOT)
|
| 129 |
+
config_path = PROJECT_ROOT / "config" / "config.yaml"
|
| 130 |
+
cfg_data = YParams(config_path, "datapipe")
|
| 131 |
+
output_path = PROJECT_ROOT / cfg_data.source.fake_data_path
|
| 132 |
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
| 133 |
+
|
| 134 |
+
payload = {
|
| 135 |
+
"train": [
|
| 136 |
+
make_graph()
|
| 137 |
+
for _ in range(cfg_data.data.train_samples * (cfg_data.data.train_steps - 1))
|
| 138 |
+
],
|
| 139 |
+
"val": [
|
| 140 |
+
make_graph()
|
| 141 |
+
for _ in range(cfg_data.data.val_samples * (cfg_data.data.val_steps - 1))
|
| 142 |
+
],
|
| 143 |
+
"test": [
|
| 144 |
+
make_graph()
|
| 145 |
+
for _ in range(cfg_data.data.test_samples * (cfg_data.data.test_steps - 1))
|
| 146 |
+
],
|
| 147 |
+
"stats": {
|
| 148 |
+
"edge_stats": {
|
| 149 |
+
"edge_mean": torch.zeros(3),
|
| 150 |
+
"edge_std": torch.ones(3),
|
| 151 |
+
},
|
| 152 |
+
"node_stats": {
|
| 153 |
+
"velocity_mean": torch.zeros(2),
|
| 154 |
+
"velocity_std": torch.ones(2),
|
| 155 |
+
"velocity_diff_mean": torch.zeros(2),
|
| 156 |
+
"velocity_diff_std": torch.ones(2),
|
| 157 |
+
"pressure_mean": torch.zeros(1),
|
| 158 |
+
"pressure_std": torch.ones(1),
|
| 159 |
+
},
|
| 160 |
+
},
|
| 161 |
+
}
|
| 162 |
+
torch.save(payload, output_path)
|
| 163 |
+
print(f"Fake data saved to {output_path.relative_to(PROJECT_ROOT)}")
|
| 164 |
+
|
| 165 |
+
|
| 166 |
+
if __name__ == "__main__":
|
| 167 |
+
main()
|
scripts/inference.py
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import logging
|
| 2 |
+
import os
|
| 3 |
+
import sys
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
|
| 6 |
+
import numpy as np
|
| 7 |
+
import torch
|
| 8 |
+
|
| 9 |
+
PROJECT_ROOT = Path(__file__).resolve().parents[1]
|
| 10 |
+
sys.path.insert(0, str(PROJECT_ROOT))
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
from model.meshgraphnet import MeshGraphNet
|
| 14 |
+
from onescience.utils.YParams import YParams
|
| 15 |
+
from onescience.launch.utils import load_checkpoint
|
| 16 |
+
from fake_data import build_cylinder_flow_datapipe
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
def build_model(model_params, device):
|
| 20 |
+
mlp_act = "silu" if model_params.recompute_activation else "relu"
|
| 21 |
+
return MeshGraphNet(
|
| 22 |
+
input_dim_nodes=model_params.num_input_features,
|
| 23 |
+
input_dim_edges=model_params.num_edge_features,
|
| 24 |
+
output_dim=model_params.num_output_features,
|
| 25 |
+
processor_size=model_params.processor_size,
|
| 26 |
+
hidden_dim_processor=model_params.hidden_dim_processor,
|
| 27 |
+
num_layers_node_processor=model_params.num_layers_node_processor,
|
| 28 |
+
num_layers_edge_processor=model_params.num_layers_edge_processor,
|
| 29 |
+
hidden_dim_node_encoder=model_params.hidden_dim_node_encoder,
|
| 30 |
+
hidden_dim_edge_encoder=model_params.hidden_dim_edge_encoder,
|
| 31 |
+
hidden_dim_node_decoder=model_params.hidden_dim_node_decoder,
|
| 32 |
+
mlp_activation_fn=mlp_act,
|
| 33 |
+
do_concat_trick=model_params.do_concat_trick,
|
| 34 |
+
num_processor_checkpoint_segments=model_params.num_processor_checkpoint_segments,
|
| 35 |
+
recompute_activation=model_params.recompute_activation,
|
| 36 |
+
).to(device)
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def resolve_device(device_name: str):
|
| 40 |
+
if device_name == "cpu":
|
| 41 |
+
return torch.device("cpu")
|
| 42 |
+
if device_name in ("cuda", "gpu"):
|
| 43 |
+
if not torch.cuda.is_available():
|
| 44 |
+
raise RuntimeError("Config requested cuda device, but torch.cuda.is_available() is false.")
|
| 45 |
+
return torch.device("cuda:0")
|
| 46 |
+
return torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
def main():
|
| 50 |
+
os.chdir(PROJECT_ROOT)
|
| 51 |
+
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
|
| 52 |
+
logger = logging.getLogger("mesh_graph_net.inference")
|
| 53 |
+
|
| 54 |
+
config_path = PROJECT_ROOT / "config" / "config.yaml"
|
| 55 |
+
cfg_model = YParams(config_path, "model")
|
| 56 |
+
cfg_data = YParams(config_path, "datapipe")
|
| 57 |
+
cfg_train = YParams(config_path, "training")
|
| 58 |
+
cfg_inference = YParams(config_path, "inference")
|
| 59 |
+
model_params = cfg_model.specific_params[cfg_model.name]
|
| 60 |
+
|
| 61 |
+
device = resolve_device(getattr(cfg_inference, "device", "auto"))
|
| 62 |
+
logger.info("Using device: %s", device)
|
| 63 |
+
datapipe = build_cylinder_flow_datapipe(
|
| 64 |
+
params=cfg_data,
|
| 65 |
+
distributed=False,
|
| 66 |
+
project_root=PROJECT_ROOT,
|
| 67 |
+
)
|
| 68 |
+
loader = datapipe.test_dataloader()
|
| 69 |
+
model = build_model(model_params, device)
|
| 70 |
+
checkpoint_dir = PROJECT_ROOT / getattr(cfg_inference, "checkpoint_dir", cfg_train.checkpoint_dir)
|
| 71 |
+
epoch = load_checkpoint(checkpoint_dir, models=model, device=device)
|
| 72 |
+
if epoch == 0:
|
| 73 |
+
logger.warning("No checkpoint found in %s; running with randomly initialized weights", checkpoint_dir)
|
| 74 |
+
model.eval()
|
| 75 |
+
|
| 76 |
+
predictions, targets = [], []
|
| 77 |
+
with torch.no_grad():
|
| 78 |
+
for batch in loader:
|
| 79 |
+
graph = batch[0] if isinstance(batch, (tuple, list)) else batch
|
| 80 |
+
graph = graph.to(device)
|
| 81 |
+
pred = model(graph.ndata["x"], graph.edata["x"], graph)
|
| 82 |
+
predictions.append(pred.cpu().numpy())
|
| 83 |
+
targets.append(graph.ndata["y"].cpu().numpy())
|
| 84 |
+
|
| 85 |
+
output_path = PROJECT_ROOT / cfg_inference.output_path
|
| 86 |
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
| 87 |
+
np.savez(output_path, prediction=np.concatenate(predictions, axis=0), target=np.concatenate(targets, axis=0))
|
| 88 |
+
logger.info("Saved inference results to %s", output_path)
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
if __name__ == "__main__":
|
| 92 |
+
main()
|
scripts/result.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import sys
|
| 3 |
+
from pathlib import Path
|
| 4 |
+
|
| 5 |
+
import numpy as np
|
| 6 |
+
|
| 7 |
+
PROJECT_ROOT = Path(__file__).resolve().parents[1]
|
| 8 |
+
sys.path.insert(0, str(PROJECT_ROOT / "model"))
|
| 9 |
+
|
| 10 |
+
from onescience.utils.YParams import YParams
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
def main():
|
| 14 |
+
os.chdir(PROJECT_ROOT)
|
| 15 |
+
cfg = YParams(PROJECT_ROOT / "config" / "config.yaml", "inference")
|
| 16 |
+
result_path = PROJECT_ROOT / cfg.output_path
|
| 17 |
+
if not result_path.exists():
|
| 18 |
+
raise FileNotFoundError(f"Result file not found: {result_path}. Run scripts/inference.py first.")
|
| 19 |
+
data = np.load(result_path)
|
| 20 |
+
prediction = data["prediction"]
|
| 21 |
+
target = data["target"]
|
| 22 |
+
mse = float(np.mean((prediction - target) ** 2))
|
| 23 |
+
print(f"result_path: {result_path.relative_to(PROJECT_ROOT)}")
|
| 24 |
+
print(f"prediction_shape: {prediction.shape}")
|
| 25 |
+
print(f"target_shape: {target.shape}")
|
| 26 |
+
print(f"mse: {mse:.8f}")
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
if __name__ == "__main__":
|
| 30 |
+
main()
|
scripts/train.py
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import logging
|
| 2 |
+
import os
|
| 3 |
+
import sys
|
| 4 |
+
import time
|
| 5 |
+
from pathlib import Path
|
| 6 |
+
|
| 7 |
+
import torch
|
| 8 |
+
import torch.nn as nn
|
| 9 |
+
from torch.amp import GradScaler, autocast
|
| 10 |
+
from torch.nn.parallel import DistributedDataParallel
|
| 11 |
+
|
| 12 |
+
PROJECT_ROOT = Path(__file__).resolve().parents[1]
|
| 13 |
+
sys.path.insert(0, str(PROJECT_ROOT))
|
| 14 |
+
|
| 15 |
+
from model.meshgraphnet import MeshGraphNet
|
| 16 |
+
from onescience.distributed import DistributedManager
|
| 17 |
+
from onescience.utils.YParams import YParams
|
| 18 |
+
from onescience.launch.utils import load_checkpoint, save_checkpoint
|
| 19 |
+
from fake_data import build_cylinder_flow_datapipe
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def setup_logging(rank: int):
|
| 23 |
+
level = logging.INFO if rank == 0 else logging.WARNING
|
| 24 |
+
logging.basicConfig(level=level, format="%(asctime)s - %(levelname)s - %(message)s")
|
| 25 |
+
return logging.getLogger("mesh_graph_net.train")
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def build_model(model_params, device):
|
| 29 |
+
mlp_act = "silu" if model_params.recompute_activation else "relu"
|
| 30 |
+
return MeshGraphNet(
|
| 31 |
+
input_dim_nodes=model_params.num_input_features,
|
| 32 |
+
input_dim_edges=model_params.num_edge_features,
|
| 33 |
+
output_dim=model_params.num_output_features,
|
| 34 |
+
processor_size=model_params.processor_size,
|
| 35 |
+
hidden_dim_processor=model_params.hidden_dim_processor,
|
| 36 |
+
num_layers_node_processor=model_params.num_layers_node_processor,
|
| 37 |
+
num_layers_edge_processor=model_params.num_layers_edge_processor,
|
| 38 |
+
hidden_dim_node_encoder=model_params.hidden_dim_node_encoder,
|
| 39 |
+
hidden_dim_edge_encoder=model_params.hidden_dim_edge_encoder,
|
| 40 |
+
hidden_dim_node_decoder=model_params.hidden_dim_node_decoder,
|
| 41 |
+
mlp_activation_fn=mlp_act,
|
| 42 |
+
do_concat_trick=model_params.do_concat_trick,
|
| 43 |
+
num_processor_checkpoint_segments=model_params.num_processor_checkpoint_segments,
|
| 44 |
+
recompute_activation=model_params.recompute_activation,
|
| 45 |
+
).to(device)
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
def graph_from_batch(batch):
|
| 49 |
+
return batch[0] if isinstance(batch, (tuple, list)) else batch
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
def resolve_device(device_name: str, manager: DistributedManager, gpuid: int):
|
| 53 |
+
if manager.world_size > 1:
|
| 54 |
+
return manager.device
|
| 55 |
+
if device_name == "cpu":
|
| 56 |
+
return torch.device("cpu")
|
| 57 |
+
if device_name in ("cuda", "gpu"):
|
| 58 |
+
if not torch.cuda.is_available():
|
| 59 |
+
raise RuntimeError("Config requested cuda device, but torch.cuda.is_available() is false.")
|
| 60 |
+
return torch.device(f"cuda:{gpuid}")
|
| 61 |
+
return torch.device(f"cuda:{gpuid}" if torch.cuda.is_available() else "cpu")
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
def main():
|
| 65 |
+
os.chdir(PROJECT_ROOT)
|
| 66 |
+
DistributedManager.initialize()
|
| 67 |
+
manager = DistributedManager()
|
| 68 |
+
logger = setup_logging(manager.rank)
|
| 69 |
+
|
| 70 |
+
config_path = PROJECT_ROOT / "config" / "config.yaml"
|
| 71 |
+
cfg_model = YParams(config_path, "model")
|
| 72 |
+
cfg_data = YParams(config_path, "datapipe")
|
| 73 |
+
cfg_train = YParams(config_path, "training")
|
| 74 |
+
model_params = cfg_model.specific_params[cfg_model.name]
|
| 75 |
+
|
| 76 |
+
datapipe = build_cylinder_flow_datapipe(
|
| 77 |
+
params=cfg_data,
|
| 78 |
+
distributed=(manager.world_size > 1),
|
| 79 |
+
project_root=PROJECT_ROOT,
|
| 80 |
+
)
|
| 81 |
+
train_loader, train_sampler = datapipe.train_dataloader()
|
| 82 |
+
val_loader, val_sampler = datapipe.val_dataloader()
|
| 83 |
+
|
| 84 |
+
device = resolve_device(getattr(cfg_train, "device", "auto"), manager, cfg_train.gpuid)
|
| 85 |
+
logger.info("Using device: %s", device)
|
| 86 |
+
model = build_model(model_params, device)
|
| 87 |
+
if manager.world_size > 1:
|
| 88 |
+
model = DistributedDataParallel(model, device_ids=[manager.local_rank], output_device=manager.local_rank)
|
| 89 |
+
|
| 90 |
+
optimizer = torch.optim.Adam(model.parameters(), lr=cfg_train.lr)
|
| 91 |
+
scheduler = torch.optim.lr_scheduler.LambdaLR(optimizer, lr_lambda=lambda step: cfg_train.lr_decay_rate**step)
|
| 92 |
+
loss_criterion = nn.MSELoss() if cfg_train.loss_criterion == "MSE" else nn.L1Loss()
|
| 93 |
+
scaler = GradScaler(enabled=bool(cfg_train.amp))
|
| 94 |
+
|
| 95 |
+
checkpoint_dir = PROJECT_ROOT / cfg_train.checkpoint_dir
|
| 96 |
+
epoch_init = load_checkpoint(checkpoint_dir, models=model, optimizer=optimizer, scheduler=scheduler, scaler=scaler, device=device)
|
| 97 |
+
best_valid_loss = float("inf")
|
| 98 |
+
best_loss_epoch = epoch_init
|
| 99 |
+
|
| 100 |
+
logger.info("Starting training")
|
| 101 |
+
for epoch in range(epoch_init, cfg_train.max_epoch):
|
| 102 |
+
if train_sampler is not None:
|
| 103 |
+
train_sampler.set_epoch(epoch)
|
| 104 |
+
start = time.time()
|
| 105 |
+
model.train()
|
| 106 |
+
train_loss = 0.0
|
| 107 |
+
for idx, batch in enumerate(train_loader):
|
| 108 |
+
graph = graph_from_batch(batch).to(device)
|
| 109 |
+
optimizer.zero_grad(set_to_none=True)
|
| 110 |
+
with autocast(device_type=device.type, enabled=bool(cfg_train.amp)):
|
| 111 |
+
pred = model(graph.ndata["x"], graph.edata["x"], graph)
|
| 112 |
+
loss = loss_criterion(pred, graph.ndata["y"])
|
| 113 |
+
scaler.scale(loss).backward()
|
| 114 |
+
scaler.step(optimizer)
|
| 115 |
+
scaler.update()
|
| 116 |
+
scheduler.step()
|
| 117 |
+
train_loss += loss.item()
|
| 118 |
+
if manager.rank == 0 and (idx + 1) % cfg_train.log_interval == 0:
|
| 119 |
+
logger.info("Epoch %s/%s batch %s/%s loss %.6f", epoch + 1, cfg_train.max_epoch, idx + 1, len(train_loader), loss.item())
|
| 120 |
+
|
| 121 |
+
train_loss /= max(len(train_loader), 1)
|
| 122 |
+
model.eval()
|
| 123 |
+
valid_loss = 0.0
|
| 124 |
+
with torch.no_grad():
|
| 125 |
+
for batch in val_loader:
|
| 126 |
+
graph = graph_from_batch(batch).to(device)
|
| 127 |
+
with autocast(device_type=device.type, enabled=bool(cfg_train.amp)):
|
| 128 |
+
pred = model(graph.ndata["x"], graph.edata["x"], graph)
|
| 129 |
+
loss = loss_criterion(pred, graph.ndata["y"])
|
| 130 |
+
valid_loss += loss.item()
|
| 131 |
+
valid_loss /= max(len(val_loader), 1)
|
| 132 |
+
|
| 133 |
+
if manager.rank == 0:
|
| 134 |
+
logger.info(
|
| 135 |
+
"Epoch %s finished in %.2fs train_loss %.6f valid_loss %.6f",
|
| 136 |
+
epoch + 1,
|
| 137 |
+
time.time() - start,
|
| 138 |
+
train_loss,
|
| 139 |
+
valid_loss,
|
| 140 |
+
)
|
| 141 |
+
if valid_loss < best_valid_loss:
|
| 142 |
+
best_valid_loss = valid_loss
|
| 143 |
+
best_loss_epoch = epoch
|
| 144 |
+
save_checkpoint(checkpoint_dir, models=model, optimizer=optimizer, scheduler=scheduler, scaler=scaler, epoch=epoch + 1)
|
| 145 |
+
logger.info("Checkpoint saved to %s", checkpoint_dir)
|
| 146 |
+
if (epoch - best_loss_epoch) > cfg_train.patience:
|
| 147 |
+
logger.warning("Early stopping after %s stale epochs", cfg_train.patience)
|
| 148 |
+
break
|
| 149 |
+
|
| 150 |
+
logger.info("Training finished")
|
| 151 |
+
|
| 152 |
+
|
| 153 |
+
if __name__ == "__main__":
|
| 154 |
+
main()
|