Upload folder using huggingface_hub
Browse files- README.md +124 -0
- model/__init__.py +14 -0
- model/__version__.py +3 -0
- model/mace.py +945 -0
- model/py.typed +1 -0
- scripts/demo/README.md +118 -0
- scripts/demo/_parse_config.py +304 -0
- scripts/demo/configs/DMC.yaml +65 -0
- scripts/demo/configs/ani1x_8dcu.yaml +84 -0
- scripts/demo/configs/nanotube_eval_l0_1dcu.yaml +35 -0
- scripts/demo/configs/nanotube_finetune_l0_8dcu.yaml +48 -0
- scripts/demo/configs/nanotube_l0_8dcu.yaml +83 -0
- scripts/demo/configs/nanotube_l2_16dcu.yaml +114 -0
- scripts/demo/configs/nanotube_l2_8dcu.yaml +103 -0
- scripts/demo/configs/nanotube_run_md_l0_1dcu.yaml +40 -0
- scripts/demo/configs/water_1dcu.yaml +80 -0
- scripts/demo/configs/water_4dcu.yaml +81 -0
- scripts/demo/configs/water_8dcu.yaml +81 -0
- scripts/demo/configs/water_finetune_mp0_8dcu.yaml +97 -0
- scripts/demo/run.sh +267 -0
- scripts/demo/templates/env_setup.sh +38 -0
- scripts/demo/templates/preflight_check.sh +57 -0
- scripts/demo/templates/slurm_header.template +9 -0
- scripts/eval_configs.py +187 -0
- scripts/fasteq/fasteq_install.sh +88 -0
- scripts/fasteq/install.sh +71 -0
- scripts/run_md.py +112 -0
- scripts/train.py +1098 -0
README.md
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<p align="center">
|
| 2 |
+
<strong>
|
| 3 |
+
<span style="font-size: 30px;">MACE</span>
|
| 4 |
+
</strong>
|
| 5 |
+
</p>
|
| 6 |
+
|
| 7 |
+
# Model Introduction
|
| 8 |
+
|
| 9 |
+
MACE is a machine-learning interatomic potential (MLIP) model for molecular and materials systems, built on E(3)-equivariant graph neural networks. It predicts energy and forces for atomic structures.
|
| 10 |
+
|
| 11 |
+
Paper: *MACE: Higher order equivariant message passing neural networks for fast and accurate force fields*
|
| 12 |
+
Reference implementation: https://github.com/ACEsuit/mace
|
| 13 |
+
|
| 14 |
+
# Model Description
|
| 15 |
+
|
| 16 |
+
MACE is based on an E(3)-equivariant graph neural network architecture and is trained on HDF5/XYZ format data. It performs energy and force prediction and structure optimization for molecular and materials systems.
|
| 17 |
+
|
| 18 |
+
# Applicable Scenarios
|
| 19 |
+
|
| 20 |
+
| Scenario | Description |
|
| 21 |
+
| :---: | :--- |
|
| 22 |
+
| Interatomic potential training | Train a MACE model using standard configurations that read HDF5/XYZ data |
|
| 23 |
+
| Distributed training pre-check | Check multi-GPU/multi-node training configuration, data paths, and statistics consistency |
|
| 24 |
+
| Validation-set evaluation | Output energy- and force-related error metrics on the validation set during training |
|
| 25 |
+
| Custom data migration | Replace the existing HDF5/XYZ data and statistics files with your own data |
|
| 26 |
+
| Environment connectivity check | Use the pre-check script to verify the OneScience matchem environment, pyyaml, h5py, and data readability |
|
| 27 |
+
|
| 28 |
+
# Usage Instructions
|
| 29 |
+
|
| 30 |
+
## 1. Using OneCode
|
| 31 |
+
|
| 32 |
+
You can try out intelligent one-click AI4S programming in the OneCode online environment:
|
| 33 |
+
|
| 34 |
+
[Try intelligent one-click AI4S programming](https://web-2069360198568017922-iaaj.ksai.scnet.cn:58043/home)
|
| 35 |
+
|
| 36 |
+
## 2. Manual Installation and Usage
|
| 37 |
+
|
| 38 |
+
**Hardware Requirements**
|
| 39 |
+
|
| 40 |
+
- GPU or DCU is recommended for training.
|
| 41 |
+
- CPU can be used for import and small-configuration connectivity checks; full training will be slow.
|
| 42 |
+
- DCU users need to install DTK in advance. DTK 25.04.2 or above, or the OneScience-recommended version matching the current cluster, is suggested.
|
| 43 |
+
|
| 44 |
+
### Download the Model Package
|
| 45 |
+
|
| 46 |
+
```bash
|
| 47 |
+
modelscope download --model OneScience/MACE --local_dir ./mace
|
| 48 |
+
cd mace
|
| 49 |
+
```
|
| 50 |
+
|
| 51 |
+
### Install the Runtime Environment
|
| 52 |
+
|
| 53 |
+
**DCU Environment**
|
| 54 |
+
|
| 55 |
+
```bash
|
| 56 |
+
# Please activate DTK and CONDA first
|
| 57 |
+
conda create -n onescience311 python=3.11 -y
|
| 58 |
+
conda activate onescience311
|
| 59 |
+
# uv installation is also supported
|
| 60 |
+
pip install onescience[matchem-dcu] -i http://mirrors.onescience.ai:3141/pypi/simple/ --trusted-host mirrors.onescience.ai
|
| 61 |
+
```
|
| 62 |
+
|
| 63 |
+
**GPU Environment**
|
| 64 |
+
|
| 65 |
+
```bash
|
| 66 |
+
# Please activate CONDA first
|
| 67 |
+
conda create -n onescience311 python=3.11 -y libstdcxx-ng=12 libgcc-ng=12 gcc_linux-64=12 gxx_linux-64=12
|
| 68 |
+
conda activate onescience311
|
| 69 |
+
# uv installation is also supported
|
| 70 |
+
pip install onescience[matchem-gpu] -i http://mirrors.onescience.ai:3141/pypi/simple/ --trusted-host mirrors.onescience.ai
|
| 71 |
+
```
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
### Training Data Description
|
| 75 |
+
|
| 76 |
+
This repository does not include built-in training data. Taking the DMC starter dataset as an example, download it from ModelScope and place it under `data/` in the repository root:
|
| 77 |
+
|
| 78 |
+
```bash
|
| 79 |
+
modelscope download --dataset OneScience/DMC --local_dir ./data
|
| 80 |
+
```
|
| 81 |
+
|
| 82 |
+
After downloading, the data path will be `data/data/DMC/`. `scripts/demo/run.sh` automatically uses the repository root as `ONESCIENCE_DATASETS_DIR`, so there is no need to manually set this variable to match the paths in the configuration file.
|
| 83 |
+
|
| 84 |
+
For other configurations (e.g., `ani1x_8dcu.yaml`, `water_*.yaml`, etc.), download the corresponding datasets and adjust the data paths in the YAML file.
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
### Training
|
| 88 |
+
|
| 89 |
+
Single-GPU:
|
| 90 |
+
|
| 91 |
+
```bash
|
| 92 |
+
bash scripts/demo/run.sh --config scripts/demo/configs/DMC.yaml
|
| 93 |
+
```
|
| 94 |
+
|
| 95 |
+
Multi-GPU:
|
| 96 |
+
|
| 97 |
+
```bash
|
| 98 |
+
# Taking 8 GPUs as an example; launch.launcher in the config should be torchrun
|
| 99 |
+
bash scripts/demo/run.sh --config scripts/demo/configs/ani1x_8dcu.yaml
|
| 100 |
+
```
|
| 101 |
+
|
| 102 |
+
SLURM submission:
|
| 103 |
+
|
| 104 |
+
```bash
|
| 105 |
+
bash scripts/demo/run.sh --config scripts/demo/configs/ani1x_8dcu.yaml --submit
|
| 106 |
+
```
|
| 107 |
+
|
| 108 |
+
### Training Weights
|
| 109 |
+
|
| 110 |
+
This repository currently does not include built-in trained weights. Weights can be obtained through the training steps above.
|
| 111 |
+
|
| 112 |
+
# OneScience Official Information
|
| 113 |
+
|
| 114 |
+
| Platform | OneScience Main Repository | Skills Repository |
|
| 115 |
+
| --- | --- | --- |
|
| 116 |
+
| Gitee | https://gitee.com/onescience-ai/onescience | https://gitee.com/onescience-ai/oneskills |
|
| 117 |
+
| GitHub | https://github.com/onescience-ai/OneScience | https://github.com/onescience-ai/oneskills |
|
| 118 |
+
|
| 119 |
+
|
| 120 |
+
|
| 121 |
+
# Citation and License
|
| 122 |
+
|
| 123 |
+
- The MACE-related code comes from the matchem example implementation in the OneScience project and references the upstream MACE project (https://github.com/ACEsuit/mace). The upstream MACE code is released under the [MIT License](https://github.com/ACEsuit/mace/blob/main/LICENSE).
|
| 124 |
+
- If you use MACE training results in scientific research, we recommend citing the original MACE paper, the relevant OneScience project information, and the sources of the datasets actually used.
|
model/__init__.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""MACE model definitions."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
from .__version__ import __version__
|
| 6 |
+
from .mace import AtomicDipolesMACE, EnergyDipolesMACE, MACE, ScaleShiftMACE
|
| 7 |
+
|
| 8 |
+
__all__ = [
|
| 9 |
+
"__version__",
|
| 10 |
+
"AtomicDipolesMACE",
|
| 11 |
+
"EnergyDipolesMACE",
|
| 12 |
+
"MACE",
|
| 13 |
+
"ScaleShiftMACE",
|
| 14 |
+
]
|
model/__version__.py
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
__version__ = "0.3.13"
|
| 2 |
+
|
| 3 |
+
__all__ = ["__version__"]
|
model/mace.py
ADDED
|
@@ -0,0 +1,945 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
# Implementation of MACE models and other models based E(3)-Equivariant MPNNs
|
| 3 |
+
|
| 4 |
+
from typing import Any, Callable, Dict, List, Optional, Type, Union
|
| 5 |
+
|
| 6 |
+
import numpy as np
|
| 7 |
+
import torch
|
| 8 |
+
from e3nn import o3
|
| 9 |
+
from e3nn.util.jit import compile_mode
|
| 10 |
+
|
| 11 |
+
from onescience.modules.layer.mace_radial import ZBLBasis
|
| 12 |
+
#from onescience.utils.mace.tools.scatter import scatter_sum
|
| 13 |
+
from onescience.datapipes.materials.tools.scatter import scatter_sum
|
| 14 |
+
|
| 15 |
+
from onescience.modules.block.mace_block import (
|
| 16 |
+
AtomicEnergiesBlock,
|
| 17 |
+
EquivariantProductBasisBlock,
|
| 18 |
+
InteractionBlock,
|
| 19 |
+
LinearDipoleReadoutBlock,
|
| 20 |
+
LinearNodeEmbeddingBlock,
|
| 21 |
+
LinearReadoutBlock,
|
| 22 |
+
NonLinearDipoleReadoutBlock,
|
| 23 |
+
NonLinearReadoutBlock,
|
| 24 |
+
RadialEmbeddingBlock,
|
| 25 |
+
ScaleShiftBlock,
|
| 26 |
+
)
|
| 27 |
+
from onescience.modules.func_utils.mace_func_utils import (
|
| 28 |
+
compute_fixed_charge_dipole,
|
| 29 |
+
get_atomic_virials_stresses,
|
| 30 |
+
get_edge_vectors_and_lengths,
|
| 31 |
+
get_outputs,
|
| 32 |
+
get_symmetric_displacement,
|
| 33 |
+
prepare_graph,
|
| 34 |
+
)
|
| 35 |
+
|
| 36 |
+
# pylint: disable=C0302
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
@compile_mode("script")
|
| 40 |
+
class MACE(torch.nn.Module):
|
| 41 |
+
def __init__(
|
| 42 |
+
self,
|
| 43 |
+
r_max: float,
|
| 44 |
+
num_bessel: int,
|
| 45 |
+
num_polynomial_cutoff: int,
|
| 46 |
+
max_ell: int,
|
| 47 |
+
interaction_cls: Type[InteractionBlock],
|
| 48 |
+
interaction_cls_first: Type[InteractionBlock],
|
| 49 |
+
num_interactions: int,
|
| 50 |
+
num_elements: int,
|
| 51 |
+
hidden_irreps: o3.Irreps,
|
| 52 |
+
MLP_irreps: o3.Irreps,
|
| 53 |
+
atomic_energies: np.ndarray,
|
| 54 |
+
avg_num_neighbors: float,
|
| 55 |
+
atomic_numbers: List[int],
|
| 56 |
+
correlation: Union[int, List[int]],
|
| 57 |
+
gate: Optional[Callable],
|
| 58 |
+
pair_repulsion: bool = False,
|
| 59 |
+
distance_transform: str = "None",
|
| 60 |
+
radial_MLP: Optional[List[int]] = None,
|
| 61 |
+
radial_type: Optional[str] = "bessel",
|
| 62 |
+
heads: Optional[List[str]] = None,
|
| 63 |
+
cueq_config: Optional[Dict[str, Any]] = None,
|
| 64 |
+
lammps_mliap: Optional[bool] = False,
|
| 65 |
+
):
|
| 66 |
+
super().__init__()
|
| 67 |
+
self.register_buffer(
|
| 68 |
+
"atomic_numbers", torch.tensor(atomic_numbers, dtype=torch.int64)
|
| 69 |
+
)
|
| 70 |
+
self.register_buffer(
|
| 71 |
+
"r_max", torch.tensor(r_max, dtype=torch.get_default_dtype())
|
| 72 |
+
)
|
| 73 |
+
self.register_buffer(
|
| 74 |
+
"num_interactions", torch.tensor(num_interactions, dtype=torch.int64)
|
| 75 |
+
)
|
| 76 |
+
if heads is None:
|
| 77 |
+
heads = ["Default"]
|
| 78 |
+
self.heads = heads
|
| 79 |
+
if isinstance(correlation, int):
|
| 80 |
+
correlation = [correlation] * num_interactions
|
| 81 |
+
self.lammps_mliap = lammps_mliap
|
| 82 |
+
# Embedding
|
| 83 |
+
node_attr_irreps = o3.Irreps([(num_elements, (0, 1))])
|
| 84 |
+
node_feats_irreps = o3.Irreps([(hidden_irreps.count(o3.Irrep(0, 1)), (0, 1))])
|
| 85 |
+
self.node_embedding = LinearNodeEmbeddingBlock(
|
| 86 |
+
irreps_in=node_attr_irreps,
|
| 87 |
+
irreps_out=node_feats_irreps,
|
| 88 |
+
cueq_config=cueq_config,
|
| 89 |
+
)
|
| 90 |
+
self.radial_embedding = RadialEmbeddingBlock(
|
| 91 |
+
r_max=r_max,
|
| 92 |
+
num_bessel=num_bessel,
|
| 93 |
+
num_polynomial_cutoff=num_polynomial_cutoff,
|
| 94 |
+
radial_type=radial_type,
|
| 95 |
+
distance_transform=distance_transform,
|
| 96 |
+
)
|
| 97 |
+
edge_feats_irreps = o3.Irreps(f"{self.radial_embedding.out_dim}x0e")
|
| 98 |
+
if pair_repulsion:
|
| 99 |
+
self.pair_repulsion_fn = ZBLBasis(p=num_polynomial_cutoff)
|
| 100 |
+
self.pair_repulsion = True
|
| 101 |
+
|
| 102 |
+
sh_irreps = o3.Irreps.spherical_harmonics(max_ell)
|
| 103 |
+
num_features = hidden_irreps.count(o3.Irrep(0, 1))
|
| 104 |
+
interaction_irreps = (sh_irreps * num_features).sort()[0].simplify()
|
| 105 |
+
self.spherical_harmonics = o3.SphericalHarmonics(
|
| 106 |
+
sh_irreps, normalize=True, normalization="component"
|
| 107 |
+
)
|
| 108 |
+
if radial_MLP is None:
|
| 109 |
+
radial_MLP = [64, 64, 64]
|
| 110 |
+
# Interactions and readout
|
| 111 |
+
self.atomic_energies_fn = AtomicEnergiesBlock(atomic_energies)
|
| 112 |
+
|
| 113 |
+
inter = interaction_cls_first(
|
| 114 |
+
node_attrs_irreps=node_attr_irreps,
|
| 115 |
+
node_feats_irreps=node_feats_irreps,
|
| 116 |
+
edge_attrs_irreps=sh_irreps,
|
| 117 |
+
edge_feats_irreps=edge_feats_irreps,
|
| 118 |
+
target_irreps=interaction_irreps,
|
| 119 |
+
hidden_irreps=hidden_irreps,
|
| 120 |
+
avg_num_neighbors=avg_num_neighbors,
|
| 121 |
+
radial_MLP=radial_MLP,
|
| 122 |
+
cueq_config=cueq_config,
|
| 123 |
+
)
|
| 124 |
+
self.interactions = torch.nn.ModuleList([inter])
|
| 125 |
+
|
| 126 |
+
# Use the appropriate self connection at the first layer for proper E0
|
| 127 |
+
use_sc_first = False
|
| 128 |
+
if "Residual" in str(interaction_cls_first):
|
| 129 |
+
use_sc_first = True
|
| 130 |
+
|
| 131 |
+
node_feats_irreps_out = inter.target_irreps
|
| 132 |
+
prod = EquivariantProductBasisBlock(
|
| 133 |
+
node_feats_irreps=node_feats_irreps_out,
|
| 134 |
+
target_irreps=hidden_irreps,
|
| 135 |
+
correlation=correlation[0],
|
| 136 |
+
num_elements=num_elements,
|
| 137 |
+
use_sc=use_sc_first,
|
| 138 |
+
cueq_config=cueq_config,
|
| 139 |
+
)
|
| 140 |
+
self.products = torch.nn.ModuleList([prod])
|
| 141 |
+
|
| 142 |
+
self.readouts = torch.nn.ModuleList()
|
| 143 |
+
self.readouts.append(
|
| 144 |
+
LinearReadoutBlock(
|
| 145 |
+
hidden_irreps, o3.Irreps(f"{len(heads)}x0e"), cueq_config
|
| 146 |
+
)
|
| 147 |
+
)
|
| 148 |
+
|
| 149 |
+
for i in range(num_interactions - 1):
|
| 150 |
+
if i == num_interactions - 2:
|
| 151 |
+
hidden_irreps_out = str(
|
| 152 |
+
hidden_irreps[0]
|
| 153 |
+
) # Select only scalars for last layer
|
| 154 |
+
else:
|
| 155 |
+
hidden_irreps_out = hidden_irreps
|
| 156 |
+
inter = interaction_cls(
|
| 157 |
+
node_attrs_irreps=node_attr_irreps,
|
| 158 |
+
node_feats_irreps=hidden_irreps,
|
| 159 |
+
edge_attrs_irreps=sh_irreps,
|
| 160 |
+
edge_feats_irreps=edge_feats_irreps,
|
| 161 |
+
target_irreps=interaction_irreps,
|
| 162 |
+
hidden_irreps=hidden_irreps_out,
|
| 163 |
+
avg_num_neighbors=avg_num_neighbors,
|
| 164 |
+
radial_MLP=radial_MLP,
|
| 165 |
+
cueq_config=cueq_config,
|
| 166 |
+
)
|
| 167 |
+
self.interactions.append(inter)
|
| 168 |
+
prod = EquivariantProductBasisBlock(
|
| 169 |
+
node_feats_irreps=interaction_irreps,
|
| 170 |
+
target_irreps=hidden_irreps_out,
|
| 171 |
+
correlation=correlation[i + 1],
|
| 172 |
+
num_elements=num_elements,
|
| 173 |
+
use_sc=True,
|
| 174 |
+
cueq_config=cueq_config,
|
| 175 |
+
)
|
| 176 |
+
self.products.append(prod)
|
| 177 |
+
if i == num_interactions - 2:
|
| 178 |
+
self.readouts.append(
|
| 179 |
+
NonLinearReadoutBlock(
|
| 180 |
+
hidden_irreps_out,
|
| 181 |
+
(len(heads) * MLP_irreps).simplify(),
|
| 182 |
+
gate,
|
| 183 |
+
o3.Irreps(f"{len(heads)}x0e"),
|
| 184 |
+
len(heads),
|
| 185 |
+
cueq_config,
|
| 186 |
+
)
|
| 187 |
+
)
|
| 188 |
+
else:
|
| 189 |
+
self.readouts.append(
|
| 190 |
+
LinearReadoutBlock(
|
| 191 |
+
hidden_irreps, o3.Irreps(f"{len(heads)}x0e"), cueq_config
|
| 192 |
+
)
|
| 193 |
+
)
|
| 194 |
+
|
| 195 |
+
def forward(
|
| 196 |
+
self,
|
| 197 |
+
data: Dict[str, torch.Tensor],
|
| 198 |
+
training: bool = False,
|
| 199 |
+
compute_force: bool = True,
|
| 200 |
+
compute_virials: bool = False,
|
| 201 |
+
compute_stress: bool = False,
|
| 202 |
+
compute_displacement: bool = False,
|
| 203 |
+
compute_hessian: bool = False,
|
| 204 |
+
compute_edge_forces: bool = False,
|
| 205 |
+
compute_atomic_stresses: bool = False,
|
| 206 |
+
lammps_mliap: bool = False,
|
| 207 |
+
) -> Dict[str, Optional[torch.Tensor]]:
|
| 208 |
+
# Setup
|
| 209 |
+
ctx = prepare_graph(
|
| 210 |
+
data,
|
| 211 |
+
compute_virials=compute_virials,
|
| 212 |
+
compute_stress=compute_stress,
|
| 213 |
+
compute_displacement=compute_displacement,
|
| 214 |
+
lammps_mliap=lammps_mliap,
|
| 215 |
+
)
|
| 216 |
+
is_lammps = ctx.is_lammps
|
| 217 |
+
num_atoms_arange = ctx.num_atoms_arange
|
| 218 |
+
num_graphs = ctx.num_graphs
|
| 219 |
+
displacement = ctx.displacement
|
| 220 |
+
positions = ctx.positions
|
| 221 |
+
vectors = ctx.vectors
|
| 222 |
+
lengths = ctx.lengths
|
| 223 |
+
cell = ctx.cell
|
| 224 |
+
node_heads = ctx.node_heads
|
| 225 |
+
interaction_kwargs = ctx.interaction_kwargs
|
| 226 |
+
lammps_natoms = interaction_kwargs.lammps_natoms
|
| 227 |
+
lammps_class = interaction_kwargs.lammps_class
|
| 228 |
+
|
| 229 |
+
# Atomic energies
|
| 230 |
+
node_e0 = self.atomic_energies_fn(data["node_attrs"])[
|
| 231 |
+
num_atoms_arange, node_heads
|
| 232 |
+
]
|
| 233 |
+
e0 = scatter_sum(
|
| 234 |
+
src=node_e0, index=data["batch"], dim=0, dim_size=num_graphs
|
| 235 |
+
) # [n_graphs, n_heads]
|
| 236 |
+
# Embeddings
|
| 237 |
+
node_feats = self.node_embedding(data["node_attrs"])
|
| 238 |
+
edge_attrs = self.spherical_harmonics(vectors)
|
| 239 |
+
edge_feats = self.radial_embedding(
|
| 240 |
+
lengths, data["node_attrs"], data["edge_index"], self.atomic_numbers
|
| 241 |
+
)
|
| 242 |
+
if hasattr(self, "pair_repulsion"):
|
| 243 |
+
pair_node_energy = self.pair_repulsion_fn(
|
| 244 |
+
lengths, data["node_attrs"], data["edge_index"], self.atomic_numbers
|
| 245 |
+
)
|
| 246 |
+
if is_lammps:
|
| 247 |
+
pair_node_energy = pair_node_energy[: lammps_natoms[0]]
|
| 248 |
+
pair_energy = scatter_sum(
|
| 249 |
+
src=pair_node_energy, index=data["batch"], dim=-1, dim_size=num_graphs
|
| 250 |
+
) # [n_graphs,]
|
| 251 |
+
else:
|
| 252 |
+
pair_node_energy = torch.zeros_like(node_e0)
|
| 253 |
+
pair_energy = torch.zeros_like(e0)
|
| 254 |
+
|
| 255 |
+
# Interactions
|
| 256 |
+
energies = [e0, pair_energy]
|
| 257 |
+
node_energies_list = [node_e0, pair_node_energy]
|
| 258 |
+
node_feats_concat: List[torch.Tensor] = []
|
| 259 |
+
|
| 260 |
+
for i, (interaction, product, readout) in enumerate(
|
| 261 |
+
zip(self.interactions, self.products, self.readouts)
|
| 262 |
+
):
|
| 263 |
+
node_attrs_slice = data["node_attrs"]
|
| 264 |
+
if is_lammps and i > 0:
|
| 265 |
+
node_attrs_slice = node_attrs_slice[: lammps_natoms[0]]
|
| 266 |
+
node_feats, sc = interaction(
|
| 267 |
+
node_attrs=node_attrs_slice,
|
| 268 |
+
node_feats=node_feats,
|
| 269 |
+
edge_attrs=edge_attrs,
|
| 270 |
+
edge_feats=edge_feats,
|
| 271 |
+
edge_index=data["edge_index"],
|
| 272 |
+
first_layer=(i == 0),
|
| 273 |
+
lammps_class=lammps_class,
|
| 274 |
+
lammps_natoms=lammps_natoms,
|
| 275 |
+
)
|
| 276 |
+
if is_lammps and i == 0:
|
| 277 |
+
node_attrs_slice = node_attrs_slice[: lammps_natoms[0]]
|
| 278 |
+
node_feats = product(
|
| 279 |
+
node_feats=node_feats, sc=sc, node_attrs=node_attrs_slice
|
| 280 |
+
)
|
| 281 |
+
node_feats_concat.append(node_feats)
|
| 282 |
+
node_es = readout(node_feats, node_heads)[num_atoms_arange, node_heads]
|
| 283 |
+
energy = scatter_sum(node_es, data["batch"], dim=0, dim_size=num_graphs)
|
| 284 |
+
energies.append(energy)
|
| 285 |
+
node_energies_list.append(node_es)
|
| 286 |
+
|
| 287 |
+
contributions = torch.stack(energies, dim=-1)
|
| 288 |
+
total_energy = torch.sum(contributions, dim=-1)
|
| 289 |
+
node_energy = torch.sum(torch.stack(node_energies_list, dim=-1), dim=-1)
|
| 290 |
+
node_feats_out = torch.cat(node_feats_concat, dim=-1)
|
| 291 |
+
node_energy = node_e0.double() + pair_node_energy.double()
|
| 292 |
+
|
| 293 |
+
forces, virials, stress, hessian, edge_forces = get_outputs(
|
| 294 |
+
energy=total_energy,
|
| 295 |
+
positions=positions,
|
| 296 |
+
displacement=displacement,
|
| 297 |
+
vectors=vectors,
|
| 298 |
+
cell=cell,
|
| 299 |
+
training=training,
|
| 300 |
+
compute_force=compute_force,
|
| 301 |
+
compute_virials=compute_virials,
|
| 302 |
+
compute_stress=compute_stress,
|
| 303 |
+
compute_hessian=compute_hessian,
|
| 304 |
+
compute_edge_forces=compute_edge_forces,
|
| 305 |
+
)
|
| 306 |
+
|
| 307 |
+
atomic_virials: Optional[torch.Tensor] = None
|
| 308 |
+
atomic_stresses: Optional[torch.Tensor] = None
|
| 309 |
+
if compute_atomic_stresses and edge_forces is not None:
|
| 310 |
+
atomic_virials, atomic_stresses = get_atomic_virials_stresses(
|
| 311 |
+
edge_forces=edge_forces,
|
| 312 |
+
edge_index=data["edge_index"],
|
| 313 |
+
vectors=vectors,
|
| 314 |
+
num_atoms=positions.shape[0],
|
| 315 |
+
batch=data["batch"],
|
| 316 |
+
cell=cell,
|
| 317 |
+
)
|
| 318 |
+
return {
|
| 319 |
+
"energy": total_energy,
|
| 320 |
+
"node_energy": node_energy,
|
| 321 |
+
"contributions": contributions,
|
| 322 |
+
"forces": forces,
|
| 323 |
+
"edge_forces": edge_forces,
|
| 324 |
+
"virials": virials,
|
| 325 |
+
"stress": stress,
|
| 326 |
+
"atomic_virials": atomic_virials,
|
| 327 |
+
"atomic_stresses": atomic_stresses,
|
| 328 |
+
"displacement": displacement,
|
| 329 |
+
"hessian": hessian,
|
| 330 |
+
"node_feats": node_feats_out,
|
| 331 |
+
}
|
| 332 |
+
|
| 333 |
+
|
| 334 |
+
@compile_mode("script")
|
| 335 |
+
class ScaleShiftMACE(MACE):
|
| 336 |
+
def __init__(
|
| 337 |
+
self,
|
| 338 |
+
atomic_inter_scale: float,
|
| 339 |
+
atomic_inter_shift: float,
|
| 340 |
+
**kwargs,
|
| 341 |
+
):
|
| 342 |
+
super().__init__(**kwargs)
|
| 343 |
+
self.scale_shift = ScaleShiftBlock(
|
| 344 |
+
scale=atomic_inter_scale, shift=atomic_inter_shift
|
| 345 |
+
)
|
| 346 |
+
|
| 347 |
+
def forward(
|
| 348 |
+
self,
|
| 349 |
+
data: Dict[str, torch.Tensor],
|
| 350 |
+
training: bool = False,
|
| 351 |
+
compute_force: bool = True,
|
| 352 |
+
compute_virials: bool = False,
|
| 353 |
+
compute_stress: bool = False,
|
| 354 |
+
compute_displacement: bool = False,
|
| 355 |
+
compute_hessian: bool = False,
|
| 356 |
+
compute_edge_forces: bool = False,
|
| 357 |
+
compute_atomic_stresses: bool = False,
|
| 358 |
+
lammps_mliap: bool = False,
|
| 359 |
+
) -> Dict[str, Optional[torch.Tensor]]:
|
| 360 |
+
# Setup
|
| 361 |
+
ctx = prepare_graph(
|
| 362 |
+
data,
|
| 363 |
+
compute_virials=compute_virials,
|
| 364 |
+
compute_stress=compute_stress,
|
| 365 |
+
compute_displacement=compute_displacement,
|
| 366 |
+
lammps_mliap=lammps_mliap,
|
| 367 |
+
)
|
| 368 |
+
|
| 369 |
+
is_lammps = ctx.is_lammps
|
| 370 |
+
num_atoms_arange = ctx.num_atoms_arange
|
| 371 |
+
num_graphs = ctx.num_graphs
|
| 372 |
+
displacement = ctx.displacement
|
| 373 |
+
positions = ctx.positions
|
| 374 |
+
vectors = ctx.vectors
|
| 375 |
+
lengths = ctx.lengths
|
| 376 |
+
cell = ctx.cell
|
| 377 |
+
node_heads = ctx.node_heads
|
| 378 |
+
interaction_kwargs = ctx.interaction_kwargs
|
| 379 |
+
lammps_natoms = interaction_kwargs.lammps_natoms
|
| 380 |
+
lammps_class = interaction_kwargs.lammps_class
|
| 381 |
+
|
| 382 |
+
# Atomic energies
|
| 383 |
+
node_e0 = self.atomic_energies_fn(data["node_attrs"])[
|
| 384 |
+
num_atoms_arange, node_heads
|
| 385 |
+
]
|
| 386 |
+
e0 = scatter_sum(
|
| 387 |
+
src=node_e0, index=data["batch"], dim=0, dim_size=num_graphs
|
| 388 |
+
) # [n_graphs, num_heads]
|
| 389 |
+
|
| 390 |
+
# Embeddings
|
| 391 |
+
node_feats = self.node_embedding(data["node_attrs"])
|
| 392 |
+
edge_attrs = self.spherical_harmonics(vectors)
|
| 393 |
+
edge_feats = self.radial_embedding(
|
| 394 |
+
lengths, data["node_attrs"], data["edge_index"], self.atomic_numbers
|
| 395 |
+
)
|
| 396 |
+
|
| 397 |
+
if hasattr(self, "pair_repulsion"):
|
| 398 |
+
pair_node_energy = self.pair_repulsion_fn(
|
| 399 |
+
lengths, data["node_attrs"], data["edge_index"], self.atomic_numbers
|
| 400 |
+
)
|
| 401 |
+
if is_lammps:
|
| 402 |
+
pair_node_energy = pair_node_energy[: lammps_natoms[0]]
|
| 403 |
+
else:
|
| 404 |
+
pair_node_energy = torch.zeros_like(node_e0)
|
| 405 |
+
|
| 406 |
+
# Interactions
|
| 407 |
+
node_es_list = [pair_node_energy]
|
| 408 |
+
node_feats_list: List[torch.Tensor] = []
|
| 409 |
+
|
| 410 |
+
for i, (interaction, product, readout) in enumerate(
|
| 411 |
+
zip(self.interactions, self.products, self.readouts)
|
| 412 |
+
):
|
| 413 |
+
node_attrs_slice = data["node_attrs"]
|
| 414 |
+
if is_lammps and i > 0:
|
| 415 |
+
node_attrs_slice = node_attrs_slice[: lammps_natoms[0]]
|
| 416 |
+
node_feats, sc = interaction(
|
| 417 |
+
node_attrs=node_attrs_slice,
|
| 418 |
+
node_feats=node_feats,
|
| 419 |
+
edge_attrs=edge_attrs,
|
| 420 |
+
edge_feats=edge_feats,
|
| 421 |
+
edge_index=data["edge_index"],
|
| 422 |
+
first_layer=(i == 0),
|
| 423 |
+
lammps_class=lammps_class,
|
| 424 |
+
lammps_natoms=lammps_natoms,
|
| 425 |
+
)
|
| 426 |
+
if is_lammps and i == 0:
|
| 427 |
+
node_attrs_slice = node_attrs_slice[: lammps_natoms[0]]
|
| 428 |
+
node_feats = product(
|
| 429 |
+
node_feats=node_feats, sc=sc, node_attrs=node_attrs_slice
|
| 430 |
+
)
|
| 431 |
+
node_feats_list.append(node_feats)
|
| 432 |
+
node_es_list.append(
|
| 433 |
+
readout(node_feats, node_heads)[num_atoms_arange, node_heads]
|
| 434 |
+
)
|
| 435 |
+
|
| 436 |
+
node_feats_out = torch.cat(node_feats_list, dim=-1)
|
| 437 |
+
node_inter_es = torch.sum(torch.stack(node_es_list, dim=0), dim=0)
|
| 438 |
+
node_inter_es = self.scale_shift(node_inter_es, node_heads)
|
| 439 |
+
inter_e = scatter_sum(node_inter_es, data["batch"], dim=-1, dim_size=num_graphs)
|
| 440 |
+
|
| 441 |
+
total_energy = e0 + inter_e
|
| 442 |
+
node_energy = node_e0.clone().double() + node_inter_es.clone().double()
|
| 443 |
+
|
| 444 |
+
forces, virials, stress, hessian, edge_forces = get_outputs(
|
| 445 |
+
energy=inter_e,
|
| 446 |
+
positions=positions,
|
| 447 |
+
displacement=displacement,
|
| 448 |
+
vectors=vectors,
|
| 449 |
+
cell=cell,
|
| 450 |
+
training=training,
|
| 451 |
+
compute_force=compute_force,
|
| 452 |
+
compute_virials=compute_virials,
|
| 453 |
+
compute_stress=compute_stress,
|
| 454 |
+
compute_hessian=compute_hessian,
|
| 455 |
+
compute_edge_forces=compute_edge_forces or compute_atomic_stresses,
|
| 456 |
+
)
|
| 457 |
+
|
| 458 |
+
atomic_virials: Optional[torch.Tensor] = None
|
| 459 |
+
atomic_stresses: Optional[torch.Tensor] = None
|
| 460 |
+
if compute_atomic_stresses and edge_forces is not None:
|
| 461 |
+
atomic_virials, atomic_stresses = get_atomic_virials_stresses(
|
| 462 |
+
edge_forces=edge_forces,
|
| 463 |
+
edge_index=data["edge_index"],
|
| 464 |
+
vectors=vectors,
|
| 465 |
+
num_atoms=positions.shape[0],
|
| 466 |
+
batch=data["batch"],
|
| 467 |
+
cell=cell,
|
| 468 |
+
)
|
| 469 |
+
return {
|
| 470 |
+
"energy": total_energy,
|
| 471 |
+
"node_energy": node_energy,
|
| 472 |
+
"interaction_energy": inter_e,
|
| 473 |
+
"forces": forces,
|
| 474 |
+
"edge_forces": edge_forces,
|
| 475 |
+
"virials": virials,
|
| 476 |
+
"stress": stress,
|
| 477 |
+
"atomic_virials": atomic_virials,
|
| 478 |
+
"atomic_stresses": atomic_stresses,
|
| 479 |
+
"hessian": hessian,
|
| 480 |
+
"displacement": displacement,
|
| 481 |
+
"node_feats": node_feats_out,
|
| 482 |
+
}
|
| 483 |
+
|
| 484 |
+
|
| 485 |
+
@compile_mode("script")
|
| 486 |
+
class AtomicDipolesMACE(torch.nn.Module):
|
| 487 |
+
def __init__(
|
| 488 |
+
self,
|
| 489 |
+
r_max: float,
|
| 490 |
+
num_bessel: int,
|
| 491 |
+
num_polynomial_cutoff: int,
|
| 492 |
+
max_ell: int,
|
| 493 |
+
interaction_cls: Type[InteractionBlock],
|
| 494 |
+
interaction_cls_first: Type[InteractionBlock],
|
| 495 |
+
num_interactions: int,
|
| 496 |
+
num_elements: int,
|
| 497 |
+
hidden_irreps: o3.Irreps,
|
| 498 |
+
MLP_irreps: o3.Irreps,
|
| 499 |
+
avg_num_neighbors: float,
|
| 500 |
+
atomic_numbers: List[int],
|
| 501 |
+
correlation: int,
|
| 502 |
+
gate: Optional[Callable],
|
| 503 |
+
atomic_energies: Optional[
|
| 504 |
+
None
|
| 505 |
+
], # Just here to make it compatible with energy models, MUST be None
|
| 506 |
+
radial_type: Optional[str] = "bessel",
|
| 507 |
+
radial_MLP: Optional[List[int]] = None,
|
| 508 |
+
cueq_config: Optional[Dict[str, Any]] = None, # pylint: disable=unused-argument
|
| 509 |
+
):
|
| 510 |
+
super().__init__()
|
| 511 |
+
self.register_buffer(
|
| 512 |
+
"atomic_numbers", torch.tensor(atomic_numbers, dtype=torch.int64)
|
| 513 |
+
)
|
| 514 |
+
self.register_buffer("r_max", torch.tensor(r_max, dtype=torch.float64))
|
| 515 |
+
self.register_buffer(
|
| 516 |
+
"num_interactions", torch.tensor(num_interactions, dtype=torch.int64)
|
| 517 |
+
)
|
| 518 |
+
assert atomic_energies is None
|
| 519 |
+
|
| 520 |
+
# Embedding
|
| 521 |
+
node_attr_irreps = o3.Irreps([(num_elements, (0, 1))])
|
| 522 |
+
node_feats_irreps = o3.Irreps([(hidden_irreps.count(o3.Irrep(0, 1)), (0, 1))])
|
| 523 |
+
self.node_embedding = LinearNodeEmbeddingBlock(
|
| 524 |
+
irreps_in=node_attr_irreps, irreps_out=node_feats_irreps
|
| 525 |
+
)
|
| 526 |
+
self.radial_embedding = RadialEmbeddingBlock(
|
| 527 |
+
r_max=r_max,
|
| 528 |
+
num_bessel=num_bessel,
|
| 529 |
+
num_polynomial_cutoff=num_polynomial_cutoff,
|
| 530 |
+
radial_type=radial_type,
|
| 531 |
+
)
|
| 532 |
+
edge_feats_irreps = o3.Irreps(f"{self.radial_embedding.out_dim}x0e")
|
| 533 |
+
|
| 534 |
+
sh_irreps = o3.Irreps.spherical_harmonics(max_ell)
|
| 535 |
+
num_features = hidden_irreps.count(o3.Irrep(0, 1))
|
| 536 |
+
interaction_irreps = (sh_irreps * num_features).sort()[0].simplify()
|
| 537 |
+
self.spherical_harmonics = o3.SphericalHarmonics(
|
| 538 |
+
sh_irreps, normalize=True, normalization="component"
|
| 539 |
+
)
|
| 540 |
+
if radial_MLP is None:
|
| 541 |
+
radial_MLP = [64, 64, 64]
|
| 542 |
+
|
| 543 |
+
# Interactions and readouts
|
| 544 |
+
inter = interaction_cls_first(
|
| 545 |
+
node_attrs_irreps=node_attr_irreps,
|
| 546 |
+
node_feats_irreps=node_feats_irreps,
|
| 547 |
+
edge_attrs_irreps=sh_irreps,
|
| 548 |
+
edge_feats_irreps=edge_feats_irreps,
|
| 549 |
+
target_irreps=interaction_irreps,
|
| 550 |
+
hidden_irreps=hidden_irreps,
|
| 551 |
+
avg_num_neighbors=avg_num_neighbors,
|
| 552 |
+
radial_MLP=radial_MLP,
|
| 553 |
+
)
|
| 554 |
+
self.interactions = torch.nn.ModuleList([inter])
|
| 555 |
+
|
| 556 |
+
# Use the appropriate self connection at the first layer
|
| 557 |
+
use_sc_first = False
|
| 558 |
+
if "Residual" in str(interaction_cls_first):
|
| 559 |
+
use_sc_first = True
|
| 560 |
+
|
| 561 |
+
node_feats_irreps_out = inter.target_irreps
|
| 562 |
+
prod = EquivariantProductBasisBlock(
|
| 563 |
+
node_feats_irreps=node_feats_irreps_out,
|
| 564 |
+
target_irreps=hidden_irreps,
|
| 565 |
+
correlation=correlation,
|
| 566 |
+
num_elements=num_elements,
|
| 567 |
+
use_sc=use_sc_first,
|
| 568 |
+
)
|
| 569 |
+
self.products = torch.nn.ModuleList([prod])
|
| 570 |
+
|
| 571 |
+
self.readouts = torch.nn.ModuleList()
|
| 572 |
+
self.readouts.append(LinearDipoleReadoutBlock(hidden_irreps, dipole_only=True))
|
| 573 |
+
|
| 574 |
+
for i in range(num_interactions - 1):
|
| 575 |
+
if i == num_interactions - 2:
|
| 576 |
+
assert (
|
| 577 |
+
len(hidden_irreps) > 1
|
| 578 |
+
), "To predict dipoles use at least l=1 hidden_irreps"
|
| 579 |
+
hidden_irreps_out = str(
|
| 580 |
+
hidden_irreps[1]
|
| 581 |
+
) # Select only l=1 vectors for last layer
|
| 582 |
+
else:
|
| 583 |
+
hidden_irreps_out = hidden_irreps
|
| 584 |
+
inter = interaction_cls(
|
| 585 |
+
node_attrs_irreps=node_attr_irreps,
|
| 586 |
+
node_feats_irreps=hidden_irreps,
|
| 587 |
+
edge_attrs_irreps=sh_irreps,
|
| 588 |
+
edge_feats_irreps=edge_feats_irreps,
|
| 589 |
+
target_irreps=interaction_irreps,
|
| 590 |
+
hidden_irreps=hidden_irreps_out,
|
| 591 |
+
avg_num_neighbors=avg_num_neighbors,
|
| 592 |
+
radial_MLP=radial_MLP,
|
| 593 |
+
)
|
| 594 |
+
self.interactions.append(inter)
|
| 595 |
+
prod = EquivariantProductBasisBlock(
|
| 596 |
+
node_feats_irreps=interaction_irreps,
|
| 597 |
+
target_irreps=hidden_irreps_out,
|
| 598 |
+
correlation=correlation,
|
| 599 |
+
num_elements=num_elements,
|
| 600 |
+
use_sc=True,
|
| 601 |
+
)
|
| 602 |
+
self.products.append(prod)
|
| 603 |
+
if i == num_interactions - 2:
|
| 604 |
+
self.readouts.append(
|
| 605 |
+
NonLinearDipoleReadoutBlock(
|
| 606 |
+
hidden_irreps_out, MLP_irreps, gate, dipole_only=True
|
| 607 |
+
)
|
| 608 |
+
)
|
| 609 |
+
else:
|
| 610 |
+
self.readouts.append(
|
| 611 |
+
LinearDipoleReadoutBlock(hidden_irreps, dipole_only=True)
|
| 612 |
+
)
|
| 613 |
+
|
| 614 |
+
def forward(
|
| 615 |
+
self,
|
| 616 |
+
data: Dict[str, torch.Tensor],
|
| 617 |
+
training: bool = False, # pylint: disable=W0613
|
| 618 |
+
compute_force: bool = False,
|
| 619 |
+
compute_virials: bool = False,
|
| 620 |
+
compute_stress: bool = False,
|
| 621 |
+
compute_displacement: bool = False,
|
| 622 |
+
compute_edge_forces: bool = False, # pylint: disable=W0613
|
| 623 |
+
compute_atomic_stresses: bool = False, # pylint: disable=W0613
|
| 624 |
+
) -> Dict[str, Optional[torch.Tensor]]:
|
| 625 |
+
assert compute_force is False
|
| 626 |
+
assert compute_virials is False
|
| 627 |
+
assert compute_stress is False
|
| 628 |
+
assert compute_displacement is False
|
| 629 |
+
# Setup
|
| 630 |
+
data["node_attrs"].requires_grad_(True)
|
| 631 |
+
data["positions"].requires_grad_(True)
|
| 632 |
+
num_graphs = data["ptr"].numel() - 1
|
| 633 |
+
|
| 634 |
+
# Embeddings
|
| 635 |
+
node_feats = self.node_embedding(data["node_attrs"])
|
| 636 |
+
vectors, lengths = get_edge_vectors_and_lengths(
|
| 637 |
+
positions=data["positions"],
|
| 638 |
+
edge_index=data["edge_index"],
|
| 639 |
+
shifts=data["shifts"],
|
| 640 |
+
)
|
| 641 |
+
edge_attrs = self.spherical_harmonics(vectors)
|
| 642 |
+
edge_feats = self.radial_embedding(
|
| 643 |
+
lengths, data["node_attrs"], data["edge_index"], self.atomic_numbers
|
| 644 |
+
)
|
| 645 |
+
|
| 646 |
+
# Interactions
|
| 647 |
+
dipoles = []
|
| 648 |
+
for interaction, product, readout in zip(
|
| 649 |
+
self.interactions, self.products, self.readouts
|
| 650 |
+
):
|
| 651 |
+
node_feats, sc = interaction(
|
| 652 |
+
node_attrs=data["node_attrs"],
|
| 653 |
+
node_feats=node_feats,
|
| 654 |
+
edge_attrs=edge_attrs,
|
| 655 |
+
edge_feats=edge_feats,
|
| 656 |
+
edge_index=data["edge_index"],
|
| 657 |
+
)
|
| 658 |
+
node_feats = product(
|
| 659 |
+
node_feats=node_feats,
|
| 660 |
+
sc=sc,
|
| 661 |
+
node_attrs=data["node_attrs"],
|
| 662 |
+
)
|
| 663 |
+
node_dipoles = readout(node_feats).squeeze(-1) # [n_nodes,3]
|
| 664 |
+
dipoles.append(node_dipoles)
|
| 665 |
+
|
| 666 |
+
# Compute the dipoles
|
| 667 |
+
contributions_dipoles = torch.stack(
|
| 668 |
+
dipoles, dim=-1
|
| 669 |
+
) # [n_nodes,3,n_contributions]
|
| 670 |
+
atomic_dipoles = torch.sum(contributions_dipoles, dim=-1) # [n_nodes,3]
|
| 671 |
+
total_dipole = scatter_sum(
|
| 672 |
+
src=atomic_dipoles,
|
| 673 |
+
index=data["batch"],
|
| 674 |
+
dim=0,
|
| 675 |
+
dim_size=num_graphs,
|
| 676 |
+
) # [n_graphs,3]
|
| 677 |
+
baseline = compute_fixed_charge_dipole(
|
| 678 |
+
charges=data["charges"],
|
| 679 |
+
positions=data["positions"],
|
| 680 |
+
batch=data["batch"],
|
| 681 |
+
num_graphs=num_graphs,
|
| 682 |
+
) # [n_graphs,3]
|
| 683 |
+
total_dipole = total_dipole + baseline
|
| 684 |
+
|
| 685 |
+
output = {
|
| 686 |
+
"dipole": total_dipole,
|
| 687 |
+
"atomic_dipoles": atomic_dipoles,
|
| 688 |
+
}
|
| 689 |
+
return output
|
| 690 |
+
|
| 691 |
+
|
| 692 |
+
@compile_mode("script")
|
| 693 |
+
class EnergyDipolesMACE(torch.nn.Module):
|
| 694 |
+
def __init__(
|
| 695 |
+
self,
|
| 696 |
+
r_max: float,
|
| 697 |
+
num_bessel: int,
|
| 698 |
+
num_polynomial_cutoff: int,
|
| 699 |
+
max_ell: int,
|
| 700 |
+
interaction_cls: Type[InteractionBlock],
|
| 701 |
+
interaction_cls_first: Type[InteractionBlock],
|
| 702 |
+
num_interactions: int,
|
| 703 |
+
num_elements: int,
|
| 704 |
+
hidden_irreps: o3.Irreps,
|
| 705 |
+
MLP_irreps: o3.Irreps,
|
| 706 |
+
avg_num_neighbors: float,
|
| 707 |
+
atomic_numbers: List[int],
|
| 708 |
+
correlation: int,
|
| 709 |
+
gate: Optional[Callable],
|
| 710 |
+
atomic_energies: Optional[np.ndarray],
|
| 711 |
+
radial_MLP: Optional[List[int]] = None,
|
| 712 |
+
cueq_config: Optional[Dict[str, Any]] = None, # pylint: disable=unused-argument
|
| 713 |
+
):
|
| 714 |
+
super().__init__()
|
| 715 |
+
self.register_buffer(
|
| 716 |
+
"atomic_numbers", torch.tensor(atomic_numbers, dtype=torch.int64)
|
| 717 |
+
)
|
| 718 |
+
self.register_buffer("r_max", torch.tensor(r_max, dtype=torch.float64))
|
| 719 |
+
self.register_buffer(
|
| 720 |
+
"num_interactions", torch.tensor(num_interactions, dtype=torch.int64)
|
| 721 |
+
)
|
| 722 |
+
# Embedding
|
| 723 |
+
node_attr_irreps = o3.Irreps([(num_elements, (0, 1))])
|
| 724 |
+
node_feats_irreps = o3.Irreps([(hidden_irreps.count(o3.Irrep(0, 1)), (0, 1))])
|
| 725 |
+
self.node_embedding = LinearNodeEmbeddingBlock(
|
| 726 |
+
irreps_in=node_attr_irreps, irreps_out=node_feats_irreps
|
| 727 |
+
)
|
| 728 |
+
self.radial_embedding = RadialEmbeddingBlock(
|
| 729 |
+
r_max=r_max,
|
| 730 |
+
num_bessel=num_bessel,
|
| 731 |
+
num_polynomial_cutoff=num_polynomial_cutoff,
|
| 732 |
+
)
|
| 733 |
+
edge_feats_irreps = o3.Irreps(f"{self.radial_embedding.out_dim}x0e")
|
| 734 |
+
|
| 735 |
+
sh_irreps = o3.Irreps.spherical_harmonics(max_ell)
|
| 736 |
+
num_features = hidden_irreps.count(o3.Irrep(0, 1))
|
| 737 |
+
interaction_irreps = (sh_irreps * num_features).sort()[0].simplify()
|
| 738 |
+
self.spherical_harmonics = o3.SphericalHarmonics(
|
| 739 |
+
sh_irreps, normalize=True, normalization="component"
|
| 740 |
+
)
|
| 741 |
+
if radial_MLP is None:
|
| 742 |
+
radial_MLP = [64, 64, 64]
|
| 743 |
+
# Interactions and readouts
|
| 744 |
+
self.atomic_energies_fn = AtomicEnergiesBlock(atomic_energies)
|
| 745 |
+
|
| 746 |
+
inter = interaction_cls_first(
|
| 747 |
+
node_attrs_irreps=node_attr_irreps,
|
| 748 |
+
node_feats_irreps=node_feats_irreps,
|
| 749 |
+
edge_attrs_irreps=sh_irreps,
|
| 750 |
+
edge_feats_irreps=edge_feats_irreps,
|
| 751 |
+
target_irreps=interaction_irreps,
|
| 752 |
+
hidden_irreps=hidden_irreps,
|
| 753 |
+
avg_num_neighbors=avg_num_neighbors,
|
| 754 |
+
radial_MLP=radial_MLP,
|
| 755 |
+
)
|
| 756 |
+
self.interactions = torch.nn.ModuleList([inter])
|
| 757 |
+
|
| 758 |
+
# Use the appropriate self connection at the first layer
|
| 759 |
+
use_sc_first = False
|
| 760 |
+
if "Residual" in str(interaction_cls_first):
|
| 761 |
+
use_sc_first = True
|
| 762 |
+
|
| 763 |
+
node_feats_irreps_out = inter.target_irreps
|
| 764 |
+
prod = EquivariantProductBasisBlock(
|
| 765 |
+
node_feats_irreps=node_feats_irreps_out,
|
| 766 |
+
target_irreps=hidden_irreps,
|
| 767 |
+
correlation=correlation,
|
| 768 |
+
num_elements=num_elements,
|
| 769 |
+
use_sc=use_sc_first,
|
| 770 |
+
)
|
| 771 |
+
self.products = torch.nn.ModuleList([prod])
|
| 772 |
+
|
| 773 |
+
self.readouts = torch.nn.ModuleList()
|
| 774 |
+
self.readouts.append(LinearDipoleReadoutBlock(hidden_irreps, dipole_only=False))
|
| 775 |
+
|
| 776 |
+
for i in range(num_interactions - 1):
|
| 777 |
+
if i == num_interactions - 2:
|
| 778 |
+
assert (
|
| 779 |
+
len(hidden_irreps) > 1
|
| 780 |
+
), "To predict dipoles use at least l=1 hidden_irreps"
|
| 781 |
+
hidden_irreps_out = str(
|
| 782 |
+
hidden_irreps[:2]
|
| 783 |
+
) # Select scalars and l=1 vectors for last layer
|
| 784 |
+
else:
|
| 785 |
+
hidden_irreps_out = hidden_irreps
|
| 786 |
+
inter = interaction_cls(
|
| 787 |
+
node_attrs_irreps=node_attr_irreps,
|
| 788 |
+
node_feats_irreps=hidden_irreps,
|
| 789 |
+
edge_attrs_irreps=sh_irreps,
|
| 790 |
+
edge_feats_irreps=edge_feats_irreps,
|
| 791 |
+
target_irreps=interaction_irreps,
|
| 792 |
+
hidden_irreps=hidden_irreps_out,
|
| 793 |
+
avg_num_neighbors=avg_num_neighbors,
|
| 794 |
+
radial_MLP=radial_MLP,
|
| 795 |
+
)
|
| 796 |
+
self.interactions.append(inter)
|
| 797 |
+
prod = EquivariantProductBasisBlock(
|
| 798 |
+
node_feats_irreps=interaction_irreps,
|
| 799 |
+
target_irreps=hidden_irreps_out,
|
| 800 |
+
correlation=correlation,
|
| 801 |
+
num_elements=num_elements,
|
| 802 |
+
use_sc=True,
|
| 803 |
+
)
|
| 804 |
+
self.products.append(prod)
|
| 805 |
+
if i == num_interactions - 2:
|
| 806 |
+
self.readouts.append(
|
| 807 |
+
NonLinearDipoleReadoutBlock(
|
| 808 |
+
hidden_irreps_out, MLP_irreps, gate, dipole_only=False
|
| 809 |
+
)
|
| 810 |
+
)
|
| 811 |
+
else:
|
| 812 |
+
self.readouts.append(
|
| 813 |
+
LinearDipoleReadoutBlock(hidden_irreps, dipole_only=False)
|
| 814 |
+
)
|
| 815 |
+
|
| 816 |
+
def forward(
|
| 817 |
+
self,
|
| 818 |
+
data: Dict[str, torch.Tensor],
|
| 819 |
+
training: bool = False,
|
| 820 |
+
compute_force: bool = True,
|
| 821 |
+
compute_virials: bool = False,
|
| 822 |
+
compute_stress: bool = False,
|
| 823 |
+
compute_displacement: bool = False,
|
| 824 |
+
compute_edge_forces: bool = False, # pylint: disable=W0613
|
| 825 |
+
compute_atomic_stresses: bool = False, # pylint: disable=W0613
|
| 826 |
+
) -> Dict[str, Optional[torch.Tensor]]:
|
| 827 |
+
# Setup
|
| 828 |
+
data["node_attrs"].requires_grad_(True)
|
| 829 |
+
data["positions"].requires_grad_(True)
|
| 830 |
+
num_graphs = data["ptr"].numel() - 1
|
| 831 |
+
num_atoms_arange = torch.arange(data["positions"].shape[0])
|
| 832 |
+
displacement = torch.zeros(
|
| 833 |
+
(num_graphs, 3, 3),
|
| 834 |
+
dtype=data["positions"].dtype,
|
| 835 |
+
device=data["positions"].device,
|
| 836 |
+
)
|
| 837 |
+
if compute_virials or compute_stress or compute_displacement:
|
| 838 |
+
(
|
| 839 |
+
data["positions"],
|
| 840 |
+
data["shifts"],
|
| 841 |
+
displacement,
|
| 842 |
+
) = get_symmetric_displacement(
|
| 843 |
+
positions=data["positions"],
|
| 844 |
+
unit_shifts=data["unit_shifts"],
|
| 845 |
+
cell=data["cell"],
|
| 846 |
+
edge_index=data["edge_index"],
|
| 847 |
+
num_graphs=num_graphs,
|
| 848 |
+
batch=data["batch"],
|
| 849 |
+
)
|
| 850 |
+
|
| 851 |
+
# Atomic energies
|
| 852 |
+
node_e0 = self.atomic_energies_fn(data["node_attrs"])[
|
| 853 |
+
num_atoms_arange, data["head"][data["batch"]]
|
| 854 |
+
]
|
| 855 |
+
e0 = scatter_sum(
|
| 856 |
+
src=node_e0, index=data["batch"], dim=-1, dim_size=num_graphs
|
| 857 |
+
) # [n_graphs,]
|
| 858 |
+
|
| 859 |
+
# Embeddings
|
| 860 |
+
node_feats = self.node_embedding(data["node_attrs"])
|
| 861 |
+
vectors, lengths = get_edge_vectors_and_lengths(
|
| 862 |
+
positions=data["positions"],
|
| 863 |
+
edge_index=data["edge_index"],
|
| 864 |
+
shifts=data["shifts"],
|
| 865 |
+
)
|
| 866 |
+
edge_attrs = self.spherical_harmonics(vectors)
|
| 867 |
+
edge_feats = self.radial_embedding(
|
| 868 |
+
lengths, data["node_attrs"], data["edge_index"], self.atomic_numbers
|
| 869 |
+
)
|
| 870 |
+
|
| 871 |
+
# Interactions
|
| 872 |
+
energies = [e0]
|
| 873 |
+
node_energies_list = [node_e0]
|
| 874 |
+
dipoles = []
|
| 875 |
+
for interaction, product, readout in zip(
|
| 876 |
+
self.interactions, self.products, self.readouts
|
| 877 |
+
):
|
| 878 |
+
node_feats, sc = interaction(
|
| 879 |
+
node_attrs=data["node_attrs"],
|
| 880 |
+
node_feats=node_feats,
|
| 881 |
+
edge_attrs=edge_attrs,
|
| 882 |
+
edge_feats=edge_feats,
|
| 883 |
+
edge_index=data["edge_index"],
|
| 884 |
+
)
|
| 885 |
+
node_feats = product(
|
| 886 |
+
node_feats=node_feats,
|
| 887 |
+
sc=sc,
|
| 888 |
+
node_attrs=data["node_attrs"],
|
| 889 |
+
)
|
| 890 |
+
node_out = readout(node_feats).squeeze(-1) # [n_nodes, ]
|
| 891 |
+
# node_energies = readout(node_feats).squeeze(-1) # [n_nodes, ]
|
| 892 |
+
node_energies = node_out[:, 0]
|
| 893 |
+
energy = scatter_sum(
|
| 894 |
+
src=node_energies, index=data["batch"], dim=-1, dim_size=num_graphs
|
| 895 |
+
) # [n_graphs,]
|
| 896 |
+
energies.append(energy)
|
| 897 |
+
node_dipoles = node_out[:, 1:]
|
| 898 |
+
dipoles.append(node_dipoles)
|
| 899 |
+
|
| 900 |
+
# Compute the energies and dipoles
|
| 901 |
+
contributions = torch.stack(energies, dim=-1)
|
| 902 |
+
total_energy = torch.sum(contributions, dim=-1) # [n_graphs, ]
|
| 903 |
+
node_energy_contributions = torch.stack(node_energies_list, dim=-1)
|
| 904 |
+
node_energy = torch.sum(node_energy_contributions, dim=-1) # [n_nodes, ]
|
| 905 |
+
contributions_dipoles = torch.stack(
|
| 906 |
+
dipoles, dim=-1
|
| 907 |
+
) # [n_nodes,3,n_contributions]
|
| 908 |
+
atomic_dipoles = torch.sum(contributions_dipoles, dim=-1) # [n_nodes,3]
|
| 909 |
+
total_dipole = scatter_sum(
|
| 910 |
+
src=atomic_dipoles,
|
| 911 |
+
index=data["batch"].unsqueeze(-1),
|
| 912 |
+
dim=0,
|
| 913 |
+
dim_size=num_graphs,
|
| 914 |
+
) # [n_graphs,3]
|
| 915 |
+
baseline = compute_fixed_charge_dipole(
|
| 916 |
+
charges=data["charges"],
|
| 917 |
+
positions=data["positions"],
|
| 918 |
+
batch=data["batch"],
|
| 919 |
+
num_graphs=num_graphs,
|
| 920 |
+
) # [n_graphs,3]
|
| 921 |
+
total_dipole = total_dipole + baseline
|
| 922 |
+
|
| 923 |
+
forces, virials, stress, _, _ = get_outputs(
|
| 924 |
+
energy=total_energy,
|
| 925 |
+
positions=data["positions"],
|
| 926 |
+
displacement=displacement,
|
| 927 |
+
cell=data["cell"],
|
| 928 |
+
training=training,
|
| 929 |
+
compute_force=compute_force,
|
| 930 |
+
compute_virials=compute_virials,
|
| 931 |
+
compute_stress=compute_stress,
|
| 932 |
+
)
|
| 933 |
+
|
| 934 |
+
output = {
|
| 935 |
+
"energy": total_energy,
|
| 936 |
+
"node_energy": node_energy,
|
| 937 |
+
"contributions": contributions,
|
| 938 |
+
"forces": forces,
|
| 939 |
+
"virials": virials,
|
| 940 |
+
"stress": stress,
|
| 941 |
+
"displacement": displacement,
|
| 942 |
+
"dipole": total_dipole,
|
| 943 |
+
"atomic_dipoles": atomic_dipoles,
|
| 944 |
+
}
|
| 945 |
+
return output
|
model/py.typed
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
|
scripts/demo/README.md
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# MACE 训练 Demo
|
| 2 |
+
|
| 3 |
+
## 快速上手
|
| 4 |
+
|
| 5 |
+
### 1. 选择配置
|
| 6 |
+
|
| 7 |
+
`configs/` 目录下提供了多个预定义的实验配置:
|
| 8 |
+
|
| 9 |
+
| 配置文件 | 数据集 | GPU 数 | 说明 |
|
| 10 |
+
| ------------------------------- | ------------ | ------ | ---------------- |
|
| 11 |
+
| `DMC.yaml` | DMC 溶剂 XTB | 1 | 入门示例,最简单 |
|
| 12 |
+
| `water_1dcu.yaml` | Water | 1 | 单卡训练 |
|
| 13 |
+
| `water_4dcu.yaml` | Water | 4 | 4 卡分布式训练 |
|
| 14 |
+
| `water_8dcu.yaml` | Water | 8 | 8 卡分布式训练 |
|
| 15 |
+
| `ani1x_8dcu.yaml` | ANI-1x | 8 | 分布式训练 |
|
| 16 |
+
| `nanotube_l0_8dcu.yaml` | 碳纳米管 | 8 | max_L=0 |
|
| 17 |
+
| `nanotube_l2_8dcu.yaml` | 碳纳米管 | 8 | max_L=2 |
|
| 18 |
+
| `nanotube_l2_16dcu.yaml` | 碳纳米管 | 2x8 | 多节点分布式 |
|
| 19 |
+
|
| 20 |
+
### 2. 运行训练
|
| 21 |
+
|
| 22 |
+
```bash
|
| 23 |
+
# 方式一:直接运行(交互式或在已分配的 SLURM 节点上)
|
| 24 |
+
bash run.sh --config configs/DMC.yaml
|
| 25 |
+
|
| 26 |
+
# 方式二:提交 SLURM 作业
|
| 27 |
+
bash run.sh --config configs/DMC.yaml --submit
|
| 28 |
+
|
| 29 |
+
# 方式三:预览命令(不执行)
|
| 30 |
+
bash run.sh --config configs/DMC.yaml --dry-run
|
| 31 |
+
```
|
| 32 |
+
|
| 33 |
+
### 3. 查看输出
|
| 34 |
+
|
| 35 |
+
训练输出自动保存到 `outputs/{实验名}_{时间戳}/` 目录,包含:
|
| 36 |
+
- 模型 checkpoint
|
| 37 |
+
- 训练日志
|
| 38 |
+
- 当次使用的配置快照 (`config.yaml`)
|
| 39 |
+
|
| 40 |
+
## 创建自定义实验
|
| 41 |
+
|
| 42 |
+
1. 复制一个最接近的配置文件:
|
| 43 |
+
```bash
|
| 44 |
+
cp configs/DMC.yaml configs/my_experiment.yaml
|
| 45 |
+
```
|
| 46 |
+
|
| 47 |
+
2. 编辑 YAML 中的参数(只需改参数值,不用碰任何 shell 脚本)
|
| 48 |
+
|
| 49 |
+
3. 运行:
|
| 50 |
+
```bash
|
| 51 |
+
bash run.sh --config configs/my_experiment.yaml
|
| 52 |
+
```
|
| 53 |
+
|
| 54 |
+
## YAML 配置字段说明
|
| 55 |
+
|
| 56 |
+
### `train_args` - 训练参数
|
| 57 |
+
|
| 58 |
+
所有字段直接映射为 `train.py` 的命令行参数。布尔值 `true` 转为标志参数(如 `swa: true` -> `--swa`),`false` 则跳过。
|
| 59 |
+
|
| 60 |
+
常用参数:
|
| 61 |
+
|
| 62 |
+
| 参数 | 说明 | 示例 |
|
| 63 |
+
| -------------- | ---------------- | ----------------------------- |
|
| 64 |
+
| `model` | 模型类型 | `MACE` |
|
| 65 |
+
| `r_max` | 截断半径 (A) | `4.0` - `6.0` |
|
| 66 |
+
| `num_channels` | 通道数 | `64`, `256` |
|
| 67 |
+
| `max_L` | 最大角动量量子数 | `0`, `2` |
|
| 68 |
+
| `batch_size` | 训练批大小 | `2` - `128` |
|
| 69 |
+
| `E0s` | 原子参考能量 | `average`, `isolated`, 显式字典 |
|
| 70 |
+
| `swa` | 启用随机权重平均 | `true` |
|
| 71 |
+
| `ema` | 启用指数移动平均 | `true` |
|
| 72 |
+
| `distributed` | 启用分布式训练 | `true` (多卡时自动添加) |
|
| 73 |
+
|
| 74 |
+
### `launch` - 启动配置
|
| 75 |
+
|
| 76 |
+
| 参数 | 说明 | 启动方式 |
|
| 77 |
+
| ------------------------------- | ---------- | ------------------------------ |
|
| 78 |
+
| `num_nodes: 1, num_gpus: 1` | 单卡 | `python train.py` |
|
| 79 |
+
| `num_nodes: 1, num_gpus: N` | 单节点多卡 | `torchrun --nproc_per_node=N` |
|
| 80 |
+
| `num_nodes: M, num_gpus: N` | 多节点 | `srun` (需 --submit) |
|
| 81 |
+
|
| 82 |
+
### `env` - 环境配置
|
| 83 |
+
|
| 84 |
+
| 参数 | 说明 |
|
| 85 |
+
| ----------- | ----------------------- |
|
| 86 |
+
| `conda_env` | conda 环境名 |
|
| 87 |
+
| `modules` | 需要加载的 module 列表 |
|
| 88 |
+
|
| 89 |
+
### `slurm` - SLURM 作业配置
|
| 90 |
+
|
| 91 |
+
| 参数 | 说明 |
|
| 92 |
+
| --------------- | -------------- |
|
| 93 |
+
| `partition` | SLURM 分区 |
|
| 94 |
+
| `time` | 作业时间限制 |
|
| 95 |
+
| `cpus_per_task` | CPU 核心数 |
|
| 96 |
+
|
| 97 |
+
### `nccl` - 多节点通信配置(可选)
|
| 98 |
+
|
| 99 |
+
| 参数 | 说明 |
|
| 100 |
+
| --------------- | ------------------- |
|
| 101 |
+
| `socket_ifname` | InfiniBand 网卡名 |
|
| 102 |
+
| `ib_hca` | IB HCA 设备名 |
|
| 103 |
+
| `proto` | NCCL 协议 |
|
| 104 |
+
|
| 105 |
+
## 目录结构
|
| 106 |
+
|
| 107 |
+
```
|
| 108 |
+
demo/
|
| 109 |
+
run.sh # 统一入口脚本
|
| 110 |
+
_parse_config.py # 配置解析器(内部使用)
|
| 111 |
+
README.md # 本文件
|
| 112 |
+
configs/ # 实验配置
|
| 113 |
+
templates/ # 脚本模板
|
| 114 |
+
env_setup.sh # 环境初始化
|
| 115 |
+
preflight_check.sh # 训练前预检
|
| 116 |
+
slurm_header.template # SLURM header 模板
|
| 117 |
+
outputs/ # 训练输出(自动创建)
|
| 118 |
+
```
|
scripts/demo/_parse_config.py
ADDED
|
@@ -0,0 +1,304 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Parse MACE demo YAML and print derived values for run.sh.
|
| 3 |
+
|
| 4 |
+
Usage:
|
| 5 |
+
python _parse_config.py <config.yaml> <action>
|
| 6 |
+
|
| 7 |
+
Actions:
|
| 8 |
+
command Print launch command
|
| 9 |
+
env Print environment exports
|
| 10 |
+
env-args Print arguments for env_setup.sh
|
| 11 |
+
data-files Print input files for preflight check
|
| 12 |
+
slurm Print SLURM variables
|
| 13 |
+
name Print experiment name
|
| 14 |
+
"""
|
| 15 |
+
|
| 16 |
+
from __future__ import annotations
|
| 17 |
+
|
| 18 |
+
import os
|
| 19 |
+
import shlex
|
| 20 |
+
import sys
|
| 21 |
+
from typing import Any
|
| 22 |
+
|
| 23 |
+
import yaml
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def load_config(path: str) -> dict[str, Any]:
|
| 27 |
+
with open(path, encoding="utf-8") as f:
|
| 28 |
+
return yaml.safe_load(f) or {}
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def _needs_quoting(value: str) -> bool:
|
| 32 |
+
special = set(" {}()[]|&;'\"\\!`<>*?~#")
|
| 33 |
+
return any(c in special for c in value)
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
def _quote_value(value: Any) -> str:
|
| 37 |
+
s = str(value)
|
| 38 |
+
if "$" in s:
|
| 39 |
+
escaped = s.replace("\\", "\\\\").replace('"', '\\"')
|
| 40 |
+
return f'"{escaped}"'
|
| 41 |
+
return shlex.quote(s)
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
def _build_cli_args(arg_map: dict[str, Any], auto_name: str | None = None) -> list[str]:
|
| 45 |
+
args: list[str] = []
|
| 46 |
+
if auto_name and "name" not in arg_map:
|
| 47 |
+
args.append(f"--name={_quote_value(auto_name)}")
|
| 48 |
+
|
| 49 |
+
for key, value in arg_map.items():
|
| 50 |
+
if isinstance(value, bool):
|
| 51 |
+
if value:
|
| 52 |
+
args.append(f"--{key}")
|
| 53 |
+
continue
|
| 54 |
+
if isinstance(value, (dict, list)):
|
| 55 |
+
args.append(f"--{key}={_quote_value(value)}")
|
| 56 |
+
continue
|
| 57 |
+
|
| 58 |
+
s = str(value)
|
| 59 |
+
if _needs_quoting(s):
|
| 60 |
+
args.append(f"--{key}={_quote_value(s)}")
|
| 61 |
+
else:
|
| 62 |
+
args.append(f"--{key}={s}")
|
| 63 |
+
return args
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
def _format_command_with_args(prefix: str, args: list[str]) -> str:
|
| 67 |
+
if not args:
|
| 68 |
+
return prefix
|
| 69 |
+
return f"{prefix} \
|
| 70 |
+
" + " \
|
| 71 |
+
".join(args)
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
def _demo_dir(config_path: str) -> str:
|
| 75 |
+
return os.path.dirname(os.path.dirname(os.path.abspath(config_path)))
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
def _mace_dir(config_path: str) -> str:
|
| 79 |
+
return os.path.dirname(_demo_dir(config_path))
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
def _get_train_py_path(config_path: str) -> str:
|
| 83 |
+
# train.py 放在 scripts/ 目录下,即 demo 的父目录
|
| 84 |
+
return os.path.join(_mace_dir(config_path), "train.py")
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
def _resolve_script_path(config_path: str, script_value: str) -> str:
|
| 88 |
+
if os.path.isabs(script_value):
|
| 89 |
+
return script_value
|
| 90 |
+
|
| 91 |
+
config_dir = os.path.dirname(os.path.abspath(config_path))
|
| 92 |
+
demo_dir = _demo_dir(config_path)
|
| 93 |
+
mace_dir = _mace_dir(config_path)
|
| 94 |
+
|
| 95 |
+
candidates = [
|
| 96 |
+
os.path.join(config_dir, script_value),
|
| 97 |
+
os.path.join(demo_dir, script_value),
|
| 98 |
+
os.path.join(mace_dir, script_value),
|
| 99 |
+
os.path.join(mace_dir, "scripts", script_value),
|
| 100 |
+
]
|
| 101 |
+
for c in candidates:
|
| 102 |
+
if os.path.exists(c):
|
| 103 |
+
return c
|
| 104 |
+
|
| 105 |
+
# Return the most likely path even if it does not exist; preflight will catch it.
|
| 106 |
+
return os.path.join(mace_dir, "scripts", script_value)
|
| 107 |
+
|
| 108 |
+
|
| 109 |
+
def _is_entrypoint_mode(cfg: dict[str, Any]) -> bool:
|
| 110 |
+
entrypoint = cfg.get("entrypoint")
|
| 111 |
+
return isinstance(entrypoint, dict) and bool(entrypoint.get("script"))
|
| 112 |
+
|
| 113 |
+
|
| 114 |
+
def _build_train_command(cfg: dict[str, Any], config_path: str) -> str:
|
| 115 |
+
launch = cfg.get("launch", {}) or {}
|
| 116 |
+
num_nodes = int(launch.get("num_nodes", 1))
|
| 117 |
+
num_gpus = int(launch.get("num_gpus", 1))
|
| 118 |
+
launcher = launch.get("launcher", "python")
|
| 119 |
+
|
| 120 |
+
train_py = _get_train_py_path(config_path)
|
| 121 |
+
train_args = _build_cli_args(
|
| 122 |
+
cfg.get("train_args", {}) or {},
|
| 123 |
+
auto_name=cfg.get("name"),
|
| 124 |
+
)
|
| 125 |
+
|
| 126 |
+
if num_nodes > 1 and "--distributed" not in train_args:
|
| 127 |
+
train_args.append("--distributed")
|
| 128 |
+
if launcher == "torchrun" and "--distributed" not in train_args:
|
| 129 |
+
train_args.append("--distributed")
|
| 130 |
+
|
| 131 |
+
if num_nodes > 1:
|
| 132 |
+
return _format_command_with_args(f"python {train_py}", train_args)
|
| 133 |
+
if launcher == "torchrun":
|
| 134 |
+
prefix = (
|
| 135 |
+
"torchrun \
|
| 136 |
+
--nnodes=1 \
|
| 137 |
+
"
|
| 138 |
+
f"--nproc_per_node={num_gpus} \
|
| 139 |
+
{train_py}"
|
| 140 |
+
)
|
| 141 |
+
return _format_command_with_args(prefix, train_args)
|
| 142 |
+
return _format_command_with_args(f"python {train_py}", train_args)
|
| 143 |
+
|
| 144 |
+
|
| 145 |
+
def _build_entrypoint_command(cfg: dict[str, Any], config_path: str) -> str:
|
| 146 |
+
entrypoint = cfg.get("entrypoint", {}) or {}
|
| 147 |
+
script_path = _resolve_script_path(config_path, str(entrypoint["script"]))
|
| 148 |
+
args = _build_cli_args(entrypoint.get("args", {}) or {})
|
| 149 |
+
return _format_command_with_args(f"python {script_path}", args)
|
| 150 |
+
|
| 151 |
+
|
| 152 |
+
def print_command(cfg: dict[str, Any], config_path: str) -> None:
|
| 153 |
+
if _is_entrypoint_mode(cfg):
|
| 154 |
+
print(_build_entrypoint_command(cfg, config_path))
|
| 155 |
+
else:
|
| 156 |
+
print(_build_train_command(cfg, config_path))
|
| 157 |
+
|
| 158 |
+
|
| 159 |
+
def print_env(cfg: dict[str, Any]) -> None:
|
| 160 |
+
launch = cfg.get("launch", {}) or {}
|
| 161 |
+
num_nodes = int(launch.get("num_nodes", 1))
|
| 162 |
+
num_gpus = int(launch.get("num_gpus", 1))
|
| 163 |
+
|
| 164 |
+
omp = int(launch.get("omp_num_threads", 1))
|
| 165 |
+
print(f"export OMP_NUM_THREADS={omp}")
|
| 166 |
+
|
| 167 |
+
if num_gpus > 1:
|
| 168 |
+
devices = ",".join(str(i) for i in range(num_gpus))
|
| 169 |
+
print(f"export HIP_VISIBLE_DEVICES={devices}")
|
| 170 |
+
elif num_gpus == 1:
|
| 171 |
+
print("export HIP_VISIBLE_DEVICES=0")
|
| 172 |
+
|
| 173 |
+
if num_nodes > 1:
|
| 174 |
+
nccl = cfg.get("nccl", {}) or {}
|
| 175 |
+
print("export HSA_FORCE_FINE_GRAIN_PCIE=1")
|
| 176 |
+
if nccl.get("socket_ifname"):
|
| 177 |
+
print(f"export NCCL_SOCKET_IFNAME={nccl['socket_ifname']}")
|
| 178 |
+
if nccl.get("ib_hca"):
|
| 179 |
+
print(f"export NCCL_IB_HCA={nccl['ib_hca']}")
|
| 180 |
+
if nccl.get("proto"):
|
| 181 |
+
print(f"export NCCL_PROTO={nccl['proto']}")
|
| 182 |
+
|
| 183 |
+
# 用户自定义额外环境变量(可覆盖上面自动生成的变量)
|
| 184 |
+
extra_env = cfg.get("extra_env", {})
|
| 185 |
+
for key, value in extra_env.items():
|
| 186 |
+
if value is None or value == "":
|
| 187 |
+
# 空值时只导出 key,保留当前 shell 中的值
|
| 188 |
+
print(f"export {key}")
|
| 189 |
+
else:
|
| 190 |
+
print(f"export {key}={_quote_value(str(value))}")
|
| 191 |
+
|
| 192 |
+
|
| 193 |
+
def print_env_args(cfg: dict[str, Any]) -> None:
|
| 194 |
+
env = cfg.get("env", {}) or {}
|
| 195 |
+
conda_env = env.get("conda_env", "chem")
|
| 196 |
+
modules = env.get("modules", []) or []
|
| 197 |
+
parts = [str(conda_env)] + [str(m) for m in modules]
|
| 198 |
+
print(" ".join(parts))
|
| 199 |
+
|
| 200 |
+
|
| 201 |
+
def _print_unique(paths: list[str]) -> None:
|
| 202 |
+
seen: set[str] = set()
|
| 203 |
+
for path in paths:
|
| 204 |
+
if not path:
|
| 205 |
+
continue
|
| 206 |
+
if path in seen:
|
| 207 |
+
continue
|
| 208 |
+
seen.add(path)
|
| 209 |
+
print(path)
|
| 210 |
+
|
| 211 |
+
|
| 212 |
+
def print_data_files(cfg: dict[str, Any], config_path: str) -> None:
|
| 213 |
+
explicit = cfg.get("preflight_files")
|
| 214 |
+
if isinstance(explicit, list):
|
| 215 |
+
_print_unique([str(x) for x in explicit])
|
| 216 |
+
return
|
| 217 |
+
|
| 218 |
+
if _is_entrypoint_mode(cfg):
|
| 219 |
+
entrypoint = cfg.get("entrypoint", {}) or {}
|
| 220 |
+
files: list[str] = []
|
| 221 |
+
data_files = entrypoint.get("data_files", [])
|
| 222 |
+
if isinstance(data_files, list):
|
| 223 |
+
files.extend(str(x) for x in data_files)
|
| 224 |
+
script_value = entrypoint.get("script")
|
| 225 |
+
if script_value:
|
| 226 |
+
files.append(_resolve_script_path(config_path, str(script_value)))
|
| 227 |
+
_print_unique(files)
|
| 228 |
+
return
|
| 229 |
+
|
| 230 |
+
train_args = cfg.get("train_args", {}) or {}
|
| 231 |
+
files: list[str] = []
|
| 232 |
+
for key in (
|
| 233 |
+
"train_file",
|
| 234 |
+
"valid_file",
|
| 235 |
+
"test_file",
|
| 236 |
+
"statistics_file",
|
| 237 |
+
"foundation_model",
|
| 238 |
+
"pt_train_file",
|
| 239 |
+
):
|
| 240 |
+
val = train_args.get(key)
|
| 241 |
+
if val:
|
| 242 |
+
files.append(str(val))
|
| 243 |
+
_print_unique(files)
|
| 244 |
+
|
| 245 |
+
|
| 246 |
+
def print_slurm(cfg: dict[str, Any]) -> None:
|
| 247 |
+
launch = cfg.get("launch", {}) or {}
|
| 248 |
+
slurm = cfg.get("slurm", {}) or {}
|
| 249 |
+
num_nodes = int(launch.get("num_nodes", 1))
|
| 250 |
+
num_gpus = int(launch.get("num_gpus", 1))
|
| 251 |
+
|
| 252 |
+
name = cfg.get("name", "mace_train")
|
| 253 |
+
partition = slurm.get("partition", "newlarge")
|
| 254 |
+
time_limit = slurm.get("time", "8:00:00")
|
| 255 |
+
cpus = int(slurm.get("cpus_per_task", 64))
|
| 256 |
+
|
| 257 |
+
if num_nodes > 1:
|
| 258 |
+
ntasks = num_gpus
|
| 259 |
+
cpus_per_task = max(1, cpus // max(1, num_gpus))
|
| 260 |
+
else:
|
| 261 |
+
ntasks = 1
|
| 262 |
+
cpus_per_task = cpus
|
| 263 |
+
|
| 264 |
+
print(f"JOB_NAME={name}")
|
| 265 |
+
print(f"PARTITION={partition}")
|
| 266 |
+
print(f"NODES={num_nodes}")
|
| 267 |
+
print(f"NTASKS_PER_NODE={ntasks}")
|
| 268 |
+
print(f"CPUS_PER_TASK={cpus_per_task}")
|
| 269 |
+
print(f"GPUS_PER_NODE={num_gpus}")
|
| 270 |
+
print(f"TIME={time_limit}")
|
| 271 |
+
|
| 272 |
+
|
| 273 |
+
def print_name(cfg: dict[str, Any]) -> None:
|
| 274 |
+
print(cfg.get("name", "mace_train"))
|
| 275 |
+
|
| 276 |
+
|
| 277 |
+
def main() -> None:
|
| 278 |
+
if len(sys.argv) < 3:
|
| 279 |
+
print(__doc__, file=sys.stderr)
|
| 280 |
+
sys.exit(1)
|
| 281 |
+
|
| 282 |
+
config_path = sys.argv[1]
|
| 283 |
+
action = sys.argv[2]
|
| 284 |
+
cfg = load_config(config_path)
|
| 285 |
+
|
| 286 |
+
actions = {
|
| 287 |
+
"command": lambda: print_command(cfg, config_path),
|
| 288 |
+
"env": lambda: print_env(cfg),
|
| 289 |
+
"env-args": lambda: print_env_args(cfg),
|
| 290 |
+
"data-files": lambda: print_data_files(cfg, config_path),
|
| 291 |
+
"slurm": lambda: print_slurm(cfg),
|
| 292 |
+
"name": lambda: print_name(cfg),
|
| 293 |
+
}
|
| 294 |
+
|
| 295 |
+
fn = actions.get(action)
|
| 296 |
+
if fn is None:
|
| 297 |
+
print(f"Unknown action: {action}", file=sys.stderr)
|
| 298 |
+
print(f"Available: {', '.join(actions.keys())}", file=sys.stderr)
|
| 299 |
+
sys.exit(1)
|
| 300 |
+
fn()
|
| 301 |
+
|
| 302 |
+
|
| 303 |
+
if __name__ == "__main__":
|
| 304 |
+
main()
|
scripts/demo/configs/DMC.yaml
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ============================================================
|
| 2 |
+
# DMC 溶剂 XTB 训练配置
|
| 3 |
+
# 入门级示例:单 GPU,小数据集,参数最少
|
| 4 |
+
# ============================================================
|
| 5 |
+
|
| 6 |
+
# 实验名称(用于输出目录和模型文件命名)
|
| 7 |
+
name: DMC
|
| 8 |
+
|
| 9 |
+
# 实验描述
|
| 10 |
+
description: "DMC solvent XTB - 单 GPU 入门示例 (200 训练帧)"
|
| 11 |
+
|
| 12 |
+
# ============================================================
|
| 13 |
+
# train.py 参数
|
| 14 |
+
# 以下所有字段将被转换为 train.py 的命令行参数
|
| 15 |
+
# 例如 model: MACE -> --model=MACE
|
| 16 |
+
# swa: true -> --swa (布尔标志)
|
| 17 |
+
# ============================================================
|
| 18 |
+
train_args:
|
| 19 |
+
# --- 模型配置 ---
|
| 20 |
+
model: MACE # 模型类型
|
| 21 |
+
|
| 22 |
+
# --- 数据配置 ---
|
| 23 |
+
# 路径中的 ${ONESCIENCE_DATASETS_DIR} 默认指向仓库根目录,
|
| 24 |
+
# 下载 ModelScope DMC 数据集到 ./data 后即可使用 可能需根据实际路径进行调整
|
| 25 |
+
train_file: "${ONESCIENCE_DATASETS_DIR}/data/data/DMC/solvent_xtb_train_200.xyz"
|
| 26 |
+
test_file: "${ONESCIENCE_DATASETS_DIR}/data/data/DMC/solvent_xtb_test.xyz"
|
| 27 |
+
valid_fraction: 0.10 # 从训练集中划分 10% 作为验证集
|
| 28 |
+
energy_key: energy_xtb # XYZ 文件中能量字段名
|
| 29 |
+
forces_key: forces_xtb # XYZ 文件中力字段名
|
| 30 |
+
E0s: average # 原子参考能量方式: average / isolated / 显式字典
|
| 31 |
+
|
| 32 |
+
# --- 训练超参 ---
|
| 33 |
+
seed: 123 # 随机种子
|
| 34 |
+
device: cuda # 设备: cuda / cpu
|
| 35 |
+
r_max: 4.0 # 截断半径 (Angstrom)
|
| 36 |
+
batch_size: 10 # 训练批大小
|
| 37 |
+
max_num_epochs: 100 # 最大训练轮数
|
| 38 |
+
eval_interval: 10 # 每 10 个 epoch 评估并打印一次
|
| 39 |
+
|
| 40 |
+
# --- SWA (Stochastic Weight Averaging) ---
|
| 41 |
+
swa: true # 启用 SWA
|
| 42 |
+
|
| 43 |
+
# ============================================================
|
| 44 |
+
# 启动配置(控制 python / torchrun / srun 的选择)
|
| 45 |
+
# ============================================================
|
| 46 |
+
launch:
|
| 47 |
+
launcher: python # 启动方式: python / torchrun (多节点时自动用 srun)
|
| 48 |
+
num_nodes: 1 # 节点数
|
| 49 |
+
num_gpus: 8 # 申请的 DCU 卡数
|
| 50 |
+
|
| 51 |
+
# ============================================================
|
| 52 |
+
# 环境配置
|
| 53 |
+
# ============================================================
|
| 54 |
+
env:
|
| 55 |
+
conda_env: matchem # conda 环境名
|
| 56 |
+
modules:
|
| 57 |
+
- sghpc-mpi-gcc/26.3 # 需要加载的 module
|
| 58 |
+
|
| 59 |
+
# ============================================================
|
| 60 |
+
# SLURM 配置(仅 --submit 模式使用)
|
| 61 |
+
# ============================================================
|
| 62 |
+
slurm:
|
| 63 |
+
partition: hpctest01 # SLURM 分区
|
| 64 |
+
time: "2:00:00" # 作业时间限制
|
| 65 |
+
cpus_per_task: 128 # CPU 核心数
|
scripts/demo/configs/ani1x_8dcu.yaml
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ============================================================
|
| 2 |
+
# ANI-1x 8 DCU 训练配置
|
| 3 |
+
# 单节点 8 DCU 分布式训练
|
| 4 |
+
# ============================================================
|
| 5 |
+
|
| 6 |
+
# 实验名称(用于输出目录和模型文件命名)
|
| 7 |
+
name: ani1x_8dcu
|
| 8 |
+
|
| 9 |
+
# 实验描述
|
| 10 |
+
description: "ANI-1x - 8 DCU 分布式训练"
|
| 11 |
+
|
| 12 |
+
# ============================================================
|
| 13 |
+
# train.py 参数
|
| 14 |
+
# 以下所有字段将被转换为 train.py 的命令行参数
|
| 15 |
+
# 例如 model: MACE -> --model=MACE
|
| 16 |
+
# swa: true -> --swa (布尔标志)
|
| 17 |
+
# ============================================================
|
| 18 |
+
train_args:
|
| 19 |
+
# --- 模型配置 ---
|
| 20 |
+
model: MACE # 模型类型
|
| 21 |
+
default_dtype: float64 # 数据精度
|
| 22 |
+
num_interactions: 2 # 交互层数
|
| 23 |
+
num_channels: 64 # 通道数
|
| 24 |
+
max_L: 0 # 最大角动量
|
| 25 |
+
correlation: 3 # 相关阶数
|
| 26 |
+
|
| 27 |
+
# --- 数据配置 ---
|
| 28 |
+
# 路径中的 ${ONESCIENCE_DATASETS_DIR} 默认指向仓库根目录,下载对应数据集到 ./data 后即可使用
|
| 29 |
+
train_file: "${ONESCIENCE_DATASETS_DIR}/data/ani1x/ANI1x_cc_DFT_rc5_train"
|
| 30 |
+
valid_file: "${ONESCIENCE_DATASETS_DIR}/data/ani1x/ANI1x_cc_DFT_rc5_val"
|
| 31 |
+
statistics_file: "${ONESCIENCE_DATASETS_DIR}/data/ani1x/ANI1x_cc_DFT_rc5_statistics.json"
|
| 32 |
+
E0s: "{1: -13.62222753701504, 6: -1029.4130839658328, 7: -1484.8710358098756, 8: -2041.8396277138045}"
|
| 33 |
+
|
| 34 |
+
# --- 训练超参 ---
|
| 35 |
+
seed: 123 # 随机种子
|
| 36 |
+
device: cuda # 设备: cuda / cpu
|
| 37 |
+
r_max: 4.5 # 截断半径 (Angstrom)
|
| 38 |
+
batch_size: 128 # 训练批大小
|
| 39 |
+
valid_batch_size: 128 # 验证批大小
|
| 40 |
+
max_num_epochs: 20 # 最大训练轮数
|
| 41 |
+
forces_weight: 1000 # 力的损失权重
|
| 42 |
+
energy_weight: 40 # 能量的损失权重
|
| 43 |
+
weight_decay: 1e-7 # 权重衰减
|
| 44 |
+
clip_grad: 1.0 # 梯度裁剪阈值
|
| 45 |
+
scheduler_patience: 20 # 学习率调度器耐心值
|
| 46 |
+
patience: 50 # 早停耐心值
|
| 47 |
+
eval_interval: 1 # 每 10 个 epoch 评估并打印一次
|
| 48 |
+
num_workers: 32 # 数据加载线程数
|
| 49 |
+
error_table: PerAtomMAE # 误差表类型
|
| 50 |
+
|
| 51 |
+
# --- SWA / EMA ---
|
| 52 |
+
ema: true # 启用 EMA
|
| 53 |
+
swa: true # 启用 SWA
|
| 54 |
+
start_swa: 15 # SWA 开始 epoch
|
| 55 |
+
swa_lr: 0.00025 # SWA 学习率
|
| 56 |
+
swa_forces_weight: 10 # SWA 力的损失权重
|
| 57 |
+
|
| 58 |
+
# --- 其他 ---
|
| 59 |
+
restart_latest: true # 从最新检查点重启
|
| 60 |
+
save_cpu: true # 保存模型到 CPU
|
| 61 |
+
|
| 62 |
+
# ============================================================
|
| 63 |
+
# 启动配置(控制 python / torchrun / srun 的选择)
|
| 64 |
+
# ============================================================
|
| 65 |
+
launch:
|
| 66 |
+
launcher: torchrun # 启动方式: python / torchrun (多节点时自动用 srun)
|
| 67 |
+
num_nodes: 1 # 节点数
|
| 68 |
+
num_gpus: 8 # 申请的 DCU 卡数
|
| 69 |
+
|
| 70 |
+
# ============================================================
|
| 71 |
+
# 环境配置
|
| 72 |
+
# ============================================================
|
| 73 |
+
env:
|
| 74 |
+
conda_env: matchem # conda 环境名
|
| 75 |
+
modules:
|
| 76 |
+
- sghpc-mpi-gcc/26.3 # 需要加载的 module
|
| 77 |
+
|
| 78 |
+
# ============================================================
|
| 79 |
+
# SLURM 配置(仅 --submit 模式使用)
|
| 80 |
+
# ============================================================
|
| 81 |
+
slurm:
|
| 82 |
+
partition: hpctest02 # SLURM 分区
|
| 83 |
+
time: "8:00:00" # 作业时间限制
|
| 84 |
+
cpus_per_task: 128 # CPU 核心数
|
scripts/demo/configs/nanotube_eval_l0_1dcu.yaml
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ============================================================
|
| 2 |
+
# Nanotube L0 evaluation (single DCU)
|
| 3 |
+
# ============================================================
|
| 4 |
+
name: nanotube_eval_l0_1dcu
|
| 5 |
+
|
| 6 |
+
description: "Evaluate nanotube L0 model with eval_configs.py"
|
| 7 |
+
|
| 8 |
+
entrypoint:
|
| 9 |
+
script: eval_configs.py
|
| 10 |
+
args:
|
| 11 |
+
configs: "${ONESCIENCE_DATASETS_DIR}/data/nanotube/nanotube_large.xyz"
|
| 12 |
+
model: "${ONESCIENCE_MODELS_DIR}/mace/nanotube_l0_8dcu.model"
|
| 13 |
+
output: "${ONESCIENCE_DATASETS_DIR}/data/nanotube/nanotube_large_eavl.xyz"
|
| 14 |
+
device: cuda
|
| 15 |
+
default_dtype: float64
|
| 16 |
+
batch_size: 2
|
| 17 |
+
info_prefix: "MACE_"
|
| 18 |
+
data_files:
|
| 19 |
+
- "${ONESCIENCE_DATASETS_DIR}/data/nanotube/nanotube_large.xyz"
|
| 20 |
+
- "${ONESCIENCE_MODELS_DIR}/mace/nanotube_l0_8dcu.model"
|
| 21 |
+
|
| 22 |
+
launch:
|
| 23 |
+
launcher: python
|
| 24 |
+
num_nodes: 1
|
| 25 |
+
num_gpus: 1
|
| 26 |
+
|
| 27 |
+
env:
|
| 28 |
+
conda_env: matchem
|
| 29 |
+
modules:
|
| 30 |
+
- sghpc-mpi-gcc/26.3
|
| 31 |
+
|
| 32 |
+
slurm:
|
| 33 |
+
partition: hpctest01
|
| 34 |
+
time: "2:00:00"
|
| 35 |
+
cpus_per_task: 16
|
scripts/demo/configs/nanotube_finetune_l0_8dcu.yaml
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ============================================================
|
| 2 |
+
# Nanotube L0 finetuning (8 DCU, single node)
|
| 3 |
+
# ============================================================
|
| 4 |
+
name: nanotube_finetune_l0_8dcu
|
| 5 |
+
|
| 6 |
+
description: "Finetune from nanotube L0 foundation model"
|
| 7 |
+
|
| 8 |
+
train_args:
|
| 9 |
+
foundation_model: "${ONESCIENCE_MODELS_DIR}/mace/nanotube_l0_8dcu.model"
|
| 10 |
+
train_file: "${ONESCIENCE_DATASETS_DIR}/data/nanotube/nanotube_large.xyz"
|
| 11 |
+
test_file: "${ONESCIENCE_DATASETS_DIR}/data/nanotube/nanotube_test.xyz"
|
| 12 |
+
valid_fraction: 0.60
|
| 13 |
+
multiheads_finetuning: false
|
| 14 |
+
E0s: average
|
| 15 |
+
energy_key: Energy
|
| 16 |
+
forces_key: forces
|
| 17 |
+
|
| 18 |
+
energy_weight: 1.0
|
| 19 |
+
forces_weight: 1.0
|
| 20 |
+
lr: 0.01
|
| 21 |
+
scaling: rms_forces_scaling
|
| 22 |
+
batch_size: 2
|
| 23 |
+
max_num_epochs: 10
|
| 24 |
+
num_samples_pt: 10
|
| 25 |
+
|
| 26 |
+
ema: true
|
| 27 |
+
swa: true
|
| 28 |
+
ema_decay: 0.99
|
| 29 |
+
amsgrad: true
|
| 30 |
+
|
| 31 |
+
default_dtype: float64
|
| 32 |
+
device: cuda
|
| 33 |
+
seed: 345
|
| 34 |
+
|
| 35 |
+
launch:
|
| 36 |
+
launcher: torchrun
|
| 37 |
+
num_nodes: 1
|
| 38 |
+
num_gpus: 8
|
| 39 |
+
|
| 40 |
+
env:
|
| 41 |
+
conda_env: matchem
|
| 42 |
+
modules:
|
| 43 |
+
- sghpc-mpi-gcc/26.3
|
| 44 |
+
|
| 45 |
+
slurm:
|
| 46 |
+
partition: hpctest01
|
| 47 |
+
time: "2:00:00"
|
| 48 |
+
cpus_per_task: 128
|
scripts/demo/configs/nanotube_l0_8dcu.yaml
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ============================================================
|
| 2 |
+
# Nanotube L=0 训练配置
|
| 3 |
+
# 单节点 8 DCU 分布式训练
|
| 4 |
+
# ============================================================
|
| 5 |
+
|
| 6 |
+
# 实验名称(用于输出目录和模型文件命名)
|
| 7 |
+
name: nanotube_l0_8dcu
|
| 8 |
+
|
| 9 |
+
# 实验描述
|
| 10 |
+
description: "Carbon nanotube max_L=0 - 8 DCU 分布式训练"
|
| 11 |
+
|
| 12 |
+
# ============================================================
|
| 13 |
+
# train.py 参数
|
| 14 |
+
# 以下所有字段将被转换为 train.py 的命令行参数
|
| 15 |
+
# 例如 model: MACE -> --model=MACE
|
| 16 |
+
# swa: true -> --swa (布尔标志)
|
| 17 |
+
# ============================================================
|
| 18 |
+
train_args:
|
| 19 |
+
# --- 模型配置 ---
|
| 20 |
+
model: MACE # 模型类型
|
| 21 |
+
default_dtype: float64 # 数据精度
|
| 22 |
+
num_interactions: 2 # 交互层数
|
| 23 |
+
num_channels: 128 # 通道数
|
| 24 |
+
max_L: 0 # 最大角动量
|
| 25 |
+
correlation: 3 # 相关阶数
|
| 26 |
+
|
| 27 |
+
# --- 数据配置 ---
|
| 28 |
+
# 路径中的 ${ONESCIENCE_DATASETS_DIR} 默认指向仓库根目录,下载对应数据集到 ./data 后即可使用
|
| 29 |
+
train_file: "${ONESCIENCE_DATASETS_DIR}/data/nanotube/nanotube_large.xyz"
|
| 30 |
+
test_file: "${ONESCIENCE_DATASETS_DIR}/data/nanotube/nanotube_test.xyz"
|
| 31 |
+
valid_fraction: 0.05 # 从训练集中划分 5% 作为验证集
|
| 32 |
+
E0s: average # 原子参考能量方式: average / isolated / 显式字典
|
| 33 |
+
energy_key: Energy # XYZ 文件中能量字段名
|
| 34 |
+
forces_key: forces # XYZ 文件中力字段名
|
| 35 |
+
|
| 36 |
+
# --- 训练超参 ---
|
| 37 |
+
seed: 123 # 随机种子
|
| 38 |
+
device: cuda # 设备: cuda / cpu
|
| 39 |
+
enable_cueq: true # 启用 cuequivariance + FastEq 加速
|
| 40 |
+
r_max: 6.0 # 截断半径 (Angstrom)
|
| 41 |
+
batch_size: 4 # 训练批大小
|
| 42 |
+
valid_batch_size: 8 # 验证批大小
|
| 43 |
+
max_num_epochs: 10 # 最大训练轮数
|
| 44 |
+
forces_weight: 1000 # 力的损失权重
|
| 45 |
+
energy_weight: 10 # 能量的损失权重
|
| 46 |
+
scheduler_patience: 5 # 学习率调度器耐心值
|
| 47 |
+
patience: 15 # 早停耐心值
|
| 48 |
+
eval_interval: 1 # 每 1 个 epoch 评估并打印一次
|
| 49 |
+
error_table: PerAtomMAE # 误差表类型
|
| 50 |
+
|
| 51 |
+
# --- SWA / EMA ---
|
| 52 |
+
ema: true # 启用 EMA
|
| 53 |
+
swa: true # 启用 SWA
|
| 54 |
+
start_swa: 7 # SWA 开始 epoch
|
| 55 |
+
swa_forces_weight: 10 # SWA 力的损失权重
|
| 56 |
+
|
| 57 |
+
# --- 其他 ---
|
| 58 |
+
restart_latest: true # 从最新检查点重启
|
| 59 |
+
save_cpu: true # 保存检查点
|
| 60 |
+
|
| 61 |
+
# ============================================================
|
| 62 |
+
# 启动配置(控制 python / torchrun / srun 的选择)
|
| 63 |
+
# ============================================================
|
| 64 |
+
launch:
|
| 65 |
+
launcher: torchrun # 启动方式: python / torchrun (多节点时自动用 srun)
|
| 66 |
+
num_nodes: 1 # 节点数
|
| 67 |
+
num_gpus: 8 # 申请的 DCU 卡数
|
| 68 |
+
|
| 69 |
+
# ============================================================
|
| 70 |
+
# 环境配置
|
| 71 |
+
# ============================================================
|
| 72 |
+
env:
|
| 73 |
+
conda_env: matchem # conda 环境名
|
| 74 |
+
modules:
|
| 75 |
+
- sghpc-mpi-gcc/26.3 # 需要加载的 module
|
| 76 |
+
|
| 77 |
+
# ============================================================
|
| 78 |
+
# SLURM 配置(仅 --submit 模式使用)
|
| 79 |
+
# ============================================================
|
| 80 |
+
slurm:
|
| 81 |
+
partition: hpctest01 # SLURM 分区
|
| 82 |
+
time: "2:00:00" # 作业时间限制
|
| 83 |
+
cpus_per_task: 128 # CPU 核心数
|
scripts/demo/configs/nanotube_l2_16dcu.yaml
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ============================================================
|
| 2 |
+
# Nanotube L=2 多节点训练配置
|
| 3 |
+
# 2 节点 x 8 DCU 多节点分布式训练
|
| 4 |
+
# ============================================================
|
| 5 |
+
|
| 6 |
+
# 实验名称(用于输出目录和模型文件命名)
|
| 7 |
+
name: nanotube_l2_16dcu
|
| 8 |
+
|
| 9 |
+
# 实验描述
|
| 10 |
+
description: "Carbon nanotube max_L=2 - 2 节点 x 8 DCU 多节点分布式训练"
|
| 11 |
+
|
| 12 |
+
# ============================================================
|
| 13 |
+
# train.py 参数
|
| 14 |
+
# 以下所有字段将被转换为 train.py 的命令行参数
|
| 15 |
+
# 例如 model: MACE -> --model=MACE
|
| 16 |
+
# swa: true -> --swa (布尔标志)
|
| 17 |
+
# ============================================================
|
| 18 |
+
train_args:
|
| 19 |
+
# --- 模型配置 ---
|
| 20 |
+
model: MACE # 模型类型
|
| 21 |
+
default_dtype: float64 # 数据精度
|
| 22 |
+
num_interactions: 2 # 交互层数
|
| 23 |
+
num_channels: 256 # 通道数
|
| 24 |
+
max_L: 2 # 最大角动量
|
| 25 |
+
correlation: 3 # 相关阶数
|
| 26 |
+
|
| 27 |
+
# --- 数据配置 ---
|
| 28 |
+
# 路径中的 ${ONESCIENCE_DATASETS_DIR} 默认指向仓库根目录,下载对应数据集到 ./data 后即可使用
|
| 29 |
+
train_file: "${ONESCIENCE_DATASETS_DIR}/data/nanotube/nanotube_large.xyz"
|
| 30 |
+
test_file: "${ONESCIENCE_DATASETS_DIR}/data/nanotube/nanotube_test.xyz"
|
| 31 |
+
valid_fraction: 0.05 # 从训练集中划分 5% 作为验证集
|
| 32 |
+
E0s: average # 原子参考能量方式: average / isolated / 显式字典
|
| 33 |
+
energy_key: Energy # XYZ 文件中能量字段名
|
| 34 |
+
forces_key: forces # XYZ 文件中力字段名
|
| 35 |
+
|
| 36 |
+
# --- 训练超参 ---
|
| 37 |
+
seed: 123 # 随机种子
|
| 38 |
+
device: cuda # 设备: cuda / cpu
|
| 39 |
+
r_max: 5.0 # 截断半径 (Angstrom)
|
| 40 |
+
batch_size: 2 # 训练批大小 (多节点时适当减小)
|
| 41 |
+
valid_batch_size: 4 # 验证批大小
|
| 42 |
+
max_num_epochs: 10 # 最大训练轮数
|
| 43 |
+
forces_weight: 1000 # 力的损失权重
|
| 44 |
+
energy_weight: 10 # 能量的损失权重
|
| 45 |
+
scheduler_patience: 5 # 学习率调度器耐心值
|
| 46 |
+
patience: 15 # 早停耐心值
|
| 47 |
+
eval_interval: 1 # 每 1 个 epoch 评估并打印一次
|
| 48 |
+
error_table: PerAtomMAE # 误差表类型
|
| 49 |
+
|
| 50 |
+
# --- SWA / EMA ---
|
| 51 |
+
ema: true # 启用 EMA
|
| 52 |
+
swa: true # 启用 SWA
|
| 53 |
+
start_swa: 7 # SWA 开始 epoch
|
| 54 |
+
swa_forces_weight: 10 # SWA 力的损失权重
|
| 55 |
+
|
| 56 |
+
# --- 分布式 ---
|
| 57 |
+
distributed: true # 启用分布式训练
|
| 58 |
+
|
| 59 |
+
# --- 其他 ---
|
| 60 |
+
restart_latest: true # 从最新检查点重启
|
| 61 |
+
save_cpu: true # 保存检查点
|
| 62 |
+
|
| 63 |
+
# ============================================================
|
| 64 |
+
# 启动配置(控制 python / torchrun / srun 的选择)
|
| 65 |
+
# ============================================================
|
| 66 |
+
launch:
|
| 67 |
+
launcher: torchrun # 启动方式: python / torchrun (多节点时自动用 srun)
|
| 68 |
+
num_nodes: 2 # 节点数
|
| 69 |
+
num_gpus: 8 # 每节点申请的 DCU 卡数
|
| 70 |
+
|
| 71 |
+
# ============================================================
|
| 72 |
+
# 环境配置
|
| 73 |
+
# ============================================================
|
| 74 |
+
env:
|
| 75 |
+
conda_env: matchem # conda 环境名
|
| 76 |
+
modules:
|
| 77 |
+
- sghpc-mpi-gcc/26.3 # 需要加载的 module
|
| 78 |
+
|
| 79 |
+
# ============================================================
|
| 80 |
+
# 多节点 NCCL 通信配置
|
| 81 |
+
# ============================================================
|
| 82 |
+
nccl:
|
| 83 |
+
socket_ifname: ib0 # NCCL 网络接口
|
| 84 |
+
ib_hca: mlx5_0 # InfiniBand HCA 设备
|
| 85 |
+
proto: Simple # NCCL 协议
|
| 86 |
+
|
| 87 |
+
# ============================================================
|
| 88 |
+
# 自定义环境变量(会覆盖自动生成的变量)
|
| 89 |
+
# ============================================================
|
| 90 |
+
extra_env:
|
| 91 |
+
NCCL_DEBUG: INFO1
|
| 92 |
+
NCCL_DEBUG_SUBSYS: ALL
|
| 93 |
+
NCCL_NET_GDR_LEVEL: 4
|
| 94 |
+
NCCL_NET_GDR_READ: 1
|
| 95 |
+
NCCL_PXN_DISABLE: 0
|
| 96 |
+
RCCL_PXN_GPU_BALANCE: 1
|
| 97 |
+
RCCL_NET_PLANE: "shca_0,shca_3|shca_1,shca_2"
|
| 98 |
+
NCCL_SOCKET_IFNAME: eno1
|
| 99 |
+
HIP_VISIBLE_DEVICES: "0,1,5,4,2,3,7,6"
|
| 100 |
+
NCCL_NET_PLUGIN: shca
|
| 101 |
+
NCCL_PLUGIN_P2P: ib
|
| 102 |
+
NCCL_WORK_FIFO_BYTES1: 4194304
|
| 103 |
+
LD_LIBRARY_PATH: "" # 空值:保持当前 shell 中已有的值
|
| 104 |
+
SHCA_DEBUG_MASK: 0
|
| 105 |
+
NCCL_TOPO_FILE: "" # 空值:保持当前 shell 中已有的值
|
| 106 |
+
UCX_IB_NUM_PATHS: 1
|
| 107 |
+
|
| 108 |
+
# ============================================================
|
| 109 |
+
# SLURM 配置(仅 --submit 模式使用)
|
| 110 |
+
# ============================================================
|
| 111 |
+
slurm:
|
| 112 |
+
partition: hpctest01 # SLURM 分区
|
| 113 |
+
time: "2:00:00" # 作业时间限制
|
| 114 |
+
cpus_per_task: 128 # CPU 核心数
|
scripts/demo/configs/nanotube_l2_8dcu.yaml
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ============================================================
|
| 2 |
+
# Nanotube L=2 训练配置
|
| 3 |
+
# 单节点 8 DCU 分布式训练
|
| 4 |
+
# ============================================================
|
| 5 |
+
|
| 6 |
+
# 实验名称(用于输出目录和模型文件命名)
|
| 7 |
+
name: nanotube_l2_8dcu
|
| 8 |
+
|
| 9 |
+
# 实验描述
|
| 10 |
+
description: "Carbon nanotube max_L=2 - 8 DCU 分布式训练"
|
| 11 |
+
|
| 12 |
+
# ============================================================
|
| 13 |
+
# train.py 参数
|
| 14 |
+
# 以下所有字段将被转换为 train.py 的命令行参数
|
| 15 |
+
# 例如 model: MACE -> --model=MACE
|
| 16 |
+
# swa: true -> --swa (布尔标志)
|
| 17 |
+
# ============================================================
|
| 18 |
+
train_args:
|
| 19 |
+
# --- 模型配置 ---
|
| 20 |
+
model: MACE # 模型类型
|
| 21 |
+
default_dtype: float64 # 数据精度
|
| 22 |
+
num_interactions: 2 # 交互层数
|
| 23 |
+
num_channels: 128 # 通道数
|
| 24 |
+
max_L: 2 # 最大角动量
|
| 25 |
+
correlation: 3 # 相关阶数
|
| 26 |
+
|
| 27 |
+
# --- 数据配置 ---
|
| 28 |
+
# 路径中的 ${ONESCIENCE_DATASETS_DIR} 默认指向仓库根目录,下载对应数据集到 ./data 后即可使用
|
| 29 |
+
train_file: "${ONESCIENCE_DATASETS_DIR}/data/nanotube/nanotube_large.xyz"
|
| 30 |
+
test_file: "${ONESCIENCE_DATASETS_DIR}/data/nanotube/nanotube_test.xyz"
|
| 31 |
+
valid_fraction: 0.05 # 从训练集中划分 5% 作为验证集
|
| 32 |
+
E0s: average # 原子参考能量方式: average / isolated / 显式字典
|
| 33 |
+
energy_key: Energy # XYZ 文件中能量字段名
|
| 34 |
+
forces_key: forces # XYZ 文件中力字段名
|
| 35 |
+
|
| 36 |
+
# --- 训练超参 ---
|
| 37 |
+
seed: 123 # 随机种子
|
| 38 |
+
device: cuda # 设备: cuda / cpu
|
| 39 |
+
r_max: 5.0 # 截断半径 (Angstrom)
|
| 40 |
+
batch_size: 2 # 训练批大小
|
| 41 |
+
valid_batch_size: 8 # 验证批大小
|
| 42 |
+
max_num_epochs: 10 # 最大训练轮数
|
| 43 |
+
forces_weight: 1000 # 力的损失权重
|
| 44 |
+
energy_weight: 10 # 能量的损失权重
|
| 45 |
+
scheduler_patience: 5 # 学习率调度器耐心值
|
| 46 |
+
patience: 15 # 早停耐心值
|
| 47 |
+
eval_interval: 1 # 每 1 个 epoch 评估并打印一次
|
| 48 |
+
error_table: PerAtomMAE # 误差表类型
|
| 49 |
+
|
| 50 |
+
# --- SWA / EMA ---
|
| 51 |
+
ema: true # 启用 EMA
|
| 52 |
+
swa: true # 启用 SWA
|
| 53 |
+
start_swa: 7 # SWA 开始 epoch
|
| 54 |
+
swa_forces_weight: 10 # SWA 力的损失权重
|
| 55 |
+
|
| 56 |
+
# --- 其他 ---
|
| 57 |
+
restart_latest: true # 从最新检查点重启
|
| 58 |
+
save_cpu: true # 保存检查点
|
| 59 |
+
|
| 60 |
+
# ============================================================
|
| 61 |
+
# 启动配置(控制 python / torchrun / srun 的选择)
|
| 62 |
+
# ============================================================
|
| 63 |
+
launch:
|
| 64 |
+
launcher: torchrun # 启动方式: python / torchrun (多节点时自动用 srun)
|
| 65 |
+
num_nodes: 1 # 节点数
|
| 66 |
+
num_gpus: 8 # 申请的 DCU 卡数
|
| 67 |
+
|
| 68 |
+
# ============================================================
|
| 69 |
+
# 环境配置
|
| 70 |
+
# ============================================================
|
| 71 |
+
env:
|
| 72 |
+
conda_env: matchem # conda 环境名
|
| 73 |
+
modules:
|
| 74 |
+
- sghpc-mpi-gcc/26.3 # 需要加载的 module
|
| 75 |
+
|
| 76 |
+
# ============================================================
|
| 77 |
+
# 自定义环境变量(会覆盖自动生成的变量)
|
| 78 |
+
# ============================================================
|
| 79 |
+
extra_env:
|
| 80 |
+
NCCL_DEBUG: INFO1
|
| 81 |
+
NCCL_DEBUG_SUBSYS: ALL
|
| 82 |
+
NCCL_NET_GDR_LEVEL: 4
|
| 83 |
+
NCCL_NET_GDR_READ: 1
|
| 84 |
+
NCCL_PXN_DISABLE: 0
|
| 85 |
+
RCCL_PXN_GPU_BALANCE: 1
|
| 86 |
+
RCCL_NET_PLANE: "shca_0,shca_3|shca_1,shca_2"
|
| 87 |
+
NCCL_SOCKET_IFNAME: eno1
|
| 88 |
+
HIP_VISIBLE_DEVICES: "0,1,5,4,2,3,7,6"
|
| 89 |
+
NCCL_NET_PLUGIN: shca
|
| 90 |
+
NCCL_PLUGIN_P2P: ib
|
| 91 |
+
NCCL_WORK_FIFO_BYTES1: 4194304
|
| 92 |
+
LD_LIBRARY_PATH: "" # 空值:保持当前 shell 中已有的值
|
| 93 |
+
SHCA_DEBUG_MASK: 0
|
| 94 |
+
NCCL_TOPO_FILE: "" # 空值:保持当前 shell 中已有的值
|
| 95 |
+
UCX_IB_NUM_PATHS: 1
|
| 96 |
+
|
| 97 |
+
# ============================================================
|
| 98 |
+
# SLURM 配置(仅 --submit 模式使用)
|
| 99 |
+
# ============================================================
|
| 100 |
+
slurm:
|
| 101 |
+
partition: hpctest02 # SLURM 分区
|
| 102 |
+
time: "8:00:00" # 作业时间限制
|
| 103 |
+
cpus_per_task: 128 # CPU 核心数
|
scripts/demo/configs/nanotube_run_md_l0_1dcu.yaml
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ============================================================
|
| 2 |
+
# Nanotube L0 MD inference (single DCU)
|
| 3 |
+
# ============================================================
|
| 4 |
+
name: nanotube_run_md_l0_1dcu
|
| 5 |
+
|
| 6 |
+
description: "Run MD using trained nanotube L0 model"
|
| 7 |
+
|
| 8 |
+
entrypoint:
|
| 9 |
+
script: run_md.py
|
| 10 |
+
args:
|
| 11 |
+
init_conf: "${ONESCIENCE_DATASETS_DIR}/data/nanotube/nanotube_init.xyz"
|
| 12 |
+
model: "${ONESCIENCE_MODELS_DIR}/mace/nanotube_l0_8dcu.model"
|
| 13 |
+
output_xyz: "md_result.xyz"
|
| 14 |
+
plot_file: "md_plot.png"
|
| 15 |
+
device: cuda
|
| 16 |
+
default_dtype: float32
|
| 17 |
+
temperature_k: 1200
|
| 18 |
+
time_step_fs: 1.0
|
| 19 |
+
friction: 0.1
|
| 20 |
+
save_interval: 10
|
| 21 |
+
steps: 1000
|
| 22 |
+
default_dtype: float64
|
| 23 |
+
data_files:
|
| 24 |
+
- "${ONESCIENCE_DATASETS_DIR}/data/nanotube/nanotube_init.xyz"
|
| 25 |
+
- "${ONESCIENCE_MODELS_DIR}/mace/nanotube_l0_8dcu.model"
|
| 26 |
+
|
| 27 |
+
launch:
|
| 28 |
+
launcher: python
|
| 29 |
+
num_nodes: 1
|
| 30 |
+
num_gpus: 1
|
| 31 |
+
|
| 32 |
+
env:
|
| 33 |
+
conda_env: matchem
|
| 34 |
+
modules:
|
| 35 |
+
- sghpc-mpi-gcc/26.3
|
| 36 |
+
|
| 37 |
+
slurm:
|
| 38 |
+
partition: hpctest01
|
| 39 |
+
time: "2:00:00"
|
| 40 |
+
cpus_per_task: 16
|
scripts/demo/configs/water_1dcu.yaml
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ============================================================
|
| 2 |
+
# Water 单 DCU 训练配置
|
| 3 |
+
# 单节点单卡训练
|
| 4 |
+
# ============================================================
|
| 5 |
+
|
| 6 |
+
# 实验名称(用于输出目录和模型文件命名)
|
| 7 |
+
name: water_1dcu
|
| 8 |
+
|
| 9 |
+
# 实验描述
|
| 10 |
+
description: "Water 势能面 - 单 DCU 训练"
|
| 11 |
+
|
| 12 |
+
# ============================================================
|
| 13 |
+
# train.py 参数
|
| 14 |
+
# 以下所有字段将被转换为 train.py 的命令行参数
|
| 15 |
+
# 例如 model: MACE -> --model=MACE
|
| 16 |
+
# swa: true -> --swa (布尔标志)
|
| 17 |
+
# ============================================================
|
| 18 |
+
train_args:
|
| 19 |
+
# --- 模型配置 ---
|
| 20 |
+
model: MACE # 模型类型
|
| 21 |
+
default_dtype: float64 # 数据精度
|
| 22 |
+
num_interactions: 2 # 交互层数
|
| 23 |
+
num_channels: 64 # 通道数
|
| 24 |
+
max_L: 0 # 最大角动量
|
| 25 |
+
correlation: 3 # 相关阶数
|
| 26 |
+
|
| 27 |
+
# --- 数据配置 ---
|
| 28 |
+
# 路径中的 ${ONESCIENCE_DATASETS_DIR} 默认指向仓库根目录,下载对应数据集到 ./data 后即可使用
|
| 29 |
+
train_file: "${ONESCIENCE_DATASETS_DIR}/data/water/water_train.xyz"
|
| 30 |
+
test_file: "${ONESCIENCE_DATASETS_DIR}/data/water/water_test.xyz"
|
| 31 |
+
valid_fraction: 0.05 # 从训练集中划分 5% 作为验证集
|
| 32 |
+
E0s: isolated # 原子参考能量方式: average / isolated / 显式字典
|
| 33 |
+
energy_key: TolEnergy # XYZ 文件中能量字段名
|
| 34 |
+
forces_key: force # XYZ 文件中力字段名
|
| 35 |
+
|
| 36 |
+
# --- 训练超参 ---
|
| 37 |
+
seed: 123 # 随机种子
|
| 38 |
+
device: cuda # 设备: cuda / cpu
|
| 39 |
+
r_max: 6.0 # 截断半径 (Angstrom)
|
| 40 |
+
batch_size: 2 # 训练批大小
|
| 41 |
+
valid_batch_size: 4 # 验证批大小
|
| 42 |
+
max_num_epochs: 10 # 最大训练轮数
|
| 43 |
+
forces_weight: 1000 # 力的损失权重
|
| 44 |
+
energy_weight: 10 # 能量的损失权重
|
| 45 |
+
scheduler_patience: 15 # 学习率调度器耐心值
|
| 46 |
+
patience: 30 # 早停耐心值
|
| 47 |
+
eval_interval: 1 # 每 1 个 epoch 评估并打印一次
|
| 48 |
+
error_table: PerAtomMAE # 误差表类型
|
| 49 |
+
|
| 50 |
+
# --- SWA / EMA ---
|
| 51 |
+
ema: true # 启用 EMA
|
| 52 |
+
swa: true # 启用 SWA
|
| 53 |
+
|
| 54 |
+
# --- 其他 ---
|
| 55 |
+
restart_latest: true # 从最新检查点重启
|
| 56 |
+
save_cpu: true # 保存检查点
|
| 57 |
+
|
| 58 |
+
# ============================================================
|
| 59 |
+
# 启动配置(控制 python / torchrun / srun 的选择)
|
| 60 |
+
# ============================================================
|
| 61 |
+
launch:
|
| 62 |
+
launcher: python # 启动方式: python / torchrun (多节点时自动用 srun)
|
| 63 |
+
num_nodes: 1 # 节点数
|
| 64 |
+
num_gpus: 1 # 申请的 DCU 卡数
|
| 65 |
+
|
| 66 |
+
# ============================================================
|
| 67 |
+
# 环境配置
|
| 68 |
+
# ============================================================
|
| 69 |
+
env:
|
| 70 |
+
conda_env: matchem # conda 环境名
|
| 71 |
+
modules:
|
| 72 |
+
- sghpc-mpi-gcc/26.3 # 需要加载的 module
|
| 73 |
+
|
| 74 |
+
# ============================================================
|
| 75 |
+
# SLURM 配置(仅 --submit 模式使用)
|
| 76 |
+
# ============================================================
|
| 77 |
+
slurm:
|
| 78 |
+
partition: hpctest02 # SLURM 分区
|
| 79 |
+
time: "8:00:00" # 作业时间限制
|
| 80 |
+
cpus_per_task: 16 # CPU 核心数 (128 / 8 DCU = 16)
|
scripts/demo/configs/water_4dcu.yaml
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ============================================================
|
| 2 |
+
# Water 4 DCU 训练配置
|
| 3 |
+
# 单节点 4 DCU 分布式训练
|
| 4 |
+
# ============================================================
|
| 5 |
+
|
| 6 |
+
# 实验名称(用于输出目录和模型文件命名)
|
| 7 |
+
name: water_4dcu
|
| 8 |
+
|
| 9 |
+
# 实验描述
|
| 10 |
+
description: "Water 势能面 - 4 DCU 分布式训练"
|
| 11 |
+
|
| 12 |
+
# ============================================================
|
| 13 |
+
# train.py 参数
|
| 14 |
+
# 以下所有字段将被转换为 train.py 的命令行参数
|
| 15 |
+
# 例如 model: MACE -> --model=MACE
|
| 16 |
+
# swa: true -> --swa (布尔标志)
|
| 17 |
+
# ============================================================
|
| 18 |
+
train_args:
|
| 19 |
+
# --- 模型配置 ---
|
| 20 |
+
model: MACE # 模型类型
|
| 21 |
+
default_dtype: float64 # 数据精度
|
| 22 |
+
num_interactions: 2 # 交互层数
|
| 23 |
+
num_channels: 64 # 通道数
|
| 24 |
+
max_L: 0 # 最大角动量
|
| 25 |
+
correlation: 3 # 相关阶数
|
| 26 |
+
|
| 27 |
+
# --- 数据配置 ---
|
| 28 |
+
# 路径中的 ${ONESCIENCE_DATASETS_DIR} 默认指向仓库根目录,下载对应数据集到 ./data 后即可使用
|
| 29 |
+
train_file: "${ONESCIENCE_DATASETS_DIR}/data/water/water_train.xyz"
|
| 30 |
+
test_file: "${ONESCIENCE_DATASETS_DIR}/data/water/water_test.xyz"
|
| 31 |
+
valid_fraction: 0.05 # 从训练集中划分 5% 作为验证集
|
| 32 |
+
E0s: isolated # 原子参考能量方式: average / isolated / 显式字典
|
| 33 |
+
energy_key: TolEnergy # XYZ 文件中能量字段名
|
| 34 |
+
forces_key: force # XYZ 文件中力字段名
|
| 35 |
+
|
| 36 |
+
# --- 训练超参 ---
|
| 37 |
+
seed: 123 # 随机种子
|
| 38 |
+
device: cuda # 设备: cuda / cpu
|
| 39 |
+
r_max: 6.0 # 截断半径 (Angstrom)
|
| 40 |
+
batch_size: 2 # 训练批大小
|
| 41 |
+
valid_batch_size: 4 # 验证批大小
|
| 42 |
+
max_num_epochs: 20 # 最大训练轮数
|
| 43 |
+
forces_weight: 1000 # 力的损失权重
|
| 44 |
+
energy_weight: 10 # 能量的损失权重
|
| 45 |
+
scheduler_patience: 15 # 学习率调度器耐心值
|
| 46 |
+
patience: 30 # 早停耐心值
|
| 47 |
+
eval_interval: 1 # 每 1 个 epoch 评估并打印一次
|
| 48 |
+
error_table: PerAtomMAE # 误差表类型
|
| 49 |
+
|
| 50 |
+
# --- SWA / EMA ---
|
| 51 |
+
ema: true # 启用 EMA
|
| 52 |
+
swa: true # 启用 SWA
|
| 53 |
+
start_swa: 15 # SWA 开始 epoch
|
| 54 |
+
|
| 55 |
+
# --- 其他 ---
|
| 56 |
+
restart_latest: true # 从最新检查点重启
|
| 57 |
+
save_cpu: true # 保存检查点
|
| 58 |
+
|
| 59 |
+
# ============================================================
|
| 60 |
+
# 启动配置(控制 python / torchrun / srun 的选择)
|
| 61 |
+
# ============================================================
|
| 62 |
+
launch:
|
| 63 |
+
launcher: torchrun # 启动方式: python / torchrun (多节点时自动用 srun)
|
| 64 |
+
num_nodes: 1 # 节点数
|
| 65 |
+
num_gpus: 4 # 申请的 DCU 卡数
|
| 66 |
+
|
| 67 |
+
# ============================================================
|
| 68 |
+
# 环境配置
|
| 69 |
+
# ============================================================
|
| 70 |
+
env:
|
| 71 |
+
conda_env: matchem # conda 环境名
|
| 72 |
+
modules:
|
| 73 |
+
- sghpc-mpi-gcc/26.3 # 需要加载的 module
|
| 74 |
+
|
| 75 |
+
# ============================================================
|
| 76 |
+
# SLURM 配置(仅 --submit 模式使用)
|
| 77 |
+
# ============================================================
|
| 78 |
+
slurm:
|
| 79 |
+
partition: hpctest02 # SLURM 分区
|
| 80 |
+
time: "8:00:00" # 作业时间限制
|
| 81 |
+
cpus_per_task: 64 # CPU 核心数
|
scripts/demo/configs/water_8dcu.yaml
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ============================================================
|
| 2 |
+
# Water 8 DCU 训练配置
|
| 3 |
+
# 单节点 8 DCU 分布式训练
|
| 4 |
+
# ============================================================
|
| 5 |
+
|
| 6 |
+
# 实验名称(用于输出目录和模型文件命名)
|
| 7 |
+
name: water_8dcu
|
| 8 |
+
|
| 9 |
+
# 实验描述
|
| 10 |
+
description: "Water 势能面 - 8 DCU 分布式训练"
|
| 11 |
+
|
| 12 |
+
# ============================================================
|
| 13 |
+
# train.py 参数
|
| 14 |
+
# 以下所有字段将被转换为 train.py 的命令行参数
|
| 15 |
+
# 例如 model: MACE -> --model=MACE
|
| 16 |
+
# swa: true -> --swa (布尔标志)
|
| 17 |
+
# ============================================================
|
| 18 |
+
train_args:
|
| 19 |
+
# --- 模型配置 ---
|
| 20 |
+
model: MACE # 模型类型
|
| 21 |
+
default_dtype: float64 # 数据精度
|
| 22 |
+
num_interactions: 2 # 交互层数
|
| 23 |
+
num_channels: 64 # 通道数
|
| 24 |
+
max_L: 0 # 最大角动量
|
| 25 |
+
correlation: 3 # 相关阶数
|
| 26 |
+
|
| 27 |
+
# --- 数据配置 ---
|
| 28 |
+
# 路径中的 ${ONESCIENCE_DATASETS_DIR} 默认指向仓库根目录,下载对应数据集到 ./data 后即可使用
|
| 29 |
+
train_file: "${ONESCIENCE_DATASETS_DIR}/data/water/water_train.xyz"
|
| 30 |
+
test_file: "${ONESCIENCE_DATASETS_DIR}/data/water/water_test.xyz"
|
| 31 |
+
valid_fraction: 0.05 # 从训练集中划分 5% 作为验证集
|
| 32 |
+
E0s: isolated # 原子参考能量方式: average / isolated / 显式字典
|
| 33 |
+
energy_key: TolEnergy # XYZ 文件中能量字段名
|
| 34 |
+
forces_key: force # XYZ 文件中力字段名
|
| 35 |
+
|
| 36 |
+
# --- 训练超参 ---
|
| 37 |
+
seed: 123 # 随机种子
|
| 38 |
+
device: cuda # 设备: cuda / cpu
|
| 39 |
+
r_max: 6.0 # 截断半径 (Angstrom)
|
| 40 |
+
batch_size: 2 # 训练批大小
|
| 41 |
+
valid_batch_size: 4 # 验证批大小
|
| 42 |
+
max_num_epochs: 20 # 最大训练轮数
|
| 43 |
+
forces_weight: 1000 # 力的损失权重
|
| 44 |
+
energy_weight: 10 # 能量的损失权重
|
| 45 |
+
scheduler_patience: 15 # 学习率调度器耐心值
|
| 46 |
+
patience: 30 # 早停耐心值
|
| 47 |
+
eval_interval: 1 # 每 1 个 epoch 评估并打印一次
|
| 48 |
+
error_table: PerAtomMAE # 误差表类型
|
| 49 |
+
|
| 50 |
+
# --- SWA / EMA ---
|
| 51 |
+
ema: true # 启用 EMA
|
| 52 |
+
swa: true # 启用 SWA
|
| 53 |
+
start_swa: 15 # SWA 开始 epoch
|
| 54 |
+
|
| 55 |
+
# --- 其他 ---
|
| 56 |
+
restart_latest: true # 从最新检查点重启
|
| 57 |
+
save_cpu: true # 保存检查点
|
| 58 |
+
|
| 59 |
+
# ============================================================
|
| 60 |
+
# 启动配置(控制 python / torchrun / srun 的选择)
|
| 61 |
+
# ============================================================
|
| 62 |
+
launch:
|
| 63 |
+
launcher: torchrun # 启动方式: python / torchrun (多节点时自动用 srun)
|
| 64 |
+
num_nodes: 1 # 节点数
|
| 65 |
+
num_gpus: 8 # 申请的 DCU 卡数
|
| 66 |
+
|
| 67 |
+
# ============================================================
|
| 68 |
+
# 环境配置
|
| 69 |
+
# ============================================================
|
| 70 |
+
env:
|
| 71 |
+
conda_env: matchem # conda 环境名
|
| 72 |
+
modules:
|
| 73 |
+
- sghpc-mpi-gcc/26.3 # 需要加载的 module
|
| 74 |
+
|
| 75 |
+
# ============================================================
|
| 76 |
+
# SLURM 配置(仅 --submit 模式使用)
|
| 77 |
+
# ============================================================
|
| 78 |
+
slurm:
|
| 79 |
+
partition: hpctest02 # SLURM 分区
|
| 80 |
+
time: "8:00:00" # 作业时间限制
|
| 81 |
+
cpus_per_task: 128 # CPU 核心数
|
scripts/demo/configs/water_finetune_mp0_8dcu.yaml
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ============================================================
|
| 2 |
+
# Water 8 DCU 微调配置 —— 基于 MACE-MP-0 基础模型
|
| 3 |
+
# 单节点 8 DCU 分布式微调
|
| 4 |
+
# ============================================================
|
| 5 |
+
|
| 6 |
+
# 实验名称(用于输出目录和模型文件命名)
|
| 7 |
+
name: water_finetune_mp0_8dcu
|
| 8 |
+
|
| 9 |
+
# 实验描述
|
| 10 |
+
description: "Water 势能面 - 基于 MACE-MP-0 (medium) 微调 - 8 DCU"
|
| 11 |
+
|
| 12 |
+
# ============================================================
|
| 13 |
+
# train.py 参数
|
| 14 |
+
# 以下所有字段将被转换为 train.py 的命令行参数
|
| 15 |
+
# ============================================================
|
| 16 |
+
train_args:
|
| 17 |
+
# --- 基础模型配置 ---
|
| 18 |
+
# 使用 mace-mp-0 medium 预训练模型(自动下载或加载本地缓存)
|
| 19 |
+
# 可选: small / medium / large / medium-mpa-0 / medium-omat-0 等
|
| 20 |
+
foundation_model: medium
|
| 21 |
+
|
| 22 |
+
# 保留基础模型的所有元素(不仅限于当前数据集出现的元素)
|
| 23 |
+
# 设置为 true 可防止元素嵌入被截断,有利于泛化
|
| 24 |
+
foundation_model_elements: true
|
| 25 |
+
|
| 26 |
+
# --- 模型配置 ---
|
| 27 |
+
# 当指定 foundation_model 时,以下参数通常会自动从基础模型继承
|
| 28 |
+
# 若需覆盖,可取消注释并修改
|
| 29 |
+
# model: MACE
|
| 30 |
+
# default_dtype: float64
|
| 31 |
+
# num_channels: 128
|
| 32 |
+
# max_L: 1
|
| 33 |
+
# num_interactions: 2
|
| 34 |
+
# correlation: 3
|
| 35 |
+
|
| 36 |
+
# --- 数据配置 ---
|
| 37 |
+
train_file: "${ONESCIENCE_DATASETS_DIR}/data/water/water_train.xyz"
|
| 38 |
+
test_file: "${ONESCIENCE_DATASETS_DIR}/data/water/water_test.xyz"
|
| 39 |
+
valid_fraction: 0.05
|
| 40 |
+
E0s: isolated
|
| 41 |
+
energy_key: TolEnergy
|
| 42 |
+
forces_key: force
|
| 43 |
+
|
| 44 |
+
# --- 训练超参(微调建议) ---
|
| 45 |
+
# 微调通常使用较小的学习率和较少的 epoch
|
| 46 |
+
seed: 123
|
| 47 |
+
device: cuda
|
| 48 |
+
r_max: 6.0
|
| 49 |
+
batch_size: 2
|
| 50 |
+
valid_batch_size: 4
|
| 51 |
+
|
| 52 |
+
# 微调 epoch 数通常较少(基础模型已具备先验知识)
|
| 53 |
+
max_num_epochs: 50
|
| 54 |
+
|
| 55 |
+
# 损失权重
|
| 56 |
+
forces_weight: 1000
|
| 57 |
+
energy_weight: 10
|
| 58 |
+
|
| 59 |
+
# 学习率相关(微调建议使用较小学习率)
|
| 60 |
+
lr: 0.001
|
| 61 |
+
scheduler_patience: 10
|
| 62 |
+
patience: 20
|
| 63 |
+
eval_interval: 1
|
| 64 |
+
error_table: PerAtomMAE
|
| 65 |
+
|
| 66 |
+
# --- SWA / EMA ---
|
| 67 |
+
ema: true
|
| 68 |
+
swa: true
|
| 69 |
+
start_swa: 35
|
| 70 |
+
|
| 71 |
+
# --- 其他 ---
|
| 72 |
+
restart_latest: true
|
| 73 |
+
save_cpu: true
|
| 74 |
+
|
| 75 |
+
# ============================================================
|
| 76 |
+
# 启动配置
|
| 77 |
+
# ============================================================
|
| 78 |
+
launch:
|
| 79 |
+
launcher: torchrun
|
| 80 |
+
num_nodes: 1
|
| 81 |
+
num_gpus: 8
|
| 82 |
+
|
| 83 |
+
# ============================================================
|
| 84 |
+
# 环境配置
|
| 85 |
+
# ============================================================
|
| 86 |
+
env:
|
| 87 |
+
conda_env: matchem
|
| 88 |
+
modules:
|
| 89 |
+
- sghpc-mpi-gcc/26.3
|
| 90 |
+
|
| 91 |
+
# ============================================================
|
| 92 |
+
# SLURM 配置(仅 --submit 模式使用)
|
| 93 |
+
# ============================================================
|
| 94 |
+
slurm:
|
| 95 |
+
partition: hpctest02
|
| 96 |
+
time: "4:00:00"
|
| 97 |
+
cpus_per_task: 128
|
scripts/demo/run.sh
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
# run.sh - MACE 训练统一入口脚本
|
| 3 |
+
#
|
| 4 |
+
# 用法:
|
| 5 |
+
# bash run.sh --config configs/DMC.yaml # 直接运行训练
|
| 6 |
+
# bash run.sh --config configs/DMC.yaml --submit # 提交 SLURM 作业
|
| 7 |
+
# bash run.sh --config configs/DMC.yaml --dry-run # 仅打印命令,不执行
|
| 8 |
+
#
|
| 9 |
+
set -euo pipefail
|
| 10 |
+
|
| 11 |
+
# ============================================================
|
| 12 |
+
# 参数解析
|
| 13 |
+
# ============================================================
|
| 14 |
+
CONFIG=""
|
| 15 |
+
SUBMIT=false
|
| 16 |
+
DRY_RUN=false
|
| 17 |
+
|
| 18 |
+
while [[ $# -gt 0 ]]; do
|
| 19 |
+
case "$1" in
|
| 20 |
+
--config)
|
| 21 |
+
CONFIG="$2"; shift 2 ;;
|
| 22 |
+
--config=*)
|
| 23 |
+
CONFIG="${1#*=}"; shift ;;
|
| 24 |
+
--submit)
|
| 25 |
+
SUBMIT=true; shift ;;
|
| 26 |
+
--dry-run)
|
| 27 |
+
DRY_RUN=true; shift ;;
|
| 28 |
+
-h|--help)
|
| 29 |
+
echo "用法: bash run.sh --config <config.yaml> [--submit] [--dry-run]"
|
| 30 |
+
echo ""
|
| 31 |
+
echo "选项:"
|
| 32 |
+
echo " --config <file> YAML 配置文件路径 (必需)"
|
| 33 |
+
echo " --submit 生成 SLURM 脚本并提交作业"
|
| 34 |
+
echo " --dry-run 仅打印训练命令,不执行"
|
| 35 |
+
exit 0 ;;
|
| 36 |
+
*)
|
| 37 |
+
echo "[ERROR] 未知参数: $1"
|
| 38 |
+
exit 1 ;;
|
| 39 |
+
esac
|
| 40 |
+
done
|
| 41 |
+
|
| 42 |
+
if [ -z "$CONFIG" ]; then
|
| 43 |
+
echo "[ERROR] 请指定配置文件: bash run.sh --config configs/xxx.yaml"
|
| 44 |
+
exit 1
|
| 45 |
+
fi
|
| 46 |
+
|
| 47 |
+
if [ ! -f "$CONFIG" ]; then
|
| 48 |
+
echo "[ERROR] 配置文件不存在: $CONFIG"
|
| 49 |
+
exit 1
|
| 50 |
+
fi
|
| 51 |
+
|
| 52 |
+
# ============================================================
|
| 53 |
+
# 路径设置
|
| 54 |
+
# ============================================================
|
| 55 |
+
DEMO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
| 56 |
+
REPO_ROOT="$(cd "$DEMO_DIR/../.." && pwd)"
|
| 57 |
+
CONFIG_ABS="$(cd "$(dirname "$CONFIG")" && pwd)/$(basename "$CONFIG")"
|
| 58 |
+
PARSE_PY="$DEMO_DIR/_parse_config.py"
|
| 59 |
+
|
| 60 |
+
# 如果用户没有设置 ONESCIENCE_DATASETS_DIR,默认指向仓库根目录
|
| 61 |
+
export ONESCIENCE_DATASETS_DIR="${ONESCIENCE_DATASETS_DIR:-$REPO_ROOT}"
|
| 62 |
+
|
| 63 |
+
# ============================================================
|
| 64 |
+
# 解析配置
|
| 65 |
+
# ============================================================
|
| 66 |
+
EXP_NAME=$(python3 "$PARSE_PY" "$CONFIG_ABS" name)
|
| 67 |
+
TRAIN_CMD=$(python3 "$PARSE_PY" "$CONFIG_ABS" command)
|
| 68 |
+
ENV_EXPORTS=$(python3 "$PARSE_PY" "$CONFIG_ABS" env)
|
| 69 |
+
ENV_ARGS=$(python3 "$PARSE_PY" "$CONFIG_ABS" env-args)
|
| 70 |
+
DATA_FILES=$(python3 "$PARSE_PY" "$CONFIG_ABS" data-files)
|
| 71 |
+
|
| 72 |
+
# ============================================================
|
| 73 |
+
# Dry-run 模式
|
| 74 |
+
# ============================================================
|
| 75 |
+
if $DRY_RUN; then
|
| 76 |
+
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
| 77 |
+
OUTPUT_DIR="$DEMO_DIR/outputs/${EXP_NAME}_${TIMESTAMP}"
|
| 78 |
+
echo "========================================="
|
| 79 |
+
echo "Dry-run: $EXP_NAME"
|
| 80 |
+
echo "Config: $CONFIG_ABS"
|
| 81 |
+
echo "Output: $OUTPUT_DIR (未创建)"
|
| 82 |
+
echo "========================================="
|
| 83 |
+
echo ""
|
| 84 |
+
echo "# 环境变量:"
|
| 85 |
+
echo "$ENV_EXPORTS"
|
| 86 |
+
echo ""
|
| 87 |
+
echo "# 训练命令:"
|
| 88 |
+
echo "$TRAIN_CMD"
|
| 89 |
+
echo ""
|
| 90 |
+
echo "# env_setup.sh 参数: $ENV_ARGS"
|
| 91 |
+
echo "# 数据文件:"
|
| 92 |
+
echo "$DATA_FILES" | sed 's/^/# /'
|
| 93 |
+
exit 0
|
| 94 |
+
fi
|
| 95 |
+
|
| 96 |
+
# ============================================================
|
| 97 |
+
# 创建输出目录
|
| 98 |
+
# ============================================================
|
| 99 |
+
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
| 100 |
+
OUTPUT_DIR="$DEMO_DIR/outputs/${EXP_NAME}_${TIMESTAMP}"
|
| 101 |
+
mkdir -p "$OUTPUT_DIR"
|
| 102 |
+
cp "$CONFIG_ABS" "$OUTPUT_DIR/config.yaml"
|
| 103 |
+
|
| 104 |
+
# ============================================================
|
| 105 |
+
# SLURM 提交模式
|
| 106 |
+
# ============================================================
|
| 107 |
+
if $SUBMIT; then
|
| 108 |
+
SLURM_VARS=$(python3 "$PARSE_PY" "$CONFIG_ABS" slurm)
|
| 109 |
+
eval "$SLURM_VARS"
|
| 110 |
+
|
| 111 |
+
# 检查是否是多节点
|
| 112 |
+
SLURM_SCRIPT="$OUTPUT_DIR/submit.sh"
|
| 113 |
+
|
| 114 |
+
# 生成 SLURM header
|
| 115 |
+
sed -e "s|{{JOB_NAME}}|${JOB_NAME}|g" \
|
| 116 |
+
-e "s|{{PARTITION}}|${PARTITION}|g" \
|
| 117 |
+
-e "s|{{NODES}}|${NODES}|g" \
|
| 118 |
+
-e "s|{{NTASKS_PER_NODE}}|${NTASKS_PER_NODE}|g" \
|
| 119 |
+
-e "s|{{CPUS_PER_TASK}}|${CPUS_PER_TASK}|g" \
|
| 120 |
+
-e "s|{{GPUS_PER_NODE}}|${GPUS_PER_NODE}|g" \
|
| 121 |
+
-e "s|{{TIME}}|${TIME}|g" \
|
| 122 |
+
"$DEMO_DIR/templates/slurm_header.template" > "$SLURM_SCRIPT"
|
| 123 |
+
|
| 124 |
+
# 添加环境初始化
|
| 125 |
+
cat >> "$SLURM_SCRIPT" << 'SETUP_BLOCK'
|
| 126 |
+
|
| 127 |
+
# 环境初始化
|
| 128 |
+
SETUP_BLOCK
|
| 129 |
+
cat >> "$SLURM_SCRIPT" << 'ENV_BLOCK'
|
| 130 |
+
set +u
|
| 131 |
+
if [ -n "${MACE_ENV_SCRIPT:-}" ] && [ -f "${MACE_ENV_SCRIPT}" ]; then
|
| 132 |
+
source "${MACE_ENV_SCRIPT}"
|
| 133 |
+
else
|
| 134 |
+
echo "[WARN] MACE_ENV_SCRIPT 未设置或文件不存在,跳过环境初始化。请自行确保 conda/matchem 环境已激活。"
|
| 135 |
+
fi
|
| 136 |
+
set -u
|
| 137 |
+
ENV_BLOCK
|
| 138 |
+
|
| 139 |
+
# 添加预检
|
| 140 |
+
echo "" >> "$SLURM_SCRIPT"
|
| 141 |
+
echo "# 预检" >> "$SLURM_SCRIPT"
|
| 142 |
+
# 将数据文件列表转为参数
|
| 143 |
+
DATA_ARGS=""
|
| 144 |
+
while IFS= read -r line; do
|
| 145 |
+
[ -z "$line" ] && continue
|
| 146 |
+
DATA_ARGS="$DATA_ARGS \"$line\""
|
| 147 |
+
done <<< "$DATA_FILES"
|
| 148 |
+
echo "bash $DEMO_DIR/templates/preflight_check.sh $DATA_ARGS" >> "$SLURM_SCRIPT"
|
| 149 |
+
|
| 150 |
+
# 添加环境变量和训练命令
|
| 151 |
+
echo "" >> "$SLURM_SCRIPT"
|
| 152 |
+
echo "# 工作目录" >> "$SLURM_SCRIPT"
|
| 153 |
+
echo "cd $OUTPUT_DIR" >> "$SLURM_SCRIPT"
|
| 154 |
+
echo "" >> "$SLURM_SCRIPT"
|
| 155 |
+
echo "# 环境变量" >> "$SLURM_SCRIPT"
|
| 156 |
+
echo "$ENV_EXPORTS" >> "$SLURM_SCRIPT"
|
| 157 |
+
echo "" >> "$SLURM_SCRIPT"
|
| 158 |
+
echo "# 将仓库根目录加入 PYTHONPATH,确保能 import 本地 model 包" >> "$SLURM_SCRIPT"
|
| 159 |
+
echo "export PYTHONPATH=\"$REPO_ROOT:\${PYTHONPATH:-}\"" >> "$SLURM_SCRIPT"
|
| 160 |
+
echo "" >> "$SLURM_SCRIPT"
|
| 161 |
+
echo "# 屏蔽 e3nn FutureWarning 和 TorchScript UserWarning" >> "$SLURM_SCRIPT"
|
| 162 |
+
echo 'export PYTHONWARNINGS="ignore::FutureWarning:e3nn.o3._wigner,ignore::UserWarning:torch.jit._check"' >> "$SLURM_SCRIPT"
|
| 163 |
+
echo "" >> "$SLURM_SCRIPT"
|
| 164 |
+
echo '# 屏蔽 PyTorch NCCL C++ INFO 日志' >> "$SLURM_SCRIPT"
|
| 165 |
+
echo 'export TORCH_CPP_LOG_LEVEL=WARNING' >> "$SLURM_SCRIPT"
|
| 166 |
+
echo 'export NCCL_DEBUG=ERROR' >> "$SLURM_SCRIPT"
|
| 167 |
+
echo '' >> "$SLURM_SCRIPT"
|
| 168 |
+
echo '# 屏蔽 glog INFO(ProcessGroupNCCL.cpp 初始化信息)' >> "$SLURM_SCRIPT"
|
| 169 |
+
echo 'export GLOG_minloglevel=1' >> "$SLURM_SCRIPT"
|
| 170 |
+
echo '' >> "$SLURM_SCRIPT"
|
| 171 |
+
echo '# AMD DCU: 避免 RCCL "Missing HSA_FORCE_FINE_GRAIN_PCIE" 警告' >> "$SLURM_SCRIPT"
|
| 172 |
+
echo 'export HSA_FORCE_FINE_GRAIN_PCIE=1' >> "$SLURM_SCRIPT"
|
| 173 |
+
|
| 174 |
+
# 多节点特殊处理
|
| 175 |
+
if [ "$NODES" -gt 1 ]; then
|
| 176 |
+
cat >> "$SLURM_SCRIPT" << 'MULTI_NODE'
|
| 177 |
+
|
| 178 |
+
# 多节点分布式设置
|
| 179 |
+
export MASTER_ADDR=$(scontrol show hostnames "$SLURM_JOB_NODELIST" | head -n 1)
|
| 180 |
+
export MASTER_PORT=29517
|
| 181 |
+
export WORLD_SIZE=$SLURM_NTASKS
|
| 182 |
+
|
| 183 |
+
echo "MASTER_ADDR: $MASTER_ADDR"
|
| 184 |
+
echo "WORLD_SIZE: $WORLD_SIZE"
|
| 185 |
+
|
| 186 |
+
# 使用 srun 启动分布式训练
|
| 187 |
+
MULTI_NODE
|
| 188 |
+
# srun 包裹训练命令
|
| 189 |
+
echo "srun --export=ALL bash -c '" >> "$SLURM_SCRIPT"
|
| 190 |
+
echo " export RANK=\$SLURM_PROCID" >> "$SLURM_SCRIPT"
|
| 191 |
+
echo " export LOCAL_RANK=\$SLURM_LOCALID" >> "$SLURM_SCRIPT"
|
| 192 |
+
echo " exec $TRAIN_CMD" >> "$SLURM_SCRIPT"
|
| 193 |
+
echo "'" >> "$SLURM_SCRIPT"
|
| 194 |
+
else
|
| 195 |
+
echo "" >> "$SLURM_SCRIPT"
|
| 196 |
+
echo "# 训练命令" >> "$SLURM_SCRIPT"
|
| 197 |
+
echo "$TRAIN_CMD" >> "$SLURM_SCRIPT"
|
| 198 |
+
fi
|
| 199 |
+
|
| 200 |
+
echo "========================================="
|
| 201 |
+
echo "SLURM 脚本已生成: $SLURM_SCRIPT"
|
| 202 |
+
echo "配置快照已保存: $OUTPUT_DIR/config.yaml"
|
| 203 |
+
echo "========================================="
|
| 204 |
+
echo ""
|
| 205 |
+
echo "提交作业..."
|
| 206 |
+
sbatch "$SLURM_SCRIPT"
|
| 207 |
+
exit 0
|
| 208 |
+
fi
|
| 209 |
+
|
| 210 |
+
# ============================================================
|
| 211 |
+
# 直接运行模式
|
| 212 |
+
# ============================================================
|
| 213 |
+
echo "========================================="
|
| 214 |
+
echo "实验: $EXP_NAME"
|
| 215 |
+
echo "配置: $CONFIG_ABS"
|
| 216 |
+
echo "输出: $OUTPUT_DIR"
|
| 217 |
+
echo "========================================="
|
| 218 |
+
|
| 219 |
+
# 加载用户环境初始化脚本(路径通过 MACE_ENV_SCRIPT 指定)
|
| 220 |
+
set +u
|
| 221 |
+
if [ -n "${MACE_ENV_SCRIPT:-}" ] && [ -f "${MACE_ENV_SCRIPT}" ]; then
|
| 222 |
+
source "${MACE_ENV_SCRIPT}"
|
| 223 |
+
else
|
| 224 |
+
echo "[WARN] MACE_ENV_SCRIPT 未设置或文件不存在,跳过环境初始化。请自行确保 conda/matchem 环境已激活。"
|
| 225 |
+
fi
|
| 226 |
+
set -u
|
| 227 |
+
|
| 228 |
+
# 预检
|
| 229 |
+
DATA_ARGS=""
|
| 230 |
+
while IFS= read -r line; do
|
| 231 |
+
[ -z "$line" ] && continue
|
| 232 |
+
DATA_ARGS="$DATA_ARGS \"$line\""
|
| 233 |
+
done <<< "$DATA_FILES"
|
| 234 |
+
eval "bash $DEMO_DIR/templates/preflight_check.sh $DATA_ARGS"
|
| 235 |
+
|
| 236 |
+
# 设置环境变量
|
| 237 |
+
eval "$ENV_EXPORTS"
|
| 238 |
+
|
| 239 |
+
# 将仓库根目录加入 PYTHONPATH,确保能 import 本地 model 包
|
| 240 |
+
export PYTHONPATH="$REPO_ROOT:${PYTHONPATH:-}"
|
| 241 |
+
|
| 242 |
+
# 屏蔽 e3nn FutureWarning 和 TorchScript UserWarning
|
| 243 |
+
export PYTHONWARNINGS="ignore::FutureWarning:e3nn.o3._wigner,ignore::UserWarning:torch.jit._check"
|
| 244 |
+
|
| 245 |
+
# 屏蔽 PyTorch NCCL C++ INFO 日志
|
| 246 |
+
export TORCH_CPP_LOG_LEVEL=WARNING
|
| 247 |
+
export NCCL_DEBUG=ERROR
|
| 248 |
+
|
| 249 |
+
# 屏蔽 glog INFO(ProcessGroupNCCL.cpp 初始化信息)
|
| 250 |
+
export GLOG_minloglevel=1
|
| 251 |
+
|
| 252 |
+
# AMD DCU: 避免 RCCL "Missing HSA_FORCE_FINE_GRAIN_PCIE" 警告
|
| 253 |
+
export HSA_FORCE_FINE_GRAIN_PCIE=1
|
| 254 |
+
|
| 255 |
+
# 切换到输出目录
|
| 256 |
+
cd "$OUTPUT_DIR"
|
| 257 |
+
|
| 258 |
+
# 执行训练
|
| 259 |
+
echo "========================================="
|
| 260 |
+
echo "开始训练..."
|
| 261 |
+
echo "========================================="
|
| 262 |
+
if [ -n "${TRAIN_CMD:-}" ]; then
|
| 263 |
+
eval "$TRAIN_CMD"
|
| 264 |
+
else
|
| 265 |
+
echo "[FATAL] TRAIN_CMD 为空,无法执行任务"
|
| 266 |
+
exit 1
|
| 267 |
+
fi
|
scripts/demo/templates/env_setup.sh
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
# env_setup.sh - 公共环境初始化脚本
|
| 3 |
+
# 用法: source templates/env_setup.sh <conda_env> <module1> [module2 ...]
|
| 4 |
+
#
|
| 5 |
+
# 参数:
|
| 6 |
+
# $1 - conda 环境名 (如 chem)
|
| 7 |
+
# $2 ... - 需要加载的 module 列表
|
| 8 |
+
|
| 9 |
+
CONDA_ENV="${1:?请指定 conda 环境名}"
|
| 10 |
+
shift
|
| 11 |
+
|
| 12 |
+
# 系统脚本 (bashrc, conda, module) 可能引用未初始化变量或返回非零退出码,
|
| 13 |
+
# 临时关闭 errexit 和 nounset 避免报错
|
| 14 |
+
set +eu
|
| 15 |
+
|
| 16 |
+
# 清理环境
|
| 17 |
+
module purge
|
| 18 |
+
source ~/.bashrc
|
| 19 |
+
|
| 20 |
+
# 激活 conda 环境
|
| 21 |
+
conda activate "$CONDA_ENV"
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
# 加载指定 modules
|
| 25 |
+
for mod in "$@"; do
|
| 26 |
+
module load "$mod"
|
| 27 |
+
done
|
| 28 |
+
|
| 29 |
+
# 恢复 errexit 和 nounset
|
| 30 |
+
set -eu
|
| 31 |
+
|
| 32 |
+
# 加载数据集路径
|
| 33 |
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
| 34 |
+
DEMO_DIR="$(dirname "$SCRIPT_DIR")"
|
| 35 |
+
REPO_ROOT="$(cd "$DEMO_DIR/../.." && pwd)"
|
| 36 |
+
if [ -f "$REPO_ROOT/env.sh" ]; then
|
| 37 |
+
source "$REPO_ROOT/env.sh"
|
| 38 |
+
fi
|
scripts/demo/templates/preflight_check.sh
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
# preflight_check.sh - 训练前预检脚本
|
| 3 |
+
# 用法: bash templates/preflight_check.sh [data_file1] [data_file2] ...
|
| 4 |
+
#
|
| 5 |
+
# 检查项:
|
| 6 |
+
# 1. ONESCIENCE_DATASETS_DIR 环境变量
|
| 7 |
+
# 2. 数据文件是否存在
|
| 8 |
+
# 3. DCU/GPU 状态
|
| 9 |
+
# 4. 打印节点信息
|
| 10 |
+
|
| 11 |
+
ERRORS=0
|
| 12 |
+
|
| 13 |
+
echo "========================================="
|
| 14 |
+
echo "Node: $(hostname)"
|
| 15 |
+
echo "Time: $(date)"
|
| 16 |
+
echo "========================================="
|
| 17 |
+
|
| 18 |
+
# 检查数据集目录环境变量
|
| 19 |
+
if [ -z "${ONESCIENCE_DATASETS_DIR:-}" ]; then
|
| 20 |
+
echo "[ERROR] ONESCIENCE_DATASETS_DIR 未设置,请先 source env.sh"
|
| 21 |
+
ERRORS=$((ERRORS + 1))
|
| 22 |
+
fi
|
| 23 |
+
|
| 24 |
+
# 检查数据文件是否存在
|
| 25 |
+
for f in "$@"; do
|
| 26 |
+
# 跳过空参数
|
| 27 |
+
[ -z "$f" ] && continue
|
| 28 |
+
# 展开环境变量后检查
|
| 29 |
+
expanded=$(eval echo "$f")
|
| 30 |
+
if [ ! -e "$expanded" ]; then
|
| 31 |
+
echo "[ERROR] 数据文件不存在: $expanded"
|
| 32 |
+
ERRORS=$((ERRORS + 1))
|
| 33 |
+
else
|
| 34 |
+
echo "[OK] 数据文件: $expanded"
|
| 35 |
+
fi
|
| 36 |
+
done
|
| 37 |
+
|
| 38 |
+
# 打印 DCU/GPU 状态
|
| 39 |
+
echo "-----------------------------------------"
|
| 40 |
+
echo ">>> DCU/GPU 状态:"
|
| 41 |
+
if command -v hy-smi &>/dev/null; then
|
| 42 |
+
hy-smi
|
| 43 |
+
elif command -v rocm-smi &>/dev/null; then
|
| 44 |
+
rocm-smi
|
| 45 |
+
elif command -v nvidia-smi &>/dev/null; then
|
| 46 |
+
nvidia-smi
|
| 47 |
+
else
|
| 48 |
+
echo "[WARN] 未找到 GPU 监控工具 (hy-smi/rocm-smi/nvidia-smi)"
|
| 49 |
+
fi
|
| 50 |
+
echo "-----------------------------------------"
|
| 51 |
+
|
| 52 |
+
if [ $ERRORS -gt 0 ]; then
|
| 53 |
+
echo "[FATAL] 预检发现 $ERRORS 个错误,终止训练"
|
| 54 |
+
exit 1
|
| 55 |
+
fi
|
| 56 |
+
|
| 57 |
+
echo "[OK] 预检通过"
|
scripts/demo/templates/slurm_header.template
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
#SBATCH -J {{JOB_NAME}}
|
| 3 |
+
#SBATCH -p {{PARTITION}}
|
| 4 |
+
#SBATCH --nodes={{NODES}}
|
| 5 |
+
#SBATCH --ntasks-per-node={{NTASKS_PER_NODE}}
|
| 6 |
+
#SBATCH --cpus-per-task={{CPUS_PER_TASK}}
|
| 7 |
+
#SBATCH --gres=dcu:{{GPUS_PER_NODE}}
|
| 8 |
+
#SBATCH --output=%j.log
|
| 9 |
+
#SBATCH --time={{TIME}}
|
scripts/eval_configs.py
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Script for evaluating configurations contained in an xyz file with a trained model
|
| 2 |
+
|
| 3 |
+
import time
|
| 4 |
+
import argparse
|
| 5 |
+
|
| 6 |
+
import ase.io
|
| 7 |
+
import numpy as np
|
| 8 |
+
import torch
|
| 9 |
+
from onescience.datapipes.materials.pyg_stack.core.utils import config_from_atoms
|
| 10 |
+
from onescience.datapipes.materials.pyg_stack.core.atomic_data import AtomicData
|
| 11 |
+
from onescience.datapipes.materials.tools import torch_geometric, torch_tools, utils
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def parse_args() -> argparse.Namespace:
|
| 15 |
+
parser = argparse.ArgumentParser(
|
| 16 |
+
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
| 17 |
+
)
|
| 18 |
+
parser.add_argument("--configs", help="path to XYZ configurations", required=True)
|
| 19 |
+
parser.add_argument("--model", help="path to model", required=True)
|
| 20 |
+
parser.add_argument("--output", help="output path", required=True)
|
| 21 |
+
parser.add_argument(
|
| 22 |
+
"--device",
|
| 23 |
+
help="select device",
|
| 24 |
+
type=str,
|
| 25 |
+
choices=["cpu", "cuda"],
|
| 26 |
+
default="cpu",
|
| 27 |
+
)
|
| 28 |
+
parser.add_argument(
|
| 29 |
+
"--default_dtype",
|
| 30 |
+
help="set default dtype",
|
| 31 |
+
type=str,
|
| 32 |
+
choices=["float32", "float64"],
|
| 33 |
+
default="float64",
|
| 34 |
+
)
|
| 35 |
+
parser.add_argument("--batch_size", help="batch size", type=int, default=64)
|
| 36 |
+
parser.add_argument(
|
| 37 |
+
"--compute_stress",
|
| 38 |
+
help="compute stress",
|
| 39 |
+
action="store_true",
|
| 40 |
+
default=False,
|
| 41 |
+
)
|
| 42 |
+
parser.add_argument(
|
| 43 |
+
"--return_contributions",
|
| 44 |
+
help="model outputs energy contributions for each body order, only supported for MACE, not ScaleShiftMACE",
|
| 45 |
+
action="store_true",
|
| 46 |
+
default=False,
|
| 47 |
+
)
|
| 48 |
+
parser.add_argument(
|
| 49 |
+
"--info_prefix",
|
| 50 |
+
help="prefix for energy, forces and stress keys",
|
| 51 |
+
type=str,
|
| 52 |
+
default="MACE_",
|
| 53 |
+
)
|
| 54 |
+
parser.add_argument(
|
| 55 |
+
"--head",
|
| 56 |
+
help="Model head used for evaluation",
|
| 57 |
+
type=str,
|
| 58 |
+
required=False,
|
| 59 |
+
default=None,
|
| 60 |
+
)
|
| 61 |
+
return parser.parse_args()
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
def main() -> None:
|
| 65 |
+
args = parse_args()
|
| 66 |
+
run(args)
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
def run(args: argparse.Namespace) -> None:
|
| 70 |
+
torch_tools.set_default_dtype(args.default_dtype)
|
| 71 |
+
device = torch_tools.init_device(args.device)
|
| 72 |
+
|
| 73 |
+
# Load model
|
| 74 |
+
model = torch.load(f=args.model, map_location=args.device)
|
| 75 |
+
model = model.to(args.device)
|
| 76 |
+
model.eval() # ✅ 保持 eval,不要关掉 requires_grad
|
| 77 |
+
|
| 78 |
+
# Load data and prepare input
|
| 79 |
+
atoms_list = ase.io.read(args.configs, index=":")
|
| 80 |
+
if args.head is not None:
|
| 81 |
+
for atoms in atoms_list:
|
| 82 |
+
atoms.info["head"] = args.head
|
| 83 |
+
configs = [config_from_atoms(atoms) for atoms in atoms_list]
|
| 84 |
+
|
| 85 |
+
z_table = utils.AtomicNumberTable([int(z) for z in model.atomic_numbers])
|
| 86 |
+
|
| 87 |
+
try:
|
| 88 |
+
heads = model.heads
|
| 89 |
+
except AttributeError:
|
| 90 |
+
heads = None
|
| 91 |
+
|
| 92 |
+
data_loader = torch_geometric.dataloader.DataLoader(
|
| 93 |
+
dataset=[
|
| 94 |
+
AtomicData.from_config(
|
| 95 |
+
config, z_table=z_table, cutoff=float(model.r_max), heads=heads
|
| 96 |
+
)
|
| 97 |
+
for config in configs
|
| 98 |
+
],
|
| 99 |
+
batch_size=args.batch_size,
|
| 100 |
+
shuffle=False,
|
| 101 |
+
drop_last=False,
|
| 102 |
+
)
|
| 103 |
+
|
| 104 |
+
# Collect data
|
| 105 |
+
energies_list = []
|
| 106 |
+
contributions_list = []
|
| 107 |
+
stresses_list = []
|
| 108 |
+
forces_collection = []
|
| 109 |
+
|
| 110 |
+
total_time = 0.0
|
| 111 |
+
num_batches = 0
|
| 112 |
+
total_cfgs = len(configs)
|
| 113 |
+
|
| 114 |
+
start_total = time.time()
|
| 115 |
+
for batch in data_loader:
|
| 116 |
+
batch = batch.to(device)
|
| 117 |
+
|
| 118 |
+
if args.device == "cuda":
|
| 119 |
+
torch.cuda.synchronize()
|
| 120 |
+
start = time.time()
|
| 121 |
+
|
| 122 |
+
output = model(batch.to_dict(), compute_stress=args.compute_stress)
|
| 123 |
+
|
| 124 |
+
if args.device == "cuda":
|
| 125 |
+
torch.cuda.synchronize()
|
| 126 |
+
end = time.time()
|
| 127 |
+
|
| 128 |
+
total_time += (end - start)
|
| 129 |
+
num_batches += 1
|
| 130 |
+
|
| 131 |
+
energies_list.append(torch_tools.to_numpy(output["energy"]))
|
| 132 |
+
if args.compute_stress:
|
| 133 |
+
stresses_list.append(torch_tools.to_numpy(output["stress"]))
|
| 134 |
+
if args.return_contributions:
|
| 135 |
+
contributions_list.append(torch_tools.to_numpy(output["contributions"]))
|
| 136 |
+
|
| 137 |
+
forces = np.split(
|
| 138 |
+
torch_tools.to_numpy(output["forces"]),
|
| 139 |
+
indices_or_sections=batch.ptr[1:],
|
| 140 |
+
axis=0,
|
| 141 |
+
)
|
| 142 |
+
forces_collection.append(forces[:-1]) # drop last as its empty
|
| 143 |
+
end_total = time.time()
|
| 144 |
+
|
| 145 |
+
# ✅ 打印时间
|
| 146 |
+
if num_batches > 0 and total_cfgs > 0:
|
| 147 |
+
avg_time_batch = total_time / num_batches
|
| 148 |
+
avg_time_cfg = total_time / total_cfgs
|
| 149 |
+
print(f"Average inference time per batch: {avg_time_batch:.6f} s")
|
| 150 |
+
print(f"Average inference time per configuration: {avg_time_cfg:.6f} s")
|
| 151 |
+
print(f"Total inference time (only forward): {total_time:.6f} s")
|
| 152 |
+
print(f"Wall clock time (data+forward): {end_total - start_total:.6f} s")
|
| 153 |
+
else:
|
| 154 |
+
print("No batches were processed.")
|
| 155 |
+
|
| 156 |
+
# Process outputs
|
| 157 |
+
energies = np.concatenate(energies_list, axis=0)
|
| 158 |
+
forces_list = [
|
| 159 |
+
forces for forces_list in forces_collection for forces in forces_list
|
| 160 |
+
]
|
| 161 |
+
assert len(atoms_list) == len(energies) == len(forces_list)
|
| 162 |
+
if args.compute_stress:
|
| 163 |
+
stresses = np.concatenate(stresses_list, axis=0)
|
| 164 |
+
assert len(atoms_list) == stresses.shape[0]
|
| 165 |
+
|
| 166 |
+
if args.return_contributions:
|
| 167 |
+
contributions = np.concatenate(contributions_list, axis=0)
|
| 168 |
+
assert len(atoms_list) == contributions.shape[0]
|
| 169 |
+
|
| 170 |
+
# Store data in atoms objects
|
| 171 |
+
for i, (atoms, energy, forces) in enumerate(zip(atoms_list, energies, forces_list)):
|
| 172 |
+
atoms.calc = None # crucial
|
| 173 |
+
atoms.info[args.info_prefix + "energy"] = energy
|
| 174 |
+
atoms.arrays[args.info_prefix + "forces"] = forces
|
| 175 |
+
|
| 176 |
+
if args.compute_stress:
|
| 177 |
+
atoms.info[args.info_prefix + "stress"] = stresses[i]
|
| 178 |
+
|
| 179 |
+
if args.return_contributions:
|
| 180 |
+
atoms.info[args.info_prefix + "BO_contributions"] = contributions[i]
|
| 181 |
+
|
| 182 |
+
# Write atoms to output path
|
| 183 |
+
ase.io.write(args.output, images=atoms_list, format="extxyz")
|
| 184 |
+
|
| 185 |
+
|
| 186 |
+
if __name__ == "__main__":
|
| 187 |
+
main()
|
scripts/fasteq/fasteq_install.sh
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
# FastEq-hip 扩展安装脚本(OneScience MACE matchem 环境)
|
| 3 |
+
# 前置条件:已安装好 OneScience 的 matchem 环境。
|
| 4 |
+
# 本脚本安装 FastEq-hip 及 patch 后的 cuequivariance-torch,
|
| 5 |
+
# 以实现 MACE 模型在 DCU 上的训练和推理加速。
|
| 6 |
+
#
|
| 7 |
+
# 使用方法:
|
| 8 |
+
# cd /public/home/easyscience2024/wangrui/onescience/examples/matchem/mace/fasteq
|
| 9 |
+
# bash install.sh
|
| 10 |
+
|
| 11 |
+
set -e
|
| 12 |
+
|
| 13 |
+
# =============================================================================
|
| 14 |
+
# 1. 环境初始化
|
| 15 |
+
# =============================================================================
|
| 16 |
+
# 必须先加载 module,再激活 conda;否则 sghpcdas 会覆盖 conda 环境变量。
|
| 17 |
+
source ~/.bashrc
|
| 18 |
+
|
| 19 |
+
module load sghpcdas/25.6
|
| 20 |
+
module load sghpc-mpi-gcc/26.3
|
| 21 |
+
|
| 22 |
+
conda activate matchem_opt
|
| 23 |
+
|
| 24 |
+
# libgalaxyhip.so.5 位于 DTK 的 lib/ 和 hip/lib/ 下,但不在默认 LD_LIBRARY_PATH 中。
|
| 25 |
+
# 不加此路径会导致导入 torch/fasteq 时报错:
|
| 26 |
+
# ImportError: libgalaxyhip.so.5: cannot open shared object file
|
| 27 |
+
export LD_LIBRARY_PATH="/public/software/sghpc_sdk.bak/Linux_x86_64/26.3/dtk/dtk-25.04.4/lib:/public/software/sghpc_sdk.bak/Linux_x86_64/26.3/dtk/dtk-25.04.4/hip/lib:${LD_LIBRARY_PATH}"
|
| 28 |
+
|
| 29 |
+
# DTK 的 cmake 配置查找 amd_comgr,但 CMAKE_PREFIX_PATH 默认为空,需要显式指定。
|
| 30 |
+
export CMAKE_PREFIX_PATH="/public/software/sghpc_sdk.bak/Linux_x86_64/26.3/dtk/dtk-25.04.4/dcc/comgr/lib64/cmake/amd_comgr:${CMAKE_PREFIX_PATH}"
|
| 31 |
+
|
| 32 |
+
# FastEq-hip 编译需要显式声明后端为 HIP,否则默认回退到 CUDA 导致找不到 nvcc。
|
| 33 |
+
export FASTEQ_BACKEND=hip
|
| 34 |
+
export ROCM_PATH=/public/software/sghpc_sdk.bak/Linux_x86_64/26.3/dtk/dtk-25.04.4
|
| 35 |
+
export HIP_PATH=/public/software/sghpc_sdk.bak/Linux_x86_64/26.3/dtk/dtk-25.04.4
|
| 36 |
+
|
| 37 |
+
# CMake 3.26 的 HIP 编译器 ABI 检测阶段(try_compile)不会继承主项目的 cache 变量,
|
| 38 |
+
# 但会读取 <Package>_DIR 环境变量。若不设置,会因找不到 amd_comgr 而报错。
|
| 39 |
+
export amd_comgr_DIR=/public/software/sghpc_sdk.bak/Linux_x86_64/26.3/dtk/dtk-25.04.4/dcc/comgr/lib64/cmake/amd_comgr
|
| 40 |
+
|
| 41 |
+
# =============================================================================
|
| 42 |
+
# 2. FastEq-hip 源码路径
|
| 43 |
+
# =============================================================================
|
| 44 |
+
# 默认路径:与 onescience 项目同级目录。
|
| 45 |
+
# 可通过环境变量覆盖:FASTEQ_HIP_ROOT=/your/path bash install.sh
|
| 46 |
+
FASTEQ_HIP_ROOT="${FASTEQ_HIP_ROOT}"
|
| 47 |
+
|
| 48 |
+
if [[ ! -d "$FASTEQ_HIP_ROOT" ]]; then
|
| 49 |
+
echo "错误:FastEq-hip 源码未找到于 $FASTEQ_HIP_ROOT"
|
| 50 |
+
echo "请先克隆仓库,或通过 FASTEQ_HIP_ROOT 环境变量指定正确路径。"
|
| 51 |
+
exit 1
|
| 52 |
+
fi
|
| 53 |
+
|
| 54 |
+
echo ">>> FastEq-hip 源码路径: $FASTEQ_HIP_ROOT"
|
| 55 |
+
|
| 56 |
+
# =============================================================================
|
| 57 |
+
# 3. 安装 FastEq-hip
|
| 58 |
+
# =============================================================================
|
| 59 |
+
# PYTHONNOUSERSITE=1 避免使用 ~/.local 下的旧版 setuptools(59.8.0),
|
| 60 |
+
# 该版本会导致 editable_wheel 构建失败(KeyError: 'unix_user')。
|
| 61 |
+
#
|
| 62 |
+
# 注意:FastEq-hip 的 setup.py 中 HIP 后端的 extra_cmake_args 需补充两项:
|
| 63 |
+
# -DCMAKE_HIP_COMPILER=/public/software/sghpc_sdk.bak/Linux_x86_64/26.3/dtk/dtk-25.04.4/llvm/bin/clang++
|
| 64 |
+
# -Damd_comgr_DIR=/public/software/sghpc_sdk.bak/Linux_x86_64/26.3/dtk/dtk-25.04.4/dcc/comgr/lib64/cmake/amd_comgr
|
| 65 |
+
# 原因:CMake 3.26 不支持将 hipcc wrapper 直接作为 CMAKE_HIP_COMPILER,
|
| 66 |
+
# 必须使用 ROCm 的 clang++;且 try_compile 阶段不会继承 cache 变量中的 amd_comgr_DIR,
|
| 67 |
+
# 需要通过环境变量(已在上方导出)或 cmake 参数显式传递。
|
| 68 |
+
echo ">>> 正在安装 FastEq-hip(editable 模式)..."
|
| 69 |
+
PYTHONNOUSERSITE=1 pip install -r "$FASTEQ_HIP_ROOT/requirements.txt"
|
| 70 |
+
PYTHONNOUSERSITE=1 pip install -e "$FASTEQ_HIP_ROOT" --no-build-isolation
|
| 71 |
+
|
| 72 |
+
# =============================================================================
|
| 73 |
+
# 4. 安装 patch 后的 cuequivariance-torch
|
| 74 |
+
# =============================================================================
|
| 75 |
+
# cuequivariance==0.8.0 基础包已由 OneScience matchem 安装。
|
| 76 |
+
# 此处只需覆盖安装本地子模块中的 HIP patch 版本。
|
| 77 |
+
echo ">>> 正在安装 patched cuequivariance-torch(editable 模式)..."
|
| 78 |
+
PYTHONNOUSERSITE=1 pip install -e "$FASTEQ_HIP_ROOT/3rdparty/cuequivariance_torch/cuequivariance_torch" --no-build-isolation
|
| 79 |
+
|
| 80 |
+
# =============================================================================
|
| 81 |
+
# 5. 验证安装
|
| 82 |
+
# =============================================================================
|
| 83 |
+
echo ">>> 验证导入..."
|
| 84 |
+
python -c "import fasteq; print(' fasteq:', fasteq.__file__)"
|
| 85 |
+
python -c "import cuequivariance_torch as cuet; print(' cuequivariance_torch:', cuet.__file__)"
|
| 86 |
+
|
| 87 |
+
echo ">>> FastEq-hip 扩展安装成功!"
|
| 88 |
+
echo ">>> 现在可以运行 MACE 训练,并传入 --enable_cueq 启用 FastEq 加速。"
|
scripts/fasteq/install.sh
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
# FastEq-hip 扩展安装脚本(OneScience MACE matchem 环境)
|
| 3 |
+
# 前置条件:已安装好 OneScience 的 matchem 环境。
|
| 4 |
+
# 本脚本安装 FastEq-hip 及 patch 后的 cuequivariance-torch,
|
| 5 |
+
# 以实现 MACE 模型在 DCU 上的训练和推理加速。
|
| 6 |
+
#
|
| 7 |
+
# 使用方法:
|
| 8 |
+
# cd /public/home/scnethpc2601/wr/onescience/examples/matchem/mace/demo/FastEq-hip
|
| 9 |
+
# bash install.sh
|
| 10 |
+
|
| 11 |
+
set -e
|
| 12 |
+
|
| 13 |
+
# =============================================================================
|
| 14 |
+
# 1. 环境初始化
|
| 15 |
+
# =============================================================================
|
| 16 |
+
# 必须先加载 module,再激活 conda;否则 sghpcdas 会覆盖 conda 环境变量。
|
| 17 |
+
module load sghpcdas/25.6
|
| 18 |
+
module load sghpc-mpi-gcc/26.3
|
| 19 |
+
|
| 20 |
+
source ~/.bashrc
|
| 21 |
+
conda activate matchem
|
| 22 |
+
|
| 23 |
+
# libgalaxyhip.so.5 位于 DTK 的 lib/ 和 hip/lib/ 下,但不在默认 LD_LIBRARY_PATH 中。
|
| 24 |
+
# 不加此路径会导致导入 torch/fasteq 时报错:
|
| 25 |
+
# ImportError: libgalaxyhip.so.5: cannot open shared object file
|
| 26 |
+
export LD_LIBRARY_PATH="/public/software/sghpc_sdk.bak/Linux_x86_64/26.3/dtk/dtk-25.04.4/lib:/public/software/sghpc_sdk.bak/Linux_x86_64/26.3/dtk/dtk-25.04.4/hip/lib:${LD_LIBRARY_PATH}"
|
| 27 |
+
|
| 28 |
+
# DTK 的 cmake 配置查找 amd_comgr,但 CMAKE_PREFIX_PATH 默认为空,需要显式指定。
|
| 29 |
+
export CMAKE_PREFIX_PATH="/public/software/sghpc_sdk.bak/Linux_x86_64/26.3/dtk/dtk-25.04.4/dcc/comgr/lib64/cmake/amd_comgr:${CMAKE_PREFIX_PATH}"
|
| 30 |
+
|
| 31 |
+
# =============================================================================
|
| 32 |
+
# 2. FastEq-hip 源码路径
|
| 33 |
+
# =============================================================================
|
| 34 |
+
# 默认路径:与 onescience 项目同级目录。
|
| 35 |
+
# 可通过环境变量覆盖:FASTEQ_HIP_ROOT=/your/path bash install.sh
|
| 36 |
+
FASTEQ_HIP_ROOT="${FASTEQ_HIP_ROOT:-/public/home/scnethpc2601/wr/project/FastEq-hip}"
|
| 37 |
+
|
| 38 |
+
if [[ ! -d "$FASTEQ_HIP_ROOT" ]]; then
|
| 39 |
+
echo "错误:FastEq-hip 源码未找到于 $FASTEQ_HIP_ROOT"
|
| 40 |
+
echo "请先克隆仓库,或通过 FASTEQ_HIP_ROOT 环境变量指定正确路径。"
|
| 41 |
+
exit 1
|
| 42 |
+
fi
|
| 43 |
+
|
| 44 |
+
echo ">>> FastEq-hip 源码路径: $FASTEQ_HIP_ROOT"
|
| 45 |
+
|
| 46 |
+
# =============================================================================
|
| 47 |
+
# 3. 安装 FastEq-hip
|
| 48 |
+
# =============================================================================
|
| 49 |
+
# PYTHONNOUSERSITE=1 避免使用 ~/.local 下的旧版 setuptools(59.8.0),
|
| 50 |
+
# 该版本会导致 editable_wheel 构建失败(KeyError: 'unix_user')。
|
| 51 |
+
echo ">>> 正在安装 FastEq-hip(editable 模式)..."
|
| 52 |
+
PYTHONNOUSERSITE=1 pip install -r "$FASTEQ_HIP_ROOT/requirements.txt"
|
| 53 |
+
PYTHONNOUSERSITE=1 pip install -e "$FASTEQ_HIP_ROOT" --no-build-isolation
|
| 54 |
+
|
| 55 |
+
# =============================================================================
|
| 56 |
+
# 4. 安装 patch 后的 cuequivariance-torch
|
| 57 |
+
# =============================================================================
|
| 58 |
+
# cuequivariance==0.8.0 基础包已由 OneScience matchem 安装。
|
| 59 |
+
# 此处只需覆盖安装本地子模块中的 HIP patch 版本。
|
| 60 |
+
echo ">>> 正在安装 patched cuequivariance-torch(editable 模式)..."
|
| 61 |
+
PYTHONNOUSERSITE=1 pip install -e "$FASTEQ_HIP_ROOT/3rdparty/cuequivariance_torch/cuequivariance_torch" --no-build-isolation
|
| 62 |
+
|
| 63 |
+
# =============================================================================
|
| 64 |
+
# 5. 验证安装
|
| 65 |
+
# =============================================================================
|
| 66 |
+
echo ">>> 验证导入..."
|
| 67 |
+
python -c "import fasteq; print(' fasteq:', fasteq.__file__)"
|
| 68 |
+
python -c "import cuequivariance_torch as cuet; print(' cuequivariance_torch:', cuet.__file__)"
|
| 69 |
+
|
| 70 |
+
echo ">>> FastEq-hip 扩展安装成功!"
|
| 71 |
+
echo ">>> 现在可以运行 MACE 训练,并传入 --enable_cueq 启用 FastEq 加速。"
|
scripts/run_md.py
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from ase.io import read, write
|
| 2 |
+
from ase import units
|
| 3 |
+
from ase.md.langevin import Langevin
|
| 4 |
+
from ase.md.velocitydistribution import Stationary, ZeroRotation, MaxwellBoltzmannDistribution
|
| 5 |
+
import random
|
| 6 |
+
import os
|
| 7 |
+
import time
|
| 8 |
+
import numpy as np
|
| 9 |
+
import matplotlib.pyplot as plt
|
| 10 |
+
import argparse
|
| 11 |
+
from onescience.utils.mace.calculators import MACECalculator
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def simpleMD(init_conf, temp, calc, fname, s, T, timestep_fs, friction, plot_file="md_plot.png"):
|
| 15 |
+
"""
|
| 16 |
+
在服务器上运行的 MD 模拟,不依赖 Jupyter,结束后保存图片。
|
| 17 |
+
"""
|
| 18 |
+
init_conf.set_calculator(calc)
|
| 19 |
+
|
| 20 |
+
# 初始化温度
|
| 21 |
+
random.seed(701)
|
| 22 |
+
MaxwellBoltzmannDistribution(init_conf, temperature_K=300)
|
| 23 |
+
Stationary(init_conf)
|
| 24 |
+
ZeroRotation(init_conf)
|
| 25 |
+
|
| 26 |
+
dyn = Langevin(
|
| 27 |
+
init_conf,
|
| 28 |
+
timestep_fs * units.fs,
|
| 29 |
+
temperature_K=temp,
|
| 30 |
+
friction=friction
|
| 31 |
+
)
|
| 32 |
+
|
| 33 |
+
time_fs = []
|
| 34 |
+
temperature = []
|
| 35 |
+
energies = []
|
| 36 |
+
|
| 37 |
+
# 删除之前存储的同名轨迹
|
| 38 |
+
if os.path.exists(fname):
|
| 39 |
+
os.system('rm -rfv ' + fname)
|
| 40 |
+
|
| 41 |
+
# 记录函数:仅存储数据,不实时绘图
|
| 42 |
+
def write_frame():
|
| 43 |
+
dyn.atoms.write(fname, append=True)
|
| 44 |
+
time_fs.append(dyn.get_time() / units.fs)
|
| 45 |
+
temperature.append(dyn.atoms.get_temperature())
|
| 46 |
+
energies.append(dyn.atoms.get_potential_energy() / len(dyn.atoms))
|
| 47 |
+
|
| 48 |
+
# 可选:每隔一段时间打印进度
|
| 49 |
+
if len(time_fs) % 10 == 0: # 每10步打印一次
|
| 50 |
+
print(f"Step {len(time_fs)}, Time: {time_fs[-1]:.1f} fs, Temp: {temperature[-1]:.1f} K, E: {energies[-1]:.4f} eV/atom")
|
| 51 |
+
|
| 52 |
+
dyn.attach(write_frame, interval=s)
|
| 53 |
+
|
| 54 |
+
t0 = time.time()
|
| 55 |
+
dyn.run(T)
|
| 56 |
+
t1 = time.time()
|
| 57 |
+
print("MD 完成于 {0:.2f} 分钟!".format((t1 - t0) / 60))
|
| 58 |
+
|
| 59 |
+
# 模拟结束后绘制并保存图片
|
| 60 |
+
if time_fs and energies and temperature:
|
| 61 |
+
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(6, 6), sharex='all')
|
| 62 |
+
ax1.plot(time_fs, energies, 'b-')
|
| 63 |
+
ax1.set_ylabel('E (eV/atom)')
|
| 64 |
+
ax2.plot(time_fs, temperature, 'r-')
|
| 65 |
+
ax2.set_ylabel('T (K)')
|
| 66 |
+
ax2.set_xlabel('Time (fs)')
|
| 67 |
+
plt.tight_layout()
|
| 68 |
+
plt.savefig(plot_file, dpi=150)
|
| 69 |
+
print(f"Plot saved to {plot_file}")
|
| 70 |
+
plt.close() # 避免在无显示器的服务器上弹出窗口
|
| 71 |
+
else:
|
| 72 |
+
print("No data to plot.")
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
if __name__ == "__main__":
|
| 76 |
+
parser = argparse.ArgumentParser()
|
| 77 |
+
parser.add_argument("--init_conf", type=str, required=True)
|
| 78 |
+
parser.add_argument("--model", type=str, required=True)
|
| 79 |
+
parser.add_argument("--output_xyz", type=str, required=True)
|
| 80 |
+
parser.add_argument("--plot_file", type=str, required=True)
|
| 81 |
+
parser.add_argument("--device", type=str, default="cuda")
|
| 82 |
+
parser.add_argument("--default_dtype", type=str, default="float64")
|
| 83 |
+
parser.add_argument("--temperature_k", type=float, required=True)
|
| 84 |
+
parser.add_argument("--time_step_fs", type=float, default=1.0)
|
| 85 |
+
parser.add_argument("--friction", type=float, default=0.1)
|
| 86 |
+
parser.add_argument("--save_interval", type=int, default=10)
|
| 87 |
+
parser.add_argument("--steps", type=int, required=True)
|
| 88 |
+
|
| 89 |
+
args = parser.parse_args()
|
| 90 |
+
|
| 91 |
+
# 读取初始构型(使用传进来的绝对路径,不再硬编码相对路径)
|
| 92 |
+
init_conf = read(args.init_conf, ':')[0].copy()
|
| 93 |
+
|
| 94 |
+
# 加载MACE模型
|
| 95 |
+
mace_calc = MACECalculator(
|
| 96 |
+
model_paths=[args.model],
|
| 97 |
+
device=args.device,
|
| 98 |
+
default_dtype=args.default_dtype
|
| 99 |
+
)
|
| 100 |
+
|
| 101 |
+
# 启动MD
|
| 102 |
+
simpleMD(
|
| 103 |
+
init_conf=init_conf,
|
| 104 |
+
temp=args.temperature_k,
|
| 105 |
+
calc=mace_calc,
|
| 106 |
+
fname=args.output_xyz,
|
| 107 |
+
s=args.save_interval,
|
| 108 |
+
T=args.steps,
|
| 109 |
+
timestep_fs=args.time_step_fs,
|
| 110 |
+
friction=args.friction,
|
| 111 |
+
plot_file=args.plot_file
|
| 112 |
+
)
|
scripts/train.py
ADDED
|
@@ -0,0 +1,1098 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
# Training script for MACE
|
| 3 |
+
|
| 4 |
+
import ast
|
| 5 |
+
import glob
|
| 6 |
+
import importlib
|
| 7 |
+
import json
|
| 8 |
+
import logging
|
| 9 |
+
import os
|
| 10 |
+
import shutil
|
| 11 |
+
import types
|
| 12 |
+
from copy import deepcopy
|
| 13 |
+
from pathlib import Path
|
| 14 |
+
from typing import List, Optional
|
| 15 |
+
|
| 16 |
+
import torch.distributed
|
| 17 |
+
import torch.nn.functional
|
| 18 |
+
from e3nn.util import jit
|
| 19 |
+
from torch.nn.parallel import DistributedDataParallel as DDP
|
| 20 |
+
from torch.optim import LBFGS
|
| 21 |
+
from torch.utils.data import ConcatDataset
|
| 22 |
+
from torch_ema import ExponentialMovingAverage
|
| 23 |
+
|
| 24 |
+
import model as mace
|
| 25 |
+
#from onescience.utils.mace import data, tools
|
| 26 |
+
from onescience.utils.mace import tools
|
| 27 |
+
from onescience.utils.mace.calculators.foundations_models import mace_mp, mace_off
|
| 28 |
+
from onescience.utils.mace.cli.convert_cueq_e3nn import run as run_cueq_to_e3nn
|
| 29 |
+
from onescience.utils.mace.cli.convert_e3nn_cueq import run as run_e3nn_to_cueq
|
| 30 |
+
from onescience.utils.mace.cli.visualise_train import TrainingPlotter
|
| 31 |
+
#from onescience.utils.mace.data import KeySpecification, update_keyspec_from_kwargs
|
| 32 |
+
from onescience.datapipes.materials.pyg_stack.core.utils import KeySpecification, update_keyspec_from_kwargs
|
| 33 |
+
#from onescience.utils.mace.tools import torch_geometric
|
| 34 |
+
from onescience.datapipes.materials.tools import torch_geometric
|
| 35 |
+
from onescience.utils.mace.tools.model_script_utils import configure_model
|
| 36 |
+
import pdb
|
| 37 |
+
import sys
|
| 38 |
+
try:
|
| 39 |
+
import model
|
| 40 |
+
import onescience.utils.mace.tools
|
| 41 |
+
except ImportError as e:
|
| 42 |
+
print(f"警告:无法导入 model 模块用于别名修复: {e}")
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
# --- 在顶部添加这些导入 ---
|
| 46 |
+
from onescience.datapipes.materials.pyg_stack.core.atomic_data import AtomicData
|
| 47 |
+
from onescience.datapipes.materials.pyg_stack.storage.hdf5_dataset import HDF5Dataset, dataset_from_sharded_hdf5
|
| 48 |
+
from onescience.datapipes.materials.pyg_stack.storage.text_dataset import TextDataset
|
| 49 |
+
from onescience.utils.mace.tools.multihead_tools import (
|
| 50 |
+
HeadConfig,
|
| 51 |
+
assemble_mp_data,
|
| 52 |
+
dict_head_to_dataclass,
|
| 53 |
+
prepare_default_head,
|
| 54 |
+
prepare_pt_head,
|
| 55 |
+
)
|
| 56 |
+
from onescience.utils.mace.tools.run_train_utils import (
|
| 57 |
+
combine_datasets,
|
| 58 |
+
load_dataset_for_path,
|
| 59 |
+
normalize_file_paths,
|
| 60 |
+
)
|
| 61 |
+
from onescience.utils.mace.tools.scripts_utils import (
|
| 62 |
+
LRScheduler,
|
| 63 |
+
SubsetCollection,
|
| 64 |
+
check_path_ase_read,
|
| 65 |
+
convert_to_json_format,
|
| 66 |
+
dict_to_array,
|
| 67 |
+
extract_config_mace_model,
|
| 68 |
+
get_atomic_energies,
|
| 69 |
+
get_avg_num_neighbors,
|
| 70 |
+
get_config_type_weights,
|
| 71 |
+
get_dataset_from_xyz,
|
| 72 |
+
get_files_with_suffix,
|
| 73 |
+
get_loss_fn,
|
| 74 |
+
get_optimizer,
|
| 75 |
+
get_params_options,
|
| 76 |
+
get_swa,
|
| 77 |
+
print_git_commit,
|
| 78 |
+
remove_pt_head,
|
| 79 |
+
setup_wandb,
|
| 80 |
+
)
|
| 81 |
+
from onescience.utils.mace.tools.slurm_distributed import DistributedEnvironment
|
| 82 |
+
from onescience.utils.mace.tools.tables_utils import create_error_table
|
| 83 |
+
|
| 84 |
+
#from onescience.utils.mace.tools.utils import AtomicNumberTable
|
| 85 |
+
from onescience.datapipes.materials.tools.utils import AtomicNumberTable
|
| 86 |
+
|
| 87 |
+
def main() -> None:
|
| 88 |
+
"""
|
| 89 |
+
This script runs the training/fine tuning for mace
|
| 90 |
+
"""
|
| 91 |
+
args = tools.build_default_arg_parser().parse_args()
|
| 92 |
+
run(args)
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
def run(args) -> None:
|
| 96 |
+
"""
|
| 97 |
+
This script runs the training/fine tuning for mace
|
| 98 |
+
"""
|
| 99 |
+
|
| 100 |
+
tag = tools.get_tag(name=args.name, seed=args.seed)
|
| 101 |
+
args, input_log_messages = tools.check_args(args)
|
| 102 |
+
|
| 103 |
+
# default keyspec to update using heads dictionary
|
| 104 |
+
args.key_specification = KeySpecification()
|
| 105 |
+
update_keyspec_from_kwargs(args.key_specification, vars(args))
|
| 106 |
+
|
| 107 |
+
if args.device == "xpu":
|
| 108 |
+
try:
|
| 109 |
+
import intel_extension_for_pytorch as ipex
|
| 110 |
+
except ImportError as e:
|
| 111 |
+
raise ImportError(
|
| 112 |
+
"Error: Intel extension for PyTorch not found, but XPU device was specified"
|
| 113 |
+
) from e
|
| 114 |
+
if args.distributed:
|
| 115 |
+
try:
|
| 116 |
+
distr_env = DistributedEnvironment()
|
| 117 |
+
except Exception as e: # pylint: disable=W0703
|
| 118 |
+
logging.error(f"Failed to initialize distributed environment: {e}")
|
| 119 |
+
return
|
| 120 |
+
world_size = distr_env.world_size
|
| 121 |
+
local_rank = distr_env.local_rank
|
| 122 |
+
rank = distr_env.rank
|
| 123 |
+
if rank == 0:
|
| 124 |
+
print(distr_env)
|
| 125 |
+
torch.distributed.init_process_group(backend="nccl")
|
| 126 |
+
else:
|
| 127 |
+
rank = int(0)
|
| 128 |
+
|
| 129 |
+
# Setup
|
| 130 |
+
tools.set_seeds(args.seed)
|
| 131 |
+
tools.setup_logger(level=args.log_level, tag=tag, directory=args.log_dir, rank=rank)
|
| 132 |
+
logging.info("===========VERIFYING SETTINGS===========")
|
| 133 |
+
for message, loglevel in input_log_messages:
|
| 134 |
+
logging.log(level=loglevel, msg=message)
|
| 135 |
+
|
| 136 |
+
if args.distributed:
|
| 137 |
+
torch.cuda.set_device(local_rank)
|
| 138 |
+
logging.info(f"!!!!!!!!!Set device {local_rank}")
|
| 139 |
+
logging.info(f"Process group initialized: {torch.distributed.is_initialized()}")
|
| 140 |
+
logging.info(f"Processes: {world_size}")
|
| 141 |
+
|
| 142 |
+
try:
|
| 143 |
+
logging.info(f"MACE version: {mace.__version__}")
|
| 144 |
+
except AttributeError:
|
| 145 |
+
logging.info("Cannot find MACE version, please install MACE via pip")
|
| 146 |
+
logging.debug(f"Configuration: {args}")
|
| 147 |
+
|
| 148 |
+
tools.set_default_dtype(args.default_dtype)
|
| 149 |
+
device = tools.init_device(args.device)
|
| 150 |
+
commit = print_git_commit()
|
| 151 |
+
model_foundation: Optional[torch.nn.Module] = None
|
| 152 |
+
foundation_model_avg_num_neighbors = 0
|
| 153 |
+
if args.foundation_model is not None:
|
| 154 |
+
if args.foundation_model in ["small", "medium", "large"]:
|
| 155 |
+
logging.info(
|
| 156 |
+
f"Using foundation model mace-mp-0 {args.foundation_model} as initial checkpoint."
|
| 157 |
+
)
|
| 158 |
+
calc = mace_mp(
|
| 159 |
+
model=args.foundation_model,
|
| 160 |
+
device=args.device,
|
| 161 |
+
default_dtype=args.default_dtype,
|
| 162 |
+
)
|
| 163 |
+
model_foundation = calc.models[0]
|
| 164 |
+
elif args.foundation_model in ["small_off", "medium_off", "large_off"]:
|
| 165 |
+
model_type = args.foundation_model.split("_")[0]
|
| 166 |
+
logging.info(
|
| 167 |
+
f"Using foundation model mace-off-2023 {model_type} as initial checkpoint. ASL license."
|
| 168 |
+
)
|
| 169 |
+
calc = mace_off(
|
| 170 |
+
model=model_type,
|
| 171 |
+
device=args.device,
|
| 172 |
+
default_dtype=args.default_dtype,
|
| 173 |
+
)
|
| 174 |
+
model_foundation = calc.models[0]
|
| 175 |
+
else:
|
| 176 |
+
# --- !! 插入这里的修复代码 !! ---
|
| 177 |
+
# (确保 'sys' 和 'model' 已经在文件顶部导入)
|
| 178 |
+
try:
|
| 179 |
+
logging.info(
|
| 180 |
+
"Creating legacy 'mace' module aliases for foundation checkpoint loading..."
|
| 181 |
+
)
|
| 182 |
+
legacy_aliases = {
|
| 183 |
+
"mace": "model",
|
| 184 |
+
"mace.tools": "onescience.utils.mace.tools",
|
| 185 |
+
"mace.data": "onescience.utils.mace.data",
|
| 186 |
+
"mace.modules.models": "model.mace",
|
| 187 |
+
"mace.modules.blocks": "onescience.modules.block.mace_block",
|
| 188 |
+
"mace.modules.radial": "onescience.modules.layer.mace_radial",
|
| 189 |
+
"mace.modules.loss": "onescience.modules.loss.mace_loss",
|
| 190 |
+
"mace.modules.utils": "onescience.modules.func_utils.mace_func_utils",
|
| 191 |
+
"mace.modules.irreps_tools": "onescience.modules.func_utils.mace_irreps_tools",
|
| 192 |
+
"mace.modules.symmetric_contraction": "onescience.modules.equivariant.mace_symmetric_contraction",
|
| 193 |
+
"mace.modules.wrapper_ops": "onescience.modules.equivariant.mace_wrapper_ops",
|
| 194 |
+
}
|
| 195 |
+
legacy_modules_pkg = sys.modules.setdefault(
|
| 196 |
+
"mace.modules", types.ModuleType("mace.modules")
|
| 197 |
+
)
|
| 198 |
+
if not hasattr(legacy_modules_pkg, "__path__"):
|
| 199 |
+
legacy_modules_pkg.__path__ = [] # type: ignore[attr-defined]
|
| 200 |
+
|
| 201 |
+
for legacy_name, new_name in legacy_aliases.items():
|
| 202 |
+
module_obj = importlib.import_module(new_name)
|
| 203 |
+
sys.modules[legacy_name] = module_obj
|
| 204 |
+
if legacy_name.startswith("mace.modules."):
|
| 205 |
+
setattr(
|
| 206 |
+
legacy_modules_pkg,
|
| 207 |
+
legacy_name.split(".")[-1],
|
| 208 |
+
module_obj,
|
| 209 |
+
)
|
| 210 |
+
logging.info("Legacy module aliases created successfully.")
|
| 211 |
+
except Exception as e:
|
| 212 |
+
logging.warning(f"Failed to create module alias: {e}")
|
| 213 |
+
# --- !! 修复代码结束 !! ---
|
| 214 |
+
|
| 215 |
+
# 现在这个 torch.load 应该可以工作了
|
| 216 |
+
model_foundation = torch.load(
|
| 217 |
+
args.foundation_model, map_location=args.device
|
| 218 |
+
)
|
| 219 |
+
logging.info(
|
| 220 |
+
f"Using foundation model {args.foundation_model} as initial checkpoint."
|
| 221 |
+
)
|
| 222 |
+
args.r_max = model_foundation.r_max.item()
|
| 223 |
+
foundation_model_avg_num_neighbors = model_foundation.interactions[
|
| 224 |
+
0
|
| 225 |
+
].avg_num_neighbors
|
| 226 |
+
if (
|
| 227 |
+
args.foundation_model not in ["small", "medium", "large"]
|
| 228 |
+
and args.pt_train_file is None
|
| 229 |
+
):
|
| 230 |
+
logging.warning(
|
| 231 |
+
"Using multiheads finetuning with a foundation model that is not a Materials Project model, need to provied a path to a pretraining file with --pt_train_file."
|
| 232 |
+
)
|
| 233 |
+
args.multiheads_finetuning = False
|
| 234 |
+
if args.multiheads_finetuning:
|
| 235 |
+
assert (
|
| 236 |
+
args.E0s != "average"
|
| 237 |
+
), "average atomic energies cannot be used for multiheads finetuning"
|
| 238 |
+
# check that the foundation model has a single head, if not, use the first head
|
| 239 |
+
if not args.force_mh_ft_lr:
|
| 240 |
+
logging.info(
|
| 241 |
+
"Multihead finetuning mode, setting learning rate to 0.0001 and EMA to True. To use a different learning rate, set --force_mh_ft_lr=True."
|
| 242 |
+
)
|
| 243 |
+
args.lr = 0.0001
|
| 244 |
+
args.ema = True
|
| 245 |
+
args.ema_decay = 0.99999
|
| 246 |
+
logging.info(
|
| 247 |
+
"Using multiheads finetuning mode, setting learning rate to 0.0001 and EMA to True"
|
| 248 |
+
)
|
| 249 |
+
if hasattr(model_foundation, "heads"):
|
| 250 |
+
if len(model_foundation.heads) > 1:
|
| 251 |
+
logging.warning(
|
| 252 |
+
"Mutlihead finetuning with models with more than one head is not supported, using the first head as foundation head."
|
| 253 |
+
)
|
| 254 |
+
model_foundation = remove_pt_head(
|
| 255 |
+
model_foundation, args.foundation_head
|
| 256 |
+
)
|
| 257 |
+
else:
|
| 258 |
+
args.multiheads_finetuning = False
|
| 259 |
+
|
| 260 |
+
if args.heads is not None:
|
| 261 |
+
args.heads = ast.literal_eval(args.heads)
|
| 262 |
+
for _, head_dict in args.heads.items():
|
| 263 |
+
# priority is global args < head property_key values < head info_keys+arrays_keys
|
| 264 |
+
head_keyspec = deepcopy(args.key_specification)
|
| 265 |
+
update_keyspec_from_kwargs(head_keyspec, head_dict)
|
| 266 |
+
head_keyspec.update(
|
| 267 |
+
info_keys=head_dict.get("info_keys", {}),
|
| 268 |
+
arrays_keys=head_dict.get("arrays_keys", {}),
|
| 269 |
+
)
|
| 270 |
+
head_dict["key_specification"] = head_keyspec
|
| 271 |
+
else:
|
| 272 |
+
args.heads = prepare_default_head(args)
|
| 273 |
+
if args.multiheads_finetuning:
|
| 274 |
+
pt_keyspec = (
|
| 275 |
+
args.heads["pt_head"]["key_specification"]
|
| 276 |
+
if "pt_head" in args.heads
|
| 277 |
+
else deepcopy(args.key_specification)
|
| 278 |
+
)
|
| 279 |
+
args.heads["pt_head"] = prepare_pt_head(
|
| 280 |
+
args, pt_keyspec, foundation_model_avg_num_neighbors
|
| 281 |
+
)
|
| 282 |
+
|
| 283 |
+
logging.info("===========LOADING INPUT DATA===========")
|
| 284 |
+
heads = list(args.heads.keys())
|
| 285 |
+
logging.info(f"Using heads: {heads}")
|
| 286 |
+
logging.info("Using the key specifications to parse data:")
|
| 287 |
+
for name, head_dict in args.heads.items():
|
| 288 |
+
head_keyspec = head_dict["key_specification"]
|
| 289 |
+
logging.info(f"{name}: {head_keyspec}")
|
| 290 |
+
|
| 291 |
+
head_configs: List[HeadConfig] = []
|
| 292 |
+
for head, head_args in args.heads.items():
|
| 293 |
+
logging.info(f"============= Processing head {head} ===========")
|
| 294 |
+
head_config = dict_head_to_dataclass(head_args, head, args)
|
| 295 |
+
|
| 296 |
+
# Handle train_file and valid_file - normalize to lists
|
| 297 |
+
if hasattr(head_config, "train_file") and head_config.train_file is not None:
|
| 298 |
+
head_config.train_file = normalize_file_paths(head_config.train_file)
|
| 299 |
+
if hasattr(head_config, "valid_file") and head_config.valid_file is not None:
|
| 300 |
+
head_config.valid_file = normalize_file_paths(head_config.valid_file)
|
| 301 |
+
if hasattr(head_config, "test_file") and head_config.test_file is not None:
|
| 302 |
+
head_config.test_file = normalize_file_paths(head_config.test_file)
|
| 303 |
+
|
| 304 |
+
if (
|
| 305 |
+
head_config.statistics_file is not None
|
| 306 |
+
and head_config.head_name != "pt_head"
|
| 307 |
+
):
|
| 308 |
+
with open(head_config.statistics_file, "r") as f: # pylint: disable=W1514
|
| 309 |
+
statistics = json.load(f)
|
| 310 |
+
logging.info("Using statistics json file")
|
| 311 |
+
head_config.atomic_numbers = statistics["atomic_numbers"]
|
| 312 |
+
head_config.mean = statistics["mean"]
|
| 313 |
+
head_config.std = statistics["std"]
|
| 314 |
+
head_config.avg_num_neighbors = statistics["avg_num_neighbors"]
|
| 315 |
+
head_config.compute_avg_num_neighbors = False
|
| 316 |
+
if isinstance(statistics["atomic_energies"], str) and statistics[
|
| 317 |
+
"atomic_energies"
|
| 318 |
+
].endswith(".json"):
|
| 319 |
+
with open(statistics["atomic_energies"], "r", encoding="utf-8") as f:
|
| 320 |
+
atomic_energies = json.load(f)
|
| 321 |
+
head_config.E0s = atomic_energies
|
| 322 |
+
head_config.atomic_energies_dict = ast.literal_eval(atomic_energies)
|
| 323 |
+
else:
|
| 324 |
+
head_config.E0s = statistics["atomic_energies"]
|
| 325 |
+
head_config.atomic_energies_dict = ast.literal_eval(
|
| 326 |
+
statistics["atomic_energies"]
|
| 327 |
+
)
|
| 328 |
+
if head_config.train_file == ["mp"]:
|
| 329 |
+
assert (
|
| 330 |
+
head_config.head_name == "pt_head"
|
| 331 |
+
), "Only pt_head should use mp as train_file"
|
| 332 |
+
logging.info(
|
| 333 |
+
"Using the full Materials Project data for replay. You can construct a different subset using `fine_tuning_select.py` script."
|
| 334 |
+
)
|
| 335 |
+
collections = assemble_mp_data(args, head_config, tag)
|
| 336 |
+
head_config.collections = collections
|
| 337 |
+
elif any(check_path_ase_read(f) for f in head_config.train_file):
|
| 338 |
+
train_files_ase_list = [
|
| 339 |
+
f for f in head_config.train_file if check_path_ase_read(f)
|
| 340 |
+
]
|
| 341 |
+
valid_files_ase_list = None
|
| 342 |
+
test_files_ase_list = None
|
| 343 |
+
if head_config.valid_file:
|
| 344 |
+
valid_files_ase_list = [
|
| 345 |
+
f for f in head_config.valid_file if check_path_ase_read(f)
|
| 346 |
+
]
|
| 347 |
+
if head_config.test_file:
|
| 348 |
+
test_files_ase_list = [
|
| 349 |
+
f for f in head_config.test_file if check_path_ase_read(f)
|
| 350 |
+
]
|
| 351 |
+
config_type_weights = get_config_type_weights(
|
| 352 |
+
head_config.config_type_weights
|
| 353 |
+
)
|
| 354 |
+
collections, atomic_energies_dict = get_dataset_from_xyz(
|
| 355 |
+
work_dir=os.path.join(args.output_dir,args.name),
|
| 356 |
+
train_path=train_files_ase_list,
|
| 357 |
+
valid_path=valid_files_ase_list,
|
| 358 |
+
valid_fraction=head_config.valid_fraction,
|
| 359 |
+
config_type_weights=config_type_weights,
|
| 360 |
+
test_path=test_files_ase_list,
|
| 361 |
+
seed=args.seed,
|
| 362 |
+
key_specification=head_config.key_specification,
|
| 363 |
+
head_name=head_config.head_name,
|
| 364 |
+
keep_isolated_atoms=head_config.keep_isolated_atoms,
|
| 365 |
+
)
|
| 366 |
+
head_config.collections = SubsetCollection(
|
| 367 |
+
train=collections.train,
|
| 368 |
+
valid=collections.valid,
|
| 369 |
+
tests=collections.tests,
|
| 370 |
+
)
|
| 371 |
+
head_config.atomic_energies_dict = atomic_energies_dict
|
| 372 |
+
logging.info(
|
| 373 |
+
f"Total number of configurations: train={len(collections.train)}, valid={len(collections.valid)}, "
|
| 374 |
+
f"tests=[{', '.join([name + ': ' + str(len(test_configs)) for name, test_configs in collections.tests])}],"
|
| 375 |
+
)
|
| 376 |
+
head_configs.append(head_config)
|
| 377 |
+
|
| 378 |
+
if all(
|
| 379 |
+
check_path_ase_read(head_config.train_file[0]) for head_config in head_configs
|
| 380 |
+
):
|
| 381 |
+
size_collections_train = sum(
|
| 382 |
+
len(head_config.collections.train) for head_config in head_configs
|
| 383 |
+
)
|
| 384 |
+
size_collections_valid = sum(
|
| 385 |
+
len(head_config.collections.valid) for head_config in head_configs
|
| 386 |
+
)
|
| 387 |
+
if size_collections_train < args.batch_size:
|
| 388 |
+
logging.error(
|
| 389 |
+
f"Batch size ({args.batch_size}) is larger than the number of training data ({size_collections_train})"
|
| 390 |
+
)
|
| 391 |
+
if size_collections_valid < args.valid_batch_size:
|
| 392 |
+
logging.warning(
|
| 393 |
+
f"Validation batch size ({args.valid_batch_size}) is larger than the number of validation data ({size_collections_valid})"
|
| 394 |
+
)
|
| 395 |
+
|
| 396 |
+
if args.multiheads_finetuning:
|
| 397 |
+
logging.info(
|
| 398 |
+
"==================Using multiheads finetuning mode=================="
|
| 399 |
+
)
|
| 400 |
+
args.loss = "universal"
|
| 401 |
+
|
| 402 |
+
all_ase_readable = all(
|
| 403 |
+
all(check_path_ase_read(f) for f in head_config.train_file)
|
| 404 |
+
for head_config in head_configs
|
| 405 |
+
)
|
| 406 |
+
head_config_pt = filter(lambda x: x.head_name == "pt_head", head_configs)
|
| 407 |
+
head_config_pt = next(head_config_pt, None)
|
| 408 |
+
assert head_config_pt is not None, "Pretraining head not found"
|
| 409 |
+
if all_ase_readable:
|
| 410 |
+
ratio_pt_ft = size_collections_train / len(head_config_pt.collections.train)
|
| 411 |
+
if ratio_pt_ft < 0.1:
|
| 412 |
+
logging.warning(
|
| 413 |
+
f"Ratio of the number of configurations in the training set and the in the pt_train_file is {ratio_pt_ft}, "
|
| 414 |
+
f"increasing the number of configurations in the fine-tuning heads by {int(0.1 / ratio_pt_ft)}"
|
| 415 |
+
)
|
| 416 |
+
for head_config in head_configs:
|
| 417 |
+
if head_config.head_name == "pt_head":
|
| 418 |
+
continue
|
| 419 |
+
head_config.collections.train += (
|
| 420 |
+
head_config.collections.train * int(0.1 / ratio_pt_ft)
|
| 421 |
+
)
|
| 422 |
+
logging.info(
|
| 423 |
+
f"Total number of configurations in pretraining: train={len(head_config_pt.collections.train)}, valid={len(head_config_pt.collections.valid)}"
|
| 424 |
+
)
|
| 425 |
+
else:
|
| 426 |
+
logging.debug(
|
| 427 |
+
"Using LMDB/HDF5 datasets for pretraining or fine-tuning - skipping ratio check"
|
| 428 |
+
)
|
| 429 |
+
|
| 430 |
+
# Atomic number table
|
| 431 |
+
# yapf: disable
|
| 432 |
+
for head_config in head_configs:
|
| 433 |
+
if head_config.atomic_numbers is None:
|
| 434 |
+
assert all(check_path_ase_read(f) for f in head_config.train_file), "Must specify atomic_numbers when using .h5 or .aselmdb train_file input"
|
| 435 |
+
z_table_head = tools.get_atomic_number_table_from_zs(
|
| 436 |
+
z
|
| 437 |
+
for configs in (head_config.collections.train, head_config.collections.valid)
|
| 438 |
+
for config in configs
|
| 439 |
+
for z in config.atomic_numbers
|
| 440 |
+
)
|
| 441 |
+
head_config.atomic_numbers = z_table_head.zs
|
| 442 |
+
head_config.z_table = z_table_head
|
| 443 |
+
else:
|
| 444 |
+
if head_config.statistics_file is None:
|
| 445 |
+
logging.info("Using atomic numbers from command line argument")
|
| 446 |
+
else:
|
| 447 |
+
logging.info("Using atomic numbers from statistics file")
|
| 448 |
+
zs_list = ast.literal_eval(head_config.atomic_numbers)
|
| 449 |
+
assert isinstance(zs_list, list)
|
| 450 |
+
z_table_head = tools.AtomicNumberTable(zs_list)
|
| 451 |
+
head_config.atomic_numbers = zs_list
|
| 452 |
+
head_config.z_table = z_table_head
|
| 453 |
+
# yapf: enable
|
| 454 |
+
all_atomic_numbers = set()
|
| 455 |
+
for head_config in head_configs:
|
| 456 |
+
all_atomic_numbers.update(head_config.atomic_numbers)
|
| 457 |
+
z_table = AtomicNumberTable(sorted(list(all_atomic_numbers)))
|
| 458 |
+
if args.foundation_model_elements and model_foundation:
|
| 459 |
+
z_table = AtomicNumberTable(sorted(model_foundation.atomic_numbers.tolist()))
|
| 460 |
+
logging.info(f"Atomic Numbers used: {z_table.zs}")
|
| 461 |
+
|
| 462 |
+
# Atomic energies
|
| 463 |
+
atomic_energies_dict = {}
|
| 464 |
+
for head_config in head_configs:
|
| 465 |
+
if head_config.atomic_energies_dict is None or len(head_config.atomic_energies_dict) == 0:
|
| 466 |
+
assert head_config.E0s is not None, "Atomic energies must be provided"
|
| 467 |
+
if all(check_path_ase_read(f) for f in head_config.train_file) and head_config.E0s.lower() != "foundation":
|
| 468 |
+
atomic_energies_dict[head_config.head_name] = get_atomic_energies(
|
| 469 |
+
head_config.E0s, head_config.collections.train, head_config.z_table
|
| 470 |
+
)
|
| 471 |
+
elif head_config.E0s.lower() == "foundation":
|
| 472 |
+
assert args.foundation_model is not None
|
| 473 |
+
z_table_foundation = AtomicNumberTable(
|
| 474 |
+
[int(z) for z in model_foundation.atomic_numbers]
|
| 475 |
+
)
|
| 476 |
+
foundation_atomic_energies = model_foundation.atomic_energies_fn.atomic_energies
|
| 477 |
+
if foundation_atomic_energies.ndim > 1:
|
| 478 |
+
foundation_atomic_energies = foundation_atomic_energies.squeeze()
|
| 479 |
+
if foundation_atomic_energies.ndim == 2:
|
| 480 |
+
foundation_atomic_energies = foundation_atomic_energies[0]
|
| 481 |
+
logging.info("Foundation model has multiple heads, using the first head as foundation E0s.")
|
| 482 |
+
atomic_energies_dict[head_config.head_name] = {
|
| 483 |
+
z: foundation_atomic_energies[
|
| 484 |
+
z_table_foundation.z_to_index(z)
|
| 485 |
+
].item()
|
| 486 |
+
for z in z_table.zs
|
| 487 |
+
}
|
| 488 |
+
else:
|
| 489 |
+
atomic_energies_dict[head_config.head_name] = get_atomic_energies(head_config.E0s, None, head_config.z_table)
|
| 490 |
+
else:
|
| 491 |
+
atomic_energies_dict[head_config.head_name] = head_config.atomic_energies_dict
|
| 492 |
+
|
| 493 |
+
# Atomic energies for multiheads finetuning
|
| 494 |
+
if args.multiheads_finetuning:
|
| 495 |
+
assert (
|
| 496 |
+
model_foundation is not None
|
| 497 |
+
), "Model foundation must be provided for multiheads finetuning"
|
| 498 |
+
z_table_foundation = AtomicNumberTable(
|
| 499 |
+
[int(z) for z in model_foundation.atomic_numbers]
|
| 500 |
+
)
|
| 501 |
+
foundation_atomic_energies = model_foundation.atomic_energies_fn.atomic_energies
|
| 502 |
+
if foundation_atomic_energies.ndim > 1:
|
| 503 |
+
foundation_atomic_energies = foundation_atomic_energies.squeeze()
|
| 504 |
+
if foundation_atomic_energies.ndim == 2:
|
| 505 |
+
foundation_atomic_energies = foundation_atomic_energies[0]
|
| 506 |
+
logging.info("Foundation model has multiple heads, using the first head as foundation E0s.")
|
| 507 |
+
atomic_energies_dict["pt_head"] = {
|
| 508 |
+
z: foundation_atomic_energies[
|
| 509 |
+
z_table_foundation.z_to_index(z)
|
| 510 |
+
].item()
|
| 511 |
+
for z in z_table.zs
|
| 512 |
+
}
|
| 513 |
+
heads = sorted(heads, key=lambda x: -1000 if x == "pt_head" else 0)
|
| 514 |
+
# Padding atomic energies if keeping all elements of the foundation model
|
| 515 |
+
if args.foundation_model_elements and model_foundation:
|
| 516 |
+
atomic_energies_dict_padded = {}
|
| 517 |
+
for head_name, head_energies in atomic_energies_dict.items():
|
| 518 |
+
energy_head_padded = {}
|
| 519 |
+
for z in z_table.zs:
|
| 520 |
+
energy_head_padded[z] = head_energies.get(z, 0.0)
|
| 521 |
+
atomic_energies_dict_padded[head_name] = energy_head_padded
|
| 522 |
+
atomic_energies_dict = atomic_energies_dict_padded
|
| 523 |
+
|
| 524 |
+
if args.model == "AtomicDipolesMACE":
|
| 525 |
+
atomic_energies = None
|
| 526 |
+
dipole_only = True
|
| 527 |
+
args.compute_dipole = True
|
| 528 |
+
args.compute_energy = False
|
| 529 |
+
args.compute_forces = False
|
| 530 |
+
args.compute_virials = False
|
| 531 |
+
args.compute_stress = False
|
| 532 |
+
else:
|
| 533 |
+
dipole_only = False
|
| 534 |
+
if args.model == "EnergyDipolesMACE":
|
| 535 |
+
args.compute_dipole = True
|
| 536 |
+
args.compute_energy = True
|
| 537 |
+
args.compute_forces = True
|
| 538 |
+
args.compute_virials = False
|
| 539 |
+
args.compute_stress = False
|
| 540 |
+
else:
|
| 541 |
+
args.compute_energy = True
|
| 542 |
+
args.compute_dipole = False
|
| 543 |
+
# atomic_energies: np.ndarray = np.array(
|
| 544 |
+
# [atomic_energies_dict[z] for z in z_table.zs]
|
| 545 |
+
# )
|
| 546 |
+
atomic_energies = dict_to_array(atomic_energies_dict, heads)
|
| 547 |
+
for head_config in head_configs:
|
| 548 |
+
try:
|
| 549 |
+
logging.info(f"Atomic Energies used (z: eV) for head {head_config.head_name}: " + "{" + ", ".join([f"{z}: {atomic_energies_dict[head_config.head_name][z]}" for z in head_config.z_table.zs]) + "}")
|
| 550 |
+
except KeyError as e:
|
| 551 |
+
raise KeyError(f"Atomic number {e} not found in atomic_energies_dict for head {head_config.head_name}, add E0s for this atomic number") from e
|
| 552 |
+
|
| 553 |
+
# Load datasets for each head, supporting multiple files per head
|
| 554 |
+
valid_sets = {head: [] for head in heads}
|
| 555 |
+
train_sets = {head: [] for head in heads}
|
| 556 |
+
|
| 557 |
+
for head_config in head_configs:
|
| 558 |
+
train_datasets = []
|
| 559 |
+
|
| 560 |
+
logging.info(f"Processing datasets for head '{head_config.head_name}'")
|
| 561 |
+
ase_files = [f for f in head_config.train_file if check_path_ase_read(f)]
|
| 562 |
+
non_ase_files = [f for f in head_config.train_file if not check_path_ase_read(f)]
|
| 563 |
+
|
| 564 |
+
if ase_files:
|
| 565 |
+
#dataset = load_dataset_for_path(
|
| 566 |
+
#file_path=ase_files,
|
| 567 |
+
#r_max=args.r_max,
|
| 568 |
+
#z_table=z_table,
|
| 569 |
+
#head_config=head_config,
|
| 570 |
+
#heads=heads,
|
| 571 |
+
#collection=head_config.collections.train,
|
| 572 |
+
#)
|
| 573 |
+
# ✨ 使用新的 TextDataset 包装内存中已划分好的数据 (collections.train)
|
| 574 |
+
logging.info(f"Wraping training data into TextDataset for head '{head_config.head_name}'")
|
| 575 |
+
dataset = TextDataset(
|
| 576 |
+
configurations=head_config.collections.train, # 直接传入预处理好的 Configuration 列表
|
| 577 |
+
r_max=args.r_max,
|
| 578 |
+
z_table=z_table,
|
| 579 |
+
head=head_config.head_name,
|
| 580 |
+
heads=heads
|
| 581 |
+
)
|
| 582 |
+
train_datasets.append(dataset)
|
| 583 |
+
logging.debug(f"Successfully loaded dataset from ASE files: {ase_files}")
|
| 584 |
+
|
| 585 |
+
for file in non_ase_files:
|
| 586 |
+
dataset = load_dataset_for_path(
|
| 587 |
+
file_path=file,
|
| 588 |
+
r_max=args.r_max,
|
| 589 |
+
z_table=z_table,
|
| 590 |
+
head_config=head_config,
|
| 591 |
+
heads=heads,
|
| 592 |
+
)
|
| 593 |
+
train_datasets.append(dataset)
|
| 594 |
+
logging.debug(f"Successfully loaded dataset from non-ASE file: {file}")
|
| 595 |
+
|
| 596 |
+
if not train_datasets:
|
| 597 |
+
raise ValueError(f"No valid training datasets found for head {head_config.head_name}")
|
| 598 |
+
|
| 599 |
+
train_sets[head_config.head_name] = combine_datasets(train_datasets, head_config.head_name)
|
| 600 |
+
|
| 601 |
+
if head_config.valid_file:
|
| 602 |
+
valid_datasets = []
|
| 603 |
+
|
| 604 |
+
valid_ase_files = [f for f in head_config.valid_file if check_path_ase_read(f)]
|
| 605 |
+
valid_non_ase_files = [f for f in head_config.valid_file if not check_path_ase_read(f)]
|
| 606 |
+
|
| 607 |
+
if valid_ase_files:
|
| 608 |
+
#valid_dataset = load_dataset_for_path(
|
| 609 |
+
# file_path=valid_ase_files,
|
| 610 |
+
# r_max=args.r_max,
|
| 611 |
+
# z_table=z_table,
|
| 612 |
+
# head_config=head_config,
|
| 613 |
+
# heads=heads,
|
| 614 |
+
# collection=head_config.collections.valid,
|
| 615 |
+
#)
|
| 616 |
+
#valid_datasets.append(valid_dataset)
|
| 617 |
+
#logging.debug(f"Successfully loaded validation dataset from ASE files: {valid_ase_files}")
|
| 618 |
+
# ✨ 使用新的 TextDataset 包装内存中已划分好的验证数据
|
| 619 |
+
logging.info(f"Wraping validation data into TextDataset for head '{head_config.head_name}'")
|
| 620 |
+
valid_dataset = TextDataset(
|
| 621 |
+
configurations=head_config.collections.valid, # 直接传入预处理好的 Configuration 列表
|
| 622 |
+
r_max=args.r_max,
|
| 623 |
+
z_table=z_table,
|
| 624 |
+
head=head_config.head_name,
|
| 625 |
+
heads=heads
|
| 626 |
+
)
|
| 627 |
+
valid_datasets.append(valid_dataset)
|
| 628 |
+
logging.debug(f"Successfully loaded validation dataset from ASE files: {valid_ase_files}")
|
| 629 |
+
for valid_file in valid_non_ase_files:
|
| 630 |
+
valid_dataset = load_dataset_for_path(
|
| 631 |
+
file_path=valid_file,
|
| 632 |
+
r_max=args.r_max,
|
| 633 |
+
z_table=z_table,
|
| 634 |
+
head_config=head_config,
|
| 635 |
+
heads=heads,
|
| 636 |
+
)
|
| 637 |
+
valid_datasets.append(valid_dataset)
|
| 638 |
+
logging.debug(f"Successfully loaded validation dataset from {valid_file}")
|
| 639 |
+
|
| 640 |
+
# Combine validation datasets
|
| 641 |
+
if valid_datasets:
|
| 642 |
+
valid_sets[head_config.head_name] = combine_datasets(valid_datasets, f"{head_config.head_name}_valid")
|
| 643 |
+
logging.info(f"Combined validation datasets for {head_config.head_name}")
|
| 644 |
+
|
| 645 |
+
# If no valid file is provided but collection exist, use the validation set from the collection
|
| 646 |
+
if head_config.valid_file is None and head_config.collections.valid:
|
| 647 |
+
valid_sets[head_config.head_name] = [
|
| 648 |
+
AtomicData.from_config(
|
| 649 |
+
config, z_table=z_table, cutoff=args.r_max, heads=heads
|
| 650 |
+
)
|
| 651 |
+
for config in head_config.collections.valid
|
| 652 |
+
]
|
| 653 |
+
if not valid_sets[head_config.head_name]:
|
| 654 |
+
raise ValueError(f"No valid datasets found for head {head_config.head_name}, please provide a valid_file or a valid_fraction")
|
| 655 |
+
|
| 656 |
+
# Create data loader for this head
|
| 657 |
+
if isinstance(train_sets[head_config.head_name], list):
|
| 658 |
+
dataset_size = len(train_sets[head_config.head_name])
|
| 659 |
+
else:
|
| 660 |
+
dataset_size = len(train_sets[head_config.head_name])
|
| 661 |
+
logging.info(f"Head '{head_config.head_name}' training dataset size: {dataset_size}")
|
| 662 |
+
|
| 663 |
+
train_sampler_head = None
|
| 664 |
+
if args.distributed:
|
| 665 |
+
train_sampler_head = torch.utils.data.distributed.DistributedSampler(
|
| 666 |
+
train_sets[head_config.head_name],
|
| 667 |
+
num_replicas=world_size,
|
| 668 |
+
rank=rank,
|
| 669 |
+
shuffle=True,
|
| 670 |
+
drop_last=(not args.lbfgs),
|
| 671 |
+
seed=args.seed,
|
| 672 |
+
)
|
| 673 |
+
train_loader_head = torch_geometric.dataloader.DataLoader(
|
| 674 |
+
dataset=train_sets[head_config.head_name],
|
| 675 |
+
batch_size=args.batch_size,
|
| 676 |
+
sampler=train_sampler_head,
|
| 677 |
+
shuffle=(train_sampler_head is None),
|
| 678 |
+
drop_last=(train_sampler_head is None and not args.lbfgs),
|
| 679 |
+
pin_memory=args.pin_memory,
|
| 680 |
+
num_workers=args.num_workers,
|
| 681 |
+
generator=torch.Generator().manual_seed(args.seed),
|
| 682 |
+
)
|
| 683 |
+
head_config.train_loader = train_loader_head
|
| 684 |
+
|
| 685 |
+
# concatenate all the trainsets
|
| 686 |
+
train_set = ConcatDataset([train_sets[head] for head in heads])
|
| 687 |
+
train_sampler, valid_sampler = None, None
|
| 688 |
+
if args.distributed:
|
| 689 |
+
train_sampler = torch.utils.data.distributed.DistributedSampler(
|
| 690 |
+
train_set,
|
| 691 |
+
num_replicas=world_size,
|
| 692 |
+
rank=rank,
|
| 693 |
+
shuffle=True,
|
| 694 |
+
drop_last=(not args.lbfgs),
|
| 695 |
+
seed=args.seed,
|
| 696 |
+
)
|
| 697 |
+
valid_samplers = {}
|
| 698 |
+
for head, valid_set in valid_sets.items():
|
| 699 |
+
valid_sampler = torch.utils.data.distributed.DistributedSampler(
|
| 700 |
+
valid_set,
|
| 701 |
+
num_replicas=world_size,
|
| 702 |
+
rank=rank,
|
| 703 |
+
shuffle=True,
|
| 704 |
+
drop_last=True,
|
| 705 |
+
seed=args.seed,
|
| 706 |
+
)
|
| 707 |
+
valid_samplers[head] = valid_sampler
|
| 708 |
+
train_loader = torch_geometric.dataloader.DataLoader(
|
| 709 |
+
dataset=train_set,
|
| 710 |
+
batch_size=args.batch_size,
|
| 711 |
+
sampler=train_sampler,
|
| 712 |
+
shuffle=(train_sampler is None),
|
| 713 |
+
drop_last=(train_sampler is None and not args.lbfgs),
|
| 714 |
+
pin_memory=args.pin_memory,
|
| 715 |
+
num_workers=args.num_workers,
|
| 716 |
+
generator=torch.Generator().manual_seed(args.seed),
|
| 717 |
+
)
|
| 718 |
+
valid_loaders = {heads[i]: None for i in range(len(heads))}
|
| 719 |
+
if not isinstance(valid_sets, dict):
|
| 720 |
+
valid_sets = {"Default": valid_sets}
|
| 721 |
+
for head, valid_set in valid_sets.items():
|
| 722 |
+
valid_loaders[head] = torch_geometric.dataloader.DataLoader(
|
| 723 |
+
dataset=valid_set,
|
| 724 |
+
batch_size=args.valid_batch_size,
|
| 725 |
+
sampler=valid_samplers[head] if args.distributed else None,
|
| 726 |
+
shuffle=False,
|
| 727 |
+
drop_last=False,
|
| 728 |
+
pin_memory=args.pin_memory,
|
| 729 |
+
num_workers=args.num_workers,
|
| 730 |
+
generator=torch.Generator().manual_seed(args.seed),
|
| 731 |
+
)
|
| 732 |
+
|
| 733 |
+
loss_fn = get_loss_fn(args, dipole_only, args.compute_dipole)
|
| 734 |
+
args.avg_num_neighbors = get_avg_num_neighbors(head_configs, args, train_loader, device)
|
| 735 |
+
|
| 736 |
+
# Model
|
| 737 |
+
model, output_args = configure_model(args, train_loader, atomic_energies, model_foundation, heads, z_table, head_configs)
|
| 738 |
+
model.to(device)
|
| 739 |
+
|
| 740 |
+
logging.debug(model)
|
| 741 |
+
logging.info(f"Total number of parameters: {tools.count_parameters(model)}")
|
| 742 |
+
logging.info("")
|
| 743 |
+
logging.info("===========OPTIMIZER INFORMATION===========")
|
| 744 |
+
logging.info(f"Using {args.optimizer.upper()} as parameter optimizer")
|
| 745 |
+
logging.info(f"Batch size: {args.batch_size}")
|
| 746 |
+
if args.ema:
|
| 747 |
+
logging.info(f"Using Exponential Moving Average with decay: {args.ema_decay}")
|
| 748 |
+
logging.info(
|
| 749 |
+
f"Number of gradient updates: {int(args.max_num_epochs*len(train_set)/args.batch_size)}"
|
| 750 |
+
)
|
| 751 |
+
logging.info(f"Learning rate: {args.lr}, weight decay: {args.weight_decay}")
|
| 752 |
+
logging.info(loss_fn)
|
| 753 |
+
|
| 754 |
+
# Cueq
|
| 755 |
+
if args.enable_cueq:
|
| 756 |
+
logging.info("Converting model to CUEQ for accelerated training")
|
| 757 |
+
assert model.__class__.__name__ in ["MACE", "ScaleShiftMACE"]
|
| 758 |
+
model = run_e3nn_to_cueq(deepcopy(model), device=device)
|
| 759 |
+
# Optimizer
|
| 760 |
+
param_options = get_params_options(args, model)
|
| 761 |
+
optimizer: torch.optim.Optimizer
|
| 762 |
+
optimizer = get_optimizer(args, param_options)
|
| 763 |
+
if args.device == "xpu":
|
| 764 |
+
logging.info("Optimzing model and optimzier for XPU")
|
| 765 |
+
model, optimizer = ipex.optimize(model, optimizer=optimizer)
|
| 766 |
+
logger = tools.MetricsLogger(
|
| 767 |
+
directory=args.plot_dir, tag=tag + "_train"
|
| 768 |
+
) # pylint: disable=E1123
|
| 769 |
+
|
| 770 |
+
lr_scheduler = LRScheduler(optimizer, args)
|
| 771 |
+
|
| 772 |
+
swa: Optional[tools.SWAContainer] = None
|
| 773 |
+
swas = [False]
|
| 774 |
+
if args.swa:
|
| 775 |
+
swa, swas = get_swa(args, model, optimizer, swas, dipole_only)
|
| 776 |
+
|
| 777 |
+
checkpoint_handler = tools.CheckpointHandler(
|
| 778 |
+
directory=args.checkpoints_dir,
|
| 779 |
+
tag=tag,
|
| 780 |
+
keep=args.keep_checkpoints,
|
| 781 |
+
swa_start=args.start_swa,
|
| 782 |
+
)
|
| 783 |
+
|
| 784 |
+
start_epoch = 0
|
| 785 |
+
restart_lbfgs = False
|
| 786 |
+
opt_start_epoch = None
|
| 787 |
+
if args.restart_latest:
|
| 788 |
+
try:
|
| 789 |
+
opt_start_epoch = checkpoint_handler.load_latest(
|
| 790 |
+
state=tools.CheckpointState(model, optimizer, lr_scheduler),
|
| 791 |
+
swa=True,
|
| 792 |
+
device=device,
|
| 793 |
+
)
|
| 794 |
+
except Exception: # pylint: disable=W0703
|
| 795 |
+
try:
|
| 796 |
+
opt_start_epoch = checkpoint_handler.load_latest(
|
| 797 |
+
state=tools.CheckpointState(model, optimizer, lr_scheduler),
|
| 798 |
+
swa=False,
|
| 799 |
+
device=device,
|
| 800 |
+
)
|
| 801 |
+
except Exception: # pylint: disable=W0703
|
| 802 |
+
restart_lbfgs = True
|
| 803 |
+
if opt_start_epoch is not None:
|
| 804 |
+
start_epoch = opt_start_epoch
|
| 805 |
+
|
| 806 |
+
ema: Optional[ExponentialMovingAverage] = None
|
| 807 |
+
if args.ema:
|
| 808 |
+
ema = ExponentialMovingAverage(model.parameters(), decay=args.ema_decay)
|
| 809 |
+
else:
|
| 810 |
+
for group in optimizer.param_groups:
|
| 811 |
+
group["lr"] = args.lr
|
| 812 |
+
|
| 813 |
+
if args.lbfgs:
|
| 814 |
+
logging.info("Switching optimizer to LBFGS")
|
| 815 |
+
optimizer = LBFGS(model.parameters(),
|
| 816 |
+
history_size=200,
|
| 817 |
+
max_iter=20,
|
| 818 |
+
line_search_fn="strong_wolfe")
|
| 819 |
+
if restart_lbfgs:
|
| 820 |
+
opt_start_epoch = checkpoint_handler.load_latest(
|
| 821 |
+
state=tools.CheckpointState(model, optimizer, lr_scheduler),
|
| 822 |
+
swa=False,
|
| 823 |
+
device=device,
|
| 824 |
+
)
|
| 825 |
+
if opt_start_epoch is not None:
|
| 826 |
+
start_epoch = opt_start_epoch
|
| 827 |
+
|
| 828 |
+
if args.wandb:
|
| 829 |
+
setup_wandb(args)
|
| 830 |
+
if args.distributed:
|
| 831 |
+
distributed_model = DDP(model, device_ids=[local_rank])
|
| 832 |
+
else:
|
| 833 |
+
distributed_model = None
|
| 834 |
+
|
| 835 |
+
|
| 836 |
+
train_valid_data_loader = {}
|
| 837 |
+
for head_config in head_configs:
|
| 838 |
+
data_loader_name = "train_" + head_config.head_name
|
| 839 |
+
train_valid_data_loader[data_loader_name] = head_config.train_loader
|
| 840 |
+
for head, valid_loader in valid_loaders.items():
|
| 841 |
+
data_load_name = "valid_" + head
|
| 842 |
+
train_valid_data_loader[data_load_name] = valid_loader
|
| 843 |
+
|
| 844 |
+
if args.plot and args.plot_frequency > 0:
|
| 845 |
+
try:
|
| 846 |
+
plotter = TrainingPlotter(
|
| 847 |
+
results_dir=logger.path,
|
| 848 |
+
heads=heads,
|
| 849 |
+
table_type=args.error_table,
|
| 850 |
+
train_valid_data=train_valid_data_loader,
|
| 851 |
+
test_data={},
|
| 852 |
+
output_args=output_args,
|
| 853 |
+
device=device,
|
| 854 |
+
plot_frequency=args.plot_frequency,
|
| 855 |
+
distributed=args.distributed,
|
| 856 |
+
swa_start=swa.start if swa else None
|
| 857 |
+
)
|
| 858 |
+
except Exception as e: # pylint: disable=W0718
|
| 859 |
+
logging.debug(f"Creating Plotter failed: {e}")
|
| 860 |
+
else:
|
| 861 |
+
plotter = None
|
| 862 |
+
|
| 863 |
+
if args.dry_run:
|
| 864 |
+
logging.info("DRY RUN mode enabled. Stopping now.")
|
| 865 |
+
return
|
| 866 |
+
|
| 867 |
+
|
| 868 |
+
tools.train(
|
| 869 |
+
model=model,
|
| 870 |
+
loss_fn=loss_fn,
|
| 871 |
+
train_loader=train_loader,
|
| 872 |
+
valid_loaders=valid_loaders,
|
| 873 |
+
optimizer=optimizer,
|
| 874 |
+
lr_scheduler=lr_scheduler,
|
| 875 |
+
checkpoint_handler=checkpoint_handler,
|
| 876 |
+
eval_interval=args.eval_interval,
|
| 877 |
+
start_epoch=start_epoch,
|
| 878 |
+
max_num_epochs=args.max_num_epochs,
|
| 879 |
+
logger=logger,
|
| 880 |
+
patience=args.patience,
|
| 881 |
+
save_all_checkpoints=args.save_all_checkpoints,
|
| 882 |
+
output_args=output_args,
|
| 883 |
+
device=device,
|
| 884 |
+
swa=swa,
|
| 885 |
+
ema=ema,
|
| 886 |
+
max_grad_norm=args.clip_grad,
|
| 887 |
+
log_errors=args.error_table,
|
| 888 |
+
log_wandb=args.wandb,
|
| 889 |
+
distributed=args.distributed,
|
| 890 |
+
distributed_model=distributed_model,
|
| 891 |
+
plotter=plotter,
|
| 892 |
+
train_sampler=train_sampler,
|
| 893 |
+
rank=rank,
|
| 894 |
+
)
|
| 895 |
+
|
| 896 |
+
logging.info("")
|
| 897 |
+
logging.info("===========RESULTS===========")
|
| 898 |
+
|
| 899 |
+
train_valid_data_loader = {}
|
| 900 |
+
for head_config in head_configs:
|
| 901 |
+
data_loader_name = "train_" + head_config.head_name
|
| 902 |
+
train_valid_data_loader[data_loader_name] = head_config.train_loader
|
| 903 |
+
for head, valid_loader in valid_loaders.items():
|
| 904 |
+
data_load_name = "valid_" + head
|
| 905 |
+
train_valid_data_loader[data_load_name] = valid_loader
|
| 906 |
+
test_sets = {}
|
| 907 |
+
stop_first_test = False
|
| 908 |
+
test_data_loader = {}
|
| 909 |
+
if all(
|
| 910 |
+
head_config.test_file == head_configs[0].test_file
|
| 911 |
+
for head_config in head_configs
|
| 912 |
+
) and head_configs[0].test_file is not None:
|
| 913 |
+
stop_first_test = True
|
| 914 |
+
if all(
|
| 915 |
+
head_config.test_dir == head_configs[0].test_dir
|
| 916 |
+
for head_config in head_configs
|
| 917 |
+
) and head_configs[0].test_dir is not None:
|
| 918 |
+
stop_first_test = True
|
| 919 |
+
for head_config in head_configs:
|
| 920 |
+
if all(check_path_ase_read(f) for f in head_config.train_file):
|
| 921 |
+
for name, subset in head_config.collections.tests:
|
| 922 |
+
test_sets[name] = [
|
| 923 |
+
AtomicData.from_config(
|
| 924 |
+
config, z_table=z_table, cutoff=args.r_max, heads=heads
|
| 925 |
+
)
|
| 926 |
+
for config in subset
|
| 927 |
+
]
|
| 928 |
+
if head_config.test_dir is not None:
|
| 929 |
+
if not args.multi_processed_test:
|
| 930 |
+
test_files = get_files_with_suffix(head_config.test_dir, "_test.h5")
|
| 931 |
+
for test_file in test_files:
|
| 932 |
+
name = os.path.splitext(os.path.basename(test_file))[0]
|
| 933 |
+
test_sets[name] = HDF5Dataset(
|
| 934 |
+
test_file, r_max=args.r_max, z_table=z_table, heads=heads, head=head_config.head_name
|
| 935 |
+
)
|
| 936 |
+
else:
|
| 937 |
+
test_folders = glob(head_config.test_dir + "/*")
|
| 938 |
+
for folder in test_folders:
|
| 939 |
+
name = os.path.splitext(os.path.basename(test_file))[0]
|
| 940 |
+
test_sets[name] = dataset_from_sharded_hdf5(
|
| 941 |
+
folder, r_max=args.r_max, z_table=z_table, heads=heads, head=head_config.head_name
|
| 942 |
+
)
|
| 943 |
+
for test_name, test_set in test_sets.items():
|
| 944 |
+
test_sampler = None
|
| 945 |
+
if args.distributed:
|
| 946 |
+
test_sampler = torch.utils.data.distributed.DistributedSampler(
|
| 947 |
+
test_set,
|
| 948 |
+
num_replicas=world_size,
|
| 949 |
+
rank=rank,
|
| 950 |
+
shuffle=True,
|
| 951 |
+
drop_last=True,
|
| 952 |
+
seed=args.seed,
|
| 953 |
+
)
|
| 954 |
+
try:
|
| 955 |
+
drop_last = test_set.drop_last
|
| 956 |
+
except AttributeError as e: # pylint: disable=W0612
|
| 957 |
+
drop_last = False
|
| 958 |
+
test_loader = torch_geometric.dataloader.DataLoader(
|
| 959 |
+
test_set,
|
| 960 |
+
batch_size=args.valid_batch_size,
|
| 961 |
+
sampler=test_sampler,
|
| 962 |
+
shuffle=(test_sampler is None),
|
| 963 |
+
drop_last=drop_last,
|
| 964 |
+
num_workers=args.num_workers,
|
| 965 |
+
pin_memory=args.pin_memory,
|
| 966 |
+
)
|
| 967 |
+
test_data_loader[test_name] = test_loader
|
| 968 |
+
if stop_first_test:
|
| 969 |
+
break
|
| 970 |
+
|
| 971 |
+
for swa_eval in swas:
|
| 972 |
+
epoch = checkpoint_handler.load_latest(
|
| 973 |
+
state=tools.CheckpointState(model, optimizer, lr_scheduler),
|
| 974 |
+
swa=swa_eval,
|
| 975 |
+
device=device,
|
| 976 |
+
)
|
| 977 |
+
model.to(device)
|
| 978 |
+
if args.distributed:
|
| 979 |
+
distributed_model = DDP(model, device_ids=[local_rank], broadcast_buffers=False)
|
| 980 |
+
model_to_evaluate = model if not args.distributed else distributed_model
|
| 981 |
+
if swa_eval:
|
| 982 |
+
logging.info(f"Loaded Stage two model from epoch {epoch} for evaluation")
|
| 983 |
+
else:
|
| 984 |
+
logging.info(f"Loaded Stage one model from epoch {epoch} for evaluation")
|
| 985 |
+
|
| 986 |
+
if rank == 0:
|
| 987 |
+
# Save entire model
|
| 988 |
+
if swa_eval:
|
| 989 |
+
model_path = Path(args.checkpoints_dir) / (tag + "_stagetwo.model")
|
| 990 |
+
else:
|
| 991 |
+
model_path = Path(args.checkpoints_dir) / (tag + ".model")
|
| 992 |
+
logging.info(f"Saving model to {model_path}")
|
| 993 |
+
model_to_save = deepcopy(model)
|
| 994 |
+
if args.enable_cueq:
|
| 995 |
+
print("RUNING CUEQ TO E3NN")
|
| 996 |
+
print("swa_eval", swa_eval)
|
| 997 |
+
model_to_save = run_cueq_to_e3nn(deepcopy(model), device=device)
|
| 998 |
+
if args.save_cpu:
|
| 999 |
+
model_to_save = model_to_save.to("cpu")
|
| 1000 |
+
torch.save(model_to_save, model_path)
|
| 1001 |
+
extra_files = {
|
| 1002 |
+
"commit.txt": commit.encode("utf-8") if commit is not None else b"",
|
| 1003 |
+
"config.yaml": json.dumps(
|
| 1004 |
+
convert_to_json_format(extract_config_mace_model(model))
|
| 1005 |
+
),
|
| 1006 |
+
}
|
| 1007 |
+
if swa_eval:
|
| 1008 |
+
torch.save(
|
| 1009 |
+
model_to_save, Path(args.model_dir) / (args.name + "_stagetwo.model")
|
| 1010 |
+
)
|
| 1011 |
+
try:
|
| 1012 |
+
path_complied = Path(args.model_dir) / (
|
| 1013 |
+
args.name + "_stagetwo_compiled.model"
|
| 1014 |
+
)
|
| 1015 |
+
logging.info(f"Compiling model, saving metadata {path_complied}")
|
| 1016 |
+
model_compiled = jit.compile(deepcopy(model_to_save))
|
| 1017 |
+
torch.jit.save(
|
| 1018 |
+
model_compiled,
|
| 1019 |
+
path_complied,
|
| 1020 |
+
_extra_files=extra_files,
|
| 1021 |
+
)
|
| 1022 |
+
except Exception as e: # pylint: disable=W0718
|
| 1023 |
+
pass
|
| 1024 |
+
else:
|
| 1025 |
+
torch.save(model_to_save, Path(args.model_dir) / (args.name + ".model"))
|
| 1026 |
+
try:
|
| 1027 |
+
path_complied = Path(args.model_dir) / (
|
| 1028 |
+
args.name + "_compiled.model"
|
| 1029 |
+
)
|
| 1030 |
+
logging.info(f"Compiling model, saving metadata to {path_complied}")
|
| 1031 |
+
model_compiled = jit.compile(deepcopy(model_to_save))
|
| 1032 |
+
torch.jit.save(
|
| 1033 |
+
model_compiled,
|
| 1034 |
+
path_complied,
|
| 1035 |
+
_extra_files=extra_files,
|
| 1036 |
+
)
|
| 1037 |
+
except Exception as e: # pylint: disable=W0718
|
| 1038 |
+
pass
|
| 1039 |
+
|
| 1040 |
+
logging.info("Computing metrics for training, validation, and test sets")
|
| 1041 |
+
for param in model.parameters():
|
| 1042 |
+
param.requires_grad = False
|
| 1043 |
+
skip_heads = args.skip_evaluate_heads.split(",") if args.skip_evaluate_heads else []
|
| 1044 |
+
if skip_heads:
|
| 1045 |
+
logging.info(f"Skipping evaluation for heads: {skip_heads}")
|
| 1046 |
+
table_train_valid = create_error_table(
|
| 1047 |
+
table_type=args.error_table,
|
| 1048 |
+
all_data_loaders=train_valid_data_loader,
|
| 1049 |
+
model=model_to_evaluate,
|
| 1050 |
+
loss_fn=loss_fn,
|
| 1051 |
+
output_args=output_args,
|
| 1052 |
+
log_wandb=args.wandb,
|
| 1053 |
+
device=device,
|
| 1054 |
+
distributed=args.distributed,
|
| 1055 |
+
skip_heads=skip_heads,
|
| 1056 |
+
)
|
| 1057 |
+
logging.info("Error-table on TRAIN and VALID:\n" + str(table_train_valid))
|
| 1058 |
+
|
| 1059 |
+
if test_data_loader:
|
| 1060 |
+
table_test = create_error_table(
|
| 1061 |
+
table_type=args.error_table,
|
| 1062 |
+
all_data_loaders=test_data_loader,
|
| 1063 |
+
model=model_to_evaluate,
|
| 1064 |
+
loss_fn=loss_fn,
|
| 1065 |
+
output_args=output_args,
|
| 1066 |
+
log_wandb=args.wandb,
|
| 1067 |
+
device=device,
|
| 1068 |
+
distributed=args.distributed,
|
| 1069 |
+
)
|
| 1070 |
+
logging.info("Error-table on TEST:\n" + str(table_test))
|
| 1071 |
+
if args.plot:
|
| 1072 |
+
try:
|
| 1073 |
+
plotter = TrainingPlotter(
|
| 1074 |
+
results_dir=logger.path,
|
| 1075 |
+
heads=heads,
|
| 1076 |
+
table_type=args.error_table,
|
| 1077 |
+
train_valid_data=train_valid_data_loader,
|
| 1078 |
+
test_data=test_data_loader,
|
| 1079 |
+
output_args=output_args,
|
| 1080 |
+
device=device,
|
| 1081 |
+
plot_frequency=args.plot_frequency,
|
| 1082 |
+
distributed=args.distributed,
|
| 1083 |
+
swa_start=swa.start if swa else None
|
| 1084 |
+
)
|
| 1085 |
+
plotter.plot(epoch, model_to_evaluate, rank)
|
| 1086 |
+
except Exception as e: # pylint: disable=W0718
|
| 1087 |
+
logging.debug(f"Plotting failed: {e}")
|
| 1088 |
+
|
| 1089 |
+
if args.distributed:
|
| 1090 |
+
torch.distributed.barrier()
|
| 1091 |
+
|
| 1092 |
+
logging.info("Done")
|
| 1093 |
+
if args.distributed:
|
| 1094 |
+
torch.distributed.destroy_process_group()
|
| 1095 |
+
|
| 1096 |
+
|
| 1097 |
+
if __name__ == "__main__":
|
| 1098 |
+
main()
|